diff options
author | Xin Li <delphij@google.com> | 2022-08-15 22:05:00 -0700 |
---|---|---|
committer | Xin Li <delphij@google.com> | 2022-08-15 22:05:00 -0700 |
commit | aad5eea70d817e1009648319a0716c0ff9829eda (patch) | |
tree | 9e78ee42e94bafd710a347d6ec32c0b943ff6569 | |
parent | 26479648b9342845863628bb86afb2c7edef31ad (diff) | |
parent | a2ab801ab3555676a32485cc58e07eb420fd6f25 (diff) | |
download | Media-aad5eea70d817e1009648319a0716c0ff9829eda.tar.gz |
DO NOT MERGE - Merge Android 13
Bug: 242648940
Merged-In: I83952c087db24a978729f4cff5d2f2cc203ec38f
Change-Id: I696b32e73e0a18a74cdd1c905e8ca409e9390b55
-rw-r--r-- | apex/aidl/private/android/media/IMediaCommunicationService.aidl | 3 | ||||
-rw-r--r-- | apex/framework/Android.bp | 57 | ||||
-rw-r--r-- | apex/framework/api/module-lib-current.txt | 1 | ||||
-rw-r--r-- | apex/framework/java/android/media/BaseMediaParceledListSlice.java | 8 | ||||
-rw-r--r-- | apex/framework/java/android/media/MediaCommunicationManager.java | 69 | ||||
-rw-r--r-- | apex/framework/java/android/media/MediaSession2.java | 7 | ||||
-rw-r--r-- | apex/framework/java/android/media/MediaSession2Service.java | 20 | ||||
-rw-r--r-- | apex/framework/java/android/media/MediaTranscodingManager.java | 8 | ||||
-rw-r--r-- | apex/framework/java/android/media/Session2Command.java | 5 | ||||
-rw-r--r-- | apex/framework/java/android/media/Session2CommandGroup.java | 2 | ||||
-rw-r--r-- | apex/framework/lint-baseline.xml | 324 | ||||
-rw-r--r-- | apex/service/Android.bp | 7 | ||||
-rw-r--r-- | apex/service/jarjar_rules.txt | 1 | ||||
-rw-r--r-- | apex/service/java/com/android/server/media/MediaCommunicationService.java | 169 | ||||
-rw-r--r-- | apex/service/java/com/android/server/media/SessionPriorityList.java | 199 | ||||
-rw-r--r-- | apex/service/lint-baseline.xml | 4 |
16 files changed, 487 insertions, 397 deletions
diff --git a/apex/aidl/private/android/media/IMediaCommunicationService.aidl b/apex/aidl/private/android/media/IMediaCommunicationService.aidl index fb3172b..e1c89e9 100644 --- a/apex/aidl/private/android/media/IMediaCommunicationService.aidl +++ b/apex/aidl/private/android/media/IMediaCommunicationService.aidl @@ -18,6 +18,7 @@ package android.media; import android.media.Session2Token; import android.media.IMediaCommunicationServiceCallback; import android.media.MediaParceledListSlice; +import android.view.KeyEvent; /** {@hide} */ interface IMediaCommunicationService { @@ -25,6 +26,8 @@ interface IMediaCommunicationService { boolean isTrusted(String controllerPackageName, int controllerPid, int controllerUid); MediaParceledListSlice getSession2Tokens(int userId); + void dispatchMediaKeyEvent(String packageName, in KeyEvent keyEvent, boolean asSystemService); + void registerCallback(IMediaCommunicationServiceCallback callback, String packageName); void unregisterCallback(IMediaCommunicationServiceCallback callback); } diff --git a/apex/framework/Android.bp b/apex/framework/Android.bp index a5e6df1..7bba456 100644 --- a/apex/framework/Android.bp +++ b/apex/framework/Android.bp @@ -17,17 +17,20 @@ package { default_applicable_licenses: ["Android-Apache-2.0"], } +// The dex jar produced by this is used in the APEX but otherwise this is not +// used. java_library { name: "updatable-media", - srcs: [ - ":updatable-media-srcs", + static_libs: [ + "framework-media.impl", ], permitted_packages: [ "android.media", ], + // Optimize the dex jar for the APEX. optimize: { enabled: true, shrink: true, @@ -37,19 +40,6 @@ java_library { installable: true, sdk_version: "module_current", - libs: [ - "androidx.annotation_annotation", - "framework-annotations-lib", - ], - static_libs: [ - "exoplayer2-extractor", - "mediatranscoding_aidl_interface-java", - "modules-annotation-minsdk", - "modules-utils-build", - ], - jarjar_rules: "jarjar_rules.txt", - - plugins: ["java_api_finder"], hostdex: true, // for hiddenapi check apex_available: [ @@ -58,10 +48,9 @@ java_library { ], min_sdk_version: "29", visibility: [ + // This MUST not be used for compilation within the media module, use + // framework-media.impl instead. "//frameworks/av/apex:__subpackages__", - "//frameworks/base/apex/media/service", - "//frameworks/base/api", // For framework-all - "//packages/modules/Media/apex/service", ], } @@ -126,14 +115,38 @@ java_sdk_library { name: "framework-media", defaults: ["framework-module-defaults"], - // This is only used to define the APIs for updatable-media. - api_only: true, - srcs: [ ":updatable-media-srcs", ], - impl_library_visibility: ["//frameworks/av/apex:__subpackages__"], + impl_library_visibility: [ + "//frameworks/base/api", // For framework-all + "//packages/modules/Media:__subpackages__", + ], + + sdk_version: "module_current", + impl_only_libs: [ + "androidx.annotation_annotation", + "framework-annotations-lib", + ], + impl_only_static_libs: [ + "exoplayer-media_apex", + "mediatranscoding_aidl_interface-java", + "modules-annotation-minsdk", + "modules-utils-build", + ], + jarjar_rules: "jarjar_rules.txt", + + plugins: ["java_api_finder"], + + apex_available: [ + "com.android.media", + "test_com.android.media", + ], + lint: { + strict_updatability_linting: true, + }, + min_sdk_version: "29", } cc_library_shared { diff --git a/apex/framework/api/module-lib-current.txt b/apex/framework/api/module-lib-current.txt index eb6397a..7317f14 100644 --- a/apex/framework/api/module-lib-current.txt +++ b/apex/framework/api/module-lib-current.txt @@ -2,6 +2,7 @@ package android.media { public class MediaCommunicationManager { + method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean); method @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public void registerSessionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaCommunicationManager.SessionCallback); method public void unregisterSessionCallback(@NonNull android.media.MediaCommunicationManager.SessionCallback); } diff --git a/apex/framework/java/android/media/BaseMediaParceledListSlice.java b/apex/framework/java/android/media/BaseMediaParceledListSlice.java index fb66609..11db293 100644 --- a/apex/framework/java/android/media/BaseMediaParceledListSlice.java +++ b/apex/framework/java/android/media/BaseMediaParceledListSlice.java @@ -43,7 +43,7 @@ import java.util.List; */ abstract class BaseMediaParceledListSlice<T> implements Parcelable { private static String TAG = "BaseMediaParceledListSlice"; - private static boolean DEBUG = false; + private static final boolean DEBUG = false; /* * TODO get this number from somewhere else. For now set it to a quarter of @@ -107,7 +107,11 @@ abstract class BaseMediaParceledListSlice<T> implements Parcelable { } while (i < N && reply.readInt() != 0) { final T parcelable = readCreator(creator, reply, loader); - verifySameType(listElementClass, parcelable.getClass()); + if (listElementClass == null) { + listElementClass = parcelable.getClass(); + } else { + verifySameType(listElementClass, parcelable.getClass()); + } mList.add(parcelable); diff --git a/apex/framework/java/android/media/MediaCommunicationManager.java b/apex/framework/java/android/media/MediaCommunicationManager.java index 40992f1..d6442d4 100644 --- a/apex/framework/java/android/media/MediaCommunicationManager.java +++ b/apex/framework/java/android/media/MediaCommunicationManager.java @@ -32,6 +32,9 @@ import android.os.RemoteException; import android.os.UserHandle; import android.service.media.MediaBrowserService; import android.util.Log; +import android.view.KeyEvent; + +import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi; @@ -66,7 +69,8 @@ public class MediaCommunicationManager { private static final int CURRENT_VERSION = VERSION_1; private final Context mContext; - private final IMediaCommunicationService mService; + // Do not access directly use getService(). + private IMediaCommunicationService mService; private final Object mLock = new Object(); private final CopyOnWriteArrayList<SessionCallbackRecord> mTokenCallbackRecords = @@ -75,6 +79,9 @@ public class MediaCommunicationManager { @GuardedBy("mLock") private MediaCommunicationServiceCallbackStub mCallbackStub; + // TODO: remove this when MCS implements dispatchMediaKeyEvent. + private MediaSessionManager mMediaSessionManager; + /** * @hide */ @@ -83,10 +90,6 @@ public class MediaCommunicationManager { throw new UnsupportedOperationException("Android version must be S or greater."); } mContext = context; - mService = IMediaCommunicationService.Stub.asInterface( - MediaFrameworkInitializer.getMediaServiceManager() - .getMediaCommunicationServiceRegisterer() - .get()); } /** @@ -108,7 +111,7 @@ public class MediaCommunicationManager { throw new IllegalArgumentException("token's type should be TYPE_SESSION"); } try { - mService.notifySession2Created(token); + getService().notifySession2Created(token); } catch (RemoteException e) { e.rethrowFromSystemServer(); } @@ -133,7 +136,7 @@ public class MediaCommunicationManager { return false; } try { - return mService.isTrusted( + return getService().isTrusted( userInfo.getPackageName(), userInfo.getPid(), userInfo.getUid()); } catch (RemoteException e) { Log.w(TAG, "Cannot communicate with the service.", e); @@ -185,7 +188,7 @@ public class MediaCommunicationManager { MediaCommunicationServiceCallbackStub callbackStub = new MediaCommunicationServiceCallbackStub(); try { - mService.registerCallback(callbackStub, mContext.getPackageName()); + getService().registerCallback(callbackStub, mContext.getPackageName()); mCallbackStub = callbackStub; } catch (RemoteException ex) { Log.e(TAG, "Failed to register callback.", ex); @@ -208,7 +211,7 @@ public class MediaCommunicationManager { synchronized (mLock) { if (mCallbackStub != null && mTokenCallbackRecords.isEmpty()) { try { - mService.unregisterCallback(mCallbackStub); + getService().unregisterCallback(mCallbackStub); } catch (RemoteException ex) { Log.e(TAG, "Failed to unregister callback.", ex); } @@ -217,9 +220,27 @@ public class MediaCommunicationManager { } } + private IMediaCommunicationService getService() { + if (mService == null) { + mService = IMediaCommunicationService.Stub.asInterface( + MediaFrameworkInitializer.getMediaServiceManager() + .getMediaCommunicationServiceRegisterer() + .get()); + } + return mService; + } + + // TODO: remove this when MCS implements dispatchMediaKeyEvent. + private MediaSessionManager getMediaSessionManager() { + if (mMediaSessionManager == null) { + mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); + } + return mMediaSessionManager; + } + private List<Session2Token> getSession2Tokens(int userId) { try { - MediaParceledListSlice slice = mService.getSession2Tokens(userId); + MediaParceledListSlice slice = getService().getSession2Tokens(userId); return slice == null ? Collections.emptyList() : slice.getList(); } catch (RemoteException e) { Log.e(TAG, "Failed to get session tokens", e); @@ -228,6 +249,34 @@ public class MediaCommunicationManager { } /** + * Sends a media key event. The receiver will be selected automatically. + * + * @param keyEvent the key event to send, non-media key events will be ignored. + * @param asSystemService if {@code true}, the event is sent to the session as if it was come + * from the system service instead of the app process. It only affects + * {@link MediaSession.Callback#getCurrentControllerInfo()}. + * @hide + */ + @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + public void dispatchMediaKeyEvent(@NonNull KeyEvent keyEvent, boolean asSystemService) { + Objects.requireNonNull(keyEvent, "keyEvent shouldn't be null"); + + // When MCS handles this, caller is changed. + // TODO: remove this when MCS implementation is done. + if (!asSystemService) { + getMediaSessionManager().dispatchMediaKeyEvent(keyEvent, false); + return; + } + + try { + getService().dispatchMediaKeyEvent(mContext.getPackageName(), + keyEvent, asSystemService); + } catch (RemoteException e) { + Log.e(TAG, "Failed to send key event.", e); + } + } + + /** * Callback for listening to changes to the sessions. * @see #registerSessionCallback(Executor, SessionCallback) * @hide diff --git a/apex/framework/java/android/media/MediaSession2.java b/apex/framework/java/android/media/MediaSession2.java index e76d61c..7d07eb3 100644 --- a/apex/framework/java/android/media/MediaSession2.java +++ b/apex/framework/java/android/media/MediaSession2.java @@ -262,7 +262,7 @@ public class MediaSession2 implements AutoCloseable { } /** - * Returns whehther the playback is active (i.e. playing something) + * Returns whether the playback is active (i.e. playing something) * * @return {@code true} if the playback active, {@code false} otherwise. */ @@ -302,8 +302,9 @@ public class MediaSession2 implements AutoCloseable { parcel.setDataPosition(0); Bundle out = parcel.readBundle(null); - // Calling Bundle#size() will trigger Bundle#unparcel(). - out.size(); + for (String key : out.keySet()) { + out.get(key); + } } catch (BadParcelableException e) { Log.d(TAG, "Custom parcelable in bundle.", e); return true; diff --git a/apex/framework/java/android/media/MediaSession2Service.java b/apex/framework/java/android/media/MediaSession2Service.java index f6fd509..9f80c43 100644 --- a/apex/framework/java/android/media/MediaSession2Service.java +++ b/apex/framework/java/android/media/MediaSession2Service.java @@ -161,19 +161,19 @@ public abstract class MediaSession2Service extends Service { public abstract MediaSession2 onGetSession(@NonNull ControllerInfo controllerInfo); /** - * Called when notification UI needs update. Override this method to show or cancel your own - * notification UI. + * Called to update the media notification when the playback state changes. * <p> - * This would be called on {@link MediaSession2}'s callback executor when playback state is - * changed. + * If playback is active and a notification is returned, the service uses it to become a + * foreground service. If playback is not active then the notification is still posted, but the + * service does not become a foreground service. * <p> - * With the notification returned here, the service becomes foreground service when the playback - * is started. Apps must request the permission - * {@link android.Manifest.permission#FOREGROUND_SERVICE} in order to use this API. It becomes - * background service after the playback is stopped. + * Apps must request the {@link android.Manifest.permission#FOREGROUND_SERVICE} permission + * in order to use this API. For apps targeting {@link android.os.Build.VERSION_CODES#TIRAMISU} + * or later, notifications will only be posted if the app has also been granted the + * {@link android.Manifest.permission#POST_NOTIFICATIONS} permission. * - * @param session a session that needs notification update. - * @return a {@link MediaNotification}. Can be {@code null}. + * @param session the session for which an updated media notification is required. + * @return the {@link MediaNotification}. Can be {@code null}. */ @Nullable public abstract MediaNotification onUpdateNotification(@NonNull MediaSession2 session); diff --git a/apex/framework/java/android/media/MediaTranscodingManager.java b/apex/framework/java/android/media/MediaTranscodingManager.java index ca35c01..aff3204 100644 --- a/apex/framework/java/android/media/MediaTranscodingManager.java +++ b/apex/framework/java/android/media/MediaTranscodingManager.java @@ -22,7 +22,6 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; -import android.app.ActivityManager; import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; @@ -124,7 +123,6 @@ public final class MediaTranscodingManager { private final String mPackageName; private final int mPid; private final int mUid; - private final boolean mIsLowRamDevice; private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); private final HashMap<Integer, TranscodingSession> mPendingTranscodingSessions = new HashMap(); private final Object mLock = new Object(); @@ -211,10 +209,7 @@ public final class MediaTranscodingManager { if (!SdkLevel.isAtLeastS()) { return null; } - // Do not try to get the service on AndroidGo (low-ram) devices. - if (mIsLowRamDevice) { - return null; - } + int retryCount = !retry ? 1 : CONNECT_SERVICE_RETRY_COUNT; Log.i(TAG, "get service with retry " + retryCount); for (int count = 1; count <= retryCount; count++) { @@ -432,7 +427,6 @@ public final class MediaTranscodingManager { mPackageName = mContext.getPackageName(); mUid = Os.getuid(); mPid = Os.getpid(); - mIsLowRamDevice = mContext.getSystemService(ActivityManager.class).isLowRamDevice(); } /** diff --git a/apex/framework/java/android/media/Session2Command.java b/apex/framework/java/android/media/Session2Command.java index 26f4568..7e71591 100644 --- a/apex/framework/java/android/media/Session2Command.java +++ b/apex/framework/java/android/media/Session2Command.java @@ -37,9 +37,8 @@ import java.util.Objects; * If {@link #getCommandCode()} is {@link #COMMAND_CODE_CUSTOM}), it's custom command and * {@link #getCustomAction()} shouldn't be {@code null}. * <p> - * Refer to the - * <a href="{@docRoot}reference/androidx/media2/SessionCommand2.html">AndroidX SessionCommand</a> - * class for the list of valid commands. + * Refer to the <a href="{@docRoot}reference/androidx/media2/session/SessionCommand.html"> + * AndroidX SessionCommand</a> class for the list of valid commands. */ public final class Session2Command implements Parcelable { /** diff --git a/apex/framework/java/android/media/Session2CommandGroup.java b/apex/framework/java/android/media/Session2CommandGroup.java index 13aabfc..af8184a 100644 --- a/apex/framework/java/android/media/Session2CommandGroup.java +++ b/apex/framework/java/android/media/Session2CommandGroup.java @@ -68,7 +68,7 @@ public final class Session2CommandGroup implements Parcelable { /** * Used by parcelable creator. */ - @SuppressWarnings("WeakerAccess") /* synthetic access */ + @SuppressWarnings({"WeakerAccess", "UnsafeParcelApi"}) /* synthetic access */ Session2CommandGroup(Parcel in) { Parcelable[] commands = in.readParcelableArray(Session2Command.class.getClassLoader()); if (commands != null) { diff --git a/apex/framework/lint-baseline.xml b/apex/framework/lint-baseline.xml index e1b1450..95eea45 100644 --- a/apex/framework/lint-baseline.xml +++ b/apex/framework/lint-baseline.xml @@ -1,312 +1,70 @@ <?xml version="1.0" encoding="UTF-8"?> -<issues format="5" by="lint 4.1.0" client="cli" variant="all" version="4.1.0"> +<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev"> <issue - id="NewApi" - message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`" - errorLine1=" new ApplicationMediaCapabilities.Builder();" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="DefaultLocale" + message="Implicitly using the default locale is a common source of bugs: Use `toLowerCase(Locale)` instead. For strings meant to be internal use `Locale.ROOT`, otherwise `Locale.getDefault()`." + errorLine1=" if (mSupportedVideoMimeTypes.contains(videoMime.toLowerCase())) {" + errorLine2=" ~~~~~~~~~~~"> <location file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java" - line="208" - column="29"/> + line="121" + column="57"/> </issue> <issue - id="NewApi" - message="Call requires API level 31 (current min is 29): `new android.media.ApplicationMediaCapabilities.Builder`" - errorLine1=" ApplicationMediaCapabilities.Builder builder = new ApplicationMediaCapabilities.Builder();" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> + id="DefaultLocale" + message="Implicitly using the default locale is a common source of bugs: Use `String.format(Locale, ...)` instead" + errorLine1=" return String.format(" session: {id: %d, status: %s, result: %s, progress: %d}"," + errorLine2=" ^"> <location - file="frameworks/base/apex/media/framework/java/android/media/ApplicationMediaCapabilities.java" - line="314" - column="56"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level R (current min is 29): `android.os.RemoteException#rethrowFromSystemServer`" - errorLine1=" e.rethrowFromSystemServer();" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaCommunicationManager.java" - line="110" - column="15"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level R (current min is 29): `android.os.Parcel#writeParcelableCreator`" - errorLine1=" dest.writeParcelableCreator((Parcelable) parcelable);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java" - line="77" - column="14"/> - </issue> - - <issue - id="NewApi" - message="Call requires API level R (current min is 29): `android.os.Parcel#readParcelableCreator`" - errorLine1=" return from.readParcelableCreator(loader);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParceledListSlice.java" - line="82" - column="21"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#mediaFormat`" - errorLine1=" this.mediaFormat = mediaFormat;" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="273" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.TrackData#drmInitData`" - errorLine1=" this.drmInitData = drmInitData;" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="274" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" this.timeMicros = timeMicros;" - errorLine2=" ~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="295" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" this.position = position;" - errorLine2=" ~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="296" - column="13"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";" - errorLine2=" ~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="302" - column="66"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" return "[timeMicros=" + timeMicros + ", position=" + position + "]";" - errorLine2=" ~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="302" - column="37"/> - </issue> - - <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.SeekPoint`" - errorLine1=" SeekPoint other = (SeekPoint) obj;" - errorLine2=" ~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="313" - column="32"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" - column="54"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" - column="66"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" + file="frameworks/base/apex/media/framework/java/android/media/MediaTranscodingManager.java" + line="1651" column="20"/> </issue> <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" return timeMicros == other.timeMicros && position == other.position;" - errorLine2=" ~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="314" - column="34"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" int result = (int) timeMicros;" - errorLine2=" ~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="319" - column="32"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" result = 31 * result + (int) position;" - errorLine2=" ~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="320" - column="42"/> - </issue> - - <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`" - errorLine1=" public interface SeekableInputReader extends InputReader {" - errorLine2=" ~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="352" - column="50"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level 31 (current min is 29): `android.media.metrics.LogSessionId#LOG_SESSION_ID_NONE`" - errorLine1=" @NonNull private LogSessionId mLogSessionId = LogSessionId.LOG_SESSION_ID_NONE;" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1071" - column="51"/> - </issue> - - <issue - id="NewApi" - message="Cast from `SeekableInputReader` to `InputReader` requires API level 30 (current min is 29)" - errorLine1=" mExoDataReader.mInputReader = seekableInputReader;" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1201" - column="39"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" mPendingSeekPosition = seekPoint.position;" - errorLine2=" ~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1287" - column="36"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" mPendingSeekTimeMicros = seekPoint.timeMicros;" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1288" - column="38"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#position`" - errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);" - errorLine2=" ~~~~~~~~~~~~~~~~~~"> - <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1290" - column="29"/> - </issue> - - <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.MediaParser.SeekPoint#timeMicros`" - errorLine1=" mExtractor.seek(seekPoint.position, seekPoint.timeMicros);" - errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead." + errorLine1=" Bundle out = parcel.readBundle(null);" + errorLine2=" ~~~~~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1290" - column="49"/> + file="frameworks/base/apex/media/framework/java/android/media/MediaSession2.java" + line="303" + column="33"/> </issue> <issue - id="NewApi" - message="Field requires API level R (current min is 29): `android.media.DrmInitData.SchemeInitData#uuid`" - errorLine1=" if (schemeInitData.uuid.equals(schemeUuid)) {" - errorLine2=" ~~~~~~~~~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead." + errorLine1=" mCustomExtras = in.readBundle();" + errorLine2=" ~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1579" - column="21"/> + file="frameworks/base/apex/media/framework/java/android/media/Session2Command.java" + line="104" + column="28"/> </issue> <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`" - errorLine1=" private static final class DataReaderAdapter implements InputReader {" - errorLine2=" ~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Passing null here (to use the default class loader) will not work if you are restoring your own classes. Consider using for example `getClass().getClassLoader()` instead." + errorLine1=" mSessionLink = in.readParcelable(null);" + errorLine2=" ~~~~~~~~~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1872" - column="61"/> + file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java" + line="141" + column="27"/> </issue> <issue - id="NewApi" - message="Class requires API level R (current min is 29): `android.media.MediaParser.InputReader`" - errorLine1=" private static final class ParsableByteArrayAdapter implements InputReader {" - errorLine2=" ~~~~~~~~~~~"> + id="ParcelClassLoader" + message="Using the default class loader will not work if you are restoring your own classes. Consider using for example `readBundle(getClass().getClassLoader())` instead." + errorLine1=" Bundle extras = in.readBundle();" + errorLine2=" ~~~~~~~~~~~~"> <location - file="frameworks/base/apex/media/framework/java/android/media/MediaParser.java" - line="1905" - column="68"/> + file="frameworks/base/apex/media/framework/java/android/media/Session2Token.java" + line="144" + column="28"/> </issue> </issues> diff --git a/apex/service/Android.bp b/apex/service/Android.bp index 60ff769..5f5eb54 100644 --- a/apex/service/Android.bp +++ b/apex/service/Android.bp @@ -36,11 +36,16 @@ java_sdk_library { ], libs: [ "androidx.annotation_annotation", - "updatable-media", + "framework-media.impl", "modules-annotation-minsdk", + "modules-utils-build", ], + jarjar_rules: "jarjar_rules.txt", sdk_version: "system_server_current", min_sdk_version: "29", // TODO: We may need to bump this at some point. + lint: { + strict_updatability_linting: true, + }, apex_available: [ "com.android.media", ], diff --git a/apex/service/jarjar_rules.txt b/apex/service/jarjar_rules.txt new file mode 100644 index 0000000..7e37c2b --- /dev/null +++ b/apex/service/jarjar_rules.txt @@ -0,0 +1 @@ +rule com.android.modules.** android.media.internal.@1 diff --git a/apex/service/java/com/android/server/media/MediaCommunicationService.java b/apex/service/java/com/android/server/media/MediaCommunicationService.java index 0dc3e79..4223fa6 100644 --- a/apex/service/java/com/android/server/media/MediaCommunicationService.java +++ b/apex/service/java/com/android/server/media/MediaCommunicationService.java @@ -31,6 +31,7 @@ import android.media.MediaController2; import android.media.MediaParceledListSlice; import android.media.Session2CommandGroup; import android.media.Session2Token; +import android.media.session.MediaSessionManager; import android.os.Binder; import android.os.Build; import android.os.Handler; @@ -43,6 +44,7 @@ import android.os.UserManager; import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; +import android.view.KeyEvent; import androidx.annotation.RequiresApi; @@ -56,7 +58,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.Executors; -import java.util.stream.Collectors; /** * A system service that manages {@link android.media.MediaSession2} creations @@ -66,7 +67,7 @@ import java.util.stream.Collectors; @MinSdk(Build.VERSION_CODES.S) @RequiresApi(Build.VERSION_CODES.S) public class MediaCommunicationService extends SystemService { - private static final String TAG = "MediaCommunicationService"; + private static final String TAG = "MediaCommunicationSrv"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); final Context mContext; @@ -81,8 +82,9 @@ public class MediaCommunicationService extends SystemService { final Executor mRecordExecutor = Executors.newSingleThreadExecutor(); @GuardedBy("mLock") - final List<CallbackRecord> mCallbackRecords = new ArrayList<>(); + final ArrayList<CallbackRecord> mCallbackRecords = new ArrayList<>(); final NotificationManager mNotificationManager; + MediaSessionManager mSessionManager; public MediaCommunicationService(Context context) { super(context); @@ -97,6 +99,17 @@ public class MediaCommunicationService extends SystemService { } @Override + public void onBootPhase(int phase) { + super.onBootPhase(phase); + switch (phase) { + // This ensures MediaSessionService is started + case PHASE_BOOT_COMPLETED: + mSessionManager = mContext.getSystemService(MediaSessionManager.class); + break; + } + } + + @Override public void onUserStarting(@NonNull TargetUser user) { if (DEBUG) Log.d(TAG, "onUserStarting: " + user); updateUser(); @@ -140,8 +153,8 @@ public class MediaCommunicationService extends SystemService { return null; } - List<Session2Token> getSession2TokensLocked(int userId) { - List<Session2Token> list = new ArrayList<>(); + ArrayList<Session2Token> getSession2TokensLocked(int userId) { + ArrayList<Session2Token> list = new ArrayList<>(); if (userId == ALL.getIdentifier()) { int size = mUserRecords.size(); for (int i = 0; i < size; i++) { @@ -227,28 +240,29 @@ public class MediaCommunicationService extends SystemService { } void dispatchSession2Changed(int userId) { - MediaParceledListSlice<Session2Token> allSession2Tokens; - MediaParceledListSlice<Session2Token> userSession2Tokens; + ArrayList<Session2Token> allSession2Tokens; + ArrayList<Session2Token> userSession2Tokens; synchronized (mLock) { - allSession2Tokens = - new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier())); - userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId)); - } - allSession2Tokens.setInlineCountLimit(1); - userSession2Tokens.setInlineCountLimit(1); + allSession2Tokens = getSession2TokensLocked(ALL.getIdentifier()); + userSession2Tokens = getSession2TokensLocked(userId); - synchronized (mLock) { for (CallbackRecord record : mCallbackRecords) { if (record.mUserId == ALL.getIdentifier()) { try { - record.mCallback.onSession2Changed(allSession2Tokens); + MediaParceledListSlice<Session2Token> toSend = + new MediaParceledListSlice<>(allSession2Tokens); + toSend.setInlineCountLimit(0); + record.mCallback.onSession2Changed(toSend); } catch (RemoteException e) { Log.w(TAG, "Failed to notify session2 tokens changed " + record); } } else if (record.mUserId == userId) { try { - record.mCallback.onSession2Changed(userSession2Tokens); + MediaParceledListSlice<Session2Token> toSend = + new MediaParceledListSlice<>(userSession2Tokens); + toSend.setInlineCountLimit(0); + record.mCallback.onSession2Changed(toSend); } catch (RemoteException e) { Log.w(TAG, "Failed to notify session2 tokens changed " + record); } @@ -273,6 +287,34 @@ public class MediaCommunicationService extends SystemService { session.close(); } + void onSessionPlaybackStateChanged(Session2Record session, boolean promotePriority) { + FullUserRecord user = session.getFullUser(); + if (user == null || !user.containsSession(session)) { + Log.d(TAG, "Unknown session changed playback state. Ignoring."); + return; + } + user.onPlaybackStateChanged(session, promotePriority); + } + + + static boolean isMediaSessionKey(int keyCode) { + switch (keyCode) { + case KeyEvent.KEYCODE_MEDIA_PLAY: + case KeyEvent.KEYCODE_MEDIA_PAUSE: + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: + case KeyEvent.KEYCODE_MUTE: + case KeyEvent.KEYCODE_HEADSETHOOK: + case KeyEvent.KEYCODE_MEDIA_STOP: + case KeyEvent.KEYCODE_MEDIA_NEXT: + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: + case KeyEvent.KEYCODE_MEDIA_REWIND: + case KeyEvent.KEYCODE_MEDIA_RECORD: + case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: + return true; + } + return false; + } + private class Stub extends IMediaCommunicationService.Stub { @Override public void notifySession2Created(Session2Token sessionToken) { @@ -344,7 +386,7 @@ public class MediaCommunicationService extends SystemService { try { // Check that they can make calls on behalf of the user and get the final user id int resolvedUserId = handleIncomingUser(pid, uid, userId, null); - List<Session2Token> result; + ArrayList<Session2Token> result; synchronized (mLock) { result = getSession2TokensLocked(resolvedUserId); } @@ -357,6 +399,25 @@ public class MediaCommunicationService extends SystemService { } @Override + public void dispatchMediaKeyEvent(String packageName, KeyEvent keyEvent, + boolean asSystemService) { + if (keyEvent == null || !isMediaSessionKey(keyEvent.getKeyCode())) { + Log.w(TAG, "Attempted to dispatch null or non-media key event."); + return; + } + + final int pid = Binder.getCallingPid(); + final int uid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + //TODO: Dispatch key event to media session 2 if required + mSessionManager.dispatchMediaKeyEvent(keyEvent, asSystemService); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public void registerCallback(IMediaCommunicationServiceCallback callback, String packageName) throws RemoteException { Objects.requireNonNull(callback, "callback should not be null"); @@ -475,26 +536,20 @@ public class MediaCommunicationService extends SystemService { final class FullUserRecord { private final int mFullUserId; - private final Object mUserLock = new Object(); - @GuardedBy("mUserLock") - private final List<Session2Record> mSessionRecords = new ArrayList<>(); + private final SessionPriorityList mSessionPriorityList = new SessionPriorityList(); FullUserRecord(int fullUserId) { mFullUserId = fullUserId; } public void addSession(Session2Record record) { - synchronized (mUserLock) { - mSessionRecords.add(record); - } + mSessionPriorityList.addSession(record); mHandler.post(() -> dispatchSession2Created(record.mSessionToken)); mHandler.post(() -> dispatchSession2Changed(mFullUserId)); } private void removeSession(Session2Record record) { - synchronized (mUserLock) { - mSessionRecords.remove(record); - } + mSessionPriorityList.removeSession(record); mHandler.post(() -> dispatchSession2Changed(mFullUserId)); //TODO: Handle if the removed session was the media button session. } @@ -504,48 +559,31 @@ public class MediaCommunicationService extends SystemService { } public List<Session2Token> getAllSession2Tokens() { - synchronized (mUserLock) { - return mSessionRecords.stream() - .map(Session2Record::getSessionToken) - .collect(Collectors.toList()); - } + return mSessionPriorityList.getAllTokens(); } public List<Session2Token> getSession2Tokens(int userId) { - synchronized (mUserLock) { - return mSessionRecords.stream() - .filter(record -> record.getUserId() == userId) - .map(Session2Record::getSessionToken) - .collect(Collectors.toList()); - } + return mSessionPriorityList.getTokensByUserId(userId); } public void destroyAllSessions() { - synchronized (mUserLock) { - for (Session2Record session : mSessionRecords) { - session.close(); - } - mSessionRecords.clear(); - } + mSessionPriorityList.destroyAllSessions(); mHandler.post(() -> dispatchSession2Changed(mFullUserId)); } public void destroySessionsForUser(int userId) { - boolean changed = false; - synchronized (mUserLock) { - for (int i = mSessionRecords.size() - 1; i >= 0; i--) { - Session2Record session = mSessionRecords.get(i); - if (session.getUserId() == userId) { - mSessionRecords.remove(i); - session.close(); - changed = true; - } - } - } - if (changed) { + if (mSessionPriorityList.destroySessionsByUserId(userId)) { mHandler.post(() -> dispatchSession2Changed(mFullUserId)); } } + + public boolean containsSession(Session2Record session) { + return mSessionPriorityList.contains(session); + } + + public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) { + mSessionPriorityList.onPlaybackStateChanged(session, promotePriority); + } } static final class Session2Record { @@ -561,6 +599,7 @@ public class MediaCommunicationService extends SystemService { @GuardedBy("mSession2RecordLock") private boolean mIsClosed; + //TODO: introduce policy (See MediaSessionPolicyProvider) Session2Record(MediaCommunicationService service, FullUserRecord fullUser, Session2Token token, Executor controllerExecutor) { mServiceRef = new WeakReference<>(service); @@ -596,6 +635,12 @@ public class MediaCommunicationService extends SystemService { return mSessionToken; } + public boolean checkPlaybackActiveState(boolean expected) { + synchronized (mSession2RecordLock) { + return mIsConnected && mController.isPlaybackActive() == expected; + } + } + private class Controller2Callback extends MediaController2.ControllerCallback { @Override public void onConnected(MediaController2 controller, @@ -621,6 +666,20 @@ public class MediaCommunicationService extends SystemService { service.onSessionDied(Session2Record.this); } } + + @Override + public void onPlaybackActiveChanged( + @NonNull MediaController2 controller, + boolean playbackActive) { + if (DEBUG) { + Log.d(TAG, "playback active changed, " + mSessionToken + ", active=" + + playbackActive); + } + MediaCommunicationService service = mServiceRef.get(); + if (service != null) { + service.onSessionPlaybackStateChanged(Session2Record.this, playbackActive); + } + } } } } diff --git a/apex/service/java/com/android/server/media/SessionPriorityList.java b/apex/service/java/com/android/server/media/SessionPriorityList.java new file mode 100644 index 0000000..8145861 --- /dev/null +++ b/apex/service/java/com/android/server/media/SessionPriorityList.java @@ -0,0 +1,199 @@ +/* + * Copyright 2021 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.media; + +import android.annotation.Nullable; +import android.media.Session2Token; +import android.os.Build; +import android.util.Log; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.GuardedBy; +import com.android.modules.annotation.MinSdk; +import com.android.server.media.MediaCommunicationService.Session2Record; + +import java.util.ArrayList; +import java.util.List; + +//TODO: Define the priority specifically. +/** + * Keeps track of media sessions and their priority for notifications, media + * button dispatch, etc. + * Higher priority session has more chance to be selected as media button session, + * which receives the media button events. + */ +@MinSdk(Build.VERSION_CODES.S) +@RequiresApi(Build.VERSION_CODES.S) +class SessionPriorityList { + private static final String TAG = "SessionPriorityList"; + private final Object mLock = new Object(); + + @GuardedBy("mLock") + private final List<Session2Record> mSessions = new ArrayList<>(); + + @Nullable + private Session2Record mMediaButtonSession; + @Nullable + private Session2Record mCachedVolumeSession; + + //TODO: integrate AudioPlayerStateMonitor + + public void addSession(Session2Record record) { + synchronized (mLock) { + mSessions.add(record); + } + } + + public void removeSession(Session2Record record) { + synchronized (mLock) { + mSessions.remove(record); + } + if (record == mMediaButtonSession) { + updateMediaButtonSession(null); + } + } + + public void destroyAllSessions() { + synchronized (mLock) { + for (Session2Record session : mSessions) { + session.close(); + } + mSessions.clear(); + } + } + + public boolean destroySessionsByUserId(int userId) { + boolean changed = false; + synchronized (mLock) { + for (int i = mSessions.size() - 1; i >= 0; i--) { + Session2Record session = mSessions.get(i); + if (session.getUserId() == userId) { + mSessions.remove(i); + session.close(); + changed = true; + } + } + } + return changed; + } + + public List<Session2Token> getAllTokens() { + List<Session2Token> sessions = new ArrayList<>(); + synchronized (mLock) { + for (Session2Record session : mSessions) { + sessions.add(session.getSessionToken()); + } + } + return sessions; + } + + public List<Session2Token> getTokensByUserId(int userId) { + List<Session2Token> sessions = new ArrayList<>(); + synchronized (mLock) { + for (Session2Record session : mSessions) { + if (session.getUserId() == userId) { + sessions.add(session.getSessionToken()); + } + } + } + return sessions; + } + + /** Gets the media button session which receives the media button events. */ + @Nullable + public Session2Record getMediaButtonSession() { + return mMediaButtonSession; + } + + /** Gets the media volume session which receives the volume key events. */ + @Nullable + public Session2Record getMediaVolumeSession() { + //TODO: if null, calculate it. + return mCachedVolumeSession; + } + + public boolean contains(Session2Record session) { + synchronized (mLock) { + return mSessions.contains(session); + } + } + + public void onPlaybackStateChanged(Session2Record session, boolean promotePriority) { + if (promotePriority) { + synchronized (mLock) { + if (mSessions.remove(session)) { + mSessions.add(0, session); + } else { + Log.w(TAG, "onPlaybackStateChanged: Ignoring unknown session"); + return; + } + } + } else if (session.checkPlaybackActiveState(false)) { + // Just clear the cached volume session when a state goes inactive + mCachedVolumeSession = null; + } + + // In most cases, playback state isn't needed for finding the media button session, + // but we only use it as a hint if an app has multiple local media sessions. + // In that case, we pick the media session whose PlaybackState matches + // the audio playback configuration. + if (mMediaButtonSession != null + && mMediaButtonSession.getSessionToken().getUid() + == session.getSessionToken().getUid()) { + Session2Record newMediaButtonSession = + findMediaButtonSession(mMediaButtonSession.getSessionToken().getUid()); + if (newMediaButtonSession != mMediaButtonSession) { + // Check if the policy states that this session should not be updated as a media + // button session. + updateMediaButtonSession(newMediaButtonSession); + } + } + } + + private void updateMediaButtonSession(@Nullable Session2Record newSession) { + mMediaButtonSession = newSession; + //TODO: invoke callbacks for media button session changed listeners + } + + /** + * Finds the media button session with the given {@param uid}. + * If the app has multiple media sessions, the media session whose playback state is not null + * and matches the audio playback state becomes the media button session. Otherwise the top + * priority session becomes the media button session. + * + * @return The media button session. Returns {@code null} if the app doesn't have a media + * session. + */ + @Nullable + private Session2Record findMediaButtonSession(int uid) { + Session2Record mediaButtonSession = null; + synchronized (mLock) { + for (Session2Record session : mSessions) { + if (uid != session.getSessionToken().getUid()) { + continue; + } + // TODO: check audio player state monitor + if (mediaButtonSession == null) { + // Pick the top priority session as a default. + mediaButtonSession = session; + } + } + } + return mediaButtonSession; + } +} diff --git a/apex/service/lint-baseline.xml b/apex/service/lint-baseline.xml new file mode 100644 index 0000000..def6baf --- /dev/null +++ b/apex/service/lint-baseline.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<issues format="6" by="lint 7.2.0-dev" type="baseline" client="" dependencies="true" name="" variant="all" version="7.2.0-dev"> + +</issues> |