From 4753adfb5275705a11a97ea7549dec12c9eda7d0 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 8 Sep 2020 12:17:03 +0800 Subject: [PATCH 1/6] [DO NOT MERGE] Fix the blank space on contextual card loading timeout Root cause: Sometimes loading contextual cards exceeds 1 second and a timeout expires. In the past, we used the timeout in order not to update homepage UI and to avoid screen scrolling. But we've introduced a mechanism of card space pre-allocation to avoid flickering, so when the timeout expires, the pre-allocated space will be always blank. Solution: Display a card on timeout if the one-card space is pre-allocated. Fixes: 165886791 Test: robotest Change-Id: I79b29c5fd6d9c4fe6b53dd4f5eab4cd3a606d76d --- .../ContextualCardManager.java | 13 +++++++-- .../ContextualCardManagerTest.java | 29 +++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java index 00755296d0a..fc39b59e33e 100644 --- a/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java +++ b/src/com/android/settings/homepage/contextualcards/ContextualCardManager.java @@ -243,7 +243,7 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory(mContext).getMetricsFeatureProvider(); - //navigate back to the homepage, screen rotate or after card dismissal + // navigate back to the homepage, screen rotate or after card dismissal if (!mIsFirstLaunch) { onContextualCardUpdated(cardsToKeep.stream() .collect(groupingBy(ContextualCard::getCardType))); @@ -266,8 +266,17 @@ public class ContextualCardManager implements ContextualCardLoader.CardContentLo SettingsEnums.ACTION_CONTEXTUAL_CARD_LOAD_TIMEOUT, SettingsEnums.SETTINGS_HOMEPAGE, null /* key */, (int) loadTime /* value */); + + // display a card on timeout if the one-card space is pre-allocated + if (!cards.isEmpty() && ContextualCardLoader.getCardCount(mContext) == 1) { + onContextualCardUpdated(cards.stream() + .collect(groupingBy(ContextualCard::getCardType))); + metricsFeatureProvider.action(mContext, + SettingsEnums.ACTION_CONTEXTUAL_CARD_SHOW, + ContextualCardLogUtils.buildCardListLog(cards)); + } } - //only log homepage display upon a fresh launch + // only log homepage display upon a fresh launch final long totalTime = System.currentTimeMillis() - mStartTime; metricsFeatureProvider.action(mContext, SettingsEnums.ACTION_CONTEXTUAL_HOME_SHOW, (int) totalTime); diff --git a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java index 69333d7ad9d..96d0c3be4db 100644 --- a/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java +++ b/tests/robotests/src/com/android/settings/homepage/contextualcards/ContextualCardManagerTest.java @@ -16,6 +16,7 @@ package com.android.settings.homepage.contextualcards; +import static com.android.settings.homepage.contextualcards.ContextualCardLoader.CONTEXTUAL_CARD_COUNT; import static com.android.settings.homepage.contextualcards.ContextualCardManager.KEY_CONTEXTUAL_CARDS; import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_FULL_WIDTH; import static com.android.settings.homepage.contextualcards.slices.SliceContextualCardRenderer.VIEW_TYPE_HALF_WIDTH; @@ -307,7 +308,7 @@ public class ContextualCardManagerTest { } @Test - public void onFinishCardLoading_fastLoad_shouldCallOnContextualCardUpdated() { + public void onFinishCardLoading_fastLoad_shouldUpdateContextualCard() { mManager.mStartTime = System.currentTimeMillis(); final ContextualCardManager manager = spy(mManager); doNothing().when(manager).onContextualCardUpdated(anyMap()); @@ -318,7 +319,7 @@ public class ContextualCardManagerTest { } @Test - public void onFinishCardLoading_slowLoad_shouldSkipOnContextualCardUpdated() { + public void onFinishCardLoading_slowLoadAndNoCard_shouldNotUpdateContextualCard() { mManager.mStartTime = 0; final ContextualCardManager manager = spy(mManager); doNothing().when(manager).onContextualCardUpdated(anyMap()); @@ -328,6 +329,30 @@ public class ContextualCardManagerTest { verify(manager, never()).onContextualCardUpdated(anyMap()); } + @Test + public void onFinishCardLoading_slowLoadAndNotPreAllocateSpace_shouldNotUpdateContextualCard() { + mManager.mStartTime = 0; + Settings.Global.putInt(mContext.getContentResolver(), CONTEXTUAL_CARD_COUNT, 3); + final ContextualCardManager manager = spy(mManager); + doNothing().when(manager).onContextualCardUpdated(anyMap()); + + manager.onFinishCardLoading(Arrays.asList(buildContextualCard(TEST_SLICE_URI))); + + verify(manager, never()).onContextualCardUpdated(anyMap()); + } + + @Test + public void onFinishCardLoading_slowLoadAndPreAllocateSpace_shouldUpdateContextualCard() { + mManager.mStartTime = 0; + Settings.Global.putInt(mContext.getContentResolver(), CONTEXTUAL_CARD_COUNT, 1); + final ContextualCardManager manager = spy(mManager); + doNothing().when(manager).onContextualCardUpdated(anyMap()); + + manager.onFinishCardLoading(Arrays.asList(buildContextualCard(TEST_SLICE_URI))); + + verify(manager).onContextualCardUpdated(anyMap()); + } + @Test public void onFinishCardLoading_newLaunch_twoLoadedCards_shouldShowTwoCards() { mManager.mStartTime = System.currentTimeMillis(); From 49fbc8fdb05779520b80d4587b64789f669f424d Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Tue, 22 Sep 2020 19:25:18 +0800 Subject: [PATCH 2/6] Fix output switcher will display previous media session Before this CL, the same request code will cause intent to be cached. It will cause the output switcher to display the previous media session. This CL uses the package name as an unique request code to fix this issue. Bug: 169077753 Test: make -j42 RunSettingsRoboTests Change-Id: Iebfc7904609e243d5bf0222307bae98a952a3331 Merged-In: Iebfc7904609e243d5bf0222307bae98a952a3331 (cherry picked from commit c3fe8d87d6c4da8404da4df8a0b2432427ae6de4) --- .../android/settings/media/MediaOutputIndicatorSlice.java | 7 ++++++- src/com/android/settings/media/MediaOutputSlice.java | 6 ++++-- src/com/android/settings/media/RemoteMediaSlice.java | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/com/android/settings/media/MediaOutputIndicatorSlice.java b/src/com/android/settings/media/MediaOutputIndicatorSlice.java index 17c8ef8a9fa..305c7df1913 100644 --- a/src/com/android/settings/media/MediaOutputIndicatorSlice.java +++ b/src/com/android/settings/media/MediaOutputIndicatorSlice.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.media.session.MediaController; import android.net.Uri; +import android.text.TextUtils; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; @@ -62,8 +63,12 @@ public class MediaOutputIndicatorSlice implements CustomSliceable { com.android.internal.R.drawable.ic_settings_bluetooth); final CharSequence title = mContext.getString(R.string.media_output_label_title, Utils.getApplicationLabel(mContext, getWorker().getPackageName())); + final int requestCode = TextUtils.isEmpty(getWorker().getPackageName()) + ? 0 + : getWorker().getPackageName().hashCode(); final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext, - 0 /* requestCode */, getMediaOutputSliceIntent(), FLAG_UPDATE_CURRENT); + requestCode, + getMediaOutputSliceIntent(), FLAG_UPDATE_CURRENT); final SliceAction primarySliceAction = SliceAction.createDeeplink( primaryActionIntent, icon, ListBuilder.ICON_IMAGE, title); @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext); diff --git a/src/com/android/settings/media/MediaOutputSlice.java b/src/com/android/settings/media/MediaOutputSlice.java index b9d43b465b9..6611e8d1cc8 100644 --- a/src/com/android/settings/media/MediaOutputSlice.java +++ b/src/com/android/settings/media/MediaOutputSlice.java @@ -257,9 +257,11 @@ public class MediaOutputSlice implements CustomSliceable { .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, getWorker().getPackageName()); - + final int requestCode = TextUtils.isEmpty(getWorker().getPackageName()) + ? 0 + : getWorker().getPackageName().hashCode(); return SliceAction.createDeeplink( - PendingIntent.getActivity(mContext, 0 /* requestCode */, intent, 0 /* flags */), + PendingIntent.getActivity(mContext, requestCode, intent, 0 /* flags */), IconCompat.createWithResource(mContext, R.drawable.ic_add_blue_24dp), ListBuilder.ICON_IMAGE, mContext.getText(R.string.add)); diff --git a/src/com/android/settings/media/RemoteMediaSlice.java b/src/com/android/settings/media/RemoteMediaSlice.java index 510a60e48d7..c4122393c8d 100644 --- a/src/com/android/settings/media/RemoteMediaSlice.java +++ b/src/com/android/settings/media/RemoteMediaSlice.java @@ -151,8 +151,9 @@ public class RemoteMediaSlice implements CustomSliceable { .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName); final IconCompat icon = IconCompat.createWithResource(mContext, R.drawable.ic_volume_remote); + final int requestCode = TextUtils.isEmpty(packageName) ? 0 : packageName.hashCode(); final PendingIntent primaryActionIntent = PendingIntent.getActivity(mContext, - 0 /* requestCode */, intent, 0 /* flags */); + requestCode, intent, 0 /* flags */); final SliceAction primarySliceAction = SliceAction.createDeeplink( primaryActionIntent, icon, ListBuilder.ICON_IMAGE, mContext.getString(R.string.media_output_label_title, From e04e9a508d20569a193941ae0f616cbc738d5152 Mon Sep 17 00:00:00 2001 From: Joshua Duong Date: Fri, 10 Apr 2020 00:22:04 -0700 Subject: [PATCH 3/6] [adbwifi] wireless debugging qstile long-press to WirelessDebuggingFragment. Previously, long-pressing any developer options quick settings tiles redirects to the application info page for Settings app. The manifest change makes the long-press by default, go into the developer options page. Also, there's a check if the long-press came from the wireless debugging tile so it can launch the WirelessDebuggingFragment. Bug: 153275926 Test: Enable the wireless debugging qstile in developer options. Then long-press the wireless debugging tile, which should direct you to the WirelessDebuggingFragment. Also, enable any other qstile and long-press them. It should direct you to the developer options page instead of the application info page. Change-Id: Id495e5c978005c21d0f33b949d7c410100bf3cf5 --- AndroidManifest.xml | 1 + .../DevelopmentSettingsDashboardFragment.java | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 9260201af6a..de4ae39e84d 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1922,6 +1922,7 @@ + diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java index f2d60f19814..8eb5c4f6c19 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -16,6 +16,8 @@ package com.android.settings.development; +import static android.service.quicksettings.TileService.ACTION_QS_TILE_PREFERENCES; + import android.app.Activity; import android.app.settings.SettingsEnums; import android.bluetooth.BluetoothA2dp; @@ -23,12 +25,14 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothCodecStatus; import android.bluetooth.BluetoothProfile; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.os.SystemProperties; import android.os.UserManager; +import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -41,6 +45,7 @@ import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; +import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.RestrictedDashboardFragment; import com.android.settings.development.autofill.AutofillLoggingLevelPreferenceController; import com.android.settings.development.autofill.AutofillResetOptionsPreferenceController; @@ -52,6 +57,7 @@ import com.android.settings.development.bluetooth.BluetoothCodecDialogPreference import com.android.settings.development.bluetooth.BluetoothHDAudioPreferenceController; import com.android.settings.development.bluetooth.BluetoothQualityDialogPreferenceController; import com.android.settings.development.bluetooth.BluetoothSampleRateDialogPreferenceController; +import com.android.settings.development.qstile.DevelopmentTiles; import com.android.settings.development.storage.SharedDataPreferenceController; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SwitchBar; @@ -199,11 +205,42 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra // Restore UI state based on whether developer options is enabled if (DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(getContext())) { enableDeveloperOptions(); + handleQsTileLongPressActionIfAny(); } else { disableDeveloperOptions(); } } + /** + * Long-pressing a developer options quick settings tile will by default (see + * QS_TILE_PREFERENCES in the manifest) take you to the developer options page. + * Some tiles may want to go into their own page within the developer options. + */ + private void handleQsTileLongPressActionIfAny() { + Intent intent = getActivity().getIntent(); + if (intent == null || !TextUtils.equals(ACTION_QS_TILE_PREFERENCES, intent.getAction())) { + return; + } + + Log.d(TAG, "Developer options started from qstile long-press"); + final ComponentName componentName = (ComponentName) intent.getParcelableExtra( + Intent.EXTRA_COMPONENT_NAME); + if (componentName == null) { + return; + } + + if (DevelopmentTiles.WirelessDebugging.class.getName().equals( + componentName.getClassName()) && getDevelopmentOptionsController( + WirelessDebuggingPreferenceController.class).isAvailable()) { + Log.d(TAG, "Long press from wireless debugging qstile"); + new SubSettingLauncher(getContext()) + .setDestination(WirelessDebuggingFragment.class.getName()) + .setSourceMetricsCategory(SettingsEnums.SETTINGS_ADB_WIRELESS) + .launch(); + } + // Add other qstiles here + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { From 455a0a3186b9b6760acf5812150e88c51f6e1c3a Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Thu, 24 Sep 2020 17:57:19 +0800 Subject: [PATCH 4/6] Disable entry point of output switcher It adds a minimum value if it shows only one available cast device in the output switcher. Because users can only change the volume slider or stop control in the output switcher. It's too hidden to have the user stop cast in the UI. - This CL will disable the entry point of the output switcher if there is only one available cast device in the list. - Update test cases. Bug: 163095048 Test: make -j42 RunSettingsRoboTests Change-Id: I8906878e1ba769d6940041f17d83b5de6b2a32c0 Merged-In: I8906878e1ba769d6940041f17d83b5de6b2a32c0 (cherry picked from commit b6840ced0d735ce09dfea6a357bfab0a736b5873) --- src/com/android/settings/Utils.java | 33 +++++++++++++++++ .../settings/media/RemoteMediaSlice.java | 36 ++++++++++++++++--- .../RemoteVolumeGroupController.java | 13 +++++-- .../src/com/android/settings/UtilsTest.java | 31 ++++++++++++++++ .../settings/media/RemoteMediaSliceTest.java | 2 ++ .../RemoteVolumeGroupControllerTest.java | 2 ++ 6 files changed, 110 insertions(+), 7 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 64919d9e809..11a5825c269 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -18,6 +18,10 @@ package com.android.settings; import static android.content.Intent.EXTRA_USER; import static android.content.Intent.EXTRA_USER_ID; +import static android.media.MediaRoute2Info.TYPE_GROUP; +import static android.media.MediaRoute2Info.TYPE_REMOTE_SPEAKER; +import static android.media.MediaRoute2Info.TYPE_REMOTE_TV; +import static android.media.MediaRoute2Info.TYPE_UNKNOWN; import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; import static android.text.format.DateUtils.FORMAT_SHOW_DATE; @@ -53,6 +57,8 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.VectorDrawable; import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; @@ -1137,4 +1143,31 @@ public final class Utils extends com.android.settingslib.Utils { drawable.draw(canvas); return roundedBitmap; } + + /** + * Returns {@code true} if needed to disable media output, otherwise returns {@code false}. + */ + public static boolean isMediaOutputDisabled( + MediaRouter2Manager router2Manager, String packageName) { + boolean isMediaOutputDisabled = false; + if (!TextUtils.isEmpty(packageName)) { + final List infos = router2Manager.getAvailableRoutes(packageName); + if (infos.size() == 1) { + final MediaRoute2Info info = infos.get(0); + final int deviceType = info.getType(); + switch (deviceType) { + case TYPE_UNKNOWN: + case TYPE_REMOTE_TV: + case TYPE_REMOTE_SPEAKER: + case TYPE_GROUP: + isMediaOutputDisabled = true; + break; + default: + isMediaOutputDisabled = false; + break; + } + } + } + return isMediaOutputDisabled; + } } diff --git a/src/com/android/settings/media/RemoteMediaSlice.java b/src/com/android/settings/media/RemoteMediaSlice.java index 510a60e48d7..15951495b57 100644 --- a/src/com/android/settings/media/RemoteMediaSlice.java +++ b/src/com/android/settings/media/RemoteMediaSlice.java @@ -17,6 +17,7 @@ package com.android.settings.media; import static android.app.slice.Slice.EXTRA_RANGE_VALUE; +import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE; import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; @@ -24,11 +25,15 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.media.MediaRouter2Manager; import android.media.RoutingSessionInfo; import android.net.Uri; +import android.text.SpannableString; import android.text.TextUtils; +import android.text.style.ForegroundColorSpan; import android.util.Log; +import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; import androidx.slice.Slice; import androidx.slice.builders.ListBuilder; @@ -59,6 +64,9 @@ public class RemoteMediaSlice implements CustomSliceable { private MediaDeviceUpdateWorker mWorker; + @VisibleForTesting + MediaRouter2Manager mRouterManager; + public RemoteMediaSlice(Context context) { mContext = context; } @@ -80,6 +88,9 @@ public class RemoteMediaSlice implements CustomSliceable { Log.e(TAG, "Unable to get the slice worker."); return listBuilder.build(); } + if (mRouterManager == null) { + mRouterManager = MediaRouter2Manager.getInstance(mContext); + } // Only displaying remote devices final List infos = getWorker().getActiveRemoteMediaDevice(); if (infos.isEmpty()) { @@ -98,8 +109,10 @@ public class RemoteMediaSlice implements CustomSliceable { + maxVolume); continue; } + final CharSequence appName = Utils.getApplicationLabel( + mContext, info.getClientPackageName()); final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title, - Utils.getApplicationLabel(mContext, info.getClientPackageName())); + appName); listBuilder.addInputRange(new InputRangeBuilder() .setTitleItem(icon, ListBuilder.ICON_IMAGE) .setTitle(castVolume) @@ -107,11 +120,21 @@ public class RemoteMediaSlice implements CustomSliceable { .setPrimaryAction(getSoundSettingAction(castVolume, icon, info.getId())) .setMax(maxVolume) .setValue(info.getVolume())); + + final boolean isMediaOutputDisabled = + Utils.isMediaOutputDisabled(mRouterManager, info.getClientPackageName()); + final SpannableString spannableTitle = new SpannableString( + TextUtils.isEmpty(appName) ? "" : appName); + spannableTitle.setSpan(new ForegroundColorSpan( + Utils.getColorAttrDefaultColor( + mContext, android.R.attr.textColorSecondary)), 0, + spannableTitle.length(), SPAN_EXCLUSIVE_EXCLUSIVE); listBuilder.addRow(new ListBuilder.RowBuilder() - .setTitle(outputTitle) + .setTitle(isMediaOutputDisabled ? spannableTitle : outputTitle) .setSubtitle(info.getName()) .setTitleItem(emptyIcon, ListBuilder.ICON_IMAGE) - .setPrimaryAction(getMediaOutputSliceAction(info.getClientPackageName()))); + .setPrimaryAction(getMediaOutputSliceAction( + info.getClientPackageName(), isMediaOutputDisabled))); } return listBuilder.build(); } @@ -144,9 +167,12 @@ public class RemoteMediaSlice implements CustomSliceable { return primarySliceAction; } - private SliceAction getMediaOutputSliceAction(String packageName) { + private SliceAction getMediaOutputSliceAction( + String packageName, boolean isMediaOutputDisabled) { final Intent intent = new Intent() - .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) + .setAction(isMediaOutputDisabled + ? "" + : MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME, packageName); final IconCompat icon = IconCompat.createWithResource(mContext, diff --git a/src/com/android/settings/notification/RemoteVolumeGroupController.java b/src/com/android/settings/notification/RemoteVolumeGroupController.java index 6d3c96dcc3d..bb62a567d58 100644 --- a/src/com/android/settings/notification/RemoteVolumeGroupController.java +++ b/src/com/android/settings/notification/RemoteVolumeGroupController.java @@ -18,6 +18,7 @@ package com.android.settings.notification; import android.content.Context; import android.content.Intent; +import android.media.MediaRouter2Manager; import android.media.RoutingSessionInfo; import android.text.TextUtils; @@ -57,6 +58,8 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem @VisibleForTesting LocalMediaManager mLocalMediaManager; + @VisibleForTesting + MediaRouter2Manager mRouterManager; public RemoteVolumeGroupController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -65,6 +68,7 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem mLocalMediaManager.registerCallback(this); mLocalMediaManager.startScan(); } + mRouterManager = MediaRouter2Manager.getInstance(context); } @Override @@ -111,8 +115,10 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem if (mPreferenceCategory.findPreference(info.getId()) != null) { continue; } + final CharSequence appName = Utils.getApplicationLabel( + mContext, info.getClientPackageName()); final CharSequence outputTitle = mContext.getString(R.string.media_output_label_title, - Utils.getApplicationLabel(mContext, info.getClientPackageName())); + appName); // Add slider final RemoteVolumeSeekBarPreference seekBarPreference = new RemoteVolumeSeekBarPreference(mContext); @@ -125,10 +131,13 @@ public class RemoteVolumeGroupController extends BasePreferenceController implem seekBarPreference.setIcon(R.drawable.ic_volume_remote); mPreferenceCategory.addPreference(seekBarPreference); // Add output indicator + final boolean isMediaOutputDisabled = Utils.isMediaOutputDisabled( + mRouterManager, info.getClientPackageName()); final Preference preference = new Preference(mContext); preference.setKey(SWITCHER_PREFIX + info.getId()); - preference.setTitle(outputTitle); + preference.setTitle(isMediaOutputDisabled ? appName : outputTitle); preference.setSummary(info.getName()); + preference.setEnabled(!isMediaOutputDisabled); mPreferenceCategory.addPreference(preference); } } diff --git a/tests/robotests/src/com/android/settings/UtilsTest.java b/tests/robotests/src/com/android/settings/UtilsTest.java index 2aa8418227f..303fb1bacce 100644 --- a/tests/robotests/src/com/android/settings/UtilsTest.java +++ b/tests/robotests/src/com/android/settings/UtilsTest.java @@ -40,6 +40,8 @@ import android.graphics.Color; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.VectorDrawable; +import android.media.MediaRoute2Info; +import android.media.MediaRouter2Manager; import android.net.ConnectivityManager; import android.net.LinkAddress; import android.net.LinkProperties; @@ -299,4 +301,33 @@ public class UtilsTest { assertThat(Utils.isSettingsIntelligence(mContext)).isFalse(); } + + @Test + public void isMediaOutputDisabled_infosSizeEqual1_returnsTrue() { + final MediaRouter2Manager router2Manager = mock(MediaRouter2Manager.class); + final MediaRoute2Info info = mock(MediaRoute2Info.class); + final List infos = new ArrayList<>(); + infos.add(info); + + when(router2Manager.getAvailableRoutes(anyString())).thenReturn(infos); + when(info.getType()).thenReturn(0); + + assertThat(Utils.isMediaOutputDisabled(router2Manager, "test")).isTrue(); + } + + @Test + public void isMediaOutputDisabled_infosSizeOverThan1_returnsFalse() { + final MediaRouter2Manager router2Manager = mock(MediaRouter2Manager.class); + final MediaRoute2Info info = mock(MediaRoute2Info.class); + final MediaRoute2Info info2 = mock(MediaRoute2Info.class); + final List infos = new ArrayList<>(); + infos.add(info); + infos.add(info2); + + when(router2Manager.getAvailableRoutes(anyString())).thenReturn(infos); + when(info.getType()).thenReturn(0); + when(info2.getType()).thenReturn(0); + + assertThat(Utils.isMediaOutputDisabled(router2Manager, "test")).isFalse(); + } } diff --git a/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java b/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java index 017faa5568f..e0e6e5018ec 100644 --- a/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java +++ b/tests/robotests/src/com/android/settings/media/RemoteMediaSliceTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; +import android.media.MediaRouter2Manager; import android.media.RoutingSessionInfo; import android.net.Uri; @@ -87,6 +88,7 @@ public class RemoteMediaSliceTest { SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); mRemoteMediaSlice = new RemoteMediaSlice(mContext); + mRemoteMediaSlice.mRouterManager = mock(MediaRouter2Manager.class); sMediaDeviceUpdateWorker = spy(new MediaDeviceUpdateWorker(mContext, REMOTE_MEDIA_SLICE_URI)); sMediaDeviceUpdateWorker.mLocalMediaManager = mLocalMediaManager; diff --git a/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java b/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java index e8245055653..09a40a7ca01 100644 --- a/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java +++ b/tests/robotests/src/com/android/settings/notification/RemoteVolumeGroupControllerTest.java @@ -30,6 +30,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageStats; +import android.media.MediaRouter2Manager; import android.media.RoutingSessionInfo; import androidx.preference.Preference; @@ -94,6 +95,7 @@ public class RemoteVolumeGroupControllerTest { mContext = RuntimeEnvironment.application; mController = new RemoteVolumeGroupController(mContext, KEY_REMOTE_VOLUME_GROUP); mController.mLocalMediaManager = mLocalMediaManager; + mController.mRouterManager = mock(MediaRouter2Manager.class); mPreferenceCategory = spy(new PreferenceCategory(mContext)); mPreferenceCategory.setKey(mController.getPreferenceKey()); From b6b49680e7901a12b19d0b088ffbb64857522025 Mon Sep 17 00:00:00 2001 From: Joshua Duong Date: Fri, 18 Sep 2020 11:56:44 -0700 Subject: [PATCH 5/6] Fix adb qr scanner text in dark mode. The text color is not right when in dark mode, which makes the text invisible. Bug: 168845829 Test: In dark/light mode, scan adb QR code and verify text is visible. Exempt-From-Owner-Approval: Already approved in ag/12640175 Change-Id: I6d8e16fd2d848d09b49673db041c97d667fea6a9 --- res/layout/adb_qrcode_scanner_fragment.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layout/adb_qrcode_scanner_fragment.xml b/res/layout/adb_qrcode_scanner_fragment.xml index f37c9a6ff4c..9a337d99859 100644 --- a/res/layout/adb_qrcode_scanner_fragment.xml +++ b/res/layout/adb_qrcode_scanner_fragment.xml @@ -19,7 +19,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:theme="@style/GlifV3Theme.Light" + android:theme="@style/GlifV3Theme" android:icon="@drawable/ic_scan_32dp"> Date: Tue, 22 Sep 2020 15:09:56 -0700 Subject: [PATCH 6/6] Fix issue where "learn more" link on 5G NR settings doesn't work. Issue is due to the fact the footer text is a spannable string which although different from the original text loaded when the preference was constructed, is the same in terms of object equality. Clearing the preference title before assigning the footer text fixes this. Fixes: 162011372 Test: Manual test; open settings and verify link is clickable now. Merged-In: I5d22c665b0d6fb842bc54dc55a2eb3045e0092a2 Change-Id: I5d22c665b0d6fb842bc54dc55a2eb3045e0092a2 (cherry picked from commit 97d8711d847109722a2c1b26b81d3f61b15210ac) Exempt-From-Owner-Approval: Straight cherry-pick. --- .../NrDisabledInDsdsFooterPreferenceController.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java b/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java index 3911fb8038e..78dfe51f007 100644 --- a/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java +++ b/src/com/android/settings/network/telephony/NrDisabledInDsdsFooterPreferenceController.java @@ -55,6 +55,13 @@ public class NrDisabledInDsdsFooterPreferenceController extends BasePreferenceCo super.updateState(preference); if (preference != null) { + // This is necessary to ensure that setting the title to the spannable string returned + // by getFooterText will be accepted. Internally, setTitle does an equality check on + // the spannable string being set to the text already set on the preference. That + // equality check apparently only takes into account the raw text and not and spannables + // that are part of the text. So we clear the title before applying the spannable + // footer to ensure it is accepted. + preference.setTitle(""); preference.setTitle(getFooterText()); } }