From 0b0d02c3e4ff465fb1e7741c35a11c0babf5daaf Mon Sep 17 00:00:00 2001 From: bachinger Date: Wed, 27 Sep 2023 13:42:47 -0700 Subject: [PATCH] Add isAutomotiveController and isAutoCompanionController Issue: androidx/media#561 Issue: androidx/media#644 Issue: androidx/media#645 PiperOrigin-RevId: 568948230 --- .../androidx/media3/session/MediaSession.java | 27 ++ .../media3/session/MediaSessionImpl.java | 31 ++- .../media3/session/MediaSessionUnitTest.java | 240 ++++++++++++++++++ 3 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java index 546e8179ef6..995e6543ccf 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSession.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSession.java @@ -818,6 +818,33 @@ public ControllerInfo getMediaNotificationControllerInfo() { return impl.getMediaNotificationControllerInfo(); } + /** + * Returns whether the given {@link ControllerInfo} belongs to an Automotive OS controller. + * + *

Note: This is not a security validation. + * + * @param controllerInfo The controller info of the connected controller. + * @return True if the controller into belongs to a connected Automotive OS controller. + */ + @UnstableApi + public final boolean isAutomotiveController(ControllerInfo controllerInfo) { + return impl.isAutomotiveController(controllerInfo); + } + + /** + * Returns whether the given {@link ControllerInfo} belongs to an Android Auto companion app + * controller. + * + *

Note: This is not a security validation. + * + * @param controllerInfo The controller info of the connected controller. + * @return True if the controller into belongs to a connected Auto companion client app. + */ + @UnstableApi + public final boolean isAutoCompanionController(ControllerInfo controllerInfo) { + return impl.isAutoCompanionController(controllerInfo); + } + /** * Sets the custom layout for the given Media3 controller. * diff --git a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java index 0901b28f966..aae28c7d111 100644 --- a/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java +++ b/libraries/session/src/main/java/androidx/media3/session/MediaSessionImpl.java @@ -86,6 +86,10 @@ /* package */ class MediaSessionImpl { + private static final String ANDROID_AUTOMOTIVE_LAUNCHER_PACKAGE_NAME = + "com.android.car.carlauncher"; + private static final String ANDROID_AUTOMOTIVE_MEDIA_PACKAGE_NAME = "com.android.car.media"; + private static final String ANDROID_AUTO_PACKAGE_NAME = "com.google.android.projection.gearhead"; private static final String SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"; private static final String WRONG_THREAD_ERROR_MESSAGE = "Player callback method is called from a wrong thread. " @@ -388,14 +392,37 @@ protected boolean isSystemUiController(@Nullable MediaSession.ControllerInfo con */ public boolean isMediaNotificationController(MediaSession.ControllerInfo controllerInfo) { return Objects.equals(controllerInfo.getPackageName(), context.getPackageName()) - && controllerInfo.getControllerVersion() - != ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION + && controllerInfo.getControllerVersion() != ControllerInfo.LEGACY_CONTROLLER_VERSION && controllerInfo .getConnectionHints() .getBoolean( MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, /* defaultValue= */ false); } + /** + * Returns whether the given {@link ControllerInfo} belongs to an Automotive OS controller. + * + * @param controllerInfo The controller info. + * @return Whether the given controller info belongs to an Automotive OS controller. + */ + public boolean isAutomotiveController(ControllerInfo controllerInfo) { + return controllerInfo.getControllerVersion() == ControllerInfo.LEGACY_CONTROLLER_VERSION + && (controllerInfo.getPackageName().equals(ANDROID_AUTOMOTIVE_MEDIA_PACKAGE_NAME) + || controllerInfo.getPackageName().equals(ANDROID_AUTOMOTIVE_LAUNCHER_PACKAGE_NAME)); + } + + /** + * Returns whether the given {@link ControllerInfo} belongs to an Android Auto companion app + * controller. + * + * @param controllerInfo The controller info. + * @return Whether the given controller info belongs to an Android Auto companion app controller. + */ + public boolean isAutoCompanionController(ControllerInfo controllerInfo) { + return controllerInfo.getControllerVersion() == ControllerInfo.LEGACY_CONTROLLER_VERSION + && controllerInfo.getPackageName().equals(ANDROID_AUTO_PACKAGE_NAME); + } + /** * Returns the {@link ControllerInfo} of the system UI notification controller, or {@code null} if * the System UI controller is not connected. diff --git a/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java b/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java new file mode 100644 index 00000000000..4aaa3686e21 --- /dev/null +++ b/libraries/session/src/test/java/androidx/media3/session/MediaSessionUnitTest.java @@ -0,0 +1,240 @@ +/* + * Copyright 2023 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 androidx.media3.session; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; +import static com.google.common.truth.Truth.assertThat; + +import android.os.Bundle; +import androidx.media.MediaSessionManager; +import androidx.media3.common.MediaLibraryInfo; +import androidx.media3.test.utils.TestExoPlayerBuilder; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link MediaSession}. */ +@RunWith(AndroidJUnit4.class) +public class MediaSessionUnitTest { // Avoid naming collision with session_current + + private MediaSession session; + + @Before + public void setUp() { + session = + new MediaSession.Builder( + getApplicationContext(), new TestExoPlayerBuilder(getApplicationContext()).build()) + .build(); + } + + @After + public void tearDown() { + session.release(); + } + + @Test + public void isAutomotiveController_automotiveLauncher_returnsTrue() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "com.android.car.carlauncher", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + + assertThat(session.isAutomotiveController(createMinimalLegacyControllerInfo(remoteUserInfo))) + .isTrue(); + } + + @Test + public void isAutomotiveController_automotiveMedia_returnsTrue() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "com.android.car.media", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + + assertThat(session.isAutomotiveController(createMinimalLegacyControllerInfo(remoteUserInfo))) + .isTrue(); + } + + @Test + public void isAutomotiveController_automotiveMediaMedia3Version_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "com.android.car.media", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + MediaSession.ControllerInfo controllerInfo = + new MediaSession.ControllerInfo( + remoteUserInfo, + MediaLibraryInfo.VERSION_INT, + MediaControllerStub.VERSION_INT, + /* trusted= */ false, + /* cb= */ null, + /* connectionHints= */ Bundle.EMPTY); + + assertThat(session.isAutomotiveController(controllerInfo)).isFalse(); + } + + @Test + public void isAutomotiveController_randomPackageName_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "another.package", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + + assertThat(session.isAutoCompanionController(createMinimalLegacyControllerInfo(remoteUserInfo))) + .isFalse(); + } + + @Test + public void isAutoCompanionController_companionController_returnsTrue() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "com.google.android.projection.gearhead", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + + assertThat(session.isAutoCompanionController(createMinimalLegacyControllerInfo(remoteUserInfo))) + .isTrue(); + } + + @Test + public void isAutoCompanionController_randomPackage_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "some.package", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + + assertThat(session.isAutoCompanionController(createMinimalLegacyControllerInfo(remoteUserInfo))) + .isFalse(); + } + + @Test + public void isAutoCompanionController_media3version_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "com.google.android.projection.gearhead", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + MediaSession.ControllerInfo controllerInfo = + new MediaSession.ControllerInfo( + remoteUserInfo, + MediaLibraryInfo.VERSION_INT, + MediaControllerStub.VERSION_INT, + /* trusted= */ false, + /* cb= */ null, + /* connectionHints= */ Bundle.EMPTY); + + assertThat(session.isAutoCompanionController(controllerInfo)).isFalse(); + } + + @Test + public void isMediaNotificationController_applicationPackage_returnsTrue() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + getApplicationContext().getPackageName(), + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + Bundle connectionHints = new Bundle(); + connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true); + MediaSession.ControllerInfo controllerInfo = + new MediaSession.ControllerInfo( + remoteUserInfo, + MediaLibraryInfo.VERSION_INT, + MediaControllerStub.VERSION_INT, + /* trusted= */ false, + /* cb= */ null, + connectionHints); + + assertThat(session.isMediaNotificationController(controllerInfo)).isTrue(); + } + + @Test + public void isMediaNotificationController_randomPackage_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + /* packageName= */ "some.package", + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + Bundle connectionHints = new Bundle(); + connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true); + MediaSession.ControllerInfo controllerInfo = + new MediaSession.ControllerInfo( + remoteUserInfo, + MediaLibraryInfo.VERSION_INT, + MediaControllerStub.VERSION_INT, + /* trusted= */ false, + /* cb= */ null, + connectionHints); + + assertThat(session.isMediaNotificationController(controllerInfo)).isFalse(); + } + + @Test + public void isMediaNotificationController_applicationPackageMissingBundle_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + getApplicationContext().getPackageName(), + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + MediaSession.ControllerInfo controllerInfo = + new MediaSession.ControllerInfo( + remoteUserInfo, + MediaLibraryInfo.VERSION_INT, + MediaControllerStub.VERSION_INT, + /* trusted= */ false, + /* cb= */ null, + /* connectionHints= */ Bundle.EMPTY); + + assertThat(session.isMediaNotificationController(controllerInfo)).isFalse(); + } + + @Test + public void isMediaNotificationController_applicationPackageLegacyVersion_returnsFalse() { + MediaSessionManager.RemoteUserInfo remoteUserInfo = + new MediaSessionManager.RemoteUserInfo( + getApplicationContext().getPackageName(), + /* pid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_PID, + /* uid= */ MediaSessionManager.RemoteUserInfo.UNKNOWN_UID); + Bundle connectionHints = new Bundle(); + connectionHints.putBoolean(MediaNotificationManager.KEY_MEDIA_NOTIFICATION_MANAGER, true); + MediaSession.ControllerInfo controllerInfo = + new MediaSession.ControllerInfo( + remoteUserInfo, + MediaSession.ControllerInfo.LEGACY_CONTROLLER_VERSION, + MediaSession.ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION, + /* trusted= */ false, + /* cb= */ null, + connectionHints); + + assertThat(session.isMediaNotificationController(controllerInfo)).isFalse(); + } + + private static MediaSession.ControllerInfo createMinimalLegacyControllerInfo( + MediaSessionManager.RemoteUserInfo remoteUserInfo) { + return new MediaSession.ControllerInfo( + remoteUserInfo, + MediaSession.ControllerInfo.LEGACY_CONTROLLER_VERSION, + MediaSession.ControllerInfo.LEGACY_CONTROLLER_INTERFACE_VERSION, + /* trusted= */ false, + /* cb= */ null, + /* connectionHints= */ Bundle.EMPTY); + } +}