From e53d70babc944ca889078d4a9156b80bd1fe900b Mon Sep 17 00:00:00 2001 From: tom hsu Date: Tue, 2 Nov 2021 20:19:23 +0800 Subject: [PATCH 1/7] [Settings] Avoid crash from rotation screen. Bug: b/200822579 Test: Local test Change-Id: Ie25b2ab4284d47abdd5db23676d05b6d547b2a73 --- AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 8a69e04ff4b..d792cdc96ea 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -233,7 +233,8 @@ + android:launchMode="singleTask" + android:configChanges="orientation|screenSize|keyboardHidden"> From 8f0291cd1a1c3b3745252818c8a099e7b61c8749 Mon Sep 17 00:00:00 2001 From: ykhung Date: Tue, 2 Nov 2021 23:40:00 +0800 Subject: [PATCH 2/7] Fix NPE issue of usage detailed page learn more button click event root cause: some OEMs can not provide valid intent for help & feedback solution: check the intent is vaild or not before show up the help content Bug: 204844010 Test: make RunSettingsRoboTests Change-Id: I4e7a7c926205be1179d55d33ada345024c8a44ab --- .../fuelgauge/AdvancedPowerUsageDetail.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 4ad4de74de5..b5992075310 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -383,12 +383,14 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements footerString = context.getString(R.string.manager_battery_usage_footer); } mFooterPreference.setTitle(footerString); - mFooterPreference.setLearnMoreAction(v -> - startActivityForResult(HelpUtils.getHelpIntent(context, - context.getString(R.string.help_url_app_usage_settings), - /*backupContext=*/ ""), /*requestCode=*/ 0)); - mFooterPreference.setLearnMoreContentDescription( - context.getString(R.string.manager_battery_usage_link_a11y)); + final Intent helpIntent = HelpUtils.getHelpIntent(context, context.getString( + R.string.help_url_app_usage_settings), /*backupContext=*/ ""); + if (helpIntent != null) { + mFooterPreference.setLearnMoreAction(v -> + startActivityForResult(helpIntent, /*requestCode=*/ 0)); + mFooterPreference.setLearnMoreContentDescription( + context.getString(R.string.manager_battery_usage_link_a11y)); + } } @Override From 3de8abe8ee10a7c214bb50fa9f65bc9e3ad3b9c9 Mon Sep 17 00:00:00 2001 From: Curtis Belmonte Date: Tue, 2 Nov 2021 14:03:47 -0700 Subject: [PATCH 3/7] Add intent action to launch face enrollment Test: adb shell am start -a android.settings.FACE_ENROLL Bug: 204100385 Change-Id: I3ac4395d80f0c36e8cab3b068c51ce03d0ecad7b --- AndroidManifest.xml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a29089a8498..ed05a83ff23 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1935,8 +1935,13 @@ android:screenOrientation="portrait"/> + android:exported="true" + android:screenOrientation="portrait"> + + + + + Date: Tue, 2 Nov 2021 17:21:06 +0800 Subject: [PATCH 4/7] Adjust optimize page logging timing - Log the optimize mode only when leave this page and mode changed - Update mOptimizationMode at onResume to make sure the state is sync with framework Bug: 195306545 Test: make SettingsRoboTests Change-Id: Iab116220cd7d2b1bdb1c170c4b47016c763bf4fe --- .../fuelgauge/AdvancedPowerUsageDetail.java | 59 +++++++++++-------- .../AdvancedPowerUsageDetailTest.java | 39 +++++++++--- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 4ad4de74de5..6b024ed705c 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -270,6 +270,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements initHeader(); if (mEnableTriState) { + mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode(); initPreferenceForTriState(getContext()); final String packageName = mBatteryOptimizeUtils.getPackageName(); FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() @@ -286,8 +287,11 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements public void onPause() { super.onPause(); if (mEnableTriState) { - Log.d(TAG, "Leave with mode: " + getSelectedPreference()); - mBatteryOptimizeUtils.setAppUsageState(getSelectedPreference()); + final int selectedPreference = getSelectedPreference(); + + logMetricCategory(selectedPreference); + mBatteryOptimizeUtils.setAppUsageState(selectedPreference); + Log.d(TAG, "Leave with mode: " + selectedPreference); } } @@ -459,32 +463,42 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements updatePreferenceState(mUnrestrictedPreference, selectedKey); updatePreferenceState(mOptimizePreference, selectedKey); updatePreferenceState(mRestrictedPreference, selectedKey); - - // Logs metric. - int metricCategory = 0; - if (selectedKey.equals(mUnrestrictedPreference.getKey())) { - metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_UNRESTRICTED; - } else if (selectedKey.equals(mOptimizePreference.getKey())) { - metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED; - } else if (selectedKey.equals(mRestrictedPreference.getKey())) { - metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_RESTRICTED; - } - if (metricCategory != 0) { - FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() - .action( - getContext(), - metricCategory, - new Pair(ConvertUtils.METRIC_KEY_PACKAGE, - mBatteryOptimizeUtils.getPackageName()), - new Pair(ConvertUtils.METRIC_KEY_BATTERY_USAGE, - getArguments().getString(EXTRA_POWER_USAGE_PERCENT))); - } } private void updatePreferenceState(RadioButtonPreference preference, String selectedKey) { preference.setChecked(selectedKey.equals(preference.getKey())); } + private void logMetricCategory(int selectedKey) { + if (selectedKey == mOptimizationMode) { + return; + } + + int metricCategory = 0; + switch (selectedKey) { + case BatteryOptimizeUtils.MODE_UNRESTRICTED: + metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_UNRESTRICTED; + break; + case BatteryOptimizeUtils.MODE_OPTIMIZED: + metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED; + break; + case BatteryOptimizeUtils.MODE_RESTRICTED: + metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_RESTRICTED; + break; + } + + if (metricCategory != 0) { + FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() + .action( + getContext(), + metricCategory, + new Pair(ConvertUtils.METRIC_KEY_PACKAGE, + mBatteryOptimizeUtils.getPackageName()), + new Pair(ConvertUtils.METRIC_KEY_BATTERY_USAGE, + getArguments().getString(EXTRA_POWER_USAGE_PERCENT))); + } + } + private void onCreateForTriState(String packageName) { mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED); mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED); @@ -496,7 +510,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements mBatteryOptimizeUtils = new BatteryOptimizeUtils( getContext(), getArguments().getInt(EXTRA_UID), packageName); - mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode(); } private int getSelectedPreference() { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java index 3501726bc1a..7e975a08e25 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -792,14 +792,39 @@ public class AdvancedPowerUsageDetailTest { assertThat(mOptimizePreference.isChecked()).isTrue(); assertThat(mRestrictedPreference.isChecked()).isFalse(); assertThat(mUnrestrictedPreference.isChecked()).isFalse(); + } + + @Test + public void testOnPause_optimizationModeChanged_logPreference() { + final int mode = BatteryOptimizeUtils.MODE_RESTRICTED; + mFragment.mOptimizationMode = mode; + when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode); + mOptimizePreference.setKey(KEY_PREF_OPTIMIZED); + + mFragment.onRadioButtonClicked(mOptimizePreference); + mFragment.onPause(); + verify(mMetricsFeatureProvider) - .action( - mContext, - SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED, - (Pair[]) new Pair[] { - new Pair(ConvertUtils.METRIC_KEY_PACKAGE, null), - new Pair(ConvertUtils.METRIC_KEY_BATTERY_USAGE, "app label") - }); + .action( + mContext, + SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED, + (Pair[]) new Pair[] { + new Pair(ConvertUtils.METRIC_KEY_PACKAGE, null), + new Pair(ConvertUtils.METRIC_KEY_BATTERY_USAGE, "app label") + }); + } + + @Test + public void testOnPause_optimizationModeIsNotChanged_notInvokeLogging() { + final int mode = BatteryOptimizeUtils.MODE_OPTIMIZED; + mFragment.mOptimizationMode = mode; + when(mBatteryOptimizeUtils.getAppOptimizationMode()).thenReturn(mode); + mOptimizePreference.setKey(KEY_PREF_OPTIMIZED); + + mFragment.onRadioButtonClicked(mOptimizePreference); + mFragment.onPause(); + + verifyZeroInteractions(mMetricsFeatureProvider); } @Test From 9fc0f18181b5afe942a80b567a0375bdc5a16bc5 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Wed, 3 Nov 2021 12:18:39 +0800 Subject: [PATCH 5/7] Define a constant Sliceable#NO_RES for no resource cases Fix: 204733076 Test: build Change-Id: Ifa3b9db0e915a3ec056ad68a9a1862811594d423 --- .../BiometricSettingsAppPreferenceController.java | 2 +- ...etricSettingsKeyguardPreferenceController.java | 2 +- .../face/FaceSettingsPreferenceController.java | 2 +- ...tionTimeZoneDetectionPreferenceController.java | 2 +- ...EmergencyGestureSoundPreferenceController.java | 2 +- .../settings/flashlight/FlashlightSlice.java | 2 +- .../TelephonyTogglePreferenceController.java | 2 +- .../BubbleNotificationPreferenceController.java | 2 +- src/com/android/settings/slices/Sliceable.java | 15 +++++++++++---- .../network/ProviderModelSliceHelperTest.java | 2 +- .../slices/SpecialCaseSliceManagerTest.java | 2 +- 11 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java index 6351621d2e1..a46ae7a728c 100644 --- a/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricSettingsAppPreferenceController.java @@ -95,6 +95,6 @@ public class BiometricSettingsAppPreferenceController extends TogglePreferenceCo @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } } diff --git a/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java b/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java index 4101963cf49..2d2255805cc 100644 --- a/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java +++ b/src/com/android/settings/biometrics/combination/BiometricSettingsKeyguardPreferenceController.java @@ -77,6 +77,6 @@ public class BiometricSettingsKeyguardPreferenceController extends TogglePrefere @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } } diff --git a/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java b/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java index a8bff6b4197..bda078f2711 100644 --- a/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java +++ b/src/com/android/settings/biometrics/face/FaceSettingsPreferenceController.java @@ -55,6 +55,6 @@ public abstract class FaceSettingsPreferenceController extends TogglePreferenceC @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } } diff --git a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java index 2bab3e97a86..93e6d0a9644 100644 --- a/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java +++ b/src/com/android/settings/datetime/LocationTimeZoneDetectionPreferenceController.java @@ -120,7 +120,7 @@ public class LocationTimeZoneDetectionPreferenceController @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } @Override diff --git a/src/com/android/settings/emergency/EmergencyGestureSoundPreferenceController.java b/src/com/android/settings/emergency/EmergencyGestureSoundPreferenceController.java index 3d6695b1a37..52694af537c 100644 --- a/src/com/android/settings/emergency/EmergencyGestureSoundPreferenceController.java +++ b/src/com/android/settings/emergency/EmergencyGestureSoundPreferenceController.java @@ -55,7 +55,7 @@ public class EmergencyGestureSoundPreferenceController extends TogglePreferenceC @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } @Override diff --git a/src/com/android/settings/flashlight/FlashlightSlice.java b/src/com/android/settings/flashlight/FlashlightSlice.java index f18d7122cf7..eaf059aa673 100644 --- a/src/com/android/settings/flashlight/FlashlightSlice.java +++ b/src/com/android/settings/flashlight/FlashlightSlice.java @@ -120,7 +120,7 @@ public class FlashlightSlice implements CustomSliceable { @Override public int getSliceHighlightMenuRes() { // no landing page in Settings - return 0; + return NO_RES; } private static String getCameraId(Context context) throws CameraAccessException { diff --git a/src/com/android/settings/network/telephony/TelephonyTogglePreferenceController.java b/src/com/android/settings/network/telephony/TelephonyTogglePreferenceController.java index 46f279db260..e3609cb214b 100644 --- a/src/com/android/settings/network/telephony/TelephonyTogglePreferenceController.java +++ b/src/com/android/settings/network/telephony/TelephonyTogglePreferenceController.java @@ -68,7 +68,7 @@ public abstract class TelephonyTogglePreferenceController extends TogglePreferen @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } /** diff --git a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java index 9e3566498b0..eeb9924f50e 100644 --- a/src/com/android/settings/notification/BubbleNotificationPreferenceController.java +++ b/src/com/android/settings/notification/BubbleNotificationPreferenceController.java @@ -91,7 +91,7 @@ public class BubbleNotificationPreferenceController extends @Override public int getSliceHighlightMenuRes() { // not needed since it's not sliceable - return 0; + return NO_RES; } @Override diff --git a/src/com/android/settings/slices/Sliceable.java b/src/com/android/settings/slices/Sliceable.java index 31555df0022..406cb3aeb73 100644 --- a/src/com/android/settings/slices/Sliceable.java +++ b/src/com/android/settings/slices/Sliceable.java @@ -25,6 +25,7 @@ import android.content.IntentFilter; import android.net.Uri; import android.widget.Toast; +import androidx.annotation.StringRes; import androidx.slice.Slice; import com.android.settings.R; @@ -135,10 +136,16 @@ public interface Sliceable { } /** - * @return a resource ID that indicates which menu entry should be highlighted in multi-pane - * mode. + * Used to mark a {@link Sliceable} that has no highlight menu string resource. */ - default int getSliceHighlightMenuRes() { - return 0; + int NO_RES = 0; + + /** + * @return a string resource declared in res/values/menu_keys.xml that indicates which menu + * entry should be highlighted in two-pane mode, or {@link #NO_RES} representing highlighting is + * not applicable. + */ + @StringRes default int getSliceHighlightMenuRes() { + return NO_RES; } } diff --git a/tests/unit/src/com/android/settings/network/ProviderModelSliceHelperTest.java b/tests/unit/src/com/android/settings/network/ProviderModelSliceHelperTest.java index df6a38b20a1..8687e5ada64 100644 --- a/tests/unit/src/com/android/settings/network/ProviderModelSliceHelperTest.java +++ b/tests/unit/src/com/android/settings/network/ProviderModelSliceHelperTest.java @@ -305,7 +305,7 @@ public class ProviderModelSliceHelperTest { @Override public int getSliceHighlightMenuRes() { - return 0; + return NO_RES; } } diff --git a/tests/unit/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java b/tests/unit/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java index 490b7bd62c6..38347e91ca1 100644 --- a/tests/unit/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java +++ b/tests/unit/src/com/android/settings/slices/SpecialCaseSliceManagerTest.java @@ -146,7 +146,7 @@ public class SpecialCaseSliceManagerTest { @Override public int getSliceHighlightMenuRes() { - return 0; + return NO_RES; } } } From 9b6eac28f30ccf73548e2281456c860fbe3eb8d2 Mon Sep 17 00:00:00 2001 From: "Wesley.CW Wang" Date: Wed, 3 Nov 2021 12:38:10 +0800 Subject: [PATCH 6/7] Move optimization mode backup timing into onPause - Move to onPause to avoid some corner case (like kill apps from recent app won't trigger onDestroy) Bug: 195306545 Test: make SettingsRoboTests Change-Id: I89445a7138c30e6c869ce1c5c9a3f818997d99a7 --- .../settings/fuelgauge/AdvancedPowerUsageDetail.java | 10 ++-------- .../fuelgauge/AdvancedPowerUsageDetailTest.java | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index 6b024ed705c..eefeb9e9ae2 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -289,22 +289,16 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements if (mEnableTriState) { final int selectedPreference = getSelectedPreference(); + notifyBackupManager(); logMetricCategory(selectedPreference); mBatteryOptimizeUtils.setAppUsageState(selectedPreference); Log.d(TAG, "Leave with mode: " + selectedPreference); } } - @Override - public void onDestroy() { - super.onDestroy(); - notifyBackupManager(); - } - @VisibleForTesting void notifyBackupManager() { - if (mEnableTriState - && mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) { + if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) { final BackupManager backupManager = mBackupManager != null ? mBackupManager : new BackupManager(getContext()); backupManager.dataChanged(); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java index 7e975a08e25..db002cb047f 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetailTest.java @@ -856,7 +856,7 @@ public class AdvancedPowerUsageDetailTest { .thenReturn(BatteryOptimizeUtils.MODE_UNRESTRICTED); mFragment.mEnableTriState = false; - mFragment.notifyBackupManager(); + mFragment.onPause(); verifyZeroInteractions(mBackupManager); } From e427cc6752fac18cdc026754a3da392ea5fc6259 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Mon, 25 Oct 2021 14:11:16 +0800 Subject: [PATCH 7/7] Search experience improvement for large screen - Support fragment and direct link in SearchResultTrampoline - Start activity for SI case and start deep link trampoline for others - Disable menu highlight whenever the search bar is clicked - Don't overwrite SettingsApplication's homepage activity in SliceDeepLinkHomepageActivity - Scroll to highlighted menu entry after homepage is loaded to prevent UI overlapping Bug: 201724410 Test: manual, robotest build pass Change-Id: I5115d17d829e85036000da2e80f0e5b0598c733f --- .../android/settings/SettingsActivity.java | 37 ++++---- .../homepage/SettingsHomepageActivity.java | 39 +++++++- .../SliceDeepLinkHomepageActivity.java | 5 ++ .../settings/homepage/TopLevelSettings.java | 14 ++- .../search/SearchFeatureProvider.java | 25 ++++-- .../search/SearchResultTrampoline.java | 88 +++++++++++++++---- .../SettingsSearchIndexablesProvider.java | 1 - ...ighlightableTopLevelPreferenceAdapter.java | 17 ++-- .../search/SearchFeatureProviderImplTest.java | 7 +- 9 files changed, 176 insertions(+), 57 deletions(-) diff --git a/src/com/android/settings/SettingsActivity.java b/src/com/android/settings/SettingsActivity.java index df30d8b546c..9c81895e976 100644 --- a/src/com/android/settings/SettingsActivity.java +++ b/src/com/android/settings/SettingsActivity.java @@ -255,7 +255,8 @@ public class SettingsActivity extends SettingsBaseActivity // Should happen before any call to getIntent() getMetaData(); final Intent intent = getIntent(); - if (launchHomepageForTwoPaneDeepLink(intent)) { + if (shouldShowTwoPaneDeepLink(intent)) { + launchHomepageForTwoPaneDeepLink(intent); finishAndRemoveTask(); return; } @@ -368,16 +369,13 @@ public class SettingsActivity extends SettingsBaseActivity intent.getBooleanExtra(EXTRA_SHOW_FRAGMENT_AS_SUBSETTING, false); } - /** Returns true if the Activity is started by a deep link intent for large screen devices. */ - private boolean launchHomepageForTwoPaneDeepLink(Intent intent) { - if (!shouldShowTwoPaneDeepLink(intent)) { - return false; - } - + /** + * Returns the deep link trampoline intent for large screen devices. + */ + public static Intent getTrampolineIntent(Intent intent, String highlightMenuKey) { final Intent detailIntent = new Intent(intent); // It's a deep link intent, SettingsHomepageActivity will set SplitPairRule and start it. final Intent trampolineIntent = new Intent(ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY); - trampolineIntent.replaceExtras(detailIntent); // Relay detail intent data to prevent failure of Intent#ParseUri. @@ -391,22 +389,27 @@ public class SettingsActivity extends SettingsBaseActivity trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI, detailIntent.toUri(Intent.URI_INTENT_SCHEME)); - if (detailIntent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { - trampolineIntent.setClass(this, SliceDeepLinkHomepageActivity.class); + trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, + highlightMenuKey); + trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + return trampolineIntent; + } + + private void launchHomepageForTwoPaneDeepLink(Intent intent) { + final Intent trampolineIntent; + if (intent.getBooleanExtra(EXTRA_IS_FROM_SLICE, false)) { // Get menu key for slice deep link case. - final String highlightMenuKey = detailIntent.getStringExtra( + final String highlightMenuKey = intent.getStringExtra( EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY); if (!TextUtils.isEmpty(highlightMenuKey)) { mHighlightMenuKey = highlightMenuKey; } + trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey); + trampolineIntent.setClass(this, SliceDeepLinkHomepageActivity.class); + } else { + trampolineIntent = getTrampolineIntent(intent, mHighlightMenuKey); } - - trampolineIntent.putExtra(EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY, - mHighlightMenuKey); - trampolineIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); startActivity(trampolineIntent); - - return true; } private boolean shouldShowTwoPaneDeepLink(Intent intent) { diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 2bbc11e2b95..ae8c61e3b30 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -27,6 +27,7 @@ import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; import android.text.TextUtils; +import android.util.ArraySet; import android.util.FeatureFlagUtils; import android.util.Log; import android.view.View; @@ -54,6 +55,7 @@ import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; import java.net.URISyntaxException; +import java.util.Set; /** Settings homepage activity */ public class SettingsHomepageActivity extends FragmentActivity implements @@ -79,10 +81,27 @@ public class SettingsHomepageActivity extends FragmentActivity implements private View mHomepageView; private View mSuggestionView; private CategoryMixin mCategoryMixin; + private Set mLoadedListeners; - @Override - public CategoryMixin getCategoryMixin() { - return mCategoryMixin; + /** A listener receiving homepage loaded events. */ + public interface HomepageLoadedListener { + /** Called when the homepage is loaded. */ + void onHomepageLoaded(); + } + + /** + * Try to register a {@link HomepageLoadedListener}. If homepage is already loaded, the + * listener will not be notified. + * + * @return Whether the listener should be registered. + */ + public boolean registerHomepageLoadedListenerIfNeeded(HomepageLoadedListener listener) { + if (mHomepageView == null) { + return false; + } else { + mLoadedListeners.add(listener); + return true; + } } /** @@ -97,17 +116,25 @@ public class SettingsHomepageActivity extends FragmentActivity implements mSuggestionView.setVisibility(showSuggestion ? View.VISIBLE : View.GONE); mHomepageView.setVisibility(View.VISIBLE); mHomepageView = null; + mLoadedListeners.forEach(listener -> listener.onHomepageLoaded()); + mLoadedListeners.clear(); + } + + @Override + public CategoryMixin getCategoryMixin() { + return mCategoryMixin; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - ((SettingsApplication) getApplication()).setHomeActivity(this); + setHomeActivity(); setContentView(R.layout.settings_homepage_container); final View appBar = findViewById(R.id.app_bar_container); appBar.setMinimumHeight(getSearchBoxHeight()); initHomepageContainer(); + mLoadedListeners = new ArraySet<>(); final Toolbar toolbar = findViewById(R.id.search_action_bar); FeatureFactory.getFactory(this).getSearchFeatureProvider() @@ -158,6 +185,10 @@ public class SettingsHomepageActivity extends FragmentActivity implements launchDeepLinkIntentToRight(); } + protected void setHomeActivity() { + ((SettingsApplication) getApplication()).setHomeActivity(this); + } + private void showSuggestionFragment() { final Class fragment = FeatureFactory.getFactory(this) .getSuggestionFeatureProvider(this).getContextualSuggestionFragment(); diff --git a/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java b/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java index 2f836127e21..61e946d3bd8 100644 --- a/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java +++ b/src/com/android/settings/homepage/SliceDeepLinkHomepageActivity.java @@ -20,6 +20,11 @@ import android.content.ComponentName; /** Activity for Slices to launch Settings deep link page */ public class SliceDeepLinkHomepageActivity extends SettingsHomepageActivity { + @Override + protected void setHomeActivity() { + // do not overwrite homepage activity in SettingsApplication + } + @Override protected ComponentName getDeepLinkComponent() { return new ComponentName(getApplicationContext(), getClass()); diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index d3bfa02ae27..e9c7ef89668 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -101,7 +101,7 @@ public class TopLevelSettings extends DashboardFragment implements public boolean onPreferenceTreeClick(Preference preference) { // Register SplitPairRule for SubSettings. ActivityEmbeddingRulesController.registerSubSettingsPairRuleIfNeeded(getContext(), - true /* clearTop*/); + true /* clearTop */); setHighlightPreferenceKey(preference.getKey()); return super.onPreferenceTreeClick(preference); @@ -184,6 +184,15 @@ public class TopLevelSettings extends DashboardFragment implements } } + /** Disable highlight on the menu entry */ + public void disableMenuHighlight() { + if (mTopLevelAdapter == null) { + return; + } + mHighlightedPreferenceKey = null; + mTopLevelAdapter.highlightPreference(mHighlightedPreferenceKey, /* scrollNeeded= */ false); + } + @Override protected boolean shouldForceRoundedIcon() { return getContext().getResources() @@ -202,7 +211,8 @@ public class TopLevelSettings extends DashboardFragment implements Log.d(TAG, "onCreateAdapter, pref key: " + mHighlightedPreferenceKey); mTopLevelAdapter = new HighlightableTopLevelPreferenceAdapter( - getActivity(), preferenceScreen, getListView(), mHighlightedPreferenceKey); + (SettingsHomepageActivity) getActivity(), preferenceScreen, getListView(), + mHighlightedPreferenceKey); return mTopLevelAdapter; } diff --git a/src/com/android/settings/search/SearchFeatureProvider.java b/src/com/android/settings/search/SearchFeatureProvider.java index 05fd2aea4de..9b081e18925 100644 --- a/src/com/android/settings/search/SearchFeatureProvider.java +++ b/src/com/android/settings/search/SearchFeatureProvider.java @@ -19,7 +19,6 @@ package com.android.settings.search; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO; import android.annotation.NonNull; -import android.app.Activity; import android.app.ActivityOptions; import android.content.ComponentName; import android.content.Context; @@ -30,8 +29,12 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toolbar; +import androidx.fragment.app.FragmentActivity; + import com.android.settings.R; import com.android.settings.Utils; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; +import com.android.settings.homepage.TopLevelSettings; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.search.SearchIndexableResources; @@ -66,7 +69,7 @@ public interface SearchFeatureProvider { /** * Initializes the search toolbar. */ - default void initSearchToolbar(Activity activity, Toolbar toolbar, int pageId) { + default void initSearchToolbar(FragmentActivity activity, Toolbar toolbar, int pageId) { if (activity == null || toolbar == null) { return; } @@ -91,7 +94,8 @@ public interface SearchFeatureProvider { toolbar.setOnClickListener(tb -> { final Context context = activity.getApplicationContext(); - final Intent intent = buildSearchIntent(context, pageId); + final Intent intent = buildSearchIntent(context, pageId) + .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); if (activity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) { @@ -103,8 +107,19 @@ public interface SearchFeatureProvider { FeatureFactory.getFactory(context).getMetricsFeatureProvider() .logSettingsTileClick(KEY_HOMEPAGE_SEARCH_BAR, pageId); - final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity).toBundle(); - activity.startActivity(intent, bundle); + + if (ActivityEmbeddingUtils.isEmbeddingActivityEnabled(context)) { + final TopLevelSettings fragment = (TopLevelSettings) activity + .getSupportFragmentManager().findFragmentById(R.id.main_content); + if (fragment != null) { + fragment.disableMenuHighlight(); + } + activity.startActivity(intent); + } else { + final Bundle bundle = ActivityOptions.makeSceneTransitionAnimation(activity) + .toBundle(); + activity.startActivity(intent, bundle); + } }); } diff --git a/src/com/android/settings/search/SearchResultTrampoline.java b/src/com/android/settings/search/SearchResultTrampoline.java index 3414efeda72..d20a2ea860d 100644 --- a/src/com/android/settings/search/SearchResultTrampoline.java +++ b/src/com/android/settings/search/SearchResultTrampoline.java @@ -20,52 +20,102 @@ import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENT import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TAB; import android.app.Activity; +import android.content.ComponentName; import android.content.Intent; import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; import com.android.settings.SettingsActivity; import com.android.settings.SubSettings; import com.android.settings.activityembedding.ActivityEmbeddingRulesController; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.overlay.FeatureFactory; +import java.net.URISyntaxException; + /** * A trampoline activity that launches setting result page. */ public class SearchResultTrampoline extends Activity { + private static final String TAG = "SearchResultTrampoline"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + final ComponentName callingActivity = getCallingActivity(); // First make sure caller has privilege to launch a search result page. FeatureFactory.getFactory(this) .getSearchFeatureProvider() - .verifyLaunchSearchResultPageCaller(this, getCallingActivity()); + .verifyLaunchSearchResultPageCaller(this, callingActivity); // Didn't crash, proceed and launch the result as a subsetting. - final Intent intent = getIntent(); + Intent intent = getIntent(); + final String highlightMenuKey = intent.getStringExtra( + Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_HIGHLIGHT_MENU_KEY); - // Hack to take EXTRA_FRAGMENT_ARG_KEY from intent and set into - // EXTRA_SHOW_FRAGMENT_ARGUMENTS. This is necessary because intent could be from external - // caller and args may not persisted. - final String settingKey = intent.getStringExtra(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); - final int tab = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TAB, 0); - final Bundle args = new Bundle(); - args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, settingKey); - args.putInt(EXTRA_SHOW_FRAGMENT_TAB, tab); - intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); + final String fragment = intent.getStringExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT); + if (!TextUtils.isEmpty(fragment)) { + // Hack to take EXTRA_FRAGMENT_ARG_KEY from intent and set into + // EXTRA_SHOW_FRAGMENT_ARGUMENTS. This is necessary because intent could be from + // external caller and args may not persisted. + final String settingKey = intent.getStringExtra( + SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); + final int tab = intent.getIntExtra(EXTRA_SHOW_FRAGMENT_TAB, 0); + final Bundle args = new Bundle(); + args.putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, settingKey); + args.putInt(EXTRA_SHOW_FRAGMENT_TAB, tab); + intent.putExtra(EXTRA_SHOW_FRAGMENT_ARGUMENTS, args); - // Register SplirPairRule for SubSettings, set clearTop false to prevent unexpected back - // navigation behavior. - ActivityEmbeddingRulesController.registerSubSettingsPairRuleIfNeeded(this /* context */, - false /* clearTop*/); + // Reroute request to SubSetting. + intent.setClass(this /* context */, SubSettings.class); + } else { + // Direct link case + final String intentUriString = intent.getStringExtra( + Settings.EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI); + if (TextUtils.isEmpty(intentUriString)) { + Log.e(TAG, "No EXTRA_SETTINGS_EMBEDDED_DEEP_LINK_INTENT_URI for deep link"); + finish(); + return; + } - // Reroute request to SubSetting. - intent.setClass(this /* context */, SubSettings.class) - .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); - startActivity(intent); + try { + intent = Intent.parseUri(intentUriString, Intent.URI_INTENT_SCHEME); + } catch (URISyntaxException e) { + Log.e(TAG, "Failed to parse deep link intent: " + e); + finish(); + return; + } + } + + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(this)) { + startActivity(intent); + } else if (isFromSettingsIntelligence(callingActivity)) { + // Register SplitPairRule for SubSettings, set clearTop false to prevent unexpected back + // navigation behavior. + ActivityEmbeddingRulesController.registerSubSettingsPairRuleIfNeeded(this, + false /* clearTop */); + // TODO: pass menu key to homepage + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } else { + // Two-pane case + intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(SettingsActivity.getTrampolineIntent(intent, highlightMenuKey)); + } // Done. finish(); } + private boolean isFromSettingsIntelligence(ComponentName callingActivity) { + return callingActivity != null && TextUtils.equals( + callingActivity.getPackageName(), + FeatureFactory.getFactory(this).getSearchFeatureProvider() + .getSettingsIntelligencePkgName(this)); + } } diff --git a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java index feb9510bff9..d6635a197c2 100644 --- a/src/com/android/settings/search/SettingsSearchIndexablesProvider.java +++ b/src/com/android/settings/search/SettingsSearchIndexablesProvider.java @@ -365,7 +365,6 @@ public class SettingsSearchIndexablesProvider extends SearchIndexablesProvider { // The classname and intent information comes from the PreIndexData // This will be more clear when provider conversion is done at PreIndex time. raw.className = bundle.getTargetClass().getName(); - } rawList.addAll(providerRaws); } diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java index 19a91f65d16..bf92bbdf679 100644 --- a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java +++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java @@ -16,7 +16,6 @@ package com.android.settings.widget; -import android.app.Activity; import android.content.Context; import android.graphics.drawable.Drawable; import android.text.TextUtils; @@ -34,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.android.settings.Utils; import com.android.settings.activityembedding.ActivityEmbeddingUtils; +import com.android.settings.homepage.SettingsHomepageActivity; /** * Adapter for highlighting top level preferences @@ -54,7 +54,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt final int mIconColorHighlight; private final Context mContext; - private final Activity mActivity; + private final SettingsHomepageActivity mHomepageActivity; private final RecyclerView mRecyclerView; private final int mNormalBackgroundRes; private String mHighlightKey; @@ -63,13 +63,13 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt private boolean mHighlightNeeded; private boolean mScrolled; - public HighlightableTopLevelPreferenceAdapter(Activity activity, + public HighlightableTopLevelPreferenceAdapter(SettingsHomepageActivity homepageActivity, PreferenceGroup preferenceGroup, RecyclerView recyclerView, String key) { super(preferenceGroup); mRecyclerView = recyclerView; mHighlightKey = key; mContext = preferenceGroup.getContext(); - mActivity = activity; + mHomepageActivity = homepageActivity; final TypedValue outValue = new TypedValue(); mContext.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, outValue, true /* resolveRefs */); @@ -115,7 +115,7 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt * A function can highlight a specific setting in recycler view. */ public void requestHighlight() { - if (mRecyclerView == null || TextUtils.isEmpty(mHighlightKey)) { + if (mRecyclerView == null) { return; } @@ -194,6 +194,11 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt return; } + if (mHomepageActivity.registerHomepageLoadedListenerIfNeeded( + () -> scrollToPositionIfNeeded(position))) { + return; + } + // Only when the recyclerView is loaded, it can be scrolled final View view = mRecyclerView.getChildAt(position); if (view == null) { @@ -236,6 +241,6 @@ public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapt } private boolean isHighlightNeeded() { - return ActivityEmbeddingUtils.isTwoPaneResolution(mActivity); + return ActivityEmbeddingUtils.isTwoPaneResolution(mHomepageActivity); } } diff --git a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java index 444a8137889..5de57b6c95e 100644 --- a/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/search/SearchFeatureProviderImplTest.java @@ -19,7 +19,6 @@ package com.android.settings.search; import static com.google.common.truth.Truth.assertThat; -import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Intent; @@ -29,6 +28,8 @@ import android.net.Uri; import android.provider.Settings; import android.widget.Toolbar; +import androidx.fragment.app.FragmentActivity; + import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowUtils; @@ -46,13 +47,13 @@ import org.robolectric.shadows.ShadowPackageManager; public class SearchFeatureProviderImplTest { private SearchFeatureProviderImpl mProvider; - private Activity mActivity; + private FragmentActivity mActivity; private ShadowPackageManager mPackageManager; @Before public void setUp() { FakeFeatureFactory.setupForTest(); - mActivity = Robolectric.setupActivity(Activity.class); + mActivity = Robolectric.setupActivity(FragmentActivity.class); mProvider = new SearchFeatureProviderImpl(); mPackageManager = Shadows.shadowOf(mActivity.getPackageManager()); Settings.Global.putInt(mActivity.getContentResolver(),