1 /*
2  * Copyright 2014, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.telecom;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.media.AudioAttributes;
22 import android.media.session.MediaSession;
23 import android.os.Handler;
24 import android.os.Looper;
25 import android.os.Message;
26 import android.telecom.Log;
27 import android.view.KeyEvent;
28 
29 /**
30  * Static class to handle listening to the headset media buttons.
31  */
32 public class HeadsetMediaButton extends CallsManagerListenerBase {
33 
34     // Types of media button presses
35     static final int SHORT_PRESS = 1;
36     static final int LONG_PRESS = 2;
37 
38     private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
39             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
40             .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
41 
42     private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
43     private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;
44 
45     private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
46         @Override
47         public boolean onMediaButtonEvent(Intent intent) {
48             KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
49             Log.v(this, "SessionCallback.onMediaButton()...  event = %s.", event);
50             if ((event != null) && ((event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) ||
51                                     (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))) {
52                 synchronized (mLock) {
53                     Log.v(this, "SessionCallback: HEADSETHOOK/MEDIA_PLAY_PAUSE");
54                     boolean consumed = handleCallMediaButton(event);
55                     Log.v(this, "==> handleCallMediaButton(): consumed = %b.", consumed);
56                     return consumed;
57                 }
58             }
59             return true;
60         }
61     };
62 
63     private final Handler mMediaSessionHandler = new Handler(Looper.getMainLooper()) {
64         @Override
65         public void handleMessage(Message msg) {
66             switch (msg.what) {
67                 case MSG_MEDIA_SESSION_INITIALIZE: {
68                     MediaSession session = new MediaSession(
69                             mContext,
70                             HeadsetMediaButton.class.getSimpleName());
71                     session.setCallback(mSessionCallback);
72                     session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
73                             | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
74                     session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
75                     mSession = session;
76                     break;
77                 }
78                 case MSG_MEDIA_SESSION_SET_ACTIVE: {
79                     if (mSession != null) {
80                         boolean activate = msg.arg1 != 0;
81                         if (activate != mSession.isActive()) {
82                             mSession.setActive(activate);
83                         }
84                     }
85                     break;
86                 }
87                 default:
88                     break;
89             }
90         }
91     };
92 
93     private final Context mContext;
94     private final CallsManager mCallsManager;
95     private final TelecomSystem.SyncRoot mLock;
96     private MediaSession mSession;
97     private KeyEvent mLastHookEvent;
98 
HeadsetMediaButton( Context context, CallsManager callsManager, TelecomSystem.SyncRoot lock)99     public HeadsetMediaButton(
100             Context context,
101             CallsManager callsManager,
102             TelecomSystem.SyncRoot lock) {
103         mContext = context;
104         mCallsManager = callsManager;
105         mLock = lock;
106 
107         // Create a MediaSession but don't enable it yet. This is a
108         // replacement for MediaButtonReceiver
109         mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_INITIALIZE).sendToTarget();
110     }
111 
112     /**
113      * Handles the wired headset button while in-call.
114      *
115      * @return true if we consumed the event.
116      */
handleCallMediaButton(KeyEvent event)117     private boolean handleCallMediaButton(KeyEvent event) {
118         Log.d(this, "handleCallMediaButton()...%s %s", event.getAction(), event.getRepeatCount());
119 
120         // Save ACTION_DOWN Event temporarily.
121         if (event.getAction() == KeyEvent.ACTION_DOWN) {
122             mLastHookEvent = event;
123         }
124 
125         if (event.isLongPress()) {
126             return mCallsManager.onMediaButton(LONG_PRESS);
127         } else if (event.getAction() == KeyEvent.ACTION_UP) {
128             // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
129             // return 0.
130             // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
131             if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
132                 return mCallsManager.onMediaButton(SHORT_PRESS);
133             }
134         }
135 
136         if (event.getAction() != KeyEvent.ACTION_DOWN) {
137             mLastHookEvent = null;
138         }
139 
140         return true;
141     }
142 
143     /** ${inheritDoc} */
144     @Override
onCallAdded(Call call)145     public void onCallAdded(Call call) {
146         if (call.isExternalCall()) {
147             return;
148         }
149         mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
150     }
151 
152     /** ${inheritDoc} */
153     @Override
onCallRemoved(Call call)154     public void onCallRemoved(Call call) {
155         if (call.isExternalCall()) {
156             return;
157         }
158         if (!mCallsManager.hasAnyCalls()) {
159             mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
160         }
161     }
162 
163     /** ${inheritDoc} */
164     @Override
onExternalCallChanged(Call call, boolean isExternalCall)165     public void onExternalCallChanged(Call call, boolean isExternalCall) {
166         if (isExternalCall) {
167             onCallRemoved(call);
168         } else {
169             onCallAdded(call);
170         }
171     }
172 }
173