diff options
-rw-r--r-- | Android.mk | 2 | ||||
-rw-r--r-- | CleanSpec.mk | 2 | ||||
-rw-r--r-- | media/java/android/media/AudioManager.java | 101 | ||||
-rw-r--r-- | media/java/android/media/IAudioService.aidl | 58 | ||||
-rw-r--r-- | media/java/android/media/IRemoteControlClient.aidl | 62 | ||||
-rw-r--r-- | media/java/android/media/IRemoteControlDisplay.aidl | 98 | ||||
-rw-r--r-- | media/java/android/media/RemoteControlClient.java | 73 | ||||
-rw-r--r-- | media/java/android/media/RemoteController.java | 453 | ||||
-rw-r--r-- | services/core/java/com/android/server/audio/AudioService.java | 34 | ||||
-rw-r--r-- | services/core/java/com/android/server/audio/MediaFocusControl.java | 1734 | ||||
-rw-r--r-- | services/core/java/com/android/server/audio/PlayerRecord.java | 360 |
11 files changed, 24 insertions, 2953 deletions
diff --git a/Android.mk b/Android.mk index 1eff5cc7a52b..7ca04a59b80b 100644 --- a/Android.mk +++ b/Android.mk @@ -344,8 +344,6 @@ LOCAL_SRC_FILES += \ media/java/android/media/IMediaRouterService.aidl \ media/java/android/media/IMediaScannerListener.aidl \ media/java/android/media/IMediaScannerService.aidl \ - media/java/android/media/IRemoteControlClient.aidl \ - media/java/android/media/IRemoteControlDisplay.aidl \ media/java/android/media/IRemoteDisplayCallback.aidl \ media/java/android/media/IRemoteDisplayProvider.aidl \ media/java/android/media/IRemoteVolumeController.aidl \ diff --git a/CleanSpec.mk b/CleanSpec.mk index 6e44d77751d5..40908f1073e1 100644 --- a/CleanSpec.mk +++ b/CleanSpec.mk @@ -236,6 +236,8 @@ $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/SHARED_LIBRARIES/libinputflinge $(call add-clean-step, rm -rf $(PRODUCT_OUT)/target/common/obj/framework.aidl) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates) $(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/DocumentsUI_intermediates) +$(call add-clean-step, rm -f $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IRemoteControlClient.*) +$(call add-clean-step, rm -f $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/media/java/android/media/IRemoteControlDisplay.*) # ****************************************************************** # NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST ABOVE THIS BANNER diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 50df55630a6a..c658675d8a7c 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -2658,107 +2658,6 @@ public class AudioManager { rctlr.stopListeningToSessions(); } - /** - * @hide - * Registers a remote control display that will be sent information by remote control clients. - * Use this method if your IRemoteControlDisplay is not going to display artwork, otherwise - * use {@link #registerRemoteControlDisplay(IRemoteControlDisplay, int, int)} to pass the - * artwork size directly, or - * {@link #remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay, int, int)} later if artwork - * is not yet needed. - * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission. - * @param rcd the IRemoteControlDisplay - */ - public void registerRemoteControlDisplay(IRemoteControlDisplay rcd) { - // passing a negative value for art work width and height as they are unknown at this stage - registerRemoteControlDisplay(rcd, /*w*/-1, /*h*/ -1); - } - - /** - * @hide - * Registers a remote control display that will be sent information by remote control clients. - * <p>Registration requires the {@link Manifest.permission#MEDIA_CONTENT_CONTROL} permission. - * @param rcd - * @param w the maximum width of the expected bitmap. Negative values indicate it is - * useless to send artwork. - * @param h the maximum height of the expected bitmap. Negative values indicate it is - * useless to send artwork. - */ - public void registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { - if (rcd == null) { - return; - } - IAudioService service = getService(); - try { - service.registerRemoteControlDisplay(rcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in registerRemoteControlDisplay " + e); - } - } - - /** - * @hide - * Unregisters a remote control display that was sent information by remote control clients. - * @param rcd - */ - public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { - if (rcd == null) { - return; - } - IAudioService service = getService(); - try { - service.unregisterRemoteControlDisplay(rcd); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in unregisterRemoteControlDisplay " + e); - } - } - - /** - * @hide - * Sets the artwork size a remote control display expects when receiving bitmaps. - * @param rcd - * @param w the maximum width of the expected bitmap. Negative values indicate it is - * useless to send artwork. - * @param h the maximum height of the expected bitmap. Negative values indicate it is - * useless to send artwork. - */ - public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { - if (rcd == null) { - return; - } - IAudioService service = getService(); - try { - service.remoteControlDisplayUsesBitmapSize(rcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in remoteControlDisplayUsesBitmapSize " + e); - } - } - - /** - * @hide - * Controls whether a remote control display needs periodic checks of the RemoteControlClient - * playback position to verify that the estimated position has not drifted from the actual - * position. By default the check is not performed. - * The IRemoteControlDisplay must have been previously registered for this to have any effect. - * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled - * or disabled. No effect is null. - * @param wantsSync if true, RemoteControlClient instances which expose their playback position - * to the framework will regularly compare the estimated playback position with the actual - * position, and will update the IRemoteControlDisplay implementation whenever a drift is - * detected. - */ - public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, - boolean wantsSync) { - if (rcd == null) { - return; - } - IAudioService service = getService(); - try { - service.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync); - } catch (RemoteException e) { - Log.e(TAG, "Dead object in remoteControlDisplayWantsPlaybackPositionSync " + e); - } - } /** * @hide diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index 8aebe117722d..693a519c234f 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -23,9 +23,6 @@ import android.media.AudioAttributes; import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; -import android.media.IRemoteControlClient; -import android.media.IRemoteControlDisplay; -import android.media.IRemoteVolumeObserver; import android.media.IRingtonePlayer; import android.media.IVolumeController; import android.media.Rating; @@ -47,8 +44,6 @@ interface IAudioService { void setStreamVolume(int streamType, int index, int flags, String callingPackage); - oneway void setRemoteStreamVolume(int index); - boolean isStreamMute(int streamType); void forceRemoteSubmixFullVolume(boolean startForcing, IBinder cb); @@ -121,59 +116,6 @@ interface IAudioService { int getCurrentAudioFocus(); - /** - * Register an IRemoteControlDisplay. - * Success of registration is subject to a check on - * the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission. - * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient - * at the top of the stack to update the new display with its information. - * @param rcd the IRemoteControlDisplay to register. No effect if null. - * @param w the maximum width of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param h the maximum height of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - */ - boolean registerRemoteControlDisplay(in IRemoteControlDisplay rcd, int w, int h); - - /** - * Like registerRemoteControlDisplay, but with success being subject to a check on - * the android.Manifest.permission.MEDIA_CONTENT_CONTROL permission, and if it fails, - * success is subject to listenerComp being one of the ENABLED_NOTIFICATION_LISTENERS - * components. - */ - boolean registerRemoteController(in IRemoteControlDisplay rcd, int w, int h, - in ComponentName listenerComp); - - /** - * Unregister an IRemoteControlDisplay. - * No effect if the IRemoteControlDisplay hasn't been successfully registered. - * @param rcd the IRemoteControlDisplay to unregister. No effect if null. - */ - oneway void unregisterRemoteControlDisplay(in IRemoteControlDisplay rcd); - /** - * Update the size of the artwork used by an IRemoteControlDisplay. - * @param rcd the IRemoteControlDisplay with the new artwork size requirement - * @param w the maximum width of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param h the maximum height of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - */ - oneway void remoteControlDisplayUsesBitmapSize(in IRemoteControlDisplay rcd, int w, int h); - /** - * Controls whether a remote control display needs periodic checks of the RemoteControlClient - * playback position to verify that the estimated position has not drifted from the actual - * position. By default the check is not performed. - * The IRemoteControlDisplay must have been previously registered for this to have any effect. - * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled - * or disabled. Not null. - * @param wantsSync if true, RemoteControlClient instances which expose their playback position - * to the framework will regularly compare the estimated playback position with the actual - * position, and will update the IRemoteControlDisplay implementation whenever a drift is - * detected. - */ - oneway void remoteControlDisplayWantsPlaybackPositionSync(in IRemoteControlDisplay rcd, - boolean wantsSync); - void startBluetoothSco(IBinder cb, int targetSdkVersion); void startBluetoothScoVirtualCall(IBinder cb); void stopBluetoothSco(IBinder cb); diff --git a/media/java/android/media/IRemoteControlClient.aidl b/media/java/android/media/IRemoteControlClient.aidl deleted file mode 100644 index aa142d6e7da8..000000000000 --- a/media/java/android/media/IRemoteControlClient.aidl +++ /dev/null @@ -1,62 +0,0 @@ -/* Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.graphics.Bitmap; -import android.media.IRemoteControlDisplay; -import android.media.Rating; - -/** - * @hide - * Interface registered by AudioManager to notify a source of remote control information - * that information is requested to be displayed on the remote control (through - * IRemoteControlDisplay). - * {@see AudioManager#registerRemoteControlClient(RemoteControlClient)}. - */ -oneway interface IRemoteControlClient -{ - /** - * Notifies a remote control client that information for the given generation ID is - * requested. If the flags contains - * {@link RemoteControlClient#FLAG_INFORMATION_REQUESTED_ALBUM_ART} then the width and height - * parameters are valid. - * @param generationId - * @param infoFlags - * FIXME: is infoFlags required? since the RCC pushes info, this might always be called - * with RC_INFO_ALL - */ - void onInformationRequested(int generationId, int infoFlags); - - /** - * Notifies a remote control client that information for the given generation ID is - * requested for the given IRemoteControlDisplay alone. - * @param rcd the display to which current info should be sent - */ - void informationRequestForDisplay(IRemoteControlDisplay rcd, int w, int h); - - /** - * Sets the generation counter of the current client that is displayed on the remote control. - */ - void setCurrentClientGenerationId(int clientGeneration); - - void plugRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h); - void unplugRemoteControlDisplay(IRemoteControlDisplay rcd); - void setBitmapSizeForDisplay(IRemoteControlDisplay rcd, int w, int h); - void setWantsSyncForDisplay(IRemoteControlDisplay rcd, boolean wantsSync); - void enableRemoteControlDisplay(IRemoteControlDisplay rcd, boolean enabled); - void seekTo(int clientGeneration, long timeMs); - void updateMetadata(int clientGeneration, int key, in Rating value); -}
\ No newline at end of file diff --git a/media/java/android/media/IRemoteControlDisplay.aidl b/media/java/android/media/IRemoteControlDisplay.aidl deleted file mode 100644 index 160903048478..000000000000 --- a/media/java/android/media/IRemoteControlDisplay.aidl +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.media; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.graphics.Bitmap; -import android.os.Bundle; - -/** - * @hide - * Interface registered through AudioManager of an object that displays information - * received from a remote control client. - * {@see AudioManager#registerRemoteControlDisplay(IRemoteControlDisplay)}. - */ -oneway interface IRemoteControlDisplay -{ - /** - * Sets the generation counter of the current client that is displayed on the remote control. - * @param clientGeneration the new RemoteControlClient generation - * @param clientMediaIntent the PendingIntent associated with the client. - * May be null, which implies there is no registered media button event receiver. - * @param clearing true if the new client generation value maps to a remote control update - * where the display should be cleared. - */ - void setCurrentClientId(int clientGeneration, in PendingIntent clientMediaIntent, - boolean clearing); - - /** - * Sets whether the controls of this display are enabled - * @param if false, the display shouldn't any commands - */ - void setEnabled(boolean enabled); - - /** - * Sets the playback information (state, position and speed) of a client. - * @param generationId the current generation ID as known by this client - * @param state the current playback state, one of the following values: - * {@link RemoteControlClient#PLAYSTATE_STOPPED}, - * {@link RemoteControlClient#PLAYSTATE_PAUSED}, - * {@link RemoteControlClient#PLAYSTATE_PLAYING}, - * {@link RemoteControlClient#PLAYSTATE_FAST_FORWARDING}, - * {@link RemoteControlClient#PLAYSTATE_REWINDING}, - * {@link RemoteControlClient#PLAYSTATE_SKIPPING_FORWARDS}, - * {@link RemoteControlClient#PLAYSTATE_SKIPPING_BACKWARDS}, - * {@link RemoteControlClient#PLAYSTATE_BUFFERING}, - * {@link RemoteControlClient#PLAYSTATE_ERROR}. - * @param stateChangeTimeMs the time at which the client reported the playback information - * @param currentPosMs a 0 or positive value for the current media position expressed in ms - * Strictly negative values imply that position is not known: - * a value of {@link RemoteControlClient#PLAYBACK_POSITION_INVALID} is intended to express - * that an application doesn't know the position (e.g. listening to a live stream of a radio) - * or that the position information is not applicable (e.g. when state - * is {@link RemoteControlClient#PLAYSTATE_BUFFERING} and nothing had played yet); - * a value of {@link RemoteControlClient#PLAYBACK_POSITION_ALWAYS_UNKNOWN} implies that the - * application uses {@link RemoteControlClient#setPlaybackState(int)} (legacy API) and will - * never pass a playback position. - * @param speed a value expressed as a ratio of 1x playback: 1.0f is normal playback, - * 2.0f is 2x, 0.5f is half-speed, -2.0f is rewind at 2x speed. 0.0f means nothing is - * playing (e.g. when state is {@link RemoteControlClient#PLAYSTATE_ERROR}). - */ - void setPlaybackState(int generationId, int state, long stateChangeTimeMs, long currentPosMs, - float speed); - - /** - * Sets the transport control flags and playback position capabilities of a client. - * @param generationId the current generation ID as known by this client - * @param transportControlFlags bitmask of the transport controls this client supports, see - * {@link RemoteControlClient#setTransportControlFlags(int)} - * @param posCapabilities a bit mask for playback position capabilities, see - * {@link RemoteControlClient#MEDIA_POSITION_READABLE} and - * {@link RemoteControlClient#MEDIA_POSITION_WRITABLE} - */ - void setTransportControlInfo(int generationId, int transportControlFlags, int posCapabilities); - - void setMetadata(int generationId, in Bundle metadata); - - void setArtwork(int generationId, in Bitmap artwork); - - /** - * To combine metadata text and artwork in one binder call - */ - void setAllMetadata(int generationId, in Bundle metadata, in Bitmap artwork); -} diff --git a/media/java/android/media/RemoteControlClient.java b/media/java/android/media/RemoteControlClient.java index c9a86d8586ef..6d32eff9e8d0 100644 --- a/media/java/android/media/RemoteControlClient.java +++ b/media/java/android/media/RemoteControlClient.java @@ -349,16 +349,6 @@ import java.lang.IllegalArgumentException; */ public RemoteControlClient(PendingIntent mediaButtonIntent) { mRcMediaIntent = mediaButtonIntent; - - Looper looper; - if ((looper = Looper.myLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else if ((looper = Looper.getMainLooper()) != null) { - mEventHandler = new EventHandler(this, looper); - } else { - mEventHandler = null; - Log.e(TAG, "RemoteControlClient() couldn't find main application thread"); - } } /** @@ -378,8 +368,6 @@ import java.lang.IllegalArgumentException; */ public RemoteControlClient(PendingIntent mediaButtonIntent, Looper looper) { mRcMediaIntent = mediaButtonIntent; - - mEventHandler = new EventHandler(this, looper); } /** @@ -707,39 +695,6 @@ import java.lang.IllegalArgumentException; } } - // TODO investigate if we still need position drift checking - private void onPositionDriftCheck() { - if (DEBUG) { Log.d(TAG, "onPositionDriftCheck()"); } - synchronized(mCacheLock) { - if ((mEventHandler == null) || (mPositionProvider == null) || !mNeedsPositionSync) { - return; - } - if ((mPlaybackPositionMs < 0) || (mPlaybackSpeed == 0.0f)) { - if (DEBUG) { Log.d(TAG, " no valid position or 0 speed, no check needed"); } - return; - } - long estPos = mPlaybackPositionMs + (long) - ((SystemClock.elapsedRealtime() - mPlaybackStateChangeTimeMs) / mPlaybackSpeed); - long actPos = mPositionProvider.onGetPlaybackPosition(); - if (actPos >= 0) { - if (Math.abs(estPos - actPos) > POSITION_DRIFT_MAX_MS) { - // drift happened, report the new position - if (DEBUG) { Log.w(TAG, " drift detected: actual=" +actPos +" est=" +estPos); } - setPlaybackState(mPlaybackState, actPos, mPlaybackSpeed); - } else { - if (DEBUG) { Log.d(TAG, " no drift: actual=" + actPos +" est=" + estPos); } - // no drift, schedule the next drift check - mEventHandler.sendMessageDelayed( - mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), - getCheckPeriodFromSpeed(mPlaybackSpeed)); - } - } else { - // invalid position (negative value), can't check for drift - mEventHandler.removeMessages(MSG_POSITION_DRIFT_CHECK); - } - } - } - /** * Sets the flags for the media transport control buttons that this client supports. * @param transportControlFlags A combination of the following flags: @@ -856,14 +811,6 @@ import java.lang.IllegalArgumentException; public void setOnGetPlaybackPositionListener(OnGetPlaybackPositionListener l) { synchronized(mCacheLock) { mPositionProvider = l; - if ((mPositionProvider != null) && (mEventHandler != null) - && playbackPositionShouldMove(mPlaybackState)) { - // playback position is already moving, but now we have a position provider, - // so schedule a drift check right now - mEventHandler.sendMessageDelayed( - mEventHandler.obtainMessage(MSG_POSITION_DRIFT_CHECK), - 0 /*check now*/); - } } } @@ -1001,26 +948,6 @@ import java.lang.IllegalArgumentException; } }; - private EventHandler mEventHandler; - private final static int MSG_POSITION_DRIFT_CHECK = 11; - - private class EventHandler extends Handler { - public EventHandler(RemoteControlClient rcc, Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_POSITION_DRIFT_CHECK: - onPositionDriftCheck(); - break; - default: - Log.e(TAG, "Unknown event " + msg.what + " in RemoteControlClient handler"); - } - } - } - //=========================================================== // Message handlers diff --git a/media/java/android/media/RemoteController.java b/media/java/android/media/RemoteController.java index d84cf30e4643..90f2163f1210 100644 --- a/media/java/android/media/RemoteController.java +++ b/media/java/android/media/RemoteController.java @@ -17,8 +17,6 @@ package android.media; import android.app.ActivityManager; -import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -32,7 +30,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.SystemClock; import android.os.UserHandle; import android.util.DisplayMetrics; import android.util.Log; @@ -61,15 +58,10 @@ import java.util.List; @Deprecated public final class RemoteController { private final static int MAX_BITMAP_DIMENSION = 512; - private final static int TRANSPORT_UNKNOWN = 0; private final static String TAG = "RemoteController"; private final static boolean DEBUG = false; - private final static boolean USE_SESSIONS = true; - private final static Object mGenLock = new Object(); private final static Object mInfoLock = new Object(); - private final RcDisplay mRcd; private final Context mContext; - private final AudioManager mAudioManager; private final int mMaxBitmapDimension; private MetadataEditor mMetadataEditor; @@ -78,15 +70,9 @@ import java.util.List; private MediaController.Callback mSessionCb = new MediaControllerCallback(); /** - * Synchronized on mGenLock - */ - private int mClientGenerationIdCurrent = 0; - - /** * Synchronized on mInfoLock */ private boolean mIsRegistered = false; - private PendingIntent mClientPendingIntentCurrent; private OnClientUpdateListener mOnClientUpdateListener; private PlaybackInfo mLastPlaybackInfo; private int mArtworkWidth = -1; @@ -136,8 +122,6 @@ import java.util.List; } mOnClientUpdateListener = updateListener; mContext = context; - mRcd = new RcDisplay(this); - mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); mSessionManager = (MediaSessionManager) context .getSystemService(Context.MEDIA_SESSION_SERVICE); mSessionListener = new TopTransportSessionListener(); @@ -207,22 +191,6 @@ import java.util.List; public void onClientMetadataUpdate(MetadataEditor metadataEditor); }; - - /** - * @hide - */ - public String getRemoteControlClientPackageName() { - if (USE_SESSIONS) { - synchronized (mInfoLock) { - return mCurrentSession != null ? mCurrentSession.getPackageName() - : null; - } - } else { - return mClientPendingIntentCurrent != null ? - mClientPendingIntentCurrent.getCreatorPackage() : null; - } - } - /** * Return the estimated playback position of the current media track or a negative value * if not available. @@ -240,37 +208,12 @@ import java.util.List; * @see OnClientUpdateListener#onClientPlaybackStateUpdate(int, long, long, float) */ public long getEstimatedMediaPosition() { - if (USE_SESSIONS) { - synchronized (mInfoLock) { - if (mCurrentSession != null) { - PlaybackState state = mCurrentSession.getPlaybackState(); - if (state != null) { - return state.getPosition(); - } - } - } - } else { - final PlaybackInfo lastPlaybackInfo; - synchronized (mInfoLock) { - lastPlaybackInfo = mLastPlaybackInfo; - } - if (lastPlaybackInfo != null) { - if (!RemoteControlClient.playbackPositionShouldMove(lastPlaybackInfo.mState)) { - return lastPlaybackInfo.mCurrentPosMs; - } - - // Take the current position at the time of state change and - // estimate. - final long thenPos = lastPlaybackInfo.mCurrentPosMs; - if (thenPos < 0) { - return -1; + synchronized (mInfoLock) { + if (mCurrentSession != null) { + PlaybackState state = mCurrentSession.getPlaybackState(); + if (state != null) { + return state.getPosition(); } - - final long now = SystemClock.elapsedRealtime(); - final long then = lastPlaybackInfo.mStateChangeTimeMs; - final long sinceThen = now - then; - final long scaledSinceThen = (long) (sinceThen * lastPlaybackInfo.mSpeed); - return thenPos + scaledSinceThen; } } return -1; @@ -308,42 +251,12 @@ import java.util.List; if (!KeyEvent.isMediaKey(keyEvent.getKeyCode())) { throw new IllegalArgumentException("not a media key event"); } - if (USE_SESSIONS) { - synchronized (mInfoLock) { - if (mCurrentSession != null) { - return mCurrentSession.dispatchMediaButtonEvent(keyEvent); - } - return false; - } - } else { - final PendingIntent pi; - synchronized (mInfoLock) { - if (!mIsRegistered) { - Log.e(TAG, - "Cannot use sendMediaKeyEvent() from an unregistered RemoteController"); - return false; - } - if (!mEnabled) { - Log.e(TAG, "Cannot use sendMediaKeyEvent() from a disabled RemoteController"); - return false; - } - pi = mClientPendingIntentCurrent; - } - if (pi != null) { - Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON); - intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - try { - pi.send(mContext, 0, intent); - } catch (CanceledException e) { - Log.e(TAG, "Error sending intent for media button down: ", e); - return false; - } - } else { - Log.i(TAG, "No-op when sending key click, no receiver right now"); - return false; + synchronized (mInfoLock) { + if (mCurrentSession != null) { + return mCurrentSession.dispatchMediaButtonEvent(keyEvent); } + return false; } - return true; } @@ -453,8 +366,7 @@ import java.util.List; Log.e(TAG, "Cannot set synchronization mode on an unregistered RemoteController"); return false; } - mAudioManager.remoteControlDisplayWantsPlaybackPositionSync(mRcd, - POSITION_SYNCHRONIZATION_CHECK == sync); + // deprecated, no-op return true; } @@ -541,154 +453,6 @@ import java.util.List; } - - //================================================== - // Implementation of IRemoteControlDisplay interface - private static class RcDisplay extends IRemoteControlDisplay.Stub { - private final WeakReference<RemoteController> mController; - - RcDisplay(RemoteController rc) { - mController = new WeakReference<RemoteController>(rc); - } - - public void setCurrentClientId(int genId, PendingIntent clientMediaIntent, - boolean clearing) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - boolean isNew = false; - synchronized(mGenLock) { - if (rc.mClientGenerationIdCurrent != genId) { - rc.mClientGenerationIdCurrent = genId; - isNew = true; - } - } - if (clientMediaIntent != null) { - sendMsg(rc.mEventHandler, MSG_NEW_PENDING_INTENT, SENDMSG_REPLACE, - genId /*arg1*/, 0, clientMediaIntent /*obj*/, 0 /*delay*/); - } - if (isNew || clearing) { - sendMsg(rc.mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, - genId /*arg1*/, clearing ? 1 : 0, null /*obj*/, 0 /*delay*/); - } - } - - public void setEnabled(boolean enabled) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - sendMsg(rc.mEventHandler, MSG_DISPLAY_ENABLE, SENDMSG_REPLACE, - enabled ? 1 : 0 /*arg1*/, 0, null /*obj*/, 0 /*delay*/); - } - - public void setPlaybackState(int genId, int state, - long stateChangeTimeMs, long currentPosMs, float speed) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - if (DEBUG) { - Log.d(TAG, "> new playback state: genId="+genId - + " state="+ state - + " changeTime="+ stateChangeTimeMs - + " pos=" + currentPosMs - + "ms speed=" + speed); - } - - synchronized(mGenLock) { - if (rc.mClientGenerationIdCurrent != genId) { - return; - } - } - final PlaybackInfo playbackInfo = - new PlaybackInfo(state, stateChangeTimeMs, currentPosMs, speed); - sendMsg(rc.mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, - genId /*arg1*/, 0, playbackInfo /*obj*/, 0 /*delay*/); - - } - - public void setTransportControlInfo(int genId, int transportControlFlags, - int posCapabilities) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - synchronized(mGenLock) { - if (rc.mClientGenerationIdCurrent != genId) { - return; - } - } - sendMsg(rc.mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, - genId /*arg1*/, transportControlFlags /*arg2*/, - null /*obj*/, 0 /*delay*/); - } - - public void setMetadata(int genId, Bundle metadata) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - if (DEBUG) { Log.e(TAG, "setMetadata("+genId+")"); } - if (metadata == null) { - return; - } - synchronized(mGenLock) { - if (rc.mClientGenerationIdCurrent != genId) { - return; - } - } - sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, - genId /*arg1*/, 0 /*arg2*/, - metadata /*obj*/, 0 /*delay*/); - } - - public void setArtwork(int genId, Bitmap artwork) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - if (DEBUG) { Log.v(TAG, "setArtwork("+genId+")"); } - synchronized(mGenLock) { - if (rc.mClientGenerationIdCurrent != genId) { - return; - } - } - Bundle metadata = new Bundle(1); - metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), artwork); - sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, - genId /*arg1*/, 0 /*arg2*/, - metadata /*obj*/, 0 /*delay*/); - } - - public void setAllMetadata(int genId, Bundle metadata, Bitmap artwork) { - final RemoteController rc = mController.get(); - if (rc == null) { - return; - } - if (DEBUG) { Log.e(TAG, "setAllMetadata("+genId+")"); } - if ((metadata == null) && (artwork == null)) { - return; - } - synchronized(mGenLock) { - if (rc.mClientGenerationIdCurrent != genId) { - return; - } - } - if (metadata == null) { - metadata = new Bundle(1); - } - if (artwork != null) { - metadata.putParcelable(String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK), - artwork); - } - sendMsg(rc.mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, - genId /*arg1*/, 0 /*arg2*/, - metadata /*obj*/, 0 /*delay*/); - } - } - /** * This receives updates when the current session changes. This is * registered to receive the updates on the handler thread so it can call @@ -734,14 +498,9 @@ import java.util.List; //================================================== // Event handling private final EventHandler mEventHandler; - private final static int MSG_NEW_PENDING_INTENT = 0; - private final static int MSG_NEW_PLAYBACK_INFO = 1; - private final static int MSG_NEW_TRANSPORT_INFO = 2; - private final static int MSG_NEW_METADATA = 3; // msg always has non-null obj parameter - private final static int MSG_CLIENT_CHANGE = 4; - private final static int MSG_DISPLAY_ENABLE = 5; - private final static int MSG_NEW_PLAYBACK_STATE = 6; - private final static int MSG_NEW_MEDIA_METADATA = 7; + private final static int MSG_CLIENT_CHANGE = 0; + private final static int MSG_NEW_PLAYBACK_STATE = 1; + private final static int MSG_NEW_MEDIA_METADATA = 2; private class EventHandler extends Handler { @@ -752,26 +511,10 @@ import java.util.List; @Override public void handleMessage(Message msg) { switch(msg.what) { - case MSG_NEW_PENDING_INTENT: - onNewPendingIntent(msg.arg1, (PendingIntent) msg.obj); - break; - case MSG_NEW_PLAYBACK_INFO: - onNewPlaybackInfo(msg.arg1, (PlaybackInfo) msg.obj); - break; - case MSG_NEW_TRANSPORT_INFO: - onNewTransportInfo(msg.arg1, msg.arg2); - break; - case MSG_NEW_METADATA: - onNewMetadata(msg.arg1, (Bundle)msg.obj); - break; case MSG_CLIENT_CHANGE: - onClientChange(msg.arg1, msg.arg2 == 1); - break; - case MSG_DISPLAY_ENABLE: - onDisplayEnable(msg.arg1 == 1); + onClientChange(msg.arg2 == 1); break; case MSG_NEW_PLAYBACK_STATE: - // same as new playback info but using new apis onNewPlaybackState((PlaybackState) msg.obj); break; case MSG_NEW_MEDIA_METADATA: @@ -835,100 +578,7 @@ import java.util.List; handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delayMs); } - ///////////// These calls are used by the old APIs with RCC and RCD ////////////////////// - private void onNewPendingIntent(int genId, PendingIntent pi) { - synchronized(mGenLock) { - if (mClientGenerationIdCurrent != genId) { - return; - } - } - synchronized(mInfoLock) { - mClientPendingIntentCurrent = pi; - } - } - - private void onNewPlaybackInfo(int genId, PlaybackInfo pi) { - synchronized(mGenLock) { - if (mClientGenerationIdCurrent != genId) { - return; - } - } - final OnClientUpdateListener l; - synchronized(mInfoLock) { - l = this.mOnClientUpdateListener; - mLastPlaybackInfo = pi; - } - if (l != null) { - if (pi.mCurrentPosMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { - l.onClientPlaybackStateUpdate(pi.mState); - } else { - l.onClientPlaybackStateUpdate(pi.mState, pi.mStateChangeTimeMs, pi.mCurrentPosMs, - pi.mSpeed); - } - } - } - - private void onNewTransportInfo(int genId, int transportControlFlags) { - synchronized(mGenLock) { - if (mClientGenerationIdCurrent != genId) { - return; - } - } - final OnClientUpdateListener l; - synchronized(mInfoLock) { - l = mOnClientUpdateListener; - } - if (l != null) { - l.onClientTransportControlUpdate(transportControlFlags); - } - } - - /** - * @param genId - * @param metadata guaranteed to be always non-null - */ - private void onNewMetadata(int genId, Bundle metadata) { - synchronized(mGenLock) { - if (mClientGenerationIdCurrent != genId) { - return; - } - } - final OnClientUpdateListener l; - final MetadataEditor metadataEditor; - // prepare the received Bundle to be used inside a MetadataEditor - final long editableKeys = metadata.getLong( - String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK), 0); - if (editableKeys != 0) { - metadata.remove(String.valueOf(MediaMetadataEditor.KEY_EDITABLE_MASK)); - } - synchronized(mInfoLock) { - l = mOnClientUpdateListener; - if ((mMetadataEditor != null) && (mMetadataEditor.mEditorMetadata != null)) { - if (mMetadataEditor.mEditorMetadata != metadata) { - // existing metadata, merge existing and new - mMetadataEditor.mEditorMetadata.putAll(metadata); - } - - mMetadataEditor.putBitmap(MediaMetadataEditor.BITMAP_KEY_ARTWORK, - (Bitmap)metadata.getParcelable( - String.valueOf(MediaMetadataEditor.BITMAP_KEY_ARTWORK))); - mMetadataEditor.cleanupBitmapFromBundle(MediaMetadataEditor.BITMAP_KEY_ARTWORK); - } else { - mMetadataEditor = new MetadataEditor(metadata, editableKeys); - } - metadataEditor = mMetadataEditor; - } - if (l != null) { - l.onClientMetadataUpdate(metadataEditor); - } - } - - private void onClientChange(int genId, boolean clearing) { - synchronized(mGenLock) { - if (mClientGenerationIdCurrent != genId) { - return; - } - } + private void onClientChange(boolean clearing) { final OnClientUpdateListener l; synchronized(mInfoLock) { l = mOnClientUpdateListener; @@ -939,39 +589,6 @@ import java.util.List; } } - private void onDisplayEnable(boolean enabled) { - final OnClientUpdateListener l; - synchronized(mInfoLock) { - mEnabled = enabled; - l = this.mOnClientUpdateListener; - } - if (!enabled) { - // when disabling, reset all info sent to the user - final int genId; - synchronized (mGenLock) { - genId = mClientGenerationIdCurrent; - } - // send "stopped" state, happened "now", playback position is 0, speed 0.0f - final PlaybackInfo pi = new PlaybackInfo(RemoteControlClient.PLAYSTATE_STOPPED, - SystemClock.elapsedRealtime() /*stateChangeTimeMs*/, - 0 /*currentPosMs*/, 0.0f /*speed*/); - sendMsg(mEventHandler, MSG_NEW_PLAYBACK_INFO, SENDMSG_REPLACE, - genId /*arg1*/, 0 /*arg2, ignored*/, pi /*obj*/, 0 /*delay*/); - // send "blank" transport control info: no controls are supported - sendMsg(mEventHandler, MSG_NEW_TRANSPORT_INFO, SENDMSG_REPLACE, - genId /*arg1*/, 0 /*arg2, no flags*/, - null /*obj, ignored*/, 0 /*delay*/); - // send dummy metadata with empty string for title and artist, duration of 0 - Bundle metadata = new Bundle(3); - metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_TITLE), ""); - metadata.putString(String.valueOf(MediaMetadataRetriever.METADATA_KEY_ARTIST), ""); - metadata.putLong(String.valueOf(MediaMetadataRetriever.METADATA_KEY_DURATION), 0); - sendMsg(mEventHandler, MSG_NEW_METADATA, SENDMSG_QUEUE, - genId /*arg1*/, 0 /*arg2, ignored*/, metadata /*obj*/, 0 /*delay*/); - } - } - - ///////////// These calls are used by the new APIs with Sessions ////////////////////// private void updateController(MediaController controller) { if (DEBUG) { Log.d(TAG, "Updating controller to " + controller + " previous controller is " @@ -983,7 +600,7 @@ import java.util.List; mCurrentSession.unregisterCallback(mSessionCb); mCurrentSession = null; sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, - 0 /* genId */, 1 /* clearing */, null /* obj */, 0 /* delay */); + 0 /* arg1 ignored */, 1 /* clearing */, null /* obj */, 0 /* delay */); } } else if (mCurrentSession == null || !controller.getSessionToken() @@ -992,17 +609,17 @@ import java.util.List; mCurrentSession.unregisterCallback(mSessionCb); } sendMsg(mEventHandler, MSG_CLIENT_CHANGE, SENDMSG_REPLACE, - 0 /* genId */, 0 /* clearing */, null /* obj */, 0 /* delay */); + 0 /* arg1 ignored */, 0 /* clearing */, null /* obj */, 0 /* delay */); mCurrentSession = controller; mCurrentSession.registerCallback(mSessionCb, mEventHandler); PlaybackState state = controller.getPlaybackState(); sendMsg(mEventHandler, MSG_NEW_PLAYBACK_STATE, SENDMSG_REPLACE, - 0 /* genId */, 0, state /* obj */, 0 /* delay */); + 0 /* arg1 ignored */, 0 /* arg2 ignored */, state /* obj */, 0 /* delay */); MediaMetadata metadata = controller.getMetadata(); sendMsg(mEventHandler, MSG_NEW_MEDIA_METADATA, SENDMSG_REPLACE, - 0 /* arg1 */, 0 /* arg2 */, metadata /* obj */, 0 /* delay */); + 0 /* arg1 ignored */, 0 /* arg2 ignored*/, metadata /* obj */, 0 /*delay*/); } // else same controller, no need to update } @@ -1069,38 +686,6 @@ import java.util.List; /** * @hide - * Used by AudioManager to mark this instance as registered. - * @param registered - */ - void setIsRegistered(boolean registered) { - synchronized (mInfoLock) { - mIsRegistered = registered; - } - } - - /** - * @hide - * Used by AudioManager to access binder to be registered/unregistered inside MediaFocusControl - * @return - */ - RcDisplay getRcDisplay() { - return mRcd; - } - - /** - * @hide - * Used by AudioManager to read the current artwork dimension - * @return array containing width (index 0) and height (index 1) of currently set artwork size - */ - int[] getArtworkSize() { - synchronized (mInfoLock) { - int[] size = { mArtworkWidth, mArtworkHeight }; - return size; - } - } - - /** - * @hide * Used by AudioManager to access user listener receiving the client update notifications * @return */ diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 75886aa44049..4f2f4860372e 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -62,7 +62,6 @@ import android.media.AudioRoutesInfo; import android.media.IAudioFocusDispatcher; import android.media.IAudioRoutesObserver; import android.media.IAudioService; -import android.media.IRemoteControlDisplay; import android.media.IRingtonePlayer; import android.media.IVolumeController; import android.media.MediaPlayer; @@ -667,8 +666,7 @@ public class AudioService extends IAudioService.Stub { mSettingsObserver = new SettingsObserver(); createStreamStates(); - mMediaFocusControl = new MediaFocusControl(mAudioHandler.getLooper(), - mContext, mVolumeController, this); + mMediaFocusControl = new MediaFocusControl(mContext); readAndSetLowRamDevice(); @@ -5235,36 +5233,6 @@ public class AudioService extends IAudioService.Stub { } } - //========================================================================================== - // RemoteControlDisplay / RemoteControlClient / Remote info - //========================================================================================== - public boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, - ComponentName listenerComp) { - return mMediaFocusControl.registerRemoteController(rcd, w, h, listenerComp); - } - - public boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { - return mMediaFocusControl.registerRemoteControlDisplay(rcd, w, h); - } - - public void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { - mMediaFocusControl.unregisterRemoteControlDisplay(rcd); - } - - public void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { - mMediaFocusControl.remoteControlDisplayUsesBitmapSize(rcd, w, h); - } - - public void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, - boolean wantsSync) { - mMediaFocusControl.remoteControlDisplayWantsPlaybackPositionSync(rcd, wantsSync); - } - - @Override - public void setRemoteStreamVolume(int index) { - enforceVolumeController("set the remote stream volume"); - mMediaFocusControl.setRemoteStreamVolume(index); - } //========================================================================================== // Audio Focus diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index f72b5987d7ac..d44d89d7b076 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -16,52 +16,18 @@ package com.android.server.audio; -import android.app.Activity; -import android.app.ActivityManager; import android.app.AppOpsManager; -import android.app.KeyguardManager; -import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; -import android.app.PendingIntent.OnFinished; -import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.ContentObserver; import android.media.AudioAttributes; import android.media.AudioFocusInfo; import android.media.AudioManager; import android.media.AudioSystem; import android.media.IAudioFocusDispatcher; -import android.media.IRemoteControlClient; -import android.media.IRemoteControlDisplay; -import android.media.IRemoteVolumeObserver; -import android.media.RemoteControlClient; import android.media.audiopolicy.IAudioPolicyCallback; -import android.net.Uri; import android.os.Binder; -import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.IDeviceIdleController; -import android.os.Looper; -import android.os.Message; -import android.os.PowerManager; import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.UserHandle; -import android.provider.Settings; -import android.speech.RecognizerIntent; -import android.telephony.PhoneStateListener; -import android.telephony.TelephonyManager; import android.util.Log; -import android.util.Slog; -import android.view.KeyEvent; - -import com.android.server.audio.PlayerRecord.RemotePlaybackState; import java.io.PrintWriter; import java.util.ArrayList; @@ -74,329 +40,22 @@ import java.text.DateFormat; * @hide * */ -public class MediaFocusControl implements OnFinished { +public class MediaFocusControl { private static final String TAG = "MediaFocusControl"; - /** Debug remote control client/display feature */ - protected static final boolean DEBUG_RC = false; - /** Debug volumes */ - protected static final boolean DEBUG_VOL = false; - - /** Used to alter media button redirection when the phone is ringing. */ - private boolean mIsRinging = false; - - private final PowerManager.WakeLock mMediaEventWakeLock; - private final MediaEventHandler mEventHandler; private final Context mContext; - private final ContentResolver mContentResolver; - private final AudioService.VolumeController mVolumeController; private final AppOpsManager mAppOps; - private final KeyguardManager mKeyguardManager; - private final AudioService mAudioService; - private final NotificationListenerObserver mNotifListenerObserver; - protected MediaFocusControl(Looper looper, Context cntxt, - AudioService.VolumeController volumeCtrl, AudioService as) { - mEventHandler = new MediaEventHandler(looper); + protected MediaFocusControl(Context cntxt) { mContext = cntxt; - mContentResolver = mContext.getContentResolver(); - mVolumeController = volumeCtrl; - mAudioService = as; - - PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - mMediaEventWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "handleMediaEvent"); - int maxMusicLevel = as.getStreamMaxVolume(AudioManager.STREAM_MUSIC); - mMainRemote = new RemotePlaybackState(-1, maxMusicLevel, maxMusicLevel); - - // Register for phone state monitoring - TelephonyManager tmgr = (TelephonyManager) - mContext.getSystemService(Context.TELEPHONY_SERVICE); - tmgr.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CALL_STATE); - mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); - mKeyguardManager = - (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - mNotifListenerObserver = new NotificationListenerObserver(); - - mHasRemotePlayback = false; - mMainRemoteIsActive = false; - - PlayerRecord.setMediaFocusControl(this); - - postReevaluateRemote(); } protected void dump(PrintWriter pw) { pw.println("\nMediaFocusControl dump time: " + DateFormat.getTimeInstance().format(new Date())); dumpFocusStack(pw); - dumpRCStack(pw); - dumpRCCStack(pw); - dumpRCDList(pw); - } - - //========================================================================================== - // Management of RemoteControlDisplay registration permissions - //========================================================================================== - private final static Uri ENABLED_NOTIFICATION_LISTENERS_URI = - Settings.Secure.getUriFor(Settings.Secure.ENABLED_NOTIFICATION_LISTENERS); - - private class NotificationListenerObserver extends ContentObserver { - - NotificationListenerObserver() { - super(mEventHandler); - mContentResolver.registerContentObserver(Settings.Secure.getUriFor( - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS), false, this); - } - - @Override - public void onChange(boolean selfChange, Uri uri) { - if (!ENABLED_NOTIFICATION_LISTENERS_URI.equals(uri) || selfChange) { - return; - } - if (DEBUG_RC) { Log.d(TAG, "NotificationListenerObserver.onChange()"); } - postReevaluateRemoteControlDisplays(); - } - } - - private final static int RCD_REG_FAILURE = 0; - private final static int RCD_REG_SUCCESS_PERMISSION = 1; - private final static int RCD_REG_SUCCESS_ENABLED_NOTIF = 2; - - /** - * Checks a caller's authorization to register an IRemoteControlDisplay. - * Authorization is granted if one of the following is true: - * <ul> - * <li>the caller has android.Manifest.permission.MEDIA_CONTENT_CONTROL permission</li> - * <li>the caller's listener is one of the enabled notification listeners</li> - * </ul> - * @return RCD_REG_FAILURE if it's not safe to proceed with the IRemoteControlDisplay - * registration. - */ - private int checkRcdRegistrationAuthorization(ComponentName listenerComp) { - // MEDIA_CONTENT_CONTROL permission check - if (PackageManager.PERMISSION_GRANTED == mContext.checkCallingOrSelfPermission( - android.Manifest.permission.MEDIA_CONTENT_CONTROL)) { - if (DEBUG_RC) { Log.d(TAG, "ok to register Rcd: has MEDIA_CONTENT_CONTROL permission");} - return RCD_REG_SUCCESS_PERMISSION; - } - - // ENABLED_NOTIFICATION_LISTENERS settings check - if (listenerComp != null) { - // this call is coming from an app, can't use its identity to read secure settings - final long ident = Binder.clearCallingIdentity(); - try { - final int currentUser = ActivityManager.getCurrentUser(); - final String enabledNotifListeners = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); - if (enabledNotifListeners != null) { - final String[] components = enabledNotifListeners.split(":"); - for (int i=0; i<components.length; i++) { - final ComponentName component = - ComponentName.unflattenFromString(components[i]); - if (component != null) { - if (listenerComp.equals(component)) { - if (DEBUG_RC) { Log.d(TAG, "ok to register RCC: " + component + - " is authorized notification listener"); } - return RCD_REG_SUCCESS_ENABLED_NOTIF; - } - } - } - } - if (DEBUG_RC) { Log.d(TAG, "not ok to register RCD, " + listenerComp + - " is not in list of ENABLED_NOTIFICATION_LISTENERS"); } - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - return RCD_REG_FAILURE; - } - - protected boolean registerRemoteController(IRemoteControlDisplay rcd, int w, int h, - ComponentName listenerComp) { - int reg = checkRcdRegistrationAuthorization(listenerComp); - if (reg != RCD_REG_FAILURE) { - registerRemoteControlDisplay_int(rcd, w, h, listenerComp); - return true; - } else { - Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + - ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + - " or be an enabled NotificationListenerService for registerRemoteController"); - return false; - } - } - - protected boolean registerRemoteControlDisplay(IRemoteControlDisplay rcd, int w, int h) { - int reg = checkRcdRegistrationAuthorization(null); - if (reg != RCD_REG_FAILURE) { - registerRemoteControlDisplay_int(rcd, w, h, null); - return true; - } else { - Slog.w(TAG, "Access denied to process: " + Binder.getCallingPid() + - ", must have permission " + android.Manifest.permission.MEDIA_CONTENT_CONTROL + - " to register IRemoteControlDisplay"); - return false; - } - } - - private void postReevaluateRemoteControlDisplays() { - sendMsg(mEventHandler, MSG_REEVALUATE_RCD, SENDMSG_QUEUE, 0, 0, null, 0); - } - - private void onReevaluateRemoteControlDisplays() { - if (DEBUG_RC) { Log.d(TAG, "onReevaluateRemoteControlDisplays()"); } - // read which components are enabled notification listeners - final int currentUser = ActivityManager.getCurrentUser(); - final String enabledNotifListeners = Settings.Secure.getStringForUser( - mContext.getContentResolver(), - Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, - currentUser); - if (DEBUG_RC) { Log.d(TAG, " > enabled list: " + enabledNotifListeners); } - synchronized(mAudioFocusLock) { - synchronized(mPRStack) { - // check whether the "enable" status of each RCD with a notification listener - // has changed - final String[] enabledComponents; - if (enabledNotifListeners == null) { - enabledComponents = null; - } else { - enabledComponents = enabledNotifListeners.split(":"); - } - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = - displayIterator.next(); - if (di.mClientNotifListComp != null) { - boolean wasEnabled = di.mEnabled; - di.mEnabled = isComponentInStringArray(di.mClientNotifListComp, - enabledComponents); - if (wasEnabled != di.mEnabled){ - try { - // tell the RCD whether it's enabled - di.mRcDisplay.setEnabled(di.mEnabled); - // tell the RCCs about the change for this RCD - enableRemoteControlDisplayForClient_syncRcStack( - di.mRcDisplay, di.mEnabled); - // when enabling, refresh the information on the display - if (di.mEnabled) { - sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, - di.mArtworkExpectedWidth /*arg1*/, - di.mArtworkExpectedHeight/*arg2*/, - di.mRcDisplay /*obj*/, 0/*delay*/); - } - } catch (RemoteException e) { - Log.e(TAG, "Error en/disabling RCD: ", e); - } - } - } - } - } - } - } - - /** - * @param comp a non-null ComponentName - * @param enabledArray may be null - * @return - */ - private boolean isComponentInStringArray(ComponentName comp, String[] enabledArray) { - if (enabledArray == null || enabledArray.length == 0) { - if (DEBUG_RC) { Log.d(TAG, " > " + comp + " is NOT enabled"); } - return false; - } - final String compString = comp.flattenToString(); - for (int i=0; i<enabledArray.length; i++) { - if (compString.equals(enabledArray[i])) { - if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is enabled"); } - return true; - } - } - if (DEBUG_RC) { Log.d(TAG, " > " + compString + " is NOT enabled"); } - return false; - } - - //========================================================================================== - // Internal event handling - //========================================================================================== - - // event handler messages - private static final int MSG_RCDISPLAY_CLEAR = 1; - private static final int MSG_RCDISPLAY_UPDATE = 2; - private static final int MSG_REEVALUATE_REMOTE = 3; - private static final int MSG_RCC_NEW_PLAYBACK_INFO = 4; - private static final int MSG_RCC_NEW_VOLUME_OBS = 5; - private static final int MSG_RCC_NEW_PLAYBACK_STATE = 6; - private static final int MSG_RCC_SEEK_REQUEST = 7; - private static final int MSG_RCC_UPDATE_METADATA = 8; - private static final int MSG_RCDISPLAY_INIT_INFO = 9; - private static final int MSG_REEVALUATE_RCD = 10; - private static final int MSG_UNREGISTER_MEDIABUTTONINTENT = 11; - - // sendMsg() flags - /** If the msg is already queued, replace it with this one. */ - private static final int SENDMSG_REPLACE = 0; - /** If the msg is already queued, ignore this one and leave the old. */ - private static final int SENDMSG_NOOP = 1; - /** If the msg is already queued, queue this one and leave the old. */ - private static final int SENDMSG_QUEUE = 2; - - private static void sendMsg(Handler handler, int msg, - int existingMsgPolicy, int arg1, int arg2, Object obj, int delay) { - - if (existingMsgPolicy == SENDMSG_REPLACE) { - handler.removeMessages(msg); - } else if (existingMsgPolicy == SENDMSG_NOOP && handler.hasMessages(msg)) { - return; - } - - handler.sendMessageDelayed(handler.obtainMessage(msg, arg1, arg2, obj), delay); - } - - private class MediaEventHandler extends Handler { - MediaEventHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch(msg.what) { - case MSG_RCDISPLAY_CLEAR: - onRcDisplayClear(); - break; - - case MSG_RCDISPLAY_UPDATE: - // msg.obj is guaranteed to be non null - onRcDisplayUpdate( (PlayerRecord) msg.obj, msg.arg1); - break; - - case MSG_REEVALUATE_REMOTE: - onReevaluateRemote(); - break; - - case MSG_RCC_NEW_VOLUME_OBS: - onRegisterVolumeObserverForRcc(msg.arg1 /* rccId */, - (IRemoteVolumeObserver)msg.obj /* rvo */); - break; - - case MSG_RCDISPLAY_INIT_INFO: - // msg.obj is guaranteed to be non null - onRcDisplayInitInfo((IRemoteControlDisplay)msg.obj /*newRcd*/, - msg.arg1/*w*/, msg.arg2/*h*/); - break; - - case MSG_REEVALUATE_RCD: - onReevaluateRemoteControlDisplays(); - break; - - case MSG_UNREGISTER_MEDIABUTTONINTENT: - unregisterMediaButtonIntent( (PendingIntent) msg.obj ); - break; - } - } } @@ -406,25 +65,6 @@ public class MediaFocusControl implements OnFinished { private final static Object mAudioFocusLock = new Object(); - private final static Object mRingingLock = new Object(); - - private PhoneStateListener mPhoneStateListener = new PhoneStateListener() { - @Override - public void onCallStateChanged(int state, String incomingNumber) { - if (state == TelephonyManager.CALL_STATE_RINGING) { - //Log.v(TAG, " CALL_STATE_RINGING"); - synchronized(mRingingLock) { - mIsRinging = true; - } - } else if ((state == TelephonyManager.CALL_STATE_OFFHOOK) - || (state == TelephonyManager.CALL_STATE_IDLE)) { - synchronized(mRingingLock) { - mIsRinging = false; - } - } - } - }; - /** * Discard the current audio focus owner. * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign @@ -865,1374 +505,4 @@ public class MediaFocusControl implements OnFinished { } } - - //========================================================================================== - // RemoteControl - //========================================================================================== - /** - * No-op if the key code for keyEvent is not a valid media key - * (see {@link #isValidMediaKeyEvent(KeyEvent)}) - * @param keyEvent the key event to send - */ - protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { - filterMediaKeyEvent(keyEvent, false /*needWakeLock*/); - } - - /** - * No-op if the key code for keyEvent is not a valid media key - * (see {@link #isValidMediaKeyEvent(KeyEvent)}) - * @param keyEvent the key event to send - */ - protected void dispatchMediaKeyEventUnderWakelock(KeyEvent keyEvent) { - filterMediaKeyEvent(keyEvent, true /*needWakeLock*/); - } - - private void filterMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { - // sanity check on the incoming key event - if (!isValidMediaKeyEvent(keyEvent)) { - Log.e(TAG, "not dispatching invalid media key event " + keyEvent); - return; - } - // event filtering for telephony - synchronized(mRingingLock) { - synchronized(mPRStack) { - if ((mMediaReceiverForCalls != null) && - (mIsRinging || (mAudioService.getMode() == AudioSystem.MODE_IN_CALL))) { - dispatchMediaKeyEventForCalls(keyEvent, needWakeLock); - return; - } - } - } - // event filtering based on voice-based interactions - if (isValidVoiceInputKeyCode(keyEvent.getKeyCode())) { - filterVoiceInputKeyEvent(keyEvent, needWakeLock); - } else { - dispatchMediaKeyEvent(keyEvent, needWakeLock); - } - } - - /** - * Handles the dispatching of the media button events to the telephony package. - * Precondition: mMediaReceiverForCalls != null - * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons - * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event - * is dispatched. - */ - private void dispatchMediaKeyEventForCalls(KeyEvent keyEvent, boolean needWakeLock) { - Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); - keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - keyIntent.setPackage(mMediaReceiverForCalls.getPackageName()); - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); - } - final long ident = Binder.clearCallingIdentity(); - try { - mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, - null, mKeyEventDone, mEventHandler, Activity.RESULT_OK, null, null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - - /** - * Handles the dispatching of the media button events to one of the registered listeners, - * or if there was none, broadcast an ACTION_MEDIA_BUTTON intent to the rest of the system. - * @param keyEvent a non-null KeyEvent whose key code is one of the supported media buttons - * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event - * is dispatched. - */ - private void dispatchMediaKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - } - Intent keyIntent = new Intent(Intent.ACTION_MEDIA_BUTTON, null); - keyIntent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent); - synchronized(mPRStack) { - if (!mPRStack.empty()) { - // send the intent that was registered by the client - try { - mPRStack.peek().getMediaButtonIntent().send(mContext, - needWakeLock ? WAKELOCK_RELEASE_ON_FINISHED : 0 /*code*/, - keyIntent, this, mEventHandler); - } catch (CanceledException e) { - Log.e(TAG, "Error sending pending intent " + mPRStack.peek()); - e.printStackTrace(); - } - } else { - // legacy behavior when nobody registered their media button event receiver - // through AudioManager - if (needWakeLock) { - keyIntent.putExtra(EXTRA_WAKELOCK_ACQUIRED, WAKELOCK_RELEASE_ON_FINISHED); - } - final long ident = Binder.clearCallingIdentity(); - try { - mContext.sendOrderedBroadcastAsUser(keyIntent, UserHandle.ALL, - null, mKeyEventDone, - mEventHandler, Activity.RESULT_OK, null, null); - } finally { - Binder.restoreCallingIdentity(ident); - } - } - } - } - - /** - * The different actions performed in response to a voice button key event. - */ - private final static int VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS = 1; - private final static int VOICEBUTTON_ACTION_START_VOICE_INPUT = 2; - private final static int VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS = 3; - - private final Object mVoiceEventLock = new Object(); - private boolean mVoiceButtonDown; - private boolean mVoiceButtonHandled; - - /** - * Filter key events that may be used for voice-based interactions - * @param keyEvent a non-null KeyEvent whose key code is that of one of the supported - * media buttons that can be used to trigger voice-based interactions. - * @param needWakeLock true if a PARTIAL_WAKE_LOCK needs to be held while this key event - * is dispatched. - */ - private void filterVoiceInputKeyEvent(KeyEvent keyEvent, boolean needWakeLock) { - if (DEBUG_RC) { - Log.v(TAG, "voice input key event: " + keyEvent + ", needWakeLock=" + needWakeLock); - } - - int voiceButtonAction = VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS; - int keyAction = keyEvent.getAction(); - synchronized (mVoiceEventLock) { - if (keyAction == KeyEvent.ACTION_DOWN) { - if (keyEvent.getRepeatCount() == 0) { - // initial down - mVoiceButtonDown = true; - mVoiceButtonHandled = false; - } else if (mVoiceButtonDown && !mVoiceButtonHandled - && (keyEvent.getFlags() & KeyEvent.FLAG_LONG_PRESS) != 0) { - // long-press, start voice-based interactions - mVoiceButtonHandled = true; - voiceButtonAction = VOICEBUTTON_ACTION_START_VOICE_INPUT; - } - } else if (keyAction == KeyEvent.ACTION_UP) { - if (mVoiceButtonDown) { - // voice button up - mVoiceButtonDown = false; - if (!mVoiceButtonHandled && !keyEvent.isCanceled()) { - voiceButtonAction = VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS; - } - } - } - }//synchronized (mVoiceEventLock) - - // take action after media button event filtering for voice-based interactions - switch (voiceButtonAction) { - case VOICEBUTTON_ACTION_DISCARD_CURRENT_KEY_PRESS: - if (DEBUG_RC) Log.v(TAG, " ignore key event"); - break; - case VOICEBUTTON_ACTION_START_VOICE_INPUT: - if (DEBUG_RC) Log.v(TAG, " start voice-based interactions"); - // then start the voice-based interactions - startVoiceBasedInteractions(needWakeLock); - break; - case VOICEBUTTON_ACTION_SIMULATE_KEY_PRESS: - if (DEBUG_RC) Log.v(TAG, " send simulated key event, wakelock=" + needWakeLock); - sendSimulatedMediaButtonEvent(keyEvent, needWakeLock); - break; - } - } - - private void sendSimulatedMediaButtonEvent(KeyEvent originalKeyEvent, boolean needWakeLock) { - // send DOWN event - KeyEvent keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_DOWN); - dispatchMediaKeyEvent(keyEvent, needWakeLock); - // send UP event - keyEvent = KeyEvent.changeAction(originalKeyEvent, KeyEvent.ACTION_UP); - dispatchMediaKeyEvent(keyEvent, needWakeLock); - - } - - private static boolean isValidMediaKeyEvent(KeyEvent keyEvent) { - if (keyEvent == null) { - return false; - } - return KeyEvent.isMediaKey(keyEvent.getKeyCode()); - } - - /** - * Checks whether the given key code is one that can trigger the launch of voice-based - * interactions. - * @param keyCode the key code associated with the key event - * @return true if the key is one of the supported voice-based interaction triggers - */ - private static boolean isValidVoiceInputKeyCode(int keyCode) { - if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK) { - return true; - } else { - return false; - } - } - - /** - * Tell the system to start voice-based interactions / voice commands - */ - private void startVoiceBasedInteractions(boolean needWakeLock) { - Intent voiceIntent = null; - // select which type of search to launch: - // - screen on and device unlocked: action is ACTION_WEB_SEARCH - // - device locked or screen off: action is ACTION_VOICE_SEARCH_HANDS_FREE - // with EXTRA_SECURE set to true if the device is securely locked - PowerManager pm = (PowerManager)mContext.getSystemService(Context.POWER_SERVICE); - boolean isLocked = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); - if (!isLocked && pm.isScreenOn()) { - voiceIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH); - Log.i(TAG, "voice-based interactions: about to use ACTION_WEB_SEARCH"); - } else { - IDeviceIdleController dic = IDeviceIdleController.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); - if (dic != null) { - try { - dic.exitIdle("voice-search"); - } catch (RemoteException e) { - } - } - voiceIntent = new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE); - voiceIntent.putExtra(RecognizerIntent.EXTRA_SECURE, - isLocked && mKeyguardManager.isKeyguardSecure()); - Log.i(TAG, "voice-based interactions: about to use ACTION_VOICE_SEARCH_HANDS_FREE"); - } - // start the search activity - if (needWakeLock) { - mMediaEventWakeLock.acquire(); - } - final long identity = Binder.clearCallingIdentity(); - try { - if (voiceIntent != null) { - voiceIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - mContext.startActivityAsUser(voiceIntent, UserHandle.CURRENT); - } - } catch (ActivityNotFoundException e) { - Log.w(TAG, "No activity for search: " + e); - } finally { - Binder.restoreCallingIdentity(identity); - if (needWakeLock) { - mMediaEventWakeLock.release(); - } - } - } - - private static final int WAKELOCK_RELEASE_ON_FINISHED = 1980; //magic number - - // only set when wakelock was acquired, no need to check value when received - private static final String EXTRA_WAKELOCK_ACQUIRED = - "android.media.AudioService.WAKELOCK_ACQUIRED"; - - public void onSendFinished(PendingIntent pendingIntent, Intent intent, - int resultCode, String resultData, Bundle resultExtras) { - if (resultCode == WAKELOCK_RELEASE_ON_FINISHED) { - mMediaEventWakeLock.release(); - } - } - - BroadcastReceiver mKeyEventDone = new BroadcastReceiver() { - public void onReceive(Context context, Intent intent) { - if (intent == null) { - return; - } - Bundle extras = intent.getExtras(); - if (extras == null) { - return; - } - if (extras.containsKey(EXTRA_WAKELOCK_ACQUIRED)) { - mMediaEventWakeLock.release(); - } - } - }; - - /** - * Synchronization on mCurrentRcLock always inside a block synchronized on mPRStack - */ - private final Object mCurrentRcLock = new Object(); - /** - * The one remote control client which will receive a request for display information. - * This object may be null. - * Access protected by mCurrentRcLock. - */ - private IRemoteControlClient mCurrentRcClient = null; - /** - * The PendingIntent associated with mCurrentRcClient. Its value is irrelevant - * if mCurrentRcClient is null - */ - private PendingIntent mCurrentRcClientIntent = null; - - private final static int RC_INFO_NONE = 0; - private final static int RC_INFO_ALL = - RemoteControlClient.FLAG_INFORMATION_REQUEST_ALBUM_ART | - RemoteControlClient.FLAG_INFORMATION_REQUEST_KEY_MEDIA | - RemoteControlClient.FLAG_INFORMATION_REQUEST_METADATA | - RemoteControlClient.FLAG_INFORMATION_REQUEST_PLAYSTATE; - - /** - * A monotonically increasing generation counter for mCurrentRcClient. - * Only accessed with a lock on mCurrentRcLock. - * No value wrap-around issues as we only act on equal values. - */ - private int mCurrentRcClientGen = 0; - - - /** - * Internal cache for the playback information of the RemoteControlClient whose volume gets to - * be controlled by the volume keys ("main"), so we don't have to iterate over the RC stack - * every time we need this info. - */ - private RemotePlaybackState mMainRemote; - /** - * Indicates whether the "main" RemoteControlClient is considered active. - * Use synchronized on mMainRemote. - */ - private boolean mMainRemoteIsActive; - /** - * Indicates whether there is remote playback going on. True even if there is no "active" - * remote playback (mMainRemoteIsActive is false), but a RemoteControlClient has declared it - * handles remote playback. - * Use synchronized on mMainRemote. - */ - private boolean mHasRemotePlayback; - - /** - * The stack of remote control event receivers. - * All read and write operations on mPRStack are synchronized. - */ - private final Stack<PlayerRecord> mPRStack = new Stack<PlayerRecord>(); - - /** - * The component the telephony package can register so telephony calls have priority to - * handle media button events - */ - private ComponentName mMediaReceiverForCalls = null; - - /** - * Helper function: - * Display in the log the current entries in the remote control focus stack - */ - private void dumpRCStack(PrintWriter pw) { - pw.println("\nRemote Control stack entries (last is top of stack):"); - synchronized(mPRStack) { - Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - stackIterator.next().dump(pw, true); - } - } - } - - /** - * Helper function: - * Display in the log the current entries in the remote control stack, focusing - * on RemoteControlClient data - */ - private void dumpRCCStack(PrintWriter pw) { - pw.println("\nRemote Control Client stack entries (last is top of stack):"); - synchronized(mPRStack) { - Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - stackIterator.next().dump(pw, false); - } - synchronized(mCurrentRcLock) { - pw.println("\nCurrent remote control generation ID = " + mCurrentRcClientGen); - } - } - synchronized (mMainRemote) { - pw.println("\nRemote Volume State:"); - pw.println(" has remote: " + mHasRemotePlayback); - pw.println(" is remote active: " + mMainRemoteIsActive); - pw.println(" rccId: " + mMainRemote.mRccId); - pw.println(" volume handling: " - + ((mMainRemote.mVolumeHandling == RemoteControlClient.PLAYBACK_VOLUME_FIXED) ? - "PLAYBACK_VOLUME_FIXED(0)" : "PLAYBACK_VOLUME_VARIABLE(1)")); - pw.println(" volume: " + mMainRemote.mVolume); - pw.println(" volume steps: " + mMainRemote.mVolumeMax); - } - } - - /** - * Helper function: - * Display in the log the current entries in the list of remote control displays - */ - private void dumpRCDList(PrintWriter pw) { - pw.println("\nRemote Control Display list entries:"); - synchronized(mPRStack) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - pw.println(" IRCD: " + di.mRcDisplay + - " -- w:" + di.mArtworkExpectedWidth + - " -- h:" + di.mArtworkExpectedHeight + - " -- wantsPosSync:" + di.mWantsPositionSync + - " -- " + (di.mEnabled ? "enabled" : "disabled")); - } - } - } - - /** - * Helper function: - * Push the new media button receiver "near" the top of the PlayerRecord stack. - * "Near the top" is defined as: - * - at the top if the current PlayerRecord at the top is not playing - * - below the entries at the top of the stack that correspond to the playing PlayerRecord - * otherwise - * Called synchronized on mPRStack - * precondition: mediaIntent != null - * @return true if the top of mPRStack was changed, false otherwise - */ - private boolean pushMediaButtonReceiver_syncPrs(PendingIntent mediaIntent, - ComponentName target, IBinder token) { - if (mPRStack.empty()) { - mPRStack.push(new PlayerRecord(mediaIntent, target, token)); - return true; - } else if (mPRStack.peek().hasMatchingMediaButtonIntent(mediaIntent)) { - // already at top of stack - return false; - } - if (mAppOps.noteOp(AppOpsManager.OP_TAKE_MEDIA_BUTTONS, Binder.getCallingUid(), - mediaIntent.getCreatorPackage()) != AppOpsManager.MODE_ALLOWED) { - return false; - } - PlayerRecord oldTopPrse = mPRStack.lastElement(); // top of the stack before any changes - boolean topChanged = false; - PlayerRecord prse = null; - int lastPlayingIndex = mPRStack.size(); - int inStackIndex = -1; - try { - // go through the stack from the top to figure out who's playing, and the position - // of this media button receiver (note that it may not be in the stack) - for (int index = mPRStack.size()-1; index >= 0; index--) { - prse = mPRStack.elementAt(index); - if (prse.isPlaybackActive()) { - lastPlayingIndex = index; - } - if (prse.hasMatchingMediaButtonIntent(mediaIntent)) { - inStackIndex = index; - } - } - - if (inStackIndex == -1) { - // is not in stack - prse = new PlayerRecord(mediaIntent, target, token); - // it's new so it's not playing (no RemoteControlClient to give a playstate), - // therefore it goes after the ones with active playback - mPRStack.add(lastPlayingIndex, prse); - } else { - // is in the stack - if (mPRStack.size() > 1) { // no need to remove and add if stack contains only 1 - prse = mPRStack.elementAt(inStackIndex); - // remove it from its old location in the stack - mPRStack.removeElementAt(inStackIndex); - if (prse.isPlaybackActive()) { - // and put it at the top - mPRStack.push(prse); - } else { - // and put it after the ones with active playback - if (inStackIndex > lastPlayingIndex) { - mPRStack.add(lastPlayingIndex, prse); - } else { - mPRStack.add(lastPlayingIndex - 1, prse); - } - } - } - } - - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification or bad index - Log.e(TAG, "Wrong index (inStack=" + inStackIndex + " lastPlaying=" + lastPlayingIndex - + " size=" + mPRStack.size() - + " accessing media button stack", e); - } - - return (topChanged); - } - - /** - * Helper function: - * Remove the remote control receiver from the RC focus stack. - * Called synchronized on mPRStack - * precondition: pi != null - */ - private void removeMediaButtonReceiver_syncPrs(PendingIntent pi) { - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - if (prse.hasMatchingMediaButtonIntent(pi)) { - prse.destroy(); - // ok to remove element while traversing the stack since we're leaving the loop - mPRStack.removeElementAt(index); - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - - /** - * Helper function: - * Called synchronized on mPRStack - */ - private boolean isCurrentRcController(PendingIntent pi) { - if (!mPRStack.empty() && mPRStack.peek().hasMatchingMediaButtonIntent(pi)) { - return true; - } - return false; - } - - //========================================================================================== - // Remote control display / client - //========================================================================================== - /** - * Update the remote control displays with the new "focused" client generation - */ - private void setNewRcClientOnDisplays_syncRcsCurrc(int newClientGeneration, - PendingIntent newMediaIntent, boolean clearing) { - synchronized(mPRStack) { - if (mRcDisplays.size() > 0) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - try { - di.mRcDisplay.setCurrentClientId( - newClientGeneration, newMediaIntent, clearing); - } catch (RemoteException e) { - Log.e(TAG, "Dead display in setNewRcClientOnDisplays_syncRcsCurrc()",e); - di.release(); - displayIterator.remove(); - } - } - } - } - } - - /** - * Update the remote control clients with the new "focused" client generation - */ - private void setNewRcClientGenerationOnClients_syncRcsCurrc(int newClientGeneration) { - // (using an iterator on the stack so we can safely remove an entry if needed, - // traversal order doesn't matter here as we update all entries) - Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - PlayerRecord se = stackIterator.next(); - if ((se != null) && (se.getRcc() != null)) { - try { - se.getRcc().setCurrentClientGenerationId(newClientGeneration); - } catch (RemoteException e) { - Log.w(TAG, "Dead client in setNewRcClientGenerationOnClients_syncRcsCurrc()",e); - stackIterator.remove(); - se.unlinkToRcClientDeath(); - } - } - } - } - - /** - * Update the displays and clients with the new "focused" client generation and name - * @param newClientGeneration the new generation value matching a client update - * @param newMediaIntent the media button event receiver associated with the client. - * May be null, which implies there is no registered media button event receiver. - * @param clearing true if the new client generation value maps to a remote control update - * where the display should be cleared. - */ - private void setNewRcClient_syncRcsCurrc(int newClientGeneration, - PendingIntent newMediaIntent, boolean clearing) { - // send the new valid client generation ID to all displays - setNewRcClientOnDisplays_syncRcsCurrc(newClientGeneration, newMediaIntent, clearing); - // send the new valid client generation ID to all clients - setNewRcClientGenerationOnClients_syncRcsCurrc(newClientGeneration); - } - - /** - * Called when processing MSG_RCDISPLAY_CLEAR event - */ - private void onRcDisplayClear() { - if (DEBUG_RC) Log.i(TAG, "Clear remote control display"); - - synchronized(mPRStack) { - synchronized(mCurrentRcLock) { - mCurrentRcClientGen++; - // synchronously update the displays and clients with the new client generation - setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, - null /*newMediaIntent*/, true /*clearing*/); - } - } - } - - /** - * Called when processing MSG_RCDISPLAY_UPDATE event - */ - private void onRcDisplayUpdate(PlayerRecord prse, int flags /* USED ?*/) { - synchronized(mPRStack) { - synchronized(mCurrentRcLock) { - if ((mCurrentRcClient != null) && (mCurrentRcClient.equals(prse.getRcc()))) { - if (DEBUG_RC) Log.i(TAG, "Display/update remote control "); - - mCurrentRcClientGen++; - // synchronously update the displays and clients with - // the new client generation - setNewRcClient_syncRcsCurrc(mCurrentRcClientGen, - prse.getMediaButtonIntent() /*newMediaIntent*/, - false /*clearing*/); - - // tell the current client that it needs to send info - try { - //TODO change name to informationRequestForAllDisplays() - mCurrentRcClient.onInformationRequested(mCurrentRcClientGen, flags); - } catch (RemoteException e) { - Log.e(TAG, "Current valid remote client is dead: "+e); - mCurrentRcClient = null; - } - } else { - // the remote control display owner has changed between the - // the message to update the display was sent, and the time it - // gets to be processed (now) - } - } - } - } - - /** - * Called when processing MSG_RCDISPLAY_INIT_INFO event - * Causes the current RemoteControlClient to send its info (metadata, playstate...) to - * a single RemoteControlDisplay, NOT all of them, as with MSG_RCDISPLAY_UPDATE. - */ - private void onRcDisplayInitInfo(IRemoteControlDisplay newRcd, int w, int h) { - synchronized(mPRStack) { - synchronized(mCurrentRcLock) { - if (mCurrentRcClient != null) { - if (DEBUG_RC) { Log.i(TAG, "Init RCD with current info"); } - try { - // synchronously update the new RCD with the current client generation - // and matching PendingIntent - newRcd.setCurrentClientId(mCurrentRcClientGen, mCurrentRcClientIntent, - false); - - // tell the current RCC that it needs to send info, but only to the new RCD - try { - mCurrentRcClient.informationRequestForDisplay(newRcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Current valid remote client is dead: ", e); - mCurrentRcClient = null; - } - } catch (RemoteException e) { - Log.e(TAG, "Dead display in onRcDisplayInitInfo()", e); - } - } - } - } - } - - /** - * Helper function: - * Called synchronized on mPRStack - */ - private void clearRemoteControlDisplay_syncPrs() { - synchronized(mCurrentRcLock) { - mCurrentRcClient = null; - } - // will cause onRcDisplayClear() to be called in AudioService's handler thread - mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_CLEAR) ); - } - - /** - * Helper function for code readability: only to be called from - * checkUpdateRemoteControlDisplay_syncPrs() which checks the preconditions for - * this method. - * Preconditions: - * - called synchronized on mPRStack - * - mPRStack.isEmpty() is false - */ - private void updateRemoteControlDisplay_syncPrs(int infoChangedFlags) { - PlayerRecord prse = mPRStack.peek(); - int infoFlagsAboutToBeUsed = infoChangedFlags; - // this is where we enforce opt-in for information display on the remote controls - // with the new AudioManager.registerRemoteControlClient() API - if (prse.getRcc() == null) { - //Log.w(TAG, "Can't update remote control display with null remote control client"); - clearRemoteControlDisplay_syncPrs(); - return; - } - synchronized(mCurrentRcLock) { - if (!prse.getRcc().equals(mCurrentRcClient)) { - // new RC client, assume every type of information shall be queried - infoFlagsAboutToBeUsed = RC_INFO_ALL; - } - mCurrentRcClient = prse.getRcc(); - mCurrentRcClientIntent = prse.getMediaButtonIntent(); - } - // will cause onRcDisplayUpdate() to be called in AudioService's handler thread - mEventHandler.sendMessage( mEventHandler.obtainMessage(MSG_RCDISPLAY_UPDATE, - infoFlagsAboutToBeUsed /* arg1 */, 0, prse /* obj, != null */) ); - } - - /** - * Helper function: - * Called synchronized on mPRStack - * Check whether the remote control display should be updated, triggers the update if required - * @param infoChangedFlags the flags corresponding to the remote control client information - * that has changed, if applicable (checking for the update conditions might trigger a - * clear, rather than an update event). - */ - private void checkUpdateRemoteControlDisplay_syncPrs(int infoChangedFlags) { - // determine whether the remote control display should be refreshed - // if the player record stack is empty, there is nothing to display, so clear the RC display - if (mPRStack.isEmpty()) { - clearRemoteControlDisplay_syncPrs(); - return; - } - - // this is where more rules for refresh go - - // refresh conditions were verified: update the remote controls - // ok to call: synchronized on mPRStack, mPRStack is not empty - updateRemoteControlDisplay_syncPrs(infoChangedFlags); - } - - /** - * see AudioManager.registerMediaButtonIntent(PendingIntent pi, ComponentName c) - * precondition: mediaIntent != null - */ - protected void registerMediaButtonIntent(PendingIntent mediaIntent, ComponentName eventReceiver, - IBinder token) { - Log.i(TAG, " Remote Control registerMediaButtonIntent() for " + mediaIntent); - - synchronized(mPRStack) { - if (pushMediaButtonReceiver_syncPrs(mediaIntent, eventReceiver, token)) { - // new RC client, assume every type of information shall be queried - checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); - } - } - } - - /** - * see AudioManager.unregisterMediaButtonIntent(PendingIntent mediaIntent) - * precondition: mediaIntent != null, eventReceiver != null - */ - protected void unregisterMediaButtonIntent(PendingIntent mediaIntent) - { - Log.i(TAG, " Remote Control unregisterMediaButtonIntent() for " + mediaIntent); - - synchronized(mPRStack) { - boolean topOfStackWillChange = isCurrentRcController(mediaIntent); - removeMediaButtonReceiver_syncPrs(mediaIntent); - if (topOfStackWillChange) { - // current RC client will change, assume every type of info needs to be queried - checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); - } - } - } - - protected void unregisterMediaButtonIntentAsync(final PendingIntent mediaIntent) { - mEventHandler.sendMessage( - mEventHandler.obtainMessage(MSG_UNREGISTER_MEDIABUTTONINTENT, 0, 0, - mediaIntent)); - } - - /** - * see AudioManager.registerMediaButtonEventReceiverForCalls(ComponentName c) - * precondition: c != null - */ - protected void registerMediaButtonEventReceiverForCalls(ComponentName c) { - if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") - != PackageManager.PERMISSION_GRANTED) { - Log.e(TAG, "Invalid permissions to register media button receiver for calls"); - return; - } - synchronized(mPRStack) { - mMediaReceiverForCalls = c; - } - } - - /** - * see AudioManager.unregisterMediaButtonEventReceiverForCalls() - */ - protected void unregisterMediaButtonEventReceiverForCalls() { - if (mContext.checkCallingPermission("android.permission.MODIFY_PHONE_STATE") - != PackageManager.PERMISSION_GRANTED) { - Log.e(TAG, "Invalid permissions to unregister media button receiver for calls"); - return; - } - synchronized(mPRStack) { - mMediaReceiverForCalls = null; - } - } - - /** - * see AudioManager.registerRemoteControlClient(ComponentName eventReceiver, ...) - * @return the unique ID of the PlayerRecord associated with the RemoteControlClient - * Note: using this method with rcClient == null is a way to "disable" the IRemoteControlClient - * without modifying the RC stack, but while still causing the display to refresh (will - * become blank as a result of this) - */ - protected int registerRemoteControlClient(PendingIntent mediaIntent, - IRemoteControlClient rcClient, String callingPackageName) { - if (DEBUG_RC) Log.i(TAG, "Register remote control client rcClient="+rcClient); - int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; - synchronized(mPRStack) { - // store the new display information - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - if(prse.hasMatchingMediaButtonIntent(mediaIntent)) { - prse.resetControllerInfoForRcc(rcClient, callingPackageName, - Binder.getCallingUid()); - - if (rcClient == null) { - break; - } - - rccId = prse.getRccId(); - - // there is a new (non-null) client: - // give the new client the displays (if any) - if (mRcDisplays.size() > 0) { - plugRemoteControlDisplaysIntoClient_syncPrs(prse.getRcc()); - } - break; - } - }//for - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - - // if the eventReceiver is at the top of the stack - // then check for potential refresh of the remote controls - if (isCurrentRcController(mediaIntent)) { - checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); - } - }//synchronized(mPRStack) - return rccId; - } - - /** - * see AudioManager.unregisterRemoteControlClient(PendingIntent pi, ...) - * rcClient is guaranteed non-null - */ - protected void unregisterRemoteControlClient(PendingIntent mediaIntent, - IRemoteControlClient rcClient) { - if (DEBUG_RC) Log.i(TAG, "Unregister remote control client rcClient="+rcClient); - synchronized(mPRStack) { - boolean topRccChange = false; - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - if ((prse.hasMatchingMediaButtonIntent(mediaIntent)) - && rcClient.equals(prse.getRcc())) { - // we found the IRemoteControlClient to unregister - prse.resetControllerInfoForNoRcc(); - topRccChange = (index == mPRStack.size()-1); - // there can only be one matching RCC in the RC stack, we're done - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - if (topRccChange) { - // no more RCC for the RCD, check for potential refresh of the remote controls - checkUpdateRemoteControlDisplay_syncPrs(RC_INFO_ALL); - } - } - } - - - /** - * A class to encapsulate all the information about a remote control display. - * After instanciation, init() must always be called before the object is added in the list - * of displays. - * Before being removed from the list of displays, release() must always be called (otherwise - * it will leak death handlers). - */ - private class DisplayInfoForServer implements IBinder.DeathRecipient { - /** may never be null */ - private final IRemoteControlDisplay mRcDisplay; - private final IBinder mRcDisplayBinder; - private int mArtworkExpectedWidth = -1; - private int mArtworkExpectedHeight = -1; - private boolean mWantsPositionSync = false; - private ComponentName mClientNotifListComp; - private boolean mEnabled = true; - - public DisplayInfoForServer(IRemoteControlDisplay rcd, int w, int h) { - if (DEBUG_RC) Log.i(TAG, "new DisplayInfoForServer for " + rcd + " w=" + w + " h=" + h); - mRcDisplay = rcd; - mRcDisplayBinder = rcd.asBinder(); - mArtworkExpectedWidth = w; - mArtworkExpectedHeight = h; - } - - public boolean init() { - try { - mRcDisplayBinder.linkToDeath(this, 0); - } catch (RemoteException e) { - // remote control display is DOA, disqualify it - Log.w(TAG, "registerRemoteControlDisplay() has a dead client " + mRcDisplayBinder); - return false; - } - return true; - } - - public void release() { - try { - mRcDisplayBinder.unlinkToDeath(this, 0); - } catch (java.util.NoSuchElementException e) { - // not much we can do here, the display should have been unregistered anyway - Log.e(TAG, "Error in DisplaInfoForServer.relase()", e); - } - } - - public void binderDied() { - synchronized(mPRStack) { - Log.w(TAG, "RemoteControl: display " + mRcDisplay + " died"); - // remove the display from the list - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - if (di.mRcDisplay == mRcDisplay) { - if (DEBUG_RC) Log.w(TAG, " RCD removed from list"); - displayIterator.remove(); - return; - } - } - } - } - } - - /** - * The remote control displays. - * Access synchronized on mPRStack - */ - private ArrayList<DisplayInfoForServer> mRcDisplays = new ArrayList<DisplayInfoForServer>(1); - - /** - * Plug each registered display into the specified client - * @param rcc, guaranteed non null - */ - private void plugRemoteControlDisplaysIntoClient_syncPrs(IRemoteControlClient rcc) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - try { - rcc.plugRemoteControlDisplay(di.mRcDisplay, di.mArtworkExpectedWidth, - di.mArtworkExpectedHeight); - if (di.mWantsPositionSync) { - rcc.setWantsSyncForDisplay(di.mRcDisplay, true); - } - } catch (RemoteException e) { - Log.e(TAG, "Error connecting RCD to RCC in RCC registration",e); - } - } - } - - private void enableRemoteControlDisplayForClient_syncRcStack(IRemoteControlDisplay rcd, - boolean enabled) { - // let all the remote control clients know whether the given display is enabled - // (so the remote control stack traversal order doesn't matter). - final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - PlayerRecord prse = stackIterator.next(); - if(prse.getRcc() != null) { - try { - prse.getRcc().enableRemoteControlDisplay(rcd, enabled); - } catch (RemoteException e) { - Log.e(TAG, "Error connecting RCD to client: ", e); - } - } - } - } - - /** - * Is the remote control display interface already registered - * @param rcd - * @return true if the IRemoteControlDisplay is already in the list of displays - */ - private boolean rcDisplayIsPluggedIn_syncRcStack(IRemoteControlDisplay rcd) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - return true; - } - } - return false; - } - - /** - * Register an IRemoteControlDisplay. - * Notify all IRemoteControlClient of the new display and cause the RemoteControlClient - * at the top of the stack to update the new display with its information. - * @see android.media.IAudioService#registerRemoteControlDisplay(android.media.IRemoteControlDisplay, int, int) - * @param rcd the IRemoteControlDisplay to register. No effect if null. - * @param w the maximum width of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param h the maximum height of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param listenerComp the component for the listener interface, may be null if it's not needed - * to verify it belongs to one of the enabled notification listeners - */ - private void registerRemoteControlDisplay_int(IRemoteControlDisplay rcd, int w, int h, - ComponentName listenerComp) { - if (DEBUG_RC) Log.d(TAG, ">>> registerRemoteControlDisplay("+rcd+")"); - synchronized(mAudioFocusLock) { - synchronized(mPRStack) { - if ((rcd == null) || rcDisplayIsPluggedIn_syncRcStack(rcd)) { - return; - } - DisplayInfoForServer di = new DisplayInfoForServer(rcd, w, h); - di.mEnabled = true; - di.mClientNotifListComp = listenerComp; - if (!di.init()) { - if (DEBUG_RC) Log.e(TAG, " error registering RCD"); - return; - } - // add RCD to list of displays - mRcDisplays.add(di); - - // let all the remote control clients know there is a new display (so the remote - // control stack traversal order doesn't matter). - Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - PlayerRecord prse = stackIterator.next(); - if(prse.getRcc() != null) { - try { - prse.getRcc().plugRemoteControlDisplay(rcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Error connecting RCD to client: ", e); - } - } - } - - // we have a new display, of which all the clients are now aware: have it be - // initialized wih the current gen ID and the current client info, do not - // reset the information for the other (existing) displays - sendMsg(mEventHandler, MSG_RCDISPLAY_INIT_INFO, SENDMSG_QUEUE, - w /*arg1*/, h /*arg2*/, - rcd /*obj*/, 0/*delay*/); - } - } - } - - /** - * Unregister an IRemoteControlDisplay. - * No effect if the IRemoteControlDisplay hasn't been successfully registered. - * @see android.media.IAudioService#unregisterRemoteControlDisplay(android.media.IRemoteControlDisplay) - * @param rcd the IRemoteControlDisplay to unregister. No effect if null. - */ - protected void unregisterRemoteControlDisplay(IRemoteControlDisplay rcd) { - if (DEBUG_RC) Log.d(TAG, "<<< unregisterRemoteControlDisplay("+rcd+")"); - synchronized(mPRStack) { - if (rcd == null) { - return; - } - - boolean displayWasPluggedIn = false; - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext() && !displayWasPluggedIn) { - final DisplayInfoForServer di = displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - displayWasPluggedIn = true; - di.release(); - displayIterator.remove(); - } - } - - if (displayWasPluggedIn) { - // disconnect this remote control display from all the clients, so the remote - // control stack traversal order doesn't matter - final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - final PlayerRecord prse = stackIterator.next(); - if(prse.getRcc() != null) { - try { - prse.getRcc().unplugRemoteControlDisplay(rcd); - } catch (RemoteException e) { - Log.e(TAG, "Error disconnecting remote control display to client: ", e); - } - } - } - } else { - if (DEBUG_RC) Log.w(TAG, " trying to unregister unregistered RCD"); - } - } - } - - /** - * Update the size of the artwork used by an IRemoteControlDisplay. - * @see android.media.IAudioService#remoteControlDisplayUsesBitmapSize(android.media.IRemoteControlDisplay, int, int) - * @param rcd the IRemoteControlDisplay with the new artwork size requirement - * @param w the maximum width of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - * @param h the maximum height of the expected bitmap. Negative or zero values indicate this - * display doesn't need to receive artwork. - */ - protected void remoteControlDisplayUsesBitmapSize(IRemoteControlDisplay rcd, int w, int h) { - synchronized(mPRStack) { - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - boolean artworkSizeUpdate = false; - while (displayIterator.hasNext() && !artworkSizeUpdate) { - final DisplayInfoForServer di = displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - if ((di.mArtworkExpectedWidth != w) || (di.mArtworkExpectedHeight != h)) { - di.mArtworkExpectedWidth = w; - di.mArtworkExpectedHeight = h; - artworkSizeUpdate = true; - } - } - } - if (artworkSizeUpdate) { - // RCD is currently plugged in and its artwork size has changed, notify all RCCs, - // stack traversal order doesn't matter - final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while(stackIterator.hasNext()) { - final PlayerRecord prse = stackIterator.next(); - if(prse.getRcc() != null) { - try { - prse.getRcc().setBitmapSizeForDisplay(rcd, w, h); - } catch (RemoteException e) { - Log.e(TAG, "Error setting bitmap size for RCD on RCC: ", e); - } - } - } - } - } - } - - /** - * Controls whether a remote control display needs periodic checks of the RemoteControlClient - * playback position to verify that the estimated position has not drifted from the actual - * position. By default the check is not performed. - * The IRemoteControlDisplay must have been previously registered for this to have any effect. - * @param rcd the IRemoteControlDisplay for which the anti-drift mechanism will be enabled - * or disabled. Not null. - * @param wantsSync if true, RemoteControlClient instances which expose their playback position - * to the framework will regularly compare the estimated playback position with the actual - * position, and will update the IRemoteControlDisplay implementation whenever a drift is - * detected. - */ - protected void remoteControlDisplayWantsPlaybackPositionSync(IRemoteControlDisplay rcd, - boolean wantsSync) { - synchronized(mPRStack) { - boolean rcdRegistered = false; - // store the information about this display - // (display stack traversal order doesn't matter). - final Iterator<DisplayInfoForServer> displayIterator = mRcDisplays.iterator(); - while (displayIterator.hasNext()) { - final DisplayInfoForServer di = displayIterator.next(); - if (di.mRcDisplay.asBinder().equals(rcd.asBinder())) { - di.mWantsPositionSync = wantsSync; - rcdRegistered = true; - break; - } - } - if (!rcdRegistered) { - return; - } - // notify all current RemoteControlClients - // (stack traversal order doesn't matter as we notify all RCCs) - final Iterator<PlayerRecord> stackIterator = mPRStack.iterator(); - while (stackIterator.hasNext()) { - final PlayerRecord prse = stackIterator.next(); - if (prse.getRcc() != null) { - try { - prse.getRcc().setWantsSyncForDisplay(rcd, wantsSync); - } catch (RemoteException e) { - Log.e(TAG, "Error setting position sync flag for RCD on RCC: ", e); - } - } - } - } - } - - // handler for MSG_RCC_NEW_VOLUME_OBS - private void onRegisterVolumeObserverForRcc(int rccId, IRemoteVolumeObserver rvo) { - synchronized(mPRStack) { - // The stack traversal order doesn't matter because there is only one stack entry - // with this RCC ID, but the matching ID is more likely at the top of the stack, so - // start iterating from the top. - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - if (prse.getRccId() == rccId) { - prse.mRemoteVolumeObs = rvo; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - } - - /** - * Checks if a remote client is active on the supplied stream type. Update the remote stream - * volume state if found and playing - * @param streamType - * @return false if no remote playing is currently playing - */ - protected boolean checkUpdateRemoteStateIfActive(int streamType) { - synchronized(mPRStack) { - // iterating from top of stack as active playback is more likely on entries at the top - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - if ((prse.mPlaybackType == RemoteControlClient.PLAYBACK_TYPE_REMOTE) - && isPlaystateActive(prse.mPlaybackState.mState) - && (prse.mPlaybackStream == streamType)) { - if (DEBUG_RC) Log.d(TAG, "remote playback active on stream " + streamType - + ", vol =" + prse.mPlaybackVolume); - synchronized (mMainRemote) { - mMainRemote.mRccId = prse.getRccId(); - mMainRemote.mVolume = prse.mPlaybackVolume; - mMainRemote.mVolumeMax = prse.mPlaybackVolumeMax; - mMainRemote.mVolumeHandling = prse.mPlaybackVolumeHandling; - mMainRemoteIsActive = true; - } - return true; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing RC stack, lock error? ", e); - } - } - synchronized (mMainRemote) { - mMainRemoteIsActive = false; - } - return false; - } - - /** - * Returns true if the given playback state is considered "active", i.e. it describes a state - * where playback is happening, or about to - * @param playState the playback state to evaluate - * @return true if active, false otherwise (inactive or unknown) - */ - protected static boolean isPlaystateActive(int playState) { - switch (playState) { - case RemoteControlClient.PLAYSTATE_PLAYING: - case RemoteControlClient.PLAYSTATE_BUFFERING: - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - case RemoteControlClient.PLAYSTATE_REWINDING: - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return true; - default: - return false; - } - } - - private void sendVolumeUpdateToRemote(int rccId, int direction) { - if (DEBUG_VOL) { Log.d(TAG, "sendVolumeUpdateToRemote(rccId="+rccId+" , dir="+direction); } - if (direction == 0) { - // only handling discrete events - return; - } - IRemoteVolumeObserver rvo = null; - synchronized (mPRStack) { - // The stack traversal order doesn't matter because there is only one stack entry - // with this RCC ID, but the matching ID is more likely at the top of the stack, so - // start iterating from the top. - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? - if (prse.getRccId() == rccId) { - rvo = prse.mRemoteVolumeObs; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - if (rvo != null) { - try { - rvo.dispatchRemoteVolumeUpdate(direction, -1); - } catch (RemoteException e) { - Log.e(TAG, "Error dispatching relative volume update", e); - } - } - } - - protected int getRemoteStreamMaxVolume() { - synchronized (mMainRemote) { - if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { - return 0; - } - return mMainRemote.mVolumeMax; - } - } - - protected int getRemoteStreamVolume() { - synchronized (mMainRemote) { - if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { - return 0; - } - return mMainRemote.mVolume; - } - } - - protected void setRemoteStreamVolume(int vol) { - if (DEBUG_VOL) { Log.d(TAG, "setRemoteStreamVolume(vol="+vol+")"); } - int rccId = RemoteControlClient.RCSE_ID_UNREGISTERED; - synchronized (mMainRemote) { - if (mMainRemote.mRccId == RemoteControlClient.RCSE_ID_UNREGISTERED) { - return; - } - rccId = mMainRemote.mRccId; - } - IRemoteVolumeObserver rvo = null; - synchronized (mPRStack) { - // The stack traversal order doesn't matter because there is only one stack entry - // with this RCC ID, but the matching ID is more likely at the top of the stack, so - // start iterating from the top. - try { - for (int index = mPRStack.size()-1; index >= 0; index--) { - final PlayerRecord prse = mPRStack.elementAt(index); - //FIXME OPTIMIZE store this info in mMainRemote so we don't have to iterate? - if (prse.getRccId() == rccId) { - rvo = prse.mRemoteVolumeObs; - break; - } - } - } catch (ArrayIndexOutOfBoundsException e) { - // not expected to happen, indicates improper concurrent modification - Log.e(TAG, "Wrong index accessing media button stack, lock error? ", e); - } - } - if (rvo != null) { - try { - rvo.dispatchRemoteVolumeUpdate(0, vol); - } catch (RemoteException e) { - Log.e(TAG, "Error dispatching absolute volume update", e); - } - } - } - - /** - * Call to make AudioService reevaluate whether it's in a mode where remote players should - * have their volume controlled. In this implementation this is only to reset whether - * VolumePanel should display remote volumes - */ - protected void postReevaluateRemote() { - sendMsg(mEventHandler, MSG_REEVALUATE_REMOTE, SENDMSG_QUEUE, 0, 0, null, 0); - } - - private void onReevaluateRemote() { - // TODO This was used to notify VolumePanel if there was remote playback - // in the stack. This is now in MediaSessionService. More code should be - // removed. - } - } diff --git a/services/core/java/com/android/server/audio/PlayerRecord.java b/services/core/java/com/android/server/audio/PlayerRecord.java deleted file mode 100644 index e98f12e275fd..000000000000 --- a/services/core/java/com/android/server/audio/PlayerRecord.java +++ /dev/null @@ -1,360 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.audio; - -import android.app.PendingIntent; -import android.content.ComponentName; -import android.media.AudioManager; -import android.media.IRemoteControlClient; -import android.media.IRemoteVolumeObserver; -import android.media.RemoteControlClient; -import android.os.IBinder; -import android.os.IBinder.DeathRecipient; -import android.os.RemoteException; -import android.util.Log; - -import java.io.PrintWriter; - -/** - * @hide - * Class to handle all the information about a media player, encapsulating information - * about its use RemoteControlClient, playback type and volume... The lifecycle of each - * instance is managed by android.media.MediaFocusControl, from its addition to the player stack - * stack to its release. - */ -class PlayerRecord implements DeathRecipient { - - // on purpose not using this classe's name, as it will only be used from MediaFocusControl - private static final String TAG = "MediaFocusControl"; - private static final boolean DEBUG = false; - - /** - * A global counter for RemoteControlClient identifiers - */ - private static int sLastRccId = 0; - - public static MediaFocusControl sController; - - /** - * The target for the ACTION_MEDIA_BUTTON events. - * Always non null. //FIXME verify - */ - final private PendingIntent mMediaIntent; - /** - * The registered media button event receiver. - */ - final private ComponentName mReceiverComponent; - - private int mRccId = -1; - - /** - * A non-null token implies this record tracks a "live" player whose death is being monitored. - */ - private IBinder mToken; - private String mCallingPackageName; - private int mCallingUid; - /** - * Provides access to the information to display on the remote control. - * May be null (when a media button event receiver is registered, - * but no remote control client has been registered) */ - private IRemoteControlClient mRcClient; - private RcClientDeathHandler mRcClientDeathHandler; - /** - * Information only used for non-local playback - */ - //FIXME private? - public int mPlaybackType; - public int mPlaybackVolume; - public int mPlaybackVolumeMax; - public int mPlaybackVolumeHandling; - public int mPlaybackStream; - public RccPlaybackState mPlaybackState; - public IRemoteVolumeObserver mRemoteVolumeObs; - - - protected static class RccPlaybackState { - public int mState; - public long mPositionMs; - public float mSpeed; - - public RccPlaybackState(int state, long positionMs, float speed) { - mState = state; - mPositionMs = positionMs; - mSpeed = speed; - } - - public void reset() { - mState = RemoteControlClient.PLAYSTATE_STOPPED; - mPositionMs = RemoteControlClient.PLAYBACK_POSITION_INVALID; - mSpeed = RemoteControlClient.PLAYBACK_SPEED_1X; - } - - @Override - public String toString() { - return stateToString() + ", " + posToString() + ", " + mSpeed + "X"; - } - - private String posToString() { - if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_INVALID) { - return "PLAYBACK_POSITION_INVALID"; - } else if (mPositionMs == RemoteControlClient.PLAYBACK_POSITION_ALWAYS_UNKNOWN) { - return "PLAYBACK_POSITION_ALWAYS_UNKNOWN"; - } else { - return (String.valueOf(mPositionMs) + "ms"); - } - } - - private String stateToString() { - switch (mState) { - case RemoteControlClient.PLAYSTATE_NONE: - return "PLAYSTATE_NONE"; - case RemoteControlClient.PLAYSTATE_STOPPED: - return "PLAYSTATE_STOPPED"; - case RemoteControlClient.PLAYSTATE_PAUSED: - return "PLAYSTATE_PAUSED"; - case RemoteControlClient.PLAYSTATE_PLAYING: - return "PLAYSTATE_PLAYING"; - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - return "PLAYSTATE_FAST_FORWARDING"; - case RemoteControlClient.PLAYSTATE_REWINDING: - return "PLAYSTATE_REWINDING"; - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - return "PLAYSTATE_SKIPPING_FORWARDS"; - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - return "PLAYSTATE_SKIPPING_BACKWARDS"; - case RemoteControlClient.PLAYSTATE_BUFFERING: - return "PLAYSTATE_BUFFERING"; - case RemoteControlClient.PLAYSTATE_ERROR: - return "PLAYSTATE_ERROR"; - default: - return "[invalid playstate]"; - } - } - } - - - /** - * Inner class to monitor remote control client deaths, and remove the client for the - * remote control stack if necessary. - */ - private class RcClientDeathHandler implements IBinder.DeathRecipient { - final private IBinder mCb; // To be notified of client's death - //FIXME needed? - final private PendingIntent mMediaIntent; - - RcClientDeathHandler(IBinder cb, PendingIntent pi) { - mCb = cb; - mMediaIntent = pi; - } - - public void binderDied() { - Log.w(TAG, " RemoteControlClient died"); - // remote control client died, make sure the displays don't use it anymore - // by setting its remote control client to null - sController.registerRemoteControlClient(mMediaIntent, null/*rcClient*/, null/*ignored*/); - // the dead client was maybe handling remote playback, the controller should reevaluate - sController.postReevaluateRemote(); - } - - public IBinder getBinder() { - return mCb; - } - } - - - protected static class RemotePlaybackState { - int mRccId; - int mVolume; - int mVolumeMax; - int mVolumeHandling; - - protected RemotePlaybackState(int id, int vol, int volMax) { - mRccId = id; - mVolume = vol; - mVolumeMax = volMax; - mVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; - } - } - - - void dump(PrintWriter pw, boolean registrationInfo) { - if (registrationInfo) { - pw.println(" pi: " + mMediaIntent + - " -- pack: " + mCallingPackageName + - " -- ercvr: " + mReceiverComponent + - " -- client: " + mRcClient + - " -- uid: " + mCallingUid + - " -- type: " + mPlaybackType + - " state: " + mPlaybackState); - } else { - // emphasis on state - pw.println(" uid: " + mCallingUid + - " -- id: " + mRccId + - " -- type: " + mPlaybackType + - " -- state: " + mPlaybackState + - " -- vol handling: " + mPlaybackVolumeHandling + - " -- vol: " + mPlaybackVolume + - " -- volMax: " + mPlaybackVolumeMax + - " -- volObs: " + mRemoteVolumeObs); - } - } - - - static protected void setMediaFocusControl(MediaFocusControl mfc) { - sController = mfc; - } - - /** precondition: mediaIntent != null */ - protected PlayerRecord(PendingIntent mediaIntent, ComponentName eventReceiver, IBinder token) - { - mMediaIntent = mediaIntent; - mReceiverComponent = eventReceiver; - mToken = token; - mCallingUid = -1; - mRcClient = null; - mRccId = ++sLastRccId; - mPlaybackState = new RccPlaybackState( - RemoteControlClient.PLAYSTATE_STOPPED, - RemoteControlClient.PLAYBACK_POSITION_INVALID, - RemoteControlClient.PLAYBACK_SPEED_1X); - - resetPlaybackInfo(); - if (mToken != null) { - try { - mToken.linkToDeath(this, 0); - } catch (RemoteException e) { - sController.unregisterMediaButtonIntentAsync(mMediaIntent); - } - } - } - - //--------------------------------------------- - // Accessors - protected int getRccId() { - return mRccId; - } - - protected IRemoteControlClient getRcc() { - return mRcClient; - } - - protected ComponentName getMediaButtonReceiver() { - return mReceiverComponent; - } - - protected PendingIntent getMediaButtonIntent() { - return mMediaIntent; - } - - protected boolean hasMatchingMediaButtonIntent(PendingIntent pi) { - if (mToken != null) { - return mMediaIntent.equals(pi); - } else { - if (mReceiverComponent != null) { - return mReceiverComponent.equals(pi.getIntent().getComponent()); - } else { - return false; - } - } - } - - protected boolean isPlaybackActive() { - return MediaFocusControl.isPlaystateActive(mPlaybackState.mState); - } - - //--------------------------------------------- - // Modify the records stored in the instance - protected void resetControllerInfoForRcc(IRemoteControlClient rcClient, - String callingPackageName, int uid) { - // already had a remote control client? - if (mRcClientDeathHandler != null) { - // stop monitoring the old client's death - unlinkToRcClientDeath(); - } - // save the new remote control client - mRcClient = rcClient; - mCallingPackageName = callingPackageName; - mCallingUid = uid; - if (rcClient == null) { - // here mcse.mRcClientDeathHandler is null; - resetPlaybackInfo(); - } else { - IBinder b = mRcClient.asBinder(); - RcClientDeathHandler rcdh = - new RcClientDeathHandler(b, mMediaIntent); - try { - b.linkToDeath(rcdh, 0); - } catch (RemoteException e) { - // remote control client is DOA, disqualify it - Log.w(TAG, "registerRemoteControlClient() has a dead client " + b); - mRcClient = null; - } - mRcClientDeathHandler = rcdh; - } - } - - protected void resetControllerInfoForNoRcc() { - // stop monitoring the RCC death - unlinkToRcClientDeath(); - // reset the RCC-related fields - mRcClient = null; - mCallingPackageName = null; - } - - public void resetPlaybackInfo() { - mPlaybackType = RemoteControlClient.PLAYBACK_TYPE_LOCAL; - mPlaybackVolume = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; - mPlaybackVolumeMax = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME; - mPlaybackVolumeHandling = RemoteControlClient.DEFAULT_PLAYBACK_VOLUME_HANDLING; - mPlaybackStream = AudioManager.STREAM_MUSIC; - mPlaybackState.reset(); - mRemoteVolumeObs = null; - } - - //--------------------------------------------- - public void unlinkToRcClientDeath() { - if ((mRcClientDeathHandler != null) && (mRcClientDeathHandler.mCb != null)) { - try { - mRcClientDeathHandler.mCb.unlinkToDeath(mRcClientDeathHandler, 0); - mRcClientDeathHandler = null; - } catch (java.util.NoSuchElementException e) { - // not much we can do here - Log.e(TAG, "Error in unlinkToRcClientDeath()", e); - } - } - } - - // FIXME rename to "release"? (as in FocusRequester class) - public void destroy() { - unlinkToRcClientDeath(); - if (mToken != null) { - mToken.unlinkToDeath(this, 0); - mToken = null; - } - } - - @Override - public void binderDied() { - sController.unregisterMediaButtonIntentAsync(mMediaIntent); - } - - @Override - protected void finalize() throws Throwable { - destroy(); // unlink exception handled inside method - super.finalize(); - } -} |