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 import com.android.internal.annotations.VisibleForTesting;
30 
31 /**
32  * Static class to handle listening to the headset media buttons.
33  */
34 public class HeadsetMediaButton extends CallsManagerListenerBase {
35 
36     // Types of media button presses
37     @VisibleForTesting
38     public static final int SHORT_PRESS = 1;
39     @VisibleForTesting
40     public static final int LONG_PRESS = 2;
41 
42     private static final AudioAttributes AUDIO_ATTRIBUTES = new AudioAttributes.Builder()
43             .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
44             .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION).build();
45 
46     private static final int MSG_MEDIA_SESSION_INITIALIZE = 0;
47     private static final int MSG_MEDIA_SESSION_SET_ACTIVE = 1;
48 
49     private final MediaSession.Callback mSessionCallback = new MediaSession.Callback() {
50         @Override
51         public boolean onMediaButtonEvent(Intent intent) {
52             try {
53                 Log.startSession("HMB.oMBE");
54                 KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
55                 Log.v(this, "SessionCallback.onMediaButton()...  event = %s.", event);
56                 if ((event != null) && ((event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK) ||
57                         (event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE))) {
58                     synchronized (mLock) {
59                         Log.v(this, "SessionCallback: HEADSETHOOK/MEDIA_PLAY_PAUSE");
60                         boolean consumed = handleCallMediaButton(event);
61                         Log.v(this, "==> handleCallMediaButton(): consumed = %b.", consumed);
62                         return consumed;
63                     }
64                 }
65                 return true;
66             } finally {
67                 Log.endSession();
68             }
69         }
70     };
71 
72     private final Handler mMediaSessionHandler = new Handler(Looper.getMainLooper()) {
73         @Override
74         public void handleMessage(Message msg) {
75             switch (msg.what) {
76                 case MSG_MEDIA_SESSION_INITIALIZE: {
77                     MediaSession session = new MediaSession(
78                             mContext,
79                             HeadsetMediaButton.class.getSimpleName());
80                     session.setCallback(mSessionCallback);
81                     session.setFlags(MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY
82                             | MediaSession.FLAG_HANDLES_MEDIA_BUTTONS);
83                     session.setPlaybackToLocal(AUDIO_ATTRIBUTES);
84                     mSession = session;
85                     break;
86                 }
87                 case MSG_MEDIA_SESSION_SET_ACTIVE: {
88                     if (mSession != null) {
89                         boolean activate = msg.arg1 != 0;
90                         if (activate != mSession.isActive()) {
91                             mSession.setActive(activate);
92                         }
93                     }
94                     break;
95                 }
96                 default:
97                     break;
98             }
99         }
100     };
101 
102     private final Context mContext;
103     private final CallsManager mCallsManager;
104     private final TelecomSystem.SyncRoot mLock;
105     private MediaSession mSession;
106     private KeyEvent mLastHookEvent;
107 
HeadsetMediaButton( Context context, CallsManager callsManager, TelecomSystem.SyncRoot lock)108     public HeadsetMediaButton(
109             Context context,
110             CallsManager callsManager,
111             TelecomSystem.SyncRoot lock) {
112         mContext = context;
113         mCallsManager = callsManager;
114         mLock = lock;
115 
116         // Create a MediaSession but don't enable it yet. This is a
117         // replacement for MediaButtonReceiver
118         mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_INITIALIZE).sendToTarget();
119     }
120 
121     /**
122      * Handles the wired headset button while in-call.
123      *
124      * @return true if we consumed the event.
125      */
handleCallMediaButton(KeyEvent event)126     private boolean handleCallMediaButton(KeyEvent event) {
127         Log.d(this, "handleCallMediaButton()...%s %s", event.getAction(), event.getRepeatCount());
128 
129         // Save ACTION_DOWN Event temporarily.
130         if (event.getAction() == KeyEvent.ACTION_DOWN) {
131             mLastHookEvent = event;
132         }
133 
134         if (event.isLongPress()) {
135             return mCallsManager.onMediaButton(LONG_PRESS);
136         } else if (event.getAction() == KeyEvent.ACTION_UP) {
137             // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
138             // return 0.
139             // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
140             if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
141                 return mCallsManager.onMediaButton(SHORT_PRESS);
142             }
143         }
144 
145         if (event.getAction() != KeyEvent.ACTION_DOWN) {
146             mLastHookEvent = null;
147         }
148 
149         return true;
150     }
151 
152     /** ${inheritDoc} */
153     @Override
onCallAdded(Call call)154     public void onCallAdded(Call call) {
155         if (call.isExternalCall()) {
156             return;
157         }
158         mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
159     }
160 
161     /** ${inheritDoc} */
162     @Override
onCallRemoved(Call call)163     public void onCallRemoved(Call call) {
164         if (call.isExternalCall()) {
165             return;
166         }
167         if (!mCallsManager.hasAnyCalls()) {
168             mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
169         }
170     }
171 
172     /** ${inheritDoc} */
173     @Override
onExternalCallChanged(Call call, boolean isExternalCall)174     public void onExternalCallChanged(Call call, boolean isExternalCall) {
175         if (isExternalCall) {
176             onCallRemoved(call);
177         } else {
178             onCallAdded(call);
179         }
180     }
181 }
182