diff options
| author | 2014-06-09 19:07:48 +0000 | |
|---|---|---|
| committer | 2014-06-09 19:07:49 +0000 | |
| commit | a3d81885566f79b0e21438bd69634cc53a80918b (patch) | |
| tree | c22e883695efce7fd7454b7cced8fd1c24f055bd | |
| parent | 55f6ab4c813b6d355ba1ba72e4e2d98b9583f6f3 (diff) | |
| parent | 2e7a9167aeefeb451f8d8c769175b9a0163744f3 (diff) | |
Merge "Adds listeners for changes to the list of active sessions" into lmp-preview-dev
7 files changed, 266 insertions, 26 deletions
diff --git a/Android.mk b/Android.mk index c1de8a05bb92..8f06d2daf5c1 100644 --- a/Android.mk +++ b/Android.mk @@ -304,14 +304,15 @@ LOCAL_SRC_FILES += \ media/java/android/media/IRemoteVolumeObserver.aidl \ media/java/android/media/IRingtonePlayer.aidl \ media/java/android/media/IVolumeController.aidl \ - media/java/android/media/routeprovider/IRouteConnection.aidl \ - media/java/android/media/routeprovider/IRouteProvider.aidl \ - media/java/android/media/routeprovider/IRouteProviderCallback.aidl \ - media/java/android/media/session/ISessionController.aidl \ - media/java/android/media/session/ISessionControllerCallback.aidl \ - media/java/android/media/session/ISession.aidl \ - media/java/android/media/session/ISessionCallback.aidl \ - media/java/android/media/session/ISessionManager.aidl \ + media/java/android/media/routeprovider/IRouteConnection.aidl \ + media/java/android/media/routeprovider/IRouteProvider.aidl \ + media/java/android/media/routeprovider/IRouteProviderCallback.aidl \ + media/java/android/media/session/IActiveSessionsListener.aidl \ + media/java/android/media/session/ISessionController.aidl \ + media/java/android/media/session/ISessionControllerCallback.aidl \ + media/java/android/media/session/ISession.aidl \ + media/java/android/media/session/ISessionCallback.aidl \ + media/java/android/media/session/ISessionManager.aidl \ media/java/android/media/tv/ITvInputClient.aidl \ media/java/android/media/tv/ITvInputHardware.aidl \ media/java/android/media/tv/ITvInputHardwareCallback.aidl \ diff --git a/media/java/android/media/session/IActiveSessionsListener.aidl b/media/java/android/media/session/IActiveSessionsListener.aidl new file mode 100644 index 000000000000..e5e24bc9f805 --- /dev/null +++ b/media/java/android/media/session/IActiveSessionsListener.aidl @@ -0,0 +1,26 @@ +/* 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 android.media.session; + +import android.media.session.MediaSessionToken; + +/** + * Listens for changes to the list of active sessions. + * @hide + */ +oneway interface IActiveSessionsListener { + void onActiveSessionsChanged(in List<MediaSessionToken> sessions); +}
\ No newline at end of file diff --git a/media/java/android/media/session/ISessionManager.aidl b/media/java/android/media/session/ISessionManager.aidl index 6d9888f09c14..bd1fa851fcc2 100644 --- a/media/java/android/media/session/ISessionManager.aidl +++ b/media/java/android/media/session/ISessionManager.aidl @@ -16,6 +16,7 @@ package android.media.session; import android.content.ComponentName; +import android.media.session.IActiveSessionsListener; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.os.Bundle; @@ -30,4 +31,7 @@ interface ISessionManager { List<IBinder> getSessions(in ComponentName compName, int userId); void dispatchMediaKeyEvent(in KeyEvent keyEvent, boolean needWakeLock); void dispatchAdjustVolumeBy(int suggestedStream, int delta, int flags); + void addSessionsListener(in IActiveSessionsListener listener, in ComponentName compName, + int userId); + void removeSessionsListener(in IActiveSessionsListener listener); }
\ No newline at end of file diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 9e8b0d36b5b2..291bfc8f9de3 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -142,6 +142,50 @@ public final class MediaSessionManager { } /** + * Add a listener to be notified when the list of active sessions + * changes.This requires the + * android.Manifest.permission.MEDIA_CONTENT_CONTROL permission be held by + * the calling app. You may also retrieve this list if your app is an + * enabled notification listener using the + * {@link NotificationListenerService} APIs, in which case you must pass the + * {@link ComponentName} of your enabled listener. + * + * @param sessionListener The listener to add. + * @param notificationListener The enabled notification listener component. + * May be null. + * @param userId The userId to listen for changes on. + * @hide + */ + public void addActiveSessionsListener(SessionListener sessionListener, + ComponentName notificationListener, int userId) { + if (sessionListener == null) { + throw new IllegalArgumentException("listener may not be null"); + } + try { + mService.addSessionsListener(sessionListener.mStub, notificationListener, userId); + } catch (RemoteException e) { + Log.e(TAG, "Error in addActiveSessionsListener.", e); + } + } + + /** + * Stop receiving active sessions updates on the specified listener. + * + * @param listener The listener to remove. + * @hide + */ + public void removeActiveSessionsListener(SessionListener listener) { + if (listener == null) { + throw new IllegalArgumentException("listener may not be null"); + } + try { + mService.removeSessionsListener(listener.mStub); + } catch (RemoteException e) { + Log.e(TAG, "Error in removeActiveSessionsListener.", e); + } + } + + /** * Send a media key event. The receiver will be selected automatically. * * @param keyEvent The KeyEvent to send. @@ -184,4 +228,35 @@ public final class MediaSessionManager { Log.e(TAG, "Failed to send adjust volume.", e); } } + + /** + * Listens for changes to the list of active sessions. This can be added + * using {@link #addActiveSessionsListener}. + * + * @hide + */ + public static abstract class SessionListener { + /** + * Called when the list of active sessions has changed. This can be due + * to a session being added or removed or the order of sessions + * changing. + * + * @param controllers The updated list of controllers for the user that + * changed. + */ + public abstract void onActiveSessionsChanged(List<MediaController> controllers); + + private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() { + @Override + public void onActiveSessionsChanged(List<MediaSessionToken> tokens) + throws RemoteException { + ArrayList<MediaController> controllers = new ArrayList<MediaController>(); + int size = tokens.size(); + for (int i = 0; i < size; i++) { + controllers.add(MediaController.fromToken(tokens.get(i))); + } + SessionListener.this.onActiveSessionsChanged(controllers); + } + }; + } } diff --git a/media/java/android/media/session/MediaSessionToken.java b/media/java/android/media/session/MediaSessionToken.java index 86f566203c6d..e5991894728b 100644 --- a/media/java/android/media/session/MediaSessionToken.java +++ b/media/java/android/media/session/MediaSessionToken.java @@ -31,7 +31,7 @@ public final class MediaSessionToken implements Parcelable { /** * @hide */ - MediaSessionToken(ISessionController binder) { + public MediaSessionToken(ISessionController binder) { mBinder = binder; } diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java index a0b796103554..67065ba33a86 100644 --- a/services/core/java/com/android/server/media/MediaSessionService.java +++ b/services/core/java/com/android/server/media/MediaSessionService.java @@ -29,9 +29,11 @@ import android.content.pm.PackageManager; import android.media.AudioManager; import android.media.IAudioService; import android.media.routeprovider.RouteRequest; +import android.media.session.IActiveSessionsListener; import android.media.session.ISession; import android.media.session.ISessionCallback; import android.media.session.ISessionManager; +import android.media.session.MediaSessionToken; import android.media.session.RouteInfo; import android.media.session.RouteOptions; import android.media.session.MediaSession; @@ -39,6 +41,7 @@ import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.PowerManager; import android.os.RemoteException; import android.os.ResultReceiver; @@ -75,10 +78,12 @@ public class MediaSessionService extends SystemService implements Monitor { private final ArrayList<MediaSessionRecord> mAllSessions = new ArrayList<MediaSessionRecord>(); private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>(); + private final ArrayList<SessionsListenerRecord> mSessionsListeners + = new ArrayList<SessionsListenerRecord>(); // private final ArrayList<MediaRouteProviderProxy> mProviders // = new ArrayList<MediaRouteProviderProxy>(); private final Object mLock = new Object(); - private final Handler mHandler = new Handler(); + private final MessageHandler mHandler = new MessageHandler(); private final PowerManager.WakeLock mMediaEventWakeLock; private KeyguardManager mKeyguardManager; @@ -200,15 +205,20 @@ public class MediaSessionService extends SystemService implements Monitor { } } } + mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0); } public void onSessionPlaystateChange(MediaSessionRecord record, int oldState, int newState) { + boolean updateSessions = false; synchronized (mLock) { if (!mAllSessions.contains(record)) { Log.d(TAG, "Unknown session changed playback state. Ignoring."); return; } - mPriorityStack.onPlaystateChange(record, oldState, newState); + updateSessions = mPriorityStack.onPlaystateChange(record, oldState, newState); + } + if (updateSessions) { + mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, record.getUserId(), 0); } } @@ -315,6 +325,8 @@ public class MediaSessionService extends SystemService implements Monitor { // ignore exceptions while destroying a session. } session.onDestroy(); + + mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, session.getUserId(), 0); } private void enforcePackageName(String packageName, int uid) { @@ -428,6 +440,8 @@ public class MediaSessionService extends SystemService implements Monitor { UserRecord user = getOrCreateUser(userId); user.addSessionLocked(session); + mHandler.post(MessageHandler.MSG_SESSIONS_CHANGED, userId, 0); + if (DEBUG) { Log.d(TAG, "Created session for package " + callerPackageName + " with tag " + tag); } @@ -453,11 +467,43 @@ public class MediaSessionService extends SystemService implements Monitor { return -1; } + private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) { + for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { + if (mSessionsListeners.get(i).mListener == listener) { + return i; + } + } + return -1; + } + private boolean isSessionDiscoverable(MediaSessionRecord record) { // TODO probably want to check more than if it's active. return record.isActive(); } + private void pushSessionsChanged(int userId) { + synchronized (mLock) { + List<MediaSessionRecord> records = mPriorityStack.getActiveSessions(userId); + int size = records.size(); + ArrayList<MediaSessionToken> tokens = new ArrayList<MediaSessionToken>(); + for (int i = 0; i < size; i++) { + tokens.add(new MediaSessionToken(records.get(i).getControllerBinder())); + } + for (int i = mSessionsListeners.size() - 1; i >= 0; i--) { + SessionsListenerRecord record = mSessionsListeners.get(i); + if (record.mUserId == UserHandle.USER_ALL || record.mUserId == userId) { + try { + record.mListener.onActiveSessionsChanged(tokens); + } catch (RemoteException e) { + Log.w(TAG, "Dead ActiveSessionsListener in pushSessionsChanged, removing", + e); + mSessionsListeners.remove(i); + } + } + } + } + } + private MediaRouteProviderProxy.RoutesListener mRoutesCallback = new MediaRouteProviderProxy.RoutesListener() { @Override @@ -613,6 +659,23 @@ public class MediaSessionService extends SystemService implements Monitor { }; } + final class SessionsListenerRecord implements IBinder.DeathRecipient { + private final IActiveSessionsListener mListener; + private final int mUserId; + + public SessionsListenerRecord(IActiveSessionsListener listener, int userId) { + mListener = listener; + mUserId = userId; + } + + @Override + public void binderDied() { + synchronized (mLock) { + mSessionsListeners.remove(this); + } + } + } + class SessionManagerImpl extends ISessionManager.Stub { private static final String EXTRA_WAKELOCK_ACQUIRED = "android.media.AudioService.WAKELOCK_ACQUIRED"; @@ -648,20 +711,7 @@ public class MediaSessionService extends SystemService implements Monitor { final long token = Binder.clearCallingIdentity(); try { - String packageName = null; - if (componentName != null) { - // If they gave us a component name verify they own the - // package - packageName = componentName.getPackageName(); - enforcePackageName(packageName, uid); - } - // Check that they can make calls on behalf of the user and - // get the final user id - int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, - true /* allowAll */, true /* requireFull */, "getSessions", packageName); - // Check if they have the permissions or their component is - // enabled for the user they're calling from. - enforceMediaPermissions(componentName, pid, uid, resolvedUserId); + int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid); ArrayList<IBinder> binders = new ArrayList<IBinder>(); synchronized (mLock) { ArrayList<MediaSessionRecord> records = mPriorityStack @@ -677,6 +727,52 @@ public class MediaSessionService extends SystemService implements Monitor { } } + @Override + public void addSessionsListener(IActiveSessionsListener listener, + ComponentName componentName, int userId) throws RemoteException { + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + + try { + int resolvedUserId = verifySessionsRequest(componentName, userId, pid, uid); + synchronized (mLock) { + int index = findIndexOfSessionsListenerLocked(listener); + if (index != -1) { + Log.w(TAG, "ActiveSessionsListener is already added, ignoring"); + return; + } + SessionsListenerRecord record = new SessionsListenerRecord(listener, + resolvedUserId); + try { + listener.asBinder().linkToDeath(record, 0); + } catch (RemoteException e) { + Log.e(TAG, "ActiveSessionsListener is dead, ignoring it", e); + return; + } + mSessionsListeners.add(record); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void removeSessionsListener(IActiveSessionsListener listener) + throws RemoteException { + synchronized (mLock) { + int index = findIndexOfSessionsListenerLocked(listener); + if (index != -1) { + SessionsListenerRecord record = mSessionsListeners.remove(index); + try { + record.mListener.asBinder().unlinkToDeath(record, 0); + } catch (Exception e) { + // ignore exceptions, the record is being removed + } + } + } + } + /** * Handles the dispatching of the media button events to one of the * registered listeners, or if there was none, broadcast an @@ -764,6 +860,25 @@ public class MediaSessionService extends SystemService implements Monitor { } } + private int verifySessionsRequest(ComponentName componentName, int userId, final int pid, + final int uid) { + String packageName = null; + if (componentName != null) { + // If they gave us a component name verify they own the + // package + packageName = componentName.getPackageName(); + enforcePackageName(packageName, uid); + } + // Check that they can make calls on behalf of the user and + // get the final user id + int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId, + true /* allowAll */, true /* requireFull */, "getSessions", packageName); + // Check if they have the permissions or their component is + // enabled for the user they're calling from. + enforceMediaPermissions(componentName, pid, uid, resolvedUserId); + return resolvedUserId; + } + private void dispatchAdjustVolumeByLocked(int suggestedStream, int delta, int flags, MediaSessionRecord session) { int direction = 0; @@ -1005,4 +1120,20 @@ public class MediaSessionService extends SystemService implements Monitor { }; } + final class MessageHandler extends Handler { + private static final int MSG_SESSIONS_CHANGED = 1; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SESSIONS_CHANGED: + pushSessionsChanged(msg.arg1); + break; + } + } + + public void post(int what, int arg1, int arg2) { + obtainMessage(what, arg1, arg2).sendToTarget(); + } + } } diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java index 803dee296fb2..144ccfafc018 100644 --- a/services/core/java/com/android/server/media/MediaSessionStack.java +++ b/services/core/java/com/android/server/media/MediaSessionStack.java @@ -88,16 +88,19 @@ public class MediaSessionStack { * @param record The record that changed. * @param oldState Its old playback state. * @param newState Its new playback state. + * @return true if the priority order was updated, false otherwise. */ - public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { + public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { if (shouldUpdatePriority(oldState, newState)) { mSessions.remove(record); mSessions.add(0, record); clearCache(); + return true; } else if (newState == PlaybackState.STATE_PAUSED) { // Just clear the volume cache in this case mCachedVolumeDefault = null; } + return false; } /** |