Loading packages/SettingsLib/res/values/strings.xml +7 −0 Original line number Diff line number Diff line Loading @@ -1315,6 +1315,13 @@ <string name="media_transfer_this_device_name" product="tablet">This tablet</string> <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> <string name="media_transfer_this_phone">This phone</string> <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] --> <string name="media_output_status_require_premium">Upgrade account to switch</string> <!-- Sub status indicates device not support download content. [CHAR LIMIT=NONE] --> <string name="media_output_status_not_support_downloads">Can\’t play downloads here</string> <!-- Sub status indicates device need to wait after ad. [CHAR LIMIT=NONE] --> <string name="media_output_status_try_after_ad">Try again after the ad</string> <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] --> <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string> Loading packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +20 −0 Original line number Diff line number Diff line Loading @@ -34,10 +34,12 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; import android.annotation.Nullable; import android.annotation.TargetApi; import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; Loading Loading @@ -191,6 +193,14 @@ public class InfoMediaManager extends MediaManager { && Api34Impl.preferRouteListingOrdering(mRouterManager, mPackageName); } @Nullable ComponentName getLinkedItemComponentName() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { return null; } return Api34Impl.getLinkedItemComponentName(mRouterManager, mPackageName); } /** * Remove a {@code device} from current media. * Loading Loading @@ -681,6 +691,16 @@ public class InfoMediaManager extends MediaManager { && !routeListingPreference.getUseSystemOrdering(); } @DoNotInline @Nullable static ComponentName getLinkedItemComponentName( MediaRouter2Manager mediaRouter2Manager, String packageName) { RouteListingPreference routeListingPreference = mediaRouter2Manager.getRouteListingPreference(packageName); return routeListingPreference == null ? null : routeListingPreference.getLinkedItemComponentName(); } @DoNotInline static void onRouteListingPreferenceUpdated( String packageName, Loading packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +11 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.AudioManager; Loading Loading @@ -216,6 +217,16 @@ public class LocalMediaManager implements BluetoothCallback { return mInfoMediaManager.preferRouteListingOrdering(); } /** * Returns required component name for system to take the user back to the app by launching an * intent with the returned {@link ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, * with the extra {@link #EXTRA_ROUTE_ID}. */ @Nullable public ComponentName getLinkedItemComponentName() { return mInfoMediaManager.getLinkedItemComponentName(); } /** * Start scan connected MediaDevice */ Loading packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +67 −9 Original line number Diff line number Diff line Loading @@ -30,8 +30,14 @@ import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION; import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE; import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE; import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED; import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM; import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED; import static android.media.RouteListingPreference.Item.SUBTEXT_NONE; import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED; import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; Loading @@ -51,6 +57,8 @@ import androidx.annotation.IntDef; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; Loading Loading @@ -194,28 +202,58 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { public abstract String getId(); /** * Get disabled reason of device * Get selection behavior of device * * @return disabled reason of device * @return selection behavior of device */ @RouteListingPreference.Item.SubText public int getDisableReason() { public int getSelectionBehavior() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null ? mItem.getSubText() : -1; ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_NONE; } /** * Checks if device is has disabled reason * Checks if device is has subtext * * @return true if device has disabled reason * @return true if device has subtext */ public boolean hasDisabledReason() { public boolean hasSubtext() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null && mItem.getSubText() != SUBTEXT_NONE; } /** * Get subtext of device * * @return subtext of device */ @RouteListingPreference.Item.SubText public int getSubtext() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null ? mItem.getSubText() : SUBTEXT_NONE; } /** * Returns subtext string for current route. * * @return subtext string for this route */ public String getSubtextString() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null ? Api34Impl.composeSubtext(mItem, mContext) : null; } /** * Checks if device has ongoing shared session, which allow user to join * * @return true if device has ongoing session */ public boolean hasOngoingSession() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && Api34Impl.hasOngoingSession(mItem); } /** * Checks if device is suggested device from application * Loading Loading @@ -513,7 +551,27 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { private static class Api34Impl { @DoNotInline static boolean isSuggestedDevice(RouteListingPreference.Item item) { return item != null && item.getFlags() == FLAG_SUGGESTED_ROUTE; return item != null && (item.getFlags() & FLAG_SUGGESTED_ROUTE) != 0; } @DoNotInline static boolean hasOngoingSession(RouteListingPreference.Item item) { return item != null && (item.getFlags() & FLAG_ONGOING_SESSION) != 0; } @DoNotInline static String composeSubtext(RouteListingPreference.Item item, Context context) { switch (item.getSubText()) { case SUBTEXT_SUBSCRIPTION_REQUIRED: return context.getString(R.string.media_output_status_require_premium); case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED: return context.getString(R.string.media_output_status_not_support_downloads); case SUBTEXT_AD_ROUTING_DISALLOWED: return context.getString(R.string.media_output_status_try_after_ad); case SUBTEXT_CUSTOM: return (String) item.getCustomSubtextMessage(); } return ""; } } } packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; Loading Loading @@ -88,6 +89,8 @@ public class InfoMediaManagerTest { private MediaManager.MediaDeviceCallback mCallback; @Mock private MediaSessionManager mMediaSessionManager; @Mock private ComponentName mComponentName; private InfoMediaManager mInfoMediaManager; private Context mContext; Loading Loading @@ -372,6 +375,24 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.preferRouteListingOrdering()).isFalse(); } @Test public void getInAppOnlyItemRoutingReceiver_oldSdkVersion_returnsNull() { assertThat(mInfoMediaManager.getLinkedItemComponentName()).isNull(); } @Test public void getInAppOnlyItemRoutingReceiver_newSdkVersionWithReceiverExist_returns() { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.UPSIDE_DOWN_CAKE); when(mRouterManager.getRouteListingPreference(any())).thenReturn( new RouteListingPreference.Builder().setItems( ImmutableList.of()).setUseSystemOrdering( false).setLinkedItemComponentName(mComponentName).build()); mInfoMediaManager.mRouterManager = mRouterManager; assertThat(mInfoMediaManager.getLinkedItemComponentName()).isEqualTo(mComponentName); } private List<MediaRoute2Info> getRoutesListWithDuplicatedIds() { final List<MediaRoute2Info> routes = new ArrayList<>(); final MediaRoute2Info info = mock(MediaRoute2Info.class); Loading Loading
packages/SettingsLib/res/values/strings.xml +7 −0 Original line number Diff line number Diff line Loading @@ -1315,6 +1315,13 @@ <string name="media_transfer_this_device_name" product="tablet">This tablet</string> <!-- Name of the phone device with an active remote session. [CHAR LIMIT=30] --> <string name="media_transfer_this_phone">This phone</string> <!-- Sub status indicates device need premium account. [CHAR LIMIT=NONE] --> <string name="media_output_status_require_premium">Upgrade account to switch</string> <!-- Sub status indicates device not support download content. [CHAR LIMIT=NONE] --> <string name="media_output_status_not_support_downloads">Can\’t play downloads here</string> <!-- Sub status indicates device need to wait after ad. [CHAR LIMIT=NONE] --> <string name="media_output_status_try_after_ad">Try again after the ad</string> <!-- Warning message to tell user is have problem during profile connect, it need to turn off device and back on. [CHAR_LIMIT=NONE] --> <string name="profile_connect_timeout_subtext">Problem connecting. Turn device off & back on</string> Loading
packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +20 −0 Original line number Diff line number Diff line Loading @@ -34,10 +34,12 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; import android.annotation.Nullable; import android.annotation.TargetApi; import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; Loading Loading @@ -191,6 +193,14 @@ public class InfoMediaManager extends MediaManager { && Api34Impl.preferRouteListingOrdering(mRouterManager, mPackageName); } @Nullable ComponentName getLinkedItemComponentName() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { return null; } return Api34Impl.getLinkedItemComponentName(mRouterManager, mPackageName); } /** * Remove a {@code device} from current media. * Loading Loading @@ -681,6 +691,16 @@ public class InfoMediaManager extends MediaManager { && !routeListingPreference.getUseSystemOrdering(); } @DoNotInline @Nullable static ComponentName getLinkedItemComponentName( MediaRouter2Manager mediaRouter2Manager, String packageName) { RouteListingPreference routeListingPreference = mediaRouter2Manager.getRouteListingPreference(packageName); return routeListingPreference == null ? null : routeListingPreference.getLinkedItemComponentName(); } @DoNotInline static void onRouteListingPreferenceUpdated( String packageName, Loading
packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java +11 −0 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR; import android.app.Notification; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.graphics.drawable.Drawable; import android.media.AudioManager; Loading Loading @@ -216,6 +217,16 @@ public class LocalMediaManager implements BluetoothCallback { return mInfoMediaManager.preferRouteListingOrdering(); } /** * Returns required component name for system to take the user back to the app by launching an * intent with the returned {@link ComponentName}, using action {@link #ACTION_TRANSFER_MEDIA}, * with the extra {@link #EXTRA_ROUTE_ID}. */ @Nullable public ComponentName getLinkedItemComponentName() { return mInfoMediaManager.getLinkedItemComponentName(); } /** * Start scan connected MediaDevice */ Loading
packages/SettingsLib/src/com/android/settingslib/media/MediaDevice.java +67 −9 Original line number Diff line number Diff line Loading @@ -30,8 +30,14 @@ import static android.media.MediaRoute2Info.TYPE_USB_DEVICE; import static android.media.MediaRoute2Info.TYPE_USB_HEADSET; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADPHONES; import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET; import static android.media.RouteListingPreference.Item.FLAG_ONGOING_SESSION; import static android.media.RouteListingPreference.Item.FLAG_SUGGESTED_ROUTE; import static android.media.RouteListingPreference.Item.SELECTION_BEHAVIOR_NONE; import static android.media.RouteListingPreference.Item.SUBTEXT_AD_ROUTING_DISALLOWED; import static android.media.RouteListingPreference.Item.SUBTEXT_CUSTOM; import static android.media.RouteListingPreference.Item.SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED; import static android.media.RouteListingPreference.Item.SUBTEXT_NONE; import static android.media.RouteListingPreference.Item.SUBTEXT_SUBSCRIPTION_REQUIRED; import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED; Loading @@ -51,6 +57,8 @@ import androidx.annotation.IntDef; import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.android.settingslib.R; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; Loading Loading @@ -194,28 +202,58 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { public abstract String getId(); /** * Get disabled reason of device * Get selection behavior of device * * @return disabled reason of device * @return selection behavior of device */ @RouteListingPreference.Item.SubText public int getDisableReason() { public int getSelectionBehavior() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null ? mItem.getSubText() : -1; ? mItem.getSelectionBehavior() : SELECTION_BEHAVIOR_NONE; } /** * Checks if device is has disabled reason * Checks if device is has subtext * * @return true if device has disabled reason * @return true if device has subtext */ public boolean hasDisabledReason() { public boolean hasSubtext() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null && mItem.getSubText() != SUBTEXT_NONE; } /** * Get subtext of device * * @return subtext of device */ @RouteListingPreference.Item.SubText public int getSubtext() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null ? mItem.getSubText() : SUBTEXT_NONE; } /** * Returns subtext string for current route. * * @return subtext string for this route */ public String getSubtextString() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && mItem != null ? Api34Impl.composeSubtext(mItem, mContext) : null; } /** * Checks if device has ongoing shared session, which allow user to join * * @return true if device has ongoing session */ public boolean hasOngoingSession() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE && Api34Impl.hasOngoingSession(mItem); } /** * Checks if device is suggested device from application * Loading Loading @@ -513,7 +551,27 @@ public abstract class MediaDevice implements Comparable<MediaDevice> { private static class Api34Impl { @DoNotInline static boolean isSuggestedDevice(RouteListingPreference.Item item) { return item != null && item.getFlags() == FLAG_SUGGESTED_ROUTE; return item != null && (item.getFlags() & FLAG_SUGGESTED_ROUTE) != 0; } @DoNotInline static boolean hasOngoingSession(RouteListingPreference.Item item) { return item != null && (item.getFlags() & FLAG_ONGOING_SESSION) != 0; } @DoNotInline static String composeSubtext(RouteListingPreference.Item item, Context context) { switch (item.getSubText()) { case SUBTEXT_SUBSCRIPTION_REQUIRED: return context.getString(R.string.media_output_status_require_premium); case SUBTEXT_DOWNLOADED_CONTENT_ROUTING_DISALLOWED: return context.getString(R.string.media_output_status_not_support_downloads); case SUBTEXT_AD_ROUTING_DISALLOWED: return context.getString(R.string.media_output_status_try_after_ad); case SUBTEXT_CUSTOM: return (String) item.getCustomSubtextMessage(); } return ""; } } }
packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +21 −0 Original line number Diff line number Diff line Loading @@ -35,6 +35,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.bluetooth.BluetoothDevice; import android.content.ComponentName; import android.content.Context; import android.media.MediaRoute2Info; import android.media.MediaRouter2Manager; Loading Loading @@ -88,6 +89,8 @@ public class InfoMediaManagerTest { private MediaManager.MediaDeviceCallback mCallback; @Mock private MediaSessionManager mMediaSessionManager; @Mock private ComponentName mComponentName; private InfoMediaManager mInfoMediaManager; private Context mContext; Loading Loading @@ -372,6 +375,24 @@ public class InfoMediaManagerTest { assertThat(mInfoMediaManager.preferRouteListingOrdering()).isFalse(); } @Test public void getInAppOnlyItemRoutingReceiver_oldSdkVersion_returnsNull() { assertThat(mInfoMediaManager.getLinkedItemComponentName()).isNull(); } @Test public void getInAppOnlyItemRoutingReceiver_newSdkVersionWithReceiverExist_returns() { ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT", Build.VERSION_CODES.UPSIDE_DOWN_CAKE); when(mRouterManager.getRouteListingPreference(any())).thenReturn( new RouteListingPreference.Builder().setItems( ImmutableList.of()).setUseSystemOrdering( false).setLinkedItemComponentName(mComponentName).build()); mInfoMediaManager.mRouterManager = mRouterManager; assertThat(mInfoMediaManager.getLinkedItemComponentName()).isEqualTo(mComponentName); } private List<MediaRoute2Info> getRoutesListWithDuplicatedIds() { final List<MediaRoute2Info> routes = new ArrayList<>(); final MediaRoute2Info info = mock(MediaRoute2Info.class); Loading