blob: f72b5987d7accb3141a8c4f7634284fb4b83fb9f [file] [log] [blame]
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001/*
2 * Copyright (C) 2013 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
John Spurlock61560172015-02-06 19:46:04 -050017package com.android.server.audio;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070018
19import android.app.Activity;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -070020import android.app.ActivityManager;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070021import android.app.AppOpsManager;
22import android.app.KeyguardManager;
23import android.app.PendingIntent;
24import android.app.PendingIntent.CanceledException;
25import android.app.PendingIntent.OnFinished;
26import android.content.ActivityNotFoundException;
27import android.content.BroadcastReceiver;
28import android.content.ComponentName;
29import android.content.ContentResolver;
30import android.content.Context;
31import android.content.Intent;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070032import android.content.pm.PackageManager;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -070033import android.database.ContentObserver;
John Spurlock61560172015-02-06 19:46:04 -050034import android.media.AudioAttributes;
35import android.media.AudioFocusInfo;
36import android.media.AudioManager;
37import android.media.AudioSystem;
38import android.media.IAudioFocusDispatcher;
John Spurlock61560172015-02-06 19:46:04 -050039import android.media.IRemoteControlClient;
40import android.media.IRemoteControlDisplay;
41import android.media.IRemoteVolumeObserver;
42import android.media.RemoteControlClient;
Jean-Michel Trivi0212be52014-11-24 14:43:10 -080043import android.media.audiopolicy.IAudioPolicyCallback;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -070044import android.net.Uri;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070045import android.os.Binder;
46import android.os.Bundle;
47import android.os.Handler;
48import android.os.IBinder;
Dianne Hackbornb6683c42015-06-18 17:40:33 -070049import android.os.IDeviceIdleController;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070050import android.os.Looper;
51import android.os.Message;
52import android.os.PowerManager;
53import android.os.RemoteException;
Dianne Hackbornb6683c42015-06-18 17:40:33 -070054import android.os.ServiceManager;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070055import android.os.UserHandle;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070056import android.provider.Settings;
57import android.speech.RecognizerIntent;
58import android.telephony.PhoneStateListener;
59import android.telephony.TelephonyManager;
60import android.util.Log;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -070061import android.util.Slog;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070062import android.view.KeyEvent;
63
John Spurlock61560172015-02-06 19:46:04 -050064import com.android.server.audio.PlayerRecord.RemotePlaybackState;
65
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070066import java.io.PrintWriter;
67import java.util.ArrayList;
Jean-Michel Trivi545fcf82015-04-07 09:45:35 -070068import java.util.Date;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070069import java.util.Iterator;
70import java.util.Stack;
Jean-Michel Trivi545fcf82015-04-07 09:45:35 -070071import java.text.DateFormat;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070072
73/**
74 * @hide
75 *
76 */
77public class MediaFocusControl implements OnFinished {
78
79 private static final String TAG = "MediaFocusControl";
80
81 /** Debug remote control client/display feature */
82 protected static final boolean DEBUG_RC = false;
83 /** Debug volumes */
84 protected static final boolean DEBUG_VOL = false;
85
86 /** Used to alter media button redirection when the phone is ringing. */
87 private boolean mIsRinging = false;
88
89 private final PowerManager.WakeLock mMediaEventWakeLock;
Jean-Michel Trivi223fd632014-03-24 11:00:13 -070090 private final MediaEventHandler mEventHandler;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070091 private final Context mContext;
92 private final ContentResolver mContentResolver;
RoboErikd09bd0c2014-06-24 17:45:19 -070093 private final AudioService.VolumeController mVolumeController;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070094 private final AppOpsManager mAppOps;
95 private final KeyguardManager mKeyguardManager;
96 private final AudioService mAudioService;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -070097 private final NotificationListenerObserver mNotifListenerObserver;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -070098
99 protected MediaFocusControl(Looper looper, Context cntxt,
RoboErikd09bd0c2014-06-24 17:45:19 -0700100 AudioService.VolumeController volumeCtrl, AudioService as) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700101 mEventHandler = new MediaEventHandler(looper);
102 mContext = cntxt;
103 mContentResolver = mContext.getContentResolver();
104 mVolumeController = volumeCtrl;
105 mAudioService = as;
106
107 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
108 mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent");
John Spurlockb6e19e32015-03-10 21:33:44 -0400109 int maxMusicLevel = as.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
110 mMainRemote = new RemotePlaybackState(-1, maxMusicLevel, maxMusicLevel);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700111
112 // Register for phone state monitoring
113 TelephonyManager tmgr = (TelephonyManager)
114 mContext.getSystemService(Context.TELEPHONY_SERVICE);
115 tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
116
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700117 mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
118 mKeyguardManager =
119 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700120 mNotifListenerObserver = new NotificationListenerObserver();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700121
122 mHasRemotePlayback = false;
123 mMainRemoteIsActive = false;
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -0700124
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700125 PlayerRecord.setMediaFocusControl(this);
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -0700126
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700127 postReevaluateRemote();
128 }
129
Jean-Michel Trivi73673ab2013-08-06 10:42:45 -0700130 protected void dump(PrintWriter pw) {
Jean-Michel Trivi545fcf82015-04-07 09:45:35 -0700131 pw.println("\nMediaFocusControl dump time: "
132 + DateFormat.getTimeInstance().format(new Date()));
Jean-Michel Trivi73673ab2013-08-06 10:42:45 -0700133 dumpFocusStack(pw);
134 dumpRCStack(pw);
135 dumpRCCStack(pw);
136 dumpRCDList(pw);
137 }
138
139 //==========================================================================================
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700140 // Management of RemoteControlDisplay registration permissions
141 //==========================================================================================
142 private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI =
143 Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS);
144
145 private class NotificationListenerObserver extends ContentObserver {
146
147 NotificationListenerObserver() {
148 super(mEventHandler);
149 mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
150 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this);
151 }
152
153 @Override
154 public void onChange(boolean selfChange, Uri uri) {
155 if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) {
156 return;
157 }
158 if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); }
159 postReevaluateRemoteControlDisplays();
160 }
161 }
162
163 private final static int RCD_REG_FAILURE = 0;
164 private final static int RCD_REG_SUCCESS_PERMISSION = 1;
165 private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2;
166
167 /**
168 * Checks a caller's authorization to register an IRemoteControlDisplay.
169 * Authorization is granted if one of the following is true:
170 * <ul>
171 * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li>
172 * <li>the caller's listener is one of the enabled notification listeners</li>
173 * </ul>
174 * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay
175 * registration.
176 */
177 private int checkRcdRegistrationAuthorization(ComponentName listenerComp) {
178 // MEDIA_CONTENT_CONTROL permission check
179 if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission(
180 android.Manifest.permission.MEDIA_CONTENT_CONTROL)) {
181 if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");}
182 return RCD_REG_SUCCESS_PERMISSION;
183 }
184
185 // ENABLED_NOTIFICATION_LISTENERS settings check
186 if (listenerComp != null) {
187 // this call is coming from an app, can't use its identity to read secure settings
188 final long ident = Binder.clearCallingIdentity();
189 try {
190 final int currentUser = ActivityManager.getCurrentUser();
191 final String enabledNotifListeners = Settings.Secure.getStringForUser(
192 mContext.getContentResolver(),
193 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
194 currentUser);
195 if (enabledNotifListeners != null) {
196 final String[] components = enabledNotifListeners.split(":");
197 for (int i=0; i<components.length; i++) {
198 final ComponentName component =
199 ComponentName.unflattenFromString(components[i]);
200 if (component != null) {
201 if (listenerComp.equals(component)) {
202 if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component +
203 " is authorized notification listener"); }
204 return RCD_REG_SUCCESS_ENABLED_NOTIF;
205 }
206 }
207 }
208 }
209 if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp +
210 " is not in list of ENABLED_NOTIFICATION_LISTENERS"); }
211 } finally {
212 Binder.restoreCallingIdentity(ident);
213 }
214 }
215
216 return RCD_REG_FAILURE;
217 }
218
219 protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h,
220 ComponentName listenerComp) {
221 int reg = checkRcdRegistrationAuthorization(listenerComp);
222 if (reg != RCD_REG_FAILURE) {
223 registerRemoteControlDisplay_int(rcd, w, h, listenerComp);
224 return true;
225 } else {
226 Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
227 ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
228 " or be an enabled NotificationListenerService for registerRemoteController");
229 return false;
230 }
231 }
232
233 protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) {
234 int reg = checkRcdRegistrationAuthorization(null);
235 if (reg != RCD_REG_FAILURE) {
236 registerRemoteControlDisplay_int(rcd, w, h, null);
237 return true;
238 } else {
239 Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() +
240 ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL +
241 " to register IRemoteControlDisplay");
242 return false;
243 }
244 }
245
246 private void postReevaluateRemoteControlDisplays() {
247 sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0);
248 }
249
250 private void onReevaluateRemoteControlDisplays() {
251 if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); }
252 // read which components are enabled notification listeners
253 final int currentUser = ActivityManager.getCurrentUser();
254 final String enabledNotifListeners = Settings.Secure.getStringForUser(
255 mContext.getContentResolver(),
256 Settings.Secure.ENABLED_NOTIFICATION_LISTENERS,
257 currentUser);
258 if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); }
259 synchronized(mAudioFocusLock) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700260 synchronized(mPRStack) {
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700261 // check whether the "enable" status of each RCD with a notification listener
262 // has changed
263 final String[] enabledComponents;
264 if (enabledNotifListeners == null) {
265 enabledComponents = null;
266 } else {
267 enabledComponents = enabledNotifListeners.split(":");
268 }
269 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
270 while (displayIterator.hasNext()) {
271 final DisplayInfoForServer di =
RoboErik01fe6612014-02-13 14:19:04 -0800272 displayIterator.next();
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700273 if (di.mClientNotifListComp != null) {
274 boolean wasEnabled = di.mEnabled;
275 di.mEnabled = isComponentInStringArray(di.mClientNotifListComp,
276 enabledComponents);
277 if (wasEnabled != di.mEnabled){
278 try {
279 // tell the RCD whether it's enabled
280 di.mRcDisplay.setEnabled(di.mEnabled);
281 // tell the RCCs about the change for this RCD
282 enableRemoteControlDisplayForClient_syncRcStack(
283 di.mRcDisplay, di.mEnabled);
Jean-Michel Trivi19566542013-10-07 17:10:08 -0700284 // when enabling, refresh the information on the display
285 if (di.mEnabled) {
286 sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
287 di.mArtworkExpectedWidth /*arg1*/,
288 di.mArtworkExpectedHeight/*arg2*/,
289 di.mRcDisplay /*obj*/, 0/*delay*/);
290 }
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700291 } catch (RemoteException e) {
292 Log.e(TAG, "Error en/disabling RCD: ", e);
293 }
294 }
295 }
296 }
297 }
298 }
299 }
300
301 /**
302 * @param comp a non-null ComponentName
303 * @param enabledArray may be null
304 * @return
305 */
306 private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) {
307 if (enabledArray == null || enabledArray.length == 0) {
308 if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); }
309 return false;
310 }
311 final String compString = comp.flattenToString();
312 for (int i=0; i<enabledArray.length; i++) {
313 if (compString.equals(enabledArray[i])) {
314 if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); }
315 return true;
316 }
317 }
318 if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); }
319 return false;
320 }
321
322 //==========================================================================================
Jean-Michel Trivi73673ab2013-08-06 10:42:45 -0700323 // Internal event handling
324 //==========================================================================================
325
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700326 // event handler messages
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700327 private static final int MSG_RCDISPLAY_CLEAR = 1;
328 private static final int MSG_RCDISPLAY_UPDATE = 2;
329 private static final int MSG_REEVALUATE_REMOTE = 3;
330 private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4;
331 private static final int MSG_RCC_NEW_VOLUME_OBS = 5;
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -0700332 private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6;
333 private static final int MSG_RCC_SEEK_REQUEST = 7;
334 private static final int MSG_RCC_UPDATE_METADATA = 8;
335 private static final int MSG_RCDISPLAY_INIT_INFO = 9;
336 private static final int MSG_REEVALUATE_RCD = 10;
337 private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700338
339 // sendMsg() flags
340 /** If the msg is already queued, replace it with this one. */
341 private static final int SENDMSG_REPLACE = 0;
342 /** If the msg is already queued, ignore this one and leave the old. */
343 private static final int SENDMSG_NOOP = 1;
344 /** If the msg is already queued, queue this one and leave the old. */
345 private static final int SENDMSG_QUEUE = 2;
346
347 private static void sendMsg(Handler handler, int msg,
348 int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) {
349
350 if (existingMsgPolicy == SENDMSG_REPLACE) {
351 handler.removeMessages(msg);
352 } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) {
353 return;
354 }
355
356 handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay);
357 }
358
Jean-Michel Trivi223fd632014-03-24 11:00:13 -0700359 private class MediaEventHandler extends Handler {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700360 MediaEventHandler(Looper looper) {
361 super(looper);
362 }
363
364 @Override
365 public void handleMessage(Message msg) {
366 switch(msg.what) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700367 case MSG_RCDISPLAY_CLEAR:
368 onRcDisplayClear();
369 break;
370
371 case MSG_RCDISPLAY_UPDATE:
372 // msg.obj is guaranteed to be non null
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700373 onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700374 break;
375
376 case MSG_REEVALUATE_REMOTE:
377 onReevaluateRemote();
378 break;
379
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700380 case MSG_RCC_NEW_VOLUME_OBS:
381 onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */,
382 (IRemoteVolumeObserver)msg.obj /* rvo */);
383 break;
Jean-Michel Trivif823fc42013-08-20 09:04:56 -0700384
Jean-Michel Trivi86142da2013-09-29 13:03:58 -0700385 case MSG_RCDISPLAY_INIT_INFO:
386 // msg.obj is guaranteed to be non null
387 onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/,
388 msg.arg1/*w*/, msg.arg2/*h*/);
389 break;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -0700390
391 case MSG_REEVALUATE_RCD:
392 onReevaluateRemoteControlDisplays();
393 break;
Jean-Michel Trivi223fd632014-03-24 11:00:13 -0700394
395 case MSG_UNREGISTER_MEDIABUTTONINTENT:
396 unregisterMediaButtonIntent( (PendingIntent) msg.obj );
397 break;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700398 }
399 }
400 }
401
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700402
403 //==========================================================================================
404 // AudioFocus
405 //==========================================================================================
406
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700407 private final static Object mAudioFocusLock = new Object();
408
409 private final static Object mRingingLock = new Object();
410
411 private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
412 @Override
413 public void onCallStateChanged(int state, String incomingNumber) {
414 if (state == TelephonyManager.CALL_STATE_RINGING) {
415 //Log.v(TAG, " CALL_STATE_RINGING");
416 synchronized(mRingingLock) {
417 mIsRinging = true;
418 }
419 } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK)
420 || (state == TelephonyManager.CALL_STATE_IDLE)) {
421 synchronized(mRingingLock) {
422 mIsRinging = false;
423 }
424 }
425 }
426 };
427
428 /**
429 * Discard the current audio focus owner.
430 * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
431 * focus), remove it from the stack, and clear the remote control display.
432 */
433 protected void discardAudioFocusOwner() {
434 synchronized(mAudioFocusLock) {
Jean-Michel Trivicbb212f2013-07-30 15:09:33 -0700435 if (!mFocusStack.empty()) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700436 // notify the current focus owner it lost focus after removing it from stack
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700437 final FocusRequester exFocusOwner = mFocusStack.pop();
Jean-Michel Trivi00bf4b12013-07-26 17:19:32 -0700438 exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS);
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700439 exFocusOwner.release();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700440 }
441 }
442 }
443
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800444 /**
445 * Called synchronized on mAudioFocusLock
446 */
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700447 private void notifyTopOfAudioFocusStack() {
448 // notify the top of the stack it gained focus
Jean-Michel Trivicbb212f2013-07-30 15:09:33 -0700449 if (!mFocusStack.empty()) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700450 if (canReassignAudioFocus()) {
Jean-Michel Trivi00bf4b12013-07-26 17:19:32 -0700451 mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700452 }
453 }
454 }
455
Jean-Michel Trivicbb212f2013-07-30 15:09:33 -0700456 /**
457 * Focus is requested, propagate the associated loss throughout the stack.
458 * @param focusGain the new focus gain that will later be added at the top of the stack
459 */
460 private void propagateFocusLossFromGain_syncAf(int focusGain) {
461 // going through the audio focus stack to signal new focus, traversing order doesn't
462 // matter as all entries respond to the same external focus gain
463 Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
464 while(stackIterator.hasNext()) {
465 stackIterator.next().handleExternalFocusGain(focusGain);
466 }
467 }
468
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700469 private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700470
471 /**
472 * Helper function:
473 * Display in the log the current entries in the audio focus stack
474 */
475 private void dumpFocusStack(PrintWriter pw) {
476 pw.println("\nAudio Focus stack entries (last is top of stack):");
477 synchronized(mAudioFocusLock) {
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700478 Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700479 while(stackIterator.hasNext()) {
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700480 stackIterator.next().dump(pw);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700481 }
482 }
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800483 pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n");
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700484 }
485
486 /**
487 * Helper function:
488 * Called synchronized on mAudioFocusLock
489 * Remove a focus listener from the focus stack.
490 * @param clientToRemove the focus listener
491 * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
492 * focus, notify the next item in the stack it gained focus.
493 */
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800494 private void removeFocusStackEntry(String clientToRemove, boolean signal,
495 boolean notifyFocusFollowers) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700496 // is the current top of the focus stack abandoning focus? (because of request, not death)
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700497 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700498 {
499 //Log.i(TAG, " removeFocusStackEntry() removing top of stack");
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700500 FocusRequester fr = mFocusStack.pop();
501 fr.release();
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800502 if (notifyFocusFollowers) {
503 final AudioFocusInfo afi = fr.toAudioFocusInfo();
504 afi.clearLossReceived();
505 notifyExtPolicyFocusLoss_syncAf(afi, false);
506 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700507 if (signal) {
508 // notify the new top of the stack it gained focus
509 notifyTopOfAudioFocusStack();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700510 }
511 } else {
512 // focus is abandoned by a client that's not at the top of the stack,
513 // no need to update focus.
514 // (using an iterator on the stack so we can safely remove an entry after having
515 // evaluated it, traversal order doesn't matter here)
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700516 Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700517 while(stackIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -0800518 FocusRequester fr = stackIterator.next();
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700519 if(fr.hasSameClient(clientToRemove)) {
Jean-Michel Trivi00bf4b12013-07-26 17:19:32 -0700520 Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for "
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700521 + clientToRemove);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700522 stackIterator.remove();
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700523 fr.release();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700524 }
525 }
526 }
527 }
528
529 /**
530 * Helper function:
531 * Called synchronized on mAudioFocusLock
532 * Remove focus listeners from the focus stack for a particular client when it has died.
533 */
534 private void removeFocusStackEntryForClient(IBinder cb) {
535 // is the owner of the audio focus part of the client to remove?
536 boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700537 mFocusStack.peek().hasSameBinder(cb);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700538 // (using an iterator on the stack so we can safely remove an entry after having
539 // evaluated it, traversal order doesn't matter here)
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700540 Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700541 while(stackIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -0800542 FocusRequester fr = stackIterator.next();
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700543 if(fr.hasSameBinder(cb)) {
544 Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700545 stackIterator.remove();
546 // the client just died, no need to unlink to its death
547 }
548 }
549 if (isTopOfStackForClientToRemove) {
550 // we removed an entry at the top of the stack:
551 // notify the new top of the stack it gained focus.
552 notifyTopOfAudioFocusStack();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700553 }
554 }
555
556 /**
557 * Helper function:
558 * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800559 * The implementation guarantees that a state where focus cannot be immediately reassigned
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800560 * implies that an "locked" focus owner is at the top of the focus stack.
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800561 * Modifications to the implementation that break this assumption will cause focus requests to
562 * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700563 */
564 private boolean canReassignAudioFocus() {
565 // focus requests are rejected during a phone call or when the phone is ringing
566 // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800567 if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700568 return false;
569 }
570 return true;
571 }
572
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800573 private boolean isLockedFocusOwner(FocusRequester fr) {
John Spurlock61560172015-02-06 19:46:04 -0500574 return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800575 }
576
577 /**
578 * Helper function
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800579 * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800580 * at the top of the focus stack
581 * Push the focus requester onto the audio focus stack at the first position immediately
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800582 * following the locked focus owners.
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800583 * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
584 * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
585 */
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800586 private int pushBelowLockedFocusOwners(FocusRequester nfr) {
587 int lastLockedFocusOwnerIndex = mFocusStack.size();
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800588 for (int index = mFocusStack.size()-1; index >= 0; index--) {
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800589 if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
590 lastLockedFocusOwnerIndex = index;
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800591 }
592 }
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800593 if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800594 // this should not happen, but handle it and log an error
595 Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
596 new Exception());
597 // no exclusive owner, push at top of stack, focus is granted, propagate change
598 propagateFocusLossFromGain_syncAf(nfr.getGainRequest());
599 mFocusStack.push(nfr);
600 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
601 } else {
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800602 mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800603 return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
604 }
605 }
606
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700607 /**
608 * Inner class to monitor audio focus client deaths, and remove them from the audio focus
609 * stack if necessary.
610 */
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700611 protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700612 private IBinder mCb; // To be notified of client's death
613
614 AudioFocusDeathHandler(IBinder cb) {
615 mCb = cb;
616 }
617
618 public void binderDied() {
619 synchronized(mAudioFocusLock) {
620 Log.w(TAG, " AudioFocus audio focus client died");
621 removeFocusStackEntryForClient(mCb);
622 }
623 }
624
625 public IBinder getBinder() {
626 return mCb;
627 }
628 }
629
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800630 /**
631 * Indicates whether to notify an audio focus owner when it loses focus
632 * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
633 * This variable being false indicates an AudioPolicy has been registered and has signaled
634 * it will handle audio ducking.
635 */
636 private boolean mNotifyFocusOwnerOnDuck = true;
637
638 protected void setDuckingInExtPolicyAvailable(boolean available) {
639 mNotifyFocusOwnerOnDuck = !available;
640 }
641
642 boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
643
644 private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
645
646 void addFocusFollower(IAudioPolicyCallback ff) {
647 if (ff == null) {
648 return;
649 }
650 synchronized(mAudioFocusLock) {
651 boolean found = false;
652 for (IAudioPolicyCallback pcb : mFocusFollowers) {
653 if (pcb.asBinder().equals(ff.asBinder())) {
654 found = true;
655 break;
656 }
657 }
658 if (found) {
659 return;
660 } else {
661 mFocusFollowers.add(ff);
Jean-Michel Trivi60fd0842015-03-13 10:11:28 -0700662 notifyExtPolicyCurrentFocusAsync(ff);
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800663 }
664 }
665 }
666
667 void removeFocusFollower(IAudioPolicyCallback ff) {
668 if (ff == null) {
669 return;
670 }
671 synchronized(mAudioFocusLock) {
672 for (IAudioPolicyCallback pcb : mFocusFollowers) {
673 if (pcb.asBinder().equals(ff.asBinder())) {
674 mFocusFollowers.remove(pcb);
675 break;
676 }
677 }
678 }
679 }
680
681 /**
Jean-Michel Trivi60fd0842015-03-13 10:11:28 -0700682 * @param pcb non null
683 */
684 void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) {
685 final IAudioPolicyCallback pcb2 = pcb;
686 final Thread thread = new Thread() {
687 @Override
688 public void run() {
689 synchronized(mAudioFocusLock) {
690 if (mFocusStack.isEmpty()) {
691 return;
692 }
693 try {
694 pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(),
695 // top of focus stack always has focus
696 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
697 } catch (RemoteException e) {
698 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
699 + pcb2.asBinder(), e);
700 }
701 }
702 }
703 };
704 thread.start();
705 }
706
707 /**
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800708 * Called synchronized on mAudioFocusLock
709 */
710 void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
711 for (IAudioPolicyCallback pcb : mFocusFollowers) {
712 try {
713 // oneway
714 pcb.notifyAudioFocusGrant(afi, requestResult);
715 } catch (RemoteException e) {
Jean-Michel Trivi60fd0842015-03-13 10:11:28 -0700716 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800717 + pcb.asBinder(), e);
718 }
719 }
720 }
721
722 /**
723 * Called synchronized on mAudioFocusLock
724 */
725 void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
726 for (IAudioPolicyCallback pcb : mFocusFollowers) {
727 try {
728 // oneway
729 pcb.notifyAudioFocusLoss(afi, wasDispatched);
730 } catch (RemoteException e) {
Jean-Michel Trivi60fd0842015-03-13 10:11:28 -0700731 Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback "
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800732 + pcb.asBinder(), e);
733 }
734 }
735 }
736
Jean-Michel Trivi23805662013-07-31 14:19:18 -0700737 protected int getCurrentAudioFocus() {
738 synchronized(mAudioFocusLock) {
739 if (mFocusStack.empty()) {
740 return AudioManager.AUDIOFOCUS_NONE;
741 } else {
742 return mFocusStack.peek().getGainRequest();
743 }
744 }
745 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700746
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800747 /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
748 protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
749 IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) {
750 Log.i(TAG, " AudioFocus requestAudioFocus() from " + clientId + " req=" + focusChangeHint +
751 "flags=0x" + Integer.toHexString(flags));
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700752 // we need a valid binder callback for clients
753 if (!cb.pingBinder()) {
754 Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
755 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
756 }
757
758 if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
759 callingPackageName) != AppOpsManager.MODE_ALLOWED) {
760 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
761 }
762
763 synchronized(mAudioFocusLock) {
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800764 boolean focusGrantDelayed = false;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700765 if (!canReassignAudioFocus()) {
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800766 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
767 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
768 } else {
769 // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
770 // granted right now, so the requester will be inserted in the focus stack
771 // to receive focus later
772 focusGrantDelayed = true;
773 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700774 }
775
776 // handle the potential premature death of the new holder of the focus
777 // (premature death == death before abandoning focus)
778 // Register for client death notification
779 AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
780 try {
781 cb.linkToDeath(afdh, 0);
782 } catch (RemoteException e) {
783 // client has already died!
784 Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death");
785 return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
786 }
787
Jean-Michel Trivi83283f22013-07-29 18:09:41 -0700788 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700789 // if focus is already owned by this client and the reason for acquiring the focus
790 // hasn't changed, don't do anything
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800791 final FocusRequester fr = mFocusStack.peek();
792 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700793 // unlink death handler so it can be gc'ed.
794 // linkToDeath() creates a JNI global reference preventing collection.
795 cb.unlinkToDeath(afdh, 0);
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800796 notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
797 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700798 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
799 }
800 // the reason for the audio focus request has changed: remove the current top of
801 // stack and respond as if we had a new focus owner
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800802 if (!focusGrantDelayed) {
803 mFocusStack.pop();
804 // the entry that was "popped" is the same that was "peeked" above
805 fr.release();
806 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700807 }
808
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700809 // focus requester might already be somewhere below in the stack, remove it
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800810 removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700811
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800812 final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800813 clientId, afdh, callingPackageName, Binder.getCallingUid(), this);
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800814 if (focusGrantDelayed) {
815 // focusGrantDelayed being true implies we can't reassign focus right now
816 // which implies the focus stack is not empty.
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800817 final int requestResult = pushBelowLockedFocusOwners(nfr);
818 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
819 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
820 }
821 return requestResult;
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800822 } else {
823 // propagate the focus change through the stack
824 if (!mFocusStack.empty()) {
825 propagateFocusLossFromGain_syncAf(focusChangeHint);
826 }
Jean-Michel Trivicbb212f2013-07-30 15:09:33 -0700827
Jean-Michel Trivifd6ad742014-11-10 14:38:30 -0800828 // push focus requester at the top of the audio focus stack
829 mFocusStack.push(nfr);
830 }
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800831 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
832 AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700833
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700834 }//synchronized(mAudioFocusLock)
835
836 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
837 }
838
Jean-Michel Trivi958876f2014-11-16 15:40:22 -0800839 /**
840 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
841 * */
842 protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) {
843 // AudioAttributes are currently ignored, to be used for zones
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700844 Log.i(TAG, " AudioFocus abandonAudioFocus() from " + clientId);
845 try {
846 // this will take care of notifying the new focus owner if needed
847 synchronized(mAudioFocusLock) {
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800848 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700849 }
850 } catch (java.util.ConcurrentModificationException cme) {
851 // Catching this exception here is temporary. It is here just to prevent
852 // a crash seen when the "Silent" notification is played. This is believed to be fixed
853 // but this try catch block is left just to be safe.
854 Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme);
855 cme.printStackTrace();
856 }
857
858 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
859 }
860
861
862 protected void unregisterAudioFocusClient(String clientId) {
863 synchronized(mAudioFocusLock) {
Jean-Michel Trivi0212be52014-11-24 14:43:10 -0800864 removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700865 }
866 }
867
868
869 //==========================================================================================
870 // RemoteControl
871 //==========================================================================================
Jean-Michel Trivia83487e2013-09-17 21:19:30 -0700872 /**
873 * No-op if the key code for keyEvent is not a valid media key
874 * (see {@link #isValidMediaKeyEvent(KeyEvent)})
875 * @param keyEvent the key event to send
876 */
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700877 protected void dispatchMediaKeyEvent(KeyEvent keyEvent) {
878 filterMediaKeyEvent(keyEvent, false /*needWakeLock*/);
879 }
880
Jean-Michel Trivia83487e2013-09-17 21:19:30 -0700881 /**
882 * No-op if the key code for keyEvent is not a valid media key
883 * (see {@link #isValidMediaKeyEvent(KeyEvent)})
884 * @param keyEvent the key event to send
885 */
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700886 protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) {
887 filterMediaKeyEvent(keyEvent, true /*needWakeLock*/);
888 }
889
890 private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
891 // sanity check on the incoming key event
892 if (!isValidMediaKeyEvent(keyEvent)) {
893 Log.e(TAG, "not dispatching invalid media key event " + keyEvent);
894 return;
895 }
896 // event filtering for telephony
897 synchronized(mRingingLock) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700898 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700899 if ((mMediaReceiverForCalls != null) &&
900 (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) {
901 dispatchMediaKeyEventForCalls(keyEvent, needWakeLock);
902 return;
903 }
904 }
905 }
906 // event filtering based on voice-based interactions
907 if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) {
908 filterVoiceInputKeyEvent(keyEvent, needWakeLock);
909 } else {
910 dispatchMediaKeyEvent(keyEvent, needWakeLock);
911 }
912 }
913
914 /**
915 * Handles the dispatching of the media button events to the telephony package.
916 * Precondition: mMediaReceiverForCalls != null
917 * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
918 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
919 * is dispatched.
920 */
921 private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) {
922 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
923 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
924 keyIntent.setPackage(mMediaReceiverForCalls.getPackageName());
925 if (needWakeLock) {
926 mMediaEventWakeLock.acquire();
927 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
928 }
929 final long ident = Binder.clearCallingIdentity();
930 try {
931 mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
932 null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null);
933 } finally {
934 Binder.restoreCallingIdentity(ident);
935 }
936 }
937
938 /**
939 * Handles the dispatching of the media button events to one of the registered listeners,
940 * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system.
941 * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons
942 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
943 * is dispatched.
944 */
945 private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
946 if (needWakeLock) {
947 mMediaEventWakeLock.acquire();
948 }
949 Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null);
950 keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700951 synchronized(mPRStack) {
952 if (!mPRStack.empty()) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700953 // send the intent that was registered by the client
954 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700955 mPRStack.peek().getMediaButtonIntent().send(mContext,
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700956 needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/,
957 keyIntent, this, mEventHandler);
958 } catch (CanceledException e) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -0700959 Log.e(TAG, "Error sending pending intent " + mPRStack.peek());
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -0700960 e.printStackTrace();
961 }
962 } else {
963 // legacy behavior when nobody registered their media button event receiver
964 // through AudioManager
965 if (needWakeLock) {
966 keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED);
967 }
968 final long ident = Binder.clearCallingIdentity();
969 try {
970 mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL,
971 null, mKeyEventDone,
972 mEventHandler, Activity.RESULT_OK, null, null);
973 } finally {
974 Binder.restoreCallingIdentity(ident);
975 }
976 }
977 }
978 }
979
980 /**
981 * The different actions performed in response to a voice button key event.
982 */
983 private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1;
984 private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2;
985 private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3;
986
987 private final Object mVoiceEventLock = new Object();
988 private boolean mVoiceButtonDown;
989 private boolean mVoiceButtonHandled;
990
991 /**
992 * Filter key events that may be used for voice-based interactions
993 * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported
994 * media buttons that can be used to trigger voice-based interactions.
995 * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event
996 * is dispatched.
997 */
998 private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) {
999 if (DEBUG_RC) {
1000 Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock);
1001 }
1002
1003 int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS;
1004 int keyAction = keyEvent.getAction();
1005 synchronized (mVoiceEventLock) {
1006 if (keyAction == KeyEvent.ACTION_DOWN) {
1007 if (keyEvent.getRepeatCount() == 0) {
1008 // initial down
1009 mVoiceButtonDown = true;
1010 mVoiceButtonHandled = false;
1011 } else if (mVoiceButtonDown && !mVoiceButtonHandled
1012 && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) {
1013 // long-press, start voice-based interactions
1014 mVoiceButtonHandled = true;
1015 voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT;
1016 }
1017 } else if (keyAction == KeyEvent.ACTION_UP) {
1018 if (mVoiceButtonDown) {
1019 // voice button up
1020 mVoiceButtonDown = false;
1021 if (!mVoiceButtonHandled && !keyEvent.isCanceled()) {
1022 voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS;
1023 }
1024 }
1025 }
1026 }//synchronized (mVoiceEventLock)
1027
1028 // take action after media button event filtering for voice-based interactions
1029 switch (voiceButtonAction) {
1030 case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS:
1031 if (DEBUG_RC) Log.v(TAG, " ignore key event");
1032 break;
1033 case VOICEBUTTON_ACTION_START_VOICE_INPUT:
1034 if (DEBUG_RC) Log.v(TAG, " start voice-based interactions");
1035 // then start the voice-based interactions
1036 startVoiceBasedInteractions(needWakeLock);
1037 break;
1038 case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS:
1039 if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock);
1040 sendSimulatedMediaButtonEvent(keyEvent, needWakeLock);
1041 break;
1042 }
1043 }
1044
1045 private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) {
1046 // send DOWN event
1047 KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN);
1048 dispatchMediaKeyEvent(keyEvent, needWakeLock);
1049 // send UP event
1050 keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP);
1051 dispatchMediaKeyEvent(keyEvent, needWakeLock);
1052
1053 }
1054
Jean-Michel Trivi7ddd2262013-09-01 18:06:45 -07001055 private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) {
1056 if (keyEvent == null) {
1057 return false;
1058 }
RoboErik01fe6612014-02-13 14:19:04 -08001059 return KeyEvent.isMediaKey(keyEvent.getKeyCode());
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001060 }
1061
1062 /**
1063 * Checks whether the given key code is one that can trigger the launch of voice-based
1064 * interactions.
1065 * @param keyCode the key code associated with the key event
1066 * @return true if the key is one of the supported voice-based interaction triggers
1067 */
1068 private static boolean isValidVoiceInputKeyCode(int keyCode) {
1069 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) {
1070 return true;
1071 } else {
1072 return false;
1073 }
1074 }
1075
1076 /**
1077 * Tell the system to start voice-based interactions / voice commands
1078 */
1079 private void startVoiceBasedInteractions(boolean needWakeLock) {
1080 Intent voiceIntent = null;
1081 // select which type of search to launch:
1082 // - screen on and device unlocked: action is ACTION_WEB_SEARCH
1083 // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE
1084 // with EXTRA_SECURE set to true if the device is securely locked
1085 PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE);
1086 boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1087 if (!isLocked && pm.isScreenOn()) {
1088 voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
1089 Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH");
1090 } else {
Dianne Hackbornb6683c42015-06-18 17:40:33 -07001091 IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface(
1092 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
1093 if (dic != null) {
1094 try {
1095 dic.exitIdle("voice-search");
1096 } catch (RemoteException e) {
1097 }
1098 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001099 voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
1100 voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE,
1101 isLocked && mKeyguardManager.isKeyguardSecure());
1102 Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE");
1103 }
1104 // start the search activity
1105 if (needWakeLock) {
1106 mMediaEventWakeLock.acquire();
1107 }
Jean-Michel Trividcd40c02013-07-22 16:46:42 -07001108 final long identity = Binder.clearCallingIdentity();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001109 try {
1110 if (voiceIntent != null) {
1111 voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1112 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Jean-Michel Trividcd40c02013-07-22 16:46:42 -07001113 mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001114 }
1115 } catch (ActivityNotFoundException e) {
1116 Log.w(TAG, "No activity for search: " + e);
1117 } finally {
Jean-Michel Trividcd40c02013-07-22 16:46:42 -07001118 Binder.restoreCallingIdentity(identity);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001119 if (needWakeLock) {
1120 mMediaEventWakeLock.release();
1121 }
1122 }
1123 }
1124
1125 private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number
1126
1127 // only set when wakelock was acquired, no need to check value when received
1128 private static final String EXTRA_WAKELOCK_ACQUIRED =
1129 "android.media.AudioService.WAKELOCK_ACQUIRED";
1130
1131 public void onSendFinished(PendingIntent pendingIntent, Intent intent,
1132 int resultCode, String resultData, Bundle resultExtras) {
1133 if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) {
1134 mMediaEventWakeLock.release();
1135 }
1136 }
1137
1138 BroadcastReceiver mKeyEventDone = new BroadcastReceiver() {
1139 public void onReceive(Context context, Intent intent) {
1140 if (intent == null) {
1141 return;
1142 }
1143 Bundle extras = intent.getExtras();
1144 if (extras == null) {
1145 return;
1146 }
1147 if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) {
1148 mMediaEventWakeLock.release();
1149 }
1150 }
1151 };
1152
1153 /**
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001154 * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001155 */
1156 private final Object mCurrentRcLock = new Object();
1157 /**
1158 * The one remote control client which will receive a request for display information.
1159 * This object may be null.
1160 * Access protected by mCurrentRcLock.
1161 */
1162 private IRemoteControlClient mCurrentRcClient = null;
Jean-Michel Trivi86142da2013-09-29 13:03:58 -07001163 /**
1164 * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant
1165 * if mCurrentRcClient is null
1166 */
1167 private PendingIntent mCurrentRcClientIntent = null;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001168
1169 private final static int RC_INFO_NONE = 0;
1170 private final static int RC_INFO_ALL =
1171 RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART |
1172 RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA |
1173 RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA |
1174 RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE;
1175
1176 /**
1177 * A monotonically increasing generation counter for mCurrentRcClient.
1178 * Only accessed with a lock on mCurrentRcLock.
1179 * No value wrap-around issues as we only act on equal values.
1180 */
1181 private int mCurrentRcClientGen = 0;
1182
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001183
1184 /**
1185 * Internal cache for the playback information of the RemoteControlClient whose volume gets to
1186 * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack
1187 * every time we need this info.
1188 */
1189 private RemotePlaybackState mMainRemote;
1190 /**
1191 * Indicates whether the "main" RemoteControlClient is considered active.
1192 * Use synchronized on mMainRemote.
1193 */
1194 private boolean mMainRemoteIsActive;
1195 /**
1196 * Indicates whether there is remote playback going on. True even if there is no "active"
1197 * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it
1198 * handles remote playback.
1199 * Use synchronized on mMainRemote.
1200 */
1201 private boolean mHasRemotePlayback;
1202
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001203 /**
Jean-Michel Trivif51e1132014-04-10 15:05:44 -07001204 * The stack of remote control event receivers.
1205 * All read and write operations on mPRStack are synchronized.
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001206 */
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001207 private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001208
1209 /**
1210 * The component the telephony package can register so telephony calls have priority to
1211 * handle media button events
1212 */
1213 private ComponentName mMediaReceiverForCalls = null;
1214
1215 /**
1216 * Helper function:
1217 * Display in the log the current entries in the remote control focus stack
1218 */
1219 private void dumpRCStack(PrintWriter pw) {
1220 pw.println("\nRemote Control stack entries (last is top of stack):");
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001221 synchronized(mPRStack) {
1222 Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001223 while(stackIterator.hasNext()) {
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -07001224 stackIterator.next().dump(pw, true);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001225 }
1226 }
1227 }
1228
1229 /**
1230 * Helper function:
1231 * Display in the log the current entries in the remote control stack, focusing
1232 * on RemoteControlClient data
1233 */
1234 private void dumpRCCStack(PrintWriter pw) {
1235 pw.println("\nRemote Control Client stack entries (last is top of stack):");
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001236 synchronized(mPRStack) {
1237 Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001238 while(stackIterator.hasNext()) {
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -07001239 stackIterator.next().dump(pw, false);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001240 }
1241 synchronized(mCurrentRcLock) {
1242 pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen);
1243 }
1244 }
1245 synchronized (mMainRemote) {
1246 pw.println("\nRemote Volume State:");
1247 pw.println(" has remote: " + mHasRemotePlayback);
1248 pw.println(" is remote active: " + mMainRemoteIsActive);
1249 pw.println(" rccId: " + mMainRemote.mRccId);
1250 pw.println(" volume handling: "
1251 + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ?
1252 "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)"));
1253 pw.println(" volume: " + mMainRemote.mVolume);
1254 pw.println(" volume steps: " + mMainRemote.mVolumeMax);
1255 }
1256 }
1257
1258 /**
1259 * Helper function:
1260 * Display in the log the current entries in the list of remote control displays
1261 */
1262 private void dumpRCDList(PrintWriter pw) {
1263 pw.println("\nRemote Control Display list entries:");
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001264 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001265 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1266 while (displayIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -08001267 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001268 pw.println(" IRCD: " + di.mRcDisplay +
1269 " -- w:" + di.mArtworkExpectedWidth +
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001270 " -- h:" + di.mArtworkExpectedHeight +
1271 " -- wantsPosSync:" + di.mWantsPositionSync +
1272 " -- " + (di.mEnabled ? "enabled" : "disabled"));
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001273 }
1274 }
1275 }
1276
1277 /**
1278 * Helper function:
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001279 * Push the new media button receiver "near" the top of the PlayerRecord stack.
1280 * "Near the top" is defined as:
1281 * - at the top if the current PlayerRecord at the top is not playing
1282 * - below the entries at the top of the stack that correspond to the playing PlayerRecord
1283 * otherwise
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001284 * Called synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001285 * precondition: mediaIntent != null
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001286 * @return true if the top of mPRStack was changed, false otherwise
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001287 */
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001288 private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent,
Jean-Michel Trivi0b605342013-10-23 14:48:12 -07001289 ComponentName target, IBinder token) {
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001290 if (mPRStack.empty()) {
1291 mPRStack.push(new PlayerRecord(mediaIntent, target, token));
1292 return true;
1293 } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) {
1294 // already at top of stack
Jean-Michel Trivi0b605342013-10-23 14:48:12 -07001295 return false;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001296 }
1297 if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(),
1298 mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) {
Jean-Michel Trivi0b605342013-10-23 14:48:12 -07001299 return false;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001300 }
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001301 PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes
1302 boolean topChanged = false;
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001303 PlayerRecord prse = null;
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001304 int lastPlayingIndex = mPRStack.size();
1305 int inStackIndex = -1;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001306 try {
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001307 // go through the stack from the top to figure out who's playing, and the position
1308 // of this media button receiver (note that it may not be in the stack)
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001309 for (int index = mPRStack.size()-1; index >= 0; index--) {
1310 prse = mPRStack.elementAt(index);
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001311 if (prse.isPlaybackActive()) {
1312 lastPlayingIndex = index;
1313 }
1314 if (prse.hasMatchingMediaButtonIntent(mediaIntent)) {
1315 inStackIndex = index;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001316 }
1317 }
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001318
1319 if (inStackIndex == -1) {
1320 // is not in stack
1321 prse = new PlayerRecord(mediaIntent, target, token);
1322 // it's new so it's not playing (no RemoteControlClient to give a playstate),
1323 // therefore it goes after the ones with active playback
1324 mPRStack.add(lastPlayingIndex, prse);
1325 } else {
1326 // is in the stack
1327 if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1
1328 prse = mPRStack.elementAt(inStackIndex);
1329 // remove it from its old location in the stack
1330 mPRStack.removeElementAt(inStackIndex);
1331 if (prse.isPlaybackActive()) {
1332 // and put it at the top
1333 mPRStack.push(prse);
1334 } else {
1335 // and put it after the ones with active playback
Jean-Michel Trivi0d908762014-04-29 16:11:36 -07001336 if (inStackIndex > lastPlayingIndex) {
1337 mPRStack.add(lastPlayingIndex, prse);
1338 } else {
1339 mPRStack.add(lastPlayingIndex - 1, prse);
1340 }
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001341 }
1342 }
1343 }
1344
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001345 } catch (ArrayIndexOutOfBoundsException e) {
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001346 // not expected to happen, indicates improper concurrent modification or bad index
1347 Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex
1348 + " size=" + mPRStack.size()
1349 + " accessing media button stack", e);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001350 }
Jean-Michel Trivi0b605342013-10-23 14:48:12 -07001351
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07001352 return (topChanged);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001353 }
1354
1355 /**
1356 * Helper function:
1357 * Remove the remote control receiver from the RC focus stack.
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001358 * Called synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001359 * precondition: pi != null
1360 */
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001361 private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001362 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001363 for (int index = mPRStack.size()-1; index >= 0; index--) {
1364 final PlayerRecord prse = mPRStack.elementAt(index);
1365 if (prse.hasMatchingMediaButtonIntent(pi)) {
1366 prse.destroy();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001367 // ok to remove element while traversing the stack since we're leaving the loop
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001368 mPRStack.removeElementAt(index);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001369 break;
1370 }
1371 }
1372 } catch (ArrayIndexOutOfBoundsException e) {
1373 // not expected to happen, indicates improper concurrent modification
1374 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
1375 }
1376 }
1377
1378 /**
1379 * Helper function:
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001380 * Called synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001381 */
1382 private boolean isCurrentRcController(PendingIntent pi) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001383 if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001384 return true;
1385 }
1386 return false;
1387 }
1388
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001389 //==========================================================================================
1390 // Remote control display / client
1391 //==========================================================================================
1392 /**
1393 * Update the remote control displays with the new "focused" client generation
1394 */
1395 private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration,
1396 PendingIntent newMediaIntent, boolean clearing) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001397 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001398 if (mRcDisplays.size() > 0) {
1399 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1400 while (displayIterator.hasNext()) {
1401 final DisplayInfoForServer di = displayIterator.next();
1402 try {
1403 di.mRcDisplay.setCurrentClientId(
1404 newClientGeneration, newMediaIntent, clearing);
1405 } catch (RemoteException e) {
1406 Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e);
1407 di.release();
1408 displayIterator.remove();
1409 }
1410 }
1411 }
1412 }
1413 }
1414
1415 /**
1416 * Update the remote control clients with the new "focused" client generation
1417 */
1418 private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) {
1419 // (using an iterator on the stack so we can safely remove an entry if needed,
1420 // traversal order doesn't matter here as we update all entries)
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001421 Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001422 while(stackIterator.hasNext()) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001423 PlayerRecord se = stackIterator.next();
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -07001424 if ((se != null) && (se.getRcc() != null)) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001425 try {
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -07001426 se.getRcc().setCurrentClientGenerationId(newClientGeneration);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001427 } catch (RemoteException e) {
1428 Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e);
1429 stackIterator.remove();
1430 se.unlinkToRcClientDeath();
1431 }
1432 }
1433 }
1434 }
1435
1436 /**
1437 * Update the displays and clients with the new "focused" client generation and name
1438 * @param newClientGeneration the new generation value matching a client update
1439 * @param newMediaIntent the media button event receiver associated with the client.
1440 * May be null, which implies there is no registered media button event receiver.
1441 * @param clearing true if the new client generation value maps to a remote control update
1442 * where the display should be cleared.
1443 */
1444 private void setNewRcClient_syncRcsCurrc(int newClientGeneration,
1445 PendingIntent newMediaIntent, boolean clearing) {
1446 // send the new valid client generation ID to all displays
1447 setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing);
1448 // send the new valid client generation ID to all clients
1449 setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration);
1450 }
1451
1452 /**
1453 * Called when processing MSG_RCDISPLAY_CLEAR event
1454 */
1455 private void onRcDisplayClear() {
1456 if (DEBUG_RC) Log.i(TAG, "Clear remote control display");
1457
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001458 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001459 synchronized(mCurrentRcLock) {
1460 mCurrentRcClientGen++;
1461 // synchronously update the displays and clients with the new client generation
1462 setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
1463 null /*newMediaIntent*/, true /*clearing*/);
1464 }
1465 }
1466 }
1467
1468 /**
1469 * Called when processing MSG_RCDISPLAY_UPDATE event
1470 */
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001471 private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) {
1472 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001473 synchronized(mCurrentRcLock) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001474 if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001475 if (DEBUG_RC) Log.i(TAG, "Display/update remote control ");
1476
1477 mCurrentRcClientGen++;
1478 // synchronously update the displays and clients with
1479 // the new client generation
1480 setNewRcClient_syncRcsCurrc(mCurrentRcClientGen,
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001481 prse.getMediaButtonIntent() /*newMediaIntent*/,
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001482 false /*clearing*/);
1483
1484 // tell the current client that it needs to send info
1485 try {
Jean-Michel Trivi86142da2013-09-29 13:03:58 -07001486 //TODO change name to informationRequestForAllDisplays()
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001487 mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags);
1488 } catch (RemoteException e) {
1489 Log.e(TAG, "Current valid remote client is dead: "+e);
1490 mCurrentRcClient = null;
1491 }
1492 } else {
1493 // the remote control display owner has changed between the
1494 // the message to update the display was sent, and the time it
1495 // gets to be processed (now)
1496 }
1497 }
1498 }
1499 }
1500
Jean-Michel Trivi86142da2013-09-29 13:03:58 -07001501 /**
1502 * Called when processing MSG_RCDISPLAY_INIT_INFO event
1503 * Causes the current RemoteControlClient to send its info (metadata, playstate...) to
1504 * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE.
1505 */
1506 private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001507 synchronized(mPRStack) {
Jean-Michel Trivi86142da2013-09-29 13:03:58 -07001508 synchronized(mCurrentRcLock) {
1509 if (mCurrentRcClient != null) {
1510 if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); }
1511 try {
1512 // synchronously update the new RCD with the current client generation
1513 // and matching PendingIntent
1514 newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent,
1515 false);
1516
1517 // tell the current RCC that it needs to send info, but only to the new RCD
1518 try {
1519 mCurrentRcClient.informationRequestForDisplay(newRcd, w, h);
1520 } catch (RemoteException e) {
1521 Log.e(TAG, "Current valid remote client is dead: ", e);
1522 mCurrentRcClient = null;
1523 }
1524 } catch (RemoteException e) {
1525 Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e);
1526 }
1527 }
1528 }
1529 }
1530 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001531
1532 /**
1533 * Helper function:
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001534 * Called synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001535 */
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001536 private void clearRemoteControlDisplay_syncPrs() {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001537 synchronized(mCurrentRcLock) {
1538 mCurrentRcClient = null;
1539 }
1540 // will cause onRcDisplayClear() to be called in AudioService's handler thread
1541 mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) );
1542 }
1543
1544 /**
1545 * Helper function for code readability: only to be called from
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001546 * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001547 * this method.
1548 * Preconditions:
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001549 * - called synchronized on mPRStack
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001550 * - mPRStack.isEmpty() is false
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001551 */
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001552 private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001553 PlayerRecord prse = mPRStack.peek();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001554 int infoFlagsAboutToBeUsed = infoChangedFlags;
1555 // this is where we enforce opt-in for information display on the remote controls
1556 // with the new AudioManager.registerRemoteControlClient() API
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001557 if (prse.getRcc() == null) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001558 //Log.w(TAG, "Can't update remote control display with null remote control client");
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001559 clearRemoteControlDisplay_syncPrs();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001560 return;
1561 }
1562 synchronized(mCurrentRcLock) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001563 if (!prse.getRcc().equals(mCurrentRcClient)) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001564 // new RC client, assume every type of information shall be queried
1565 infoFlagsAboutToBeUsed = RC_INFO_ALL;
1566 }
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001567 mCurrentRcClient = prse.getRcc();
1568 mCurrentRcClientIntent = prse.getMediaButtonIntent();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001569 }
1570 // will cause onRcDisplayUpdate() to be called in AudioService's handler thread
1571 mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE,
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001572 infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) );
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001573 }
1574
1575 /**
1576 * Helper function:
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001577 * Called synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001578 * Check whether the remote control display should be updated, triggers the update if required
1579 * @param infoChangedFlags the flags corresponding to the remote control client information
1580 * that has changed, if applicable (checking for the update conditions might trigger a
1581 * clear, rather than an update event).
1582 */
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001583 private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001584 // determine whether the remote control display should be refreshed
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001585 // if the player record stack is empty, there is nothing to display, so clear the RC display
1586 if (mPRStack.isEmpty()) {
1587 clearRemoteControlDisplay_syncPrs();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001588 return;
1589 }
1590
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001591 // this is where more rules for refresh go
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001592
1593 // refresh conditions were verified: update the remote controls
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001594 // ok to call: synchronized on mPRStack, mPRStack is not empty
1595 updateRemoteControlDisplay_syncPrs(infoChangedFlags);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001596 }
1597
1598 /**
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001599 * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c)
1600 * precondition: mediaIntent != null
1601 */
1602 protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver,
1603 IBinder token) {
1604 Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent);
1605
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001606 synchronized(mPRStack) {
1607 if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) {
1608 // new RC client, assume every type of information shall be queried
1609 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001610 }
1611 }
1612 }
1613
1614 /**
1615 * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent)
1616 * precondition: mediaIntent != null, eventReceiver != null
1617 */
1618 protected void unregisterMediaButtonIntent(PendingIntent mediaIntent)
1619 {
1620 Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent);
1621
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001622 synchronized(mPRStack) {
1623 boolean topOfStackWillChange = isCurrentRcController(mediaIntent);
1624 removeMediaButtonReceiver_syncPrs(mediaIntent);
1625 if (topOfStackWillChange) {
1626 // current RC client will change, assume every type of info needs to be queried
1627 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001628 }
1629 }
1630 }
1631
Jean-Michel Trivi223fd632014-03-24 11:00:13 -07001632 protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) {
1633 mEventHandler.sendMessage(
1634 mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0,
1635 mediaIntent));
1636 }
1637
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001638 /**
1639 * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c)
1640 * precondition: c != null
1641 */
1642 protected void registerMediaButtonEventReceiverForCalls(ComponentName c) {
1643 if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
1644 != PackageManager.PERMISSION_GRANTED) {
1645 Log.e(TAG, "Invalid permissions to register media button receiver for calls");
1646 return;
1647 }
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001648 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001649 mMediaReceiverForCalls = c;
1650 }
1651 }
1652
1653 /**
1654 * see AudioManager.unregisterMediaButtonEventReceiverForCalls()
1655 */
1656 protected void unregisterMediaButtonEventReceiverForCalls() {
1657 if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE")
1658 != PackageManager.PERMISSION_GRANTED) {
1659 Log.e(TAG, "Invalid permissions to unregister media button receiver for calls");
1660 return;
1661 }
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001662 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001663 mMediaReceiverForCalls = null;
1664 }
1665 }
1666
1667 /**
1668 * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...)
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001669 * @return the unique ID of the PlayerRecord associated with the RemoteControlClient
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001670 * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient
1671 * without modifying the RC stack, but while still causing the display to refresh (will
1672 * become blank as a result of this)
1673 */
1674 protected int registerRemoteControlClient(PendingIntent mediaIntent,
1675 IRemoteControlClient rcClient, String callingPackageName) {
1676 if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient);
1677 int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001678 synchronized(mPRStack) {
1679 // store the new display information
1680 try {
1681 for (int index = mPRStack.size()-1; index >= 0; index--) {
1682 final PlayerRecord prse = mPRStack.elementAt(index);
1683 if(prse.hasMatchingMediaButtonIntent(mediaIntent)) {
1684 prse.resetControllerInfoForRcc(rcClient, callingPackageName,
1685 Binder.getCallingUid());
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -07001686
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001687 if (rcClient == null) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001688 break;
1689 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001690
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001691 rccId = prse.getRccId();
1692
1693 // there is a new (non-null) client:
1694 // give the new client the displays (if any)
1695 if (mRcDisplays.size() > 0) {
1696 plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc());
1697 }
1698 break;
1699 }
1700 }//for
1701 } catch (ArrayIndexOutOfBoundsException e) {
1702 // not expected to happen, indicates improper concurrent modification
1703 Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
1704 }
1705
1706 // if the eventReceiver is at the top of the stack
1707 // then check for potential refresh of the remote controls
1708 if (isCurrentRcController(mediaIntent)) {
1709 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
1710 }
1711 }//synchronized(mPRStack)
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001712 return rccId;
1713 }
1714
1715 /**
1716 * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...)
1717 * rcClient is guaranteed non-null
1718 */
1719 protected void unregisterRemoteControlClient(PendingIntent mediaIntent,
1720 IRemoteControlClient rcClient) {
1721 if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient);
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001722 synchronized(mPRStack) {
1723 boolean topRccChange = false;
1724 try {
1725 for (int index = mPRStack.size()-1; index >= 0; index--) {
1726 final PlayerRecord prse = mPRStack.elementAt(index);
1727 if ((prse.hasMatchingMediaButtonIntent(mediaIntent))
1728 && rcClient.equals(prse.getRcc())) {
1729 // we found the IRemoteControlClient to unregister
1730 prse.resetControllerInfoForNoRcc();
1731 topRccChange = (index == mPRStack.size()-1);
1732 // there can only be one matching RCC in the RC stack, we're done
1733 break;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001734 }
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001735 }
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001736 } catch (ArrayIndexOutOfBoundsException e) {
1737 // not expected to happen, indicates improper concurrent modification
1738 Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
1739 }
1740 if (topRccChange) {
1741 // no more RCC for the RCD, check for potential refresh of the remote controls
1742 checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001743 }
1744 }
1745 }
1746
1747
1748 /**
1749 * A class to encapsulate all the information about a remote control display.
1750 * After instanciation, init() must always be called before the object is added in the list
1751 * of displays.
1752 * Before being removed from the list of displays, release() must always be called (otherwise
1753 * it will leak death handlers).
1754 */
1755 private class DisplayInfoForServer implements IBinder.DeathRecipient {
1756 /** may never be null */
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001757 private final IRemoteControlDisplay mRcDisplay;
1758 private final IBinder mRcDisplayBinder;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001759 private int mArtworkExpectedWidth = -1;
1760 private int mArtworkExpectedHeight = -1;
1761 private boolean mWantsPositionSync = false;
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001762 private ComponentName mClientNotifListComp;
1763 private boolean mEnabled = true;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001764
1765 public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) {
1766 if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h);
1767 mRcDisplay = rcd;
1768 mRcDisplayBinder = rcd.asBinder();
1769 mArtworkExpectedWidth = w;
1770 mArtworkExpectedHeight = h;
1771 }
1772
1773 public boolean init() {
1774 try {
1775 mRcDisplayBinder.linkToDeath(this, 0);
1776 } catch (RemoteException e) {
1777 // remote control display is DOA, disqualify it
1778 Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder);
1779 return false;
1780 }
1781 return true;
1782 }
1783
1784 public void release() {
1785 try {
1786 mRcDisplayBinder.unlinkToDeath(this, 0);
1787 } catch (java.util.NoSuchElementException e) {
1788 // not much we can do here, the display should have been unregistered anyway
1789 Log.e(TAG, "Error in DisplaInfoForServer.relase()", e);
1790 }
1791 }
1792
1793 public void binderDied() {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001794 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001795 Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died");
1796 // remove the display from the list
1797 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1798 while (displayIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -08001799 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001800 if (di.mRcDisplay == mRcDisplay) {
1801 if (DEBUG_RC) Log.w(TAG, " RCD removed from list");
1802 displayIterator.remove();
1803 return;
1804 }
1805 }
1806 }
1807 }
1808 }
1809
1810 /**
1811 * The remote control displays.
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001812 * Access synchronized on mPRStack
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001813 */
1814 private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1);
1815
1816 /**
1817 * Plug each registered display into the specified client
1818 * @param rcc, guaranteed non null
1819 */
Jean-Michel Trivib4168382014-04-08 15:13:57 -07001820 private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001821 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1822 while (displayIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -08001823 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001824 try {
1825 rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth,
1826 di.mArtworkExpectedHeight);
1827 if (di.mWantsPositionSync) {
1828 rcc.setWantsSyncForDisplay(di.mRcDisplay, true);
1829 }
1830 } catch (RemoteException e) {
1831 Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e);
1832 }
1833 }
1834 }
1835
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001836 private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd,
1837 boolean enabled) {
1838 // let all the remote control clients know whether the given display is enabled
1839 // (so the remote control stack traversal order doesn't matter).
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001840 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001841 while(stackIterator.hasNext()) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001842 PlayerRecord prse = stackIterator.next();
1843 if(prse.getRcc() != null) {
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001844 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001845 prse.getRcc().enableRemoteControlDisplay(rcd, enabled);
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001846 } catch (RemoteException e) {
1847 Log.e(TAG, "Error connecting RCD to client: ", e);
1848 }
1849 }
1850 }
1851 }
1852
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001853 /**
1854 * Is the remote control display interface already registered
1855 * @param rcd
1856 * @return true if the IRemoteControlDisplay is already in the list of displays
1857 */
1858 private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) {
1859 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1860 while (displayIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -08001861 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001862 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1863 return true;
1864 }
1865 }
1866 return false;
1867 }
1868
1869 /**
1870 * Register an IRemoteControlDisplay.
1871 * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient
1872 * at the top of the stack to update the new display with its information.
1873 * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int)
1874 * @param rcd the IRemoteControlDisplay to register. No effect if null.
1875 * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
1876 * display doesn't need to receive artwork.
1877 * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
1878 * display doesn't need to receive artwork.
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001879 * @param listenerComp the component for the listener interface, may be null if it's not needed
1880 * to verify it belongs to one of the enabled notification listeners
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001881 */
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001882 private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h,
1883 ComponentName listenerComp) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001884 if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")");
1885 synchronized(mAudioFocusLock) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001886 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001887 if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) {
1888 return;
1889 }
1890 DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h);
Jean-Michel Trivif108cdd92013-09-27 18:37:36 -07001891 di.mEnabled = true;
1892 di.mClientNotifListComp = listenerComp;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001893 if (!di.init()) {
1894 if (DEBUG_RC) Log.e(TAG, " error registering RCD");
1895 return;
1896 }
1897 // add RCD to list of displays
1898 mRcDisplays.add(di);
1899
1900 // let all the remote control clients know there is a new display (so the remote
1901 // control stack traversal order doesn't matter).
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001902 Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001903 while(stackIterator.hasNext()) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001904 PlayerRecord prse = stackIterator.next();
1905 if(prse.getRcc() != null) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001906 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001907 prse.getRcc().plugRemoteControlDisplay(rcd, w, h);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001908 } catch (RemoteException e) {
1909 Log.e(TAG, "Error connecting RCD to client: ", e);
1910 }
1911 }
1912 }
1913
Jean-Michel Trivi86142da2013-09-29 13:03:58 -07001914 // we have a new display, of which all the clients are now aware: have it be
1915 // initialized wih the current gen ID and the current client info, do not
1916 // reset the information for the other (existing) displays
1917 sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE,
1918 w /*arg1*/, h /*arg2*/,
1919 rcd /*obj*/, 0/*delay*/);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001920 }
1921 }
1922 }
1923
1924 /**
1925 * Unregister an IRemoteControlDisplay.
1926 * No effect if the IRemoteControlDisplay hasn't been successfully registered.
1927 * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay)
1928 * @param rcd the IRemoteControlDisplay to unregister. No effect if null.
1929 */
1930 protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) {
1931 if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")");
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001932 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001933 if (rcd == null) {
1934 return;
1935 }
1936
1937 boolean displayWasPluggedIn = false;
1938 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1939 while (displayIterator.hasNext() && !displayWasPluggedIn) {
RoboErik01fe6612014-02-13 14:19:04 -08001940 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001941 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1942 displayWasPluggedIn = true;
1943 di.release();
1944 displayIterator.remove();
1945 }
1946 }
1947
1948 if (displayWasPluggedIn) {
1949 // disconnect this remote control display from all the clients, so the remote
1950 // control stack traversal order doesn't matter
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001951 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001952 while(stackIterator.hasNext()) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001953 final PlayerRecord prse = stackIterator.next();
1954 if(prse.getRcc() != null) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001955 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001956 prse.getRcc().unplugRemoteControlDisplay(rcd);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001957 } catch (RemoteException e) {
1958 Log.e(TAG, "Error disconnecting remote control display to client: ", e);
1959 }
1960 }
1961 }
1962 } else {
1963 if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD");
1964 }
1965 }
1966 }
1967
1968 /**
1969 * Update the size of the artwork used by an IRemoteControlDisplay.
1970 * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int)
1971 * @param rcd the IRemoteControlDisplay with the new artwork size requirement
1972 * @param w the maximum width of the expected bitmap. Negative or zero values indicate this
1973 * display doesn't need to receive artwork.
1974 * @param h the maximum height of the expected bitmap. Negative or zero values indicate this
1975 * display doesn't need to receive artwork.
1976 */
1977 protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001978 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001979 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
1980 boolean artworkSizeUpdate = false;
1981 while (displayIterator.hasNext() && !artworkSizeUpdate) {
RoboErik01fe6612014-02-13 14:19:04 -08001982 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001983 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
1984 if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) {
1985 di.mArtworkExpectedWidth = w;
1986 di.mArtworkExpectedHeight = h;
1987 artworkSizeUpdate = true;
1988 }
1989 }
1990 }
1991 if (artworkSizeUpdate) {
1992 // RCD is currently plugged in and its artwork size has changed, notify all RCCs,
1993 // stack traversal order doesn't matter
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001994 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001995 while(stackIterator.hasNext()) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001996 final PlayerRecord prse = stackIterator.next();
1997 if(prse.getRcc() != null) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07001998 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07001999 prse.getRcc().setBitmapSizeForDisplay(rcd, w, h);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002000 } catch (RemoteException e) {
2001 Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e);
2002 }
2003 }
2004 }
2005 }
2006 }
2007 }
2008
2009 /**
2010 * Controls whether a remote control display needs periodic checks of the RemoteControlClient
2011 * playback position to verify that the estimated position has not drifted from the actual
2012 * position. By default the check is not performed.
2013 * The IRemoteControlDisplay must have been previously registered for this to have any effect.
2014 * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled
2015 * or disabled. Not null.
2016 * @param wantsSync if true, RemoteControlClient instances which expose their playback position
2017 * to the framework will regularly compare the estimated playback position with the actual
2018 * position, and will update the IRemoteControlDisplay implementation whenever a drift is
2019 * detected.
2020 */
2021 protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd,
2022 boolean wantsSync) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002023 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002024 boolean rcdRegistered = false;
2025 // store the information about this display
2026 // (display stack traversal order doesn't matter).
2027 final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator();
2028 while (displayIterator.hasNext()) {
RoboErik01fe6612014-02-13 14:19:04 -08002029 final DisplayInfoForServer di = displayIterator.next();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002030 if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) {
2031 di.mWantsPositionSync = wantsSync;
2032 rcdRegistered = true;
2033 break;
2034 }
2035 }
2036 if (!rcdRegistered) {
2037 return;
2038 }
2039 // notify all current RemoteControlClients
2040 // (stack traversal order doesn't matter as we notify all RCCs)
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002041 final Iterator<PlayerRecord> stackIterator = mPRStack.iterator();
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002042 while (stackIterator.hasNext()) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002043 final PlayerRecord prse = stackIterator.next();
2044 if (prse.getRcc() != null) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002045 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002046 prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002047 } catch (RemoteException e) {
2048 Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e);
2049 }
2050 }
2051 }
2052 }
2053 }
2054
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002055 // handler for MSG_RCC_NEW_VOLUME_OBS
2056 private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002057 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002058 // The stack traversal order doesn't matter because there is only one stack entry
2059 // with this RCC ID, but the matching ID is more likely at the top of the stack, so
2060 // start iterating from the top.
2061 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002062 for (int index = mPRStack.size()-1; index >= 0; index--) {
2063 final PlayerRecord prse = mPRStack.elementAt(index);
2064 if (prse.getRccId() == rccId) {
2065 prse.mRemoteVolumeObs = rvo;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002066 break;
2067 }
2068 }
2069 } catch (ArrayIndexOutOfBoundsException e) {
2070 // not expected to happen, indicates improper concurrent modification
2071 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
2072 }
2073 }
2074 }
2075
2076 /**
2077 * Checks if a remote client is active on the supplied stream type. Update the remote stream
2078 * volume state if found and playing
2079 * @param streamType
2080 * @return false if no remote playing is currently playing
2081 */
2082 protected boolean checkUpdateRemoteStateIfActive(int streamType) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002083 synchronized(mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002084 // iterating from top of stack as active playback is more likely on entries at the top
2085 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002086 for (int index = mPRStack.size()-1; index >= 0; index--) {
2087 final PlayerRecord prse = mPRStack.elementAt(index);
2088 if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE)
2089 && isPlaystateActive(prse.mPlaybackState.mState)
2090 && (prse.mPlaybackStream == streamType)) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002091 if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002092 + ", vol =" + prse.mPlaybackVolume);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002093 synchronized (mMainRemote) {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002094 mMainRemote.mRccId = prse.getRccId();
2095 mMainRemote.mVolume = prse.mPlaybackVolume;
2096 mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax;
2097 mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002098 mMainRemoteIsActive = true;
2099 }
2100 return true;
2101 }
2102 }
2103 } catch (ArrayIndexOutOfBoundsException e) {
2104 // not expected to happen, indicates improper concurrent modification
2105 Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e);
2106 }
2107 }
2108 synchronized (mMainRemote) {
2109 mMainRemoteIsActive = false;
2110 }
2111 return false;
2112 }
2113
2114 /**
2115 * Returns true if the given playback state is considered "active", i.e. it describes a state
2116 * where playback is happening, or about to
2117 * @param playState the playback state to evaluate
2118 * @return true if active, false otherwise (inactive or unknown)
2119 */
Jean-Michel Trivi8be88d12014-04-10 15:00:35 -07002120 protected static boolean isPlaystateActive(int playState) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002121 switch (playState) {
2122 case RemoteControlClient.PLAYSTATE_PLAYING:
2123 case RemoteControlClient.PLAYSTATE_BUFFERING:
2124 case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
2125 case RemoteControlClient.PLAYSTATE_REWINDING:
2126 case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
2127 case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
2128 return true;
2129 default:
2130 return false;
2131 }
2132 }
2133
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002134 private void sendVolumeUpdateToRemote(int rccId, int direction) {
2135 if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); }
2136 if (direction == 0) {
2137 // only handling discrete events
2138 return;
2139 }
2140 IRemoteVolumeObserver rvo = null;
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002141 synchronized (mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002142 // The stack traversal order doesn't matter because there is only one stack entry
2143 // with this RCC ID, but the matching ID is more likely at the top of the stack, so
2144 // start iterating from the top.
2145 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002146 for (int index = mPRStack.size()-1; index >= 0; index--) {
2147 final PlayerRecord prse = mPRStack.elementAt(index);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002148 //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002149 if (prse.getRccId() == rccId) {
2150 rvo = prse.mRemoteVolumeObs;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002151 break;
2152 }
2153 }
2154 } catch (ArrayIndexOutOfBoundsException e) {
2155 // not expected to happen, indicates improper concurrent modification
2156 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
2157 }
2158 }
2159 if (rvo != null) {
2160 try {
2161 rvo.dispatchRemoteVolumeUpdate(direction, -1);
2162 } catch (RemoteException e) {
2163 Log.e(TAG, "Error dispatching relative volume update", e);
2164 }
2165 }
2166 }
2167
2168 protected int getRemoteStreamMaxVolume() {
2169 synchronized (mMainRemote) {
2170 if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
2171 return 0;
2172 }
2173 return mMainRemote.mVolumeMax;
2174 }
2175 }
2176
2177 protected int getRemoteStreamVolume() {
2178 synchronized (mMainRemote) {
2179 if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
2180 return 0;
2181 }
2182 return mMainRemote.mVolume;
2183 }
2184 }
2185
2186 protected void setRemoteStreamVolume(int vol) {
2187 if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); }
2188 int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED;
2189 synchronized (mMainRemote) {
2190 if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) {
2191 return;
2192 }
2193 rccId = mMainRemote.mRccId;
2194 }
2195 IRemoteVolumeObserver rvo = null;
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002196 synchronized (mPRStack) {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002197 // The stack traversal order doesn't matter because there is only one stack entry
2198 // with this RCC ID, but the matching ID is more likely at the top of the stack, so
2199 // start iterating from the top.
2200 try {
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002201 for (int index = mPRStack.size()-1; index >= 0; index--) {
2202 final PlayerRecord prse = mPRStack.elementAt(index);
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002203 //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate?
Jean-Michel Trividda1c402014-03-25 17:25:25 -07002204 if (prse.getRccId() == rccId) {
2205 rvo = prse.mRemoteVolumeObs;
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002206 break;
2207 }
2208 }
2209 } catch (ArrayIndexOutOfBoundsException e) {
2210 // not expected to happen, indicates improper concurrent modification
2211 Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e);
2212 }
2213 }
2214 if (rvo != null) {
2215 try {
2216 rvo.dispatchRemoteVolumeUpdate(0, vol);
2217 } catch (RemoteException e) {
2218 Log.e(TAG, "Error dispatching absolute volume update", e);
2219 }
2220 }
2221 }
2222
2223 /**
2224 * Call to make AudioService reevaluate whether it's in a mode where remote players should
2225 * have their volume controlled. In this implementation this is only to reset whether
2226 * VolumePanel should display remote volumes
2227 */
Jean-Michel Trivi7d3168c2014-03-25 10:41:24 -07002228 protected void postReevaluateRemote() {
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002229 sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0);
2230 }
2231
2232 private void onReevaluateRemote() {
RoboErik19c95182014-06-23 15:38:48 -07002233 // TODO This was used to notify VolumePanel if there was remote playback
2234 // in the stack. This is now in MediaSessionService. More code should be
2235 // removed.
Jean-Michel Trivifa9a6982013-06-27 16:22:58 -07002236 }
2237
2238}