From 71aca3b7a9b8cbf9fae9cbf18dbcfd90a259660e Mon Sep 17 00:00:00 2001 From: menghanli Date: Thu, 7 Oct 2021 19:08:18 +0800 Subject: [PATCH 1/4] Apply fade transition for font size and display size Suw page - Create font size & screen size controler to simply xml - Add onPuase animation Bug: 194447311 Test: atest AccessibilityScreenSizeForSetupWizardActivityTest Test: atest FontSizePreferenceControllerTest Test: atest ScreenSizePreferenceControllerTest Change-Id: I91f3fd4c22aba4f8cd46f6e82e50987544942245 --- res/values/integers.xml | 5 - ...ccessibility_settings_for_setup_wizard.xml | 18 +--- ...ilityScreenSizeForSetupWizardActivity.java | 67 ++++++++---- .../AccessibilitySettingsForSetupWizard.java | 17 ++- ...ibilitySettingsForSetupWizardActivity.java | 6 +- .../FontSizePreferenceController.java | 56 ++++++++++ .../ScreenSizePreferenceController.java | 56 ++++++++++ ...yScreenSizeForSetupWizardActivityTest.java | 102 ++++++++++++++---- ...itySettingsForSetupWizardActivityTest.java | 3 +- .../FontSizePreferenceControllerTest.java | 75 +++++++++++++ .../ScreenSizePreferenceControllerTest.java | 75 +++++++++++++ 11 files changed, 412 insertions(+), 68 deletions(-) create mode 100644 src/com/android/settings/accessibility/FontSizePreferenceController.java create mode 100644 src/com/android/settings/accessibility/ScreenSizePreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FontSizePreferenceControllerTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/ScreenSizePreferenceControllerTest.java diff --git a/res/values/integers.xml b/res/values/integers.xml index 53543889766..3d73f64fabe 100644 --- a/res/values/integers.xml +++ b/res/values/integers.xml @@ -21,9 +21,4 @@ 102 103 104 - - - 0 - 1 diff --git a/res/xml/accessibility_settings_for_setup_wizard.xml b/res/xml/accessibility_settings_for_setup_wizard.xml index d1b4f4a5bab..047a2bdf223 100644 --- a/res/xml/accessibility_settings_for_setup_wizard.xml +++ b/res/xml/accessibility_settings_for_setup_wizard.xml @@ -24,25 +24,15 @@ android:key="font_size_preference" android:icon="@drawable/ic_font_size" android:summary="@string/short_summary_font_size" - android:title="@string/title_font_size"> - - - - + android:title="@string/title_font_size" + settings:controller="com.android.settings.accessibility.FontSizePreferenceController" /> - - - - + android:title="@string/screen_zoom_title" + settings:controller="com.android.settings.accessibility.ScreenSizePreferenceController" /> { - onBackPressed(); - }; + final View.OnClickListener nextButtonListener = v -> onBackPressed(); final FooterButton primaryButton = new FooterButton.Builder(this) .setText(R.string.done) @@ -124,4 +145,12 @@ public class AccessibilityScreenSizeForSetupWizardActivity extends InstrumentedA } }); } + + private int getTransitionType(Intent intent) { + return intent.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_NONE); + } + + private int getFragmentType(Intent intent) { + return intent.getIntExtra(VISION_FRAGMENT_NO, FragmentType.FONT_SIZE); + } } diff --git a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java index da95df9f590..a365566a6fb 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java +++ b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizard.java @@ -38,7 +38,7 @@ import androidx.preference.Preference; import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; -import com.android.settings.SettingsPreferenceFragment; +import com.android.settings.dashboard.DashboardFragment; import com.android.settingslib.RestrictedPreference; import com.google.android.setupdesign.GlifPreferenceLayout; @@ -48,8 +48,9 @@ import java.util.List; /** * Activity with the accessibility settings specific to Setup Wizard. */ -public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragment +public class AccessibilitySettingsForSetupWizard extends DashboardFragment implements Preference.OnPreferenceChangeListener { + private static final String TAG = "AccessibilitySettingsForSetupWizard"; // Preferences. private static final String DISPLAY_MAGNIFICATION_PREFERENCE = @@ -97,8 +98,6 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); - addPreferencesFromResource(R.xml.accessibility_settings_for_setup_wizard); - mDisplayMagnificationPreference = findPreference(DISPLAY_MAGNIFICATION_PREFERENCE); mScreenReaderPreference = findPreference(SCREEN_READER_PREFERENCE); mSelectToSpeakPreference = findPreference(SELECT_TO_SPEAK_PREFERENCE); @@ -137,6 +136,16 @@ public class AccessibilitySettingsForSetupWizard extends SettingsPreferenceFragm return super.onPreferenceTreeClick(preference); } + @Override + protected int getPreferenceScreenResId() { + return R.xml.accessibility_settings_for_setup_wizard; + } + + @Override + protected String getLogTag() { + return TAG; + } + /** * Returns accessibility service info by given package name and service name. * diff --git a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java index 0625fd28700..3a6bea9caaa 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java +++ b/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivity.java @@ -32,11 +32,13 @@ import androidx.preference.PreferenceFragmentCompat; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SetupWizardUtils; +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; import com.android.settings.core.SubSettingLauncher; import com.android.settings.search.actionbar.SearchMenuController; import com.android.settings.support.actionbar.HelpResourceProvider; import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.transition.SettingsTransitionHelper; +import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.util.ThemeHelper; @@ -127,8 +129,8 @@ public class AccessibilitySettingsForSetupWizardActivity extends SettingsActivit getIntent().getComponent())) { final Intent intent = new Intent(this, AccessibilityScreenSizeForSetupWizardActivity.class); - intent.putExtra(VISION_FRAGMENT_NO, - getResources().getInteger(R.integer.suw_font_size_fragment_no)); + intent.putExtra(VISION_FRAGMENT_NO, FragmentType.FONT_SIZE); + intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_FADE); startActivity(intent); Log.d(LOG_TAG, "Launch font size settings"); finish(); diff --git a/src/com/android/settings/accessibility/FontSizePreferenceController.java b/src/com/android/settings/accessibility/FontSizePreferenceController.java new file mode 100644 index 00000000000..35f96d4fd02 --- /dev/null +++ b/src/com/android/settings/accessibility/FontSizePreferenceController.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.VISION_FRAGMENT_NO; +import static com.android.settings.core.SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE; + +import android.content.Context; +import android.content.Intent; + +import androidx.preference.Preference; + +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; + +/** PreferenceController for displaying font size page. */ +public class FontSizePreferenceController extends BasePreferenceController { + + public FontSizePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!mPreferenceKey.equals(preference.getKey())) { + return false; + } + + final Intent intent = new Intent(mContext, + AccessibilityScreenSizeForSetupWizardActivity.class); + intent.putExtra(VISION_FRAGMENT_NO, FragmentType.FONT_SIZE); + intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_FADE); + mContext.startActivity(intent); + return true; + } +} diff --git a/src/com/android/settings/accessibility/ScreenSizePreferenceController.java b/src/com/android/settings/accessibility/ScreenSizePreferenceController.java new file mode 100644 index 00000000000..9734dc64b0f --- /dev/null +++ b/src/com/android/settings/accessibility/ScreenSizePreferenceController.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.VISION_FRAGMENT_NO; +import static com.android.settings.core.SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE; + +import android.content.Context; +import android.content.Intent; + +import androidx.preference.Preference; + +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; + +/** PreferenceController for displaying screen size page. */ +public class ScreenSizePreferenceController extends BasePreferenceController { + + public ScreenSizePreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!mPreferenceKey.equals(preference.getKey())) { + return false; + } + + final Intent intent = new Intent(mContext, + AccessibilityScreenSizeForSetupWizardActivity.class); + intent.putExtra(VISION_FRAGMENT_NO, FragmentType.SCREEN_SIZE); + intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_FADE); + mContext.startActivity(intent); + return true; + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityScreenSizeForSetupWizardActivityTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityScreenSizeForSetupWizardActivityTest.java index 05e5b3217c0..29e921c6b5a 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityScreenSizeForSetupWizardActivityTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityScreenSizeForSetupWizardActivityTest.java @@ -17,20 +17,24 @@ package com.android.settings.accessibility; import static com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.VISION_FRAGMENT_NO; +import static com.android.settings.core.SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE; import static com.google.common.truth.Truth.assertThat; +import static org.robolectric.Shadows.shadowOf; + import android.content.Context; import android.content.Intent; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; +import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupdesign.GlifLayout; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -39,46 +43,98 @@ import org.robolectric.RobolectricTestRunner; /** Tests for {@link AccessibilityScreenSizeForSetupWizardActivity} */ @RunWith(RobolectricTestRunner.class) public class AccessibilityScreenSizeForSetupWizardActivityTest { - private static final int DISPLAY_SIZE_FRAGMENT_NO = 1; private Context mContext = ApplicationProvider.getApplicationContext(); - private AccessibilityScreenSizeForSetupWizardActivity mActivity; - @Before - public void setup() { + private AccessibilityScreenSizeForSetupWizardActivity setupActivity(int fragmentType) { final Intent intent = new Intent(); - intent.putExtra(VISION_FRAGMENT_NO, - mContext.getResources().getInteger(R.integer.suw_font_size_fragment_no)); - mActivity = Robolectric.buildActivity(AccessibilityScreenSizeForSetupWizardActivity.class, + intent.putExtra(VISION_FRAGMENT_NO, fragmentType); + return Robolectric.buildActivity(AccessibilityScreenSizeForSetupWizardActivity.class, + intent).create().get(); + } + + private AccessibilityScreenSizeForSetupWizardActivity setupActivity(int fragmentType, + int transitionType) { + final Intent intent = new Intent(); + intent.putExtra(VISION_FRAGMENT_NO, fragmentType); + intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, transitionType); + return Robolectric.buildActivity(AccessibilityScreenSizeForSetupWizardActivity.class, intent).create().get(); } @Test - public void generateHeader_setPageNoAsFontSize_returnFontSizeTitle() { - mActivity.generateHeader( - mActivity.getResources().getInteger(R.integer.suw_font_size_fragment_no)); - - final GlifLayout layout = mActivity.findViewById(R.id.setup_wizard_layout); + public void setupActivity_fontSizePage_returnFontSizeTitle() { + final AccessibilityScreenSizeForSetupWizardActivity activity = + setupActivity(FragmentType.FONT_SIZE, TransitionType.TRANSITION_FADE); + final GlifLayout layout = activity.findViewById(R.id.setup_wizard_layout); assertThat(layout.getHeaderText()).isEqualTo(mContext.getText(R.string.title_font_size)); } @Test - public void generateHeader_setPageNoAsDisplaySize_returnDisplaySizeTitle() { - mActivity.generateHeader(DISPLAY_SIZE_FRAGMENT_NO); + public void setupActivity_generateDoneButton() { + final AccessibilityScreenSizeForSetupWizardActivity activity = + setupActivity(FragmentType.FONT_SIZE, TransitionType.TRANSITION_FADE); - final GlifLayout layout = mActivity.findViewById(R.id.setup_wizard_layout); - - assertThat(layout.getHeaderText()).isEqualTo(mContext.getText(R.string.screen_zoom_title)); + final GlifLayout layout = activity.findViewById(R.id.setup_wizard_layout); + final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); + assertThat(mixin.getPrimaryButton().getText()).isEqualTo(mContext.getText(R.string.done)); } @Test - public void initFooterButton_generateDoneButton() { - mActivity.initFooterButton(); + public void onPause_getPendingTransitionEnterAnimationResourceId_transitionFade_should() { + final AccessibilityScreenSizeForSetupWizardActivity activity = + setupActivity(FragmentType.FONT_SIZE, TransitionType.TRANSITION_FADE); - final GlifLayout layout = mActivity.findViewById(R.id.setup_wizard_layout); - final FooterBarMixin mixin = layout.getMixin(FooterBarMixin.class); + activity.onPause(); - assertThat(mixin.getPrimaryButton().getText()).isEqualTo(mContext.getText(R.string.done)); + assertThat(shadowOf(activity).getPendingTransitionEnterAnimationResourceId()) + .isEqualTo(R.anim.sud_stay); + } + + @Test + public void onPause_getPendingTransitionExitAnimationResourceId_transitionFade_should() { + final AccessibilityScreenSizeForSetupWizardActivity activity = + setupActivity(FragmentType.FONT_SIZE, TransitionType.TRANSITION_FADE); + + activity.onPause(); + + assertThat(shadowOf(activity).getPendingTransitionExitAnimationResourceId()) + .isEqualTo(android.R.anim.fade_out); + } + + @Test + public void onPause_getPendingTransitionEnterAnimationResourceId_transitionNone_should() { + final AccessibilityScreenSizeForSetupWizardActivity activity = + setupActivity(FragmentType.FONT_SIZE); + + activity.onPause(); + + assertThat(shadowOf(activity).getPendingTransitionEnterAnimationResourceId()) + .isNotEqualTo(R.anim.sud_stay); + } + + @Test + public void onPause_getPendingTransitionExitAnimationResourceId_transitionNone_should() { + final AccessibilityScreenSizeForSetupWizardActivity activity = + setupActivity(FragmentType.FONT_SIZE); + + activity.onPause(); + + assertThat(shadowOf(activity).getPendingTransitionExitAnimationResourceId()) + .isNotEqualTo(android.R.anim.fade_out); + } + + @Test + public void generateHeader_displaySizePage_returnDisplaySizeTitle() { + final Intent intent = new Intent(); + intent.putExtra(VISION_FRAGMENT_NO, FragmentType.SCREEN_SIZE); + intent.putExtra(EXTRA_PAGE_TRANSITION_TYPE, TransitionType.TRANSITION_FADE); + final AccessibilityScreenSizeForSetupWizardActivity activity = Robolectric.buildActivity( + AccessibilityScreenSizeForSetupWizardActivity.class, intent).get(); + activity.setContentView(R.layout.accessibility_screen_size_setup_wizard); + activity.generateHeader(); + final GlifLayout layout = activity.findViewById(R.id.setup_wizard_layout); + assertThat(layout.getHeaderText()).isEqualTo(mContext.getText(R.string.screen_zoom_title)); } } diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivityTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivityTest.java index 4d9668a6ab0..0849eb2bd1f 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivityTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsForSetupWizardActivityTest.java @@ -27,6 +27,7 @@ import android.content.Intent; import androidx.test.filters.SmallTest; import com.android.settings.R; +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; import com.google.android.setupcompat.util.WizardManagerHelper; @@ -66,7 +67,7 @@ public class AccessibilitySettingsForSetupWizardActivityTest { final Intent launchIntent = Shadows.shadowOf(activity).getNextStartedActivity(); assertThat(launchIntent).isNotNull(); assertThat(launchIntent.getIntExtra(VISION_FRAGMENT_NO, -1)).isEqualTo( - activity.getResources().getInteger(R.integer.suw_font_size_fragment_no)); + FragmentType.FONT_SIZE); assertThat(activity.isFinishing()).isTrue(); } diff --git a/tests/robotests/src/com/android/settings/accessibility/FontSizePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/FontSizePreferenceControllerTest.java new file mode 100644 index 00000000000..a52c0fe0e0b --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FontSizePreferenceControllerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.VISION_FRAGMENT_NO; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.content.Intent; + +import androidx.preference.Preference; + +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; +import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link FontSizePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class FontSizePreferenceControllerTest { + private static final String TEST_KEY = "test_key"; + + private Activity mActivity; + private FontSizePreferenceController mController; + Preference mPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mActivity = Robolectric.setupActivity(Activity.class); + mController = new FontSizePreferenceController(mActivity, TEST_KEY); + mPreference = new Preference(mActivity); + mPreference.setKey(TEST_KEY); + } + + @Test + public void getAvailabilityStatus_returnAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void handlePreferenceTreeClick_launchActivityWithExpectedValues() { + mController.handlePreferenceTreeClick(mPreference); + + final Intent nextActivity = shadowOf(mActivity).getNextStartedActivity(); + assertThat(nextActivity.getIntExtra(VISION_FRAGMENT_NO, /* defaultValue= */-1)) + .isEqualTo(FragmentType.FONT_SIZE); + assertThat(nextActivity.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, /* defaultValue= */-1)) + .isEqualTo(TransitionType.TRANSITION_FADE); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/ScreenSizePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/ScreenSizePreferenceControllerTest.java new file mode 100644 index 00000000000..1cbf78ea84c --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/ScreenSizePreferenceControllerTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.accessibility; + +import static com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.VISION_FRAGMENT_NO; +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.SettingsBaseActivity.EXTRA_PAGE_TRANSITION_TYPE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.robolectric.Shadows.shadowOf; + +import android.app.Activity; +import android.content.Intent; + +import androidx.preference.Preference; + +import com.android.settings.accessibility.AccessibilityScreenSizeForSetupWizardActivity.FragmentType; +import com.android.settingslib.transition.SettingsTransitionHelper.TransitionType; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link ScreenSizePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class ScreenSizePreferenceControllerTest { + private static final String TEST_KEY = "test_key"; + + private Activity mActivity; + private ScreenSizePreferenceController mController; + Preference mPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mActivity = Robolectric.setupActivity(Activity.class); + mController = new ScreenSizePreferenceController(mActivity, TEST_KEY); + mPreference = new Preference(mActivity); + mPreference.setKey(TEST_KEY); + } + + @Test + public void getAvailabilityStatus_returnAvailable() { + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void handlePreferenceTreeClick_launchActivityWithExpectedValues() { + mController.handlePreferenceTreeClick(mPreference); + + final Intent nextActivity = shadowOf(mActivity).getNextStartedActivity(); + assertThat(nextActivity.getIntExtra(VISION_FRAGMENT_NO, /* defaultValue= */-1)) + .isEqualTo(FragmentType.SCREEN_SIZE); + assertThat(nextActivity.getIntExtra(EXTRA_PAGE_TRANSITION_TYPE, /* defaultValue= */-1)) + .isEqualTo(TransitionType.TRANSITION_FADE); + } +} From 29a09e55ba8596a92c51e222c0736706856b3bb1 Mon Sep 17 00:00:00 2001 From: Jason Chiu Date: Tue, 12 Oct 2021 17:24:34 +0800 Subject: [PATCH 2/4] Revert "Revert "Support highlightable Settings homepage menu for 2-pane"" This reverts commit 096c090b805acbed1ea5a45f584ff5a24bd2fa3b. Reason for revert: crash is fixed in this change Bug: 202510128 Test: manual, build Change-Id: Ifff8d58cfdcf43e123b11dee308665b2617cb4b3 --- AndroidManifest.xml | 132 +++++++++- src/com/android/settings/Settings.java | 11 +- .../android/settings/SettingsActivity.java | 25 +- .../settings/SettingsPreferenceFragment.java | 3 +- src/com/android/settings/Utils.java | 8 + .../ActivityEmbeddingUtils.java | 8 + .../settings/dashboard/CategoryManager.java | 16 ++ .../dashboard/DashboardFeatureProvider.java | 4 +- .../DashboardFeatureProviderImpl.java | 24 +- .../settings/dashboard/DashboardFragment.java | 6 +- .../profileselector/ProfileSelectDialog.java | 14 +- .../settings/homepage/HighlightableMenu.java | 117 +++++++++ .../homepage/SettingsHomepageActivity.java | 54 +++- .../settings/homepage/TopLevelSettings.java | 99 ++++++++ .../search/SearchFeatureProviderImpl.java | 2 +- ...ighlightableTopLevelPreferenceAdapter.java | 238 ++++++++++++++++++ .../AccountDetailDashboardFragmentTest.java | 7 +- .../DashboardFeatureProviderImplTest.java | 104 ++++---- 18 files changed, 781 insertions(+), 91 deletions(-) create mode 100644 src/com/android/settings/homepage/HighlightableMenu.java create mode 100644 src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index d04b884f426..674ab61eb27 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -136,7 +136,7 @@ android:taskAffinity="com.android.settings.root" android:launchMode="singleTask" android:exported="true" - android:configChanges="keyboard|keyboardHidden|screenSize|screenLayout"> + android:configChanges="orientation|keyboard|keyboardHidden|screenSize|screenLayout"> @@ -228,6 +228,8 @@ + @@ -257,6 +259,8 @@ + @@ -314,6 +318,8 @@ + @@ -479,6 +485,8 @@ + @@ -489,6 +497,8 @@ android:clearTaskOnLaunch="true"> + + + + + + + + @@ -618,6 +642,8 @@ android:targetActivity="Settings$TetherSettingsActivity"> + + + @@ -661,6 +691,8 @@ + + @@ -858,6 +892,8 @@ + @@ -911,6 +947,8 @@ + @@ -921,6 +959,8 @@ android:exported="true"> + + @@ -991,6 +1033,8 @@ android:value="com.android.settings.notification.zen.ZenModeEventRuleSettings" /> + + @@ -1028,6 +1074,8 @@ + + @@ -1065,6 +1115,8 @@ + @@ -1088,6 +1140,8 @@ android:value="true" /> + + @@ -1353,6 +1409,8 @@ + + @@ -1629,6 +1689,8 @@ + @@ -1644,6 +1706,8 @@ + @@ -1697,6 +1761,8 @@ + @@ -1715,6 +1781,8 @@ + @@ -1729,6 +1797,8 @@ + @@ -1743,6 +1813,8 @@ + @@ -1757,6 +1829,8 @@ + @@ -2429,6 +2503,8 @@ + + + + + + + + + + + + @@ -2821,6 +2919,8 @@ + @@ -2835,6 +2935,8 @@ + + @@ -2893,6 +2997,8 @@ + @@ -3301,6 +3407,8 @@ android:value="true" /> + + @@ -3362,6 +3472,8 @@ android:exported="true"> + + @@ -3394,6 +3508,8 @@ + + @@ -3577,6 +3695,8 @@ + @@ -3722,6 +3842,8 @@ + + + + @@ -3782,7 +3910,7 @@ android:permission="android.permission.BLUETOOTH_CONNECT"> - /> + = getMinCurrentScreenSplitWidthPx(appContext) + && dm.heightPixels >= getMinSmallestScreenSplitWidthPx(appContext); + } } diff --git a/src/com/android/settings/dashboard/CategoryManager.java b/src/com/android/settings/dashboard/CategoryManager.java index 2a82abe4859..b6ec4ca3269 100644 --- a/src/com/android/settings/dashboard/CategoryManager.java +++ b/src/com/android/settings/dashboard/CategoryManager.java @@ -17,6 +17,7 @@ package com.android.settings.dashboard; import android.content.ComponentName; import android.content.Context; +import android.text.TextUtils; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; @@ -24,6 +25,7 @@ import android.util.Pair; import androidx.annotation.VisibleForTesting; +import com.android.settings.homepage.HighlightableMenu; import com.android.settingslib.applications.InterestingConfigChanges; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.DashboardCategory; @@ -153,6 +155,20 @@ public class CategoryManager { filterDuplicateTiles(mCategoryByKeyMap); if (firstLoading) { logTiles(context); + + final DashboardCategory homepageCategory = mCategoryByKeyMap.get( + CategoryKey.CATEGORY_HOMEPAGE); + if (homepageCategory == null) { + return; + } + for (Tile tile : homepageCategory.getTiles()) { + final String key = tile.getKey(context); + if (TextUtils.isEmpty(key)) { + Log.w(TAG, "Key hint missing for homepage tile: " + tile.getTitle(context)); + continue; + } + HighlightableMenu.addMenuKey(key); + } } } } diff --git a/src/com/android/settings/dashboard/DashboardFeatureProvider.java b/src/com/android/settings/dashboard/DashboardFeatureProvider.java index 8c872f0b8b0..b88ecd4240b 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProvider.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProvider.java @@ -47,9 +47,9 @@ public interface DashboardFeatureProvider { * Binds preference to data provided by tile and gets dynamic data observers. * * @param activity If tile contains intent to launch, it will be launched from this activity + * @param fragment The fragment that the preference will be bound to * @param forceRoundedIcon Whether or not injected tiles from other packages should be forced to * rounded icon. - * @param sourceMetricsCategory The context (source) from which an action is performed * @param pref The preference to bind data * @param tile The binding data * @param key They key for preference. If null, we will generate one from tile data @@ -58,7 +58,7 @@ public interface DashboardFeatureProvider { * @return The list of dynamic data observers */ List bindPreferenceToTileAndGetObservers(FragmentActivity activity, - boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, + DashboardFragment fragment, boolean forceRoundedIcon, Preference pref, Tile tile, String key, int baseOrder); /** diff --git a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java index 27b20cf4ef7..f8185db7178 100644 --- a/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java +++ b/src/com/android/settings/dashboard/DashboardFeatureProviderImpl.java @@ -36,6 +36,7 @@ import static com.android.settingslib.drawer.TileUtils.META_DATA_PREFERENCE_TITL import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface.OnCancelListener; import android.content.IContentProvider; import android.content.Intent; import android.content.pm.PackageManager; @@ -60,6 +61,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; +import com.android.settings.homepage.TopLevelSettings; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.PrimarySwitchPreference; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; @@ -123,7 +125,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { @Override public List bindPreferenceToTileAndGetObservers(FragmentActivity activity, - boolean forceRoundedIcon, int sourceMetricsCategory, Preference pref, Tile tile, + DashboardFragment fragment, boolean forceRoundedIcon, Preference pref, Tile tile, String key, int baseOrder) { if (pref == null) { return null; @@ -149,6 +151,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { bindIcon(pref, tile, forceRoundedIcon); if (tile instanceof ActivityTile) { + final int sourceMetricsCategory = fragment.getMetricsCategory(); final Bundle metadata = tile.getMetaData(); String clsName = null; String action = null; @@ -166,7 +169,17 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { intent.setAction(action); } pref.setOnPreferenceClickListener(preference -> { - launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory); + OnCancelListener listener = null; + if (fragment instanceof TopLevelSettings) { + final TopLevelSettings topLevelSettings = (TopLevelSettings) fragment; + // Highlight the tile immediately whenever it's clicked + topLevelSettings.setHighlightPreferenceKey(key); + // If the tile allows users to select profile, the pop-op dialog may be + // cancelled and then the previous highlight entry should be restored. + listener = dialog -> topLevelSettings.restorePreviousHighlight(); + } + launchIntentOrSelectProfile(activity, tile, intent, sourceMetricsCategory, + listener); return true; }); } @@ -198,7 +211,8 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { .putExtra(MetricsFeatureProvider.EXTRA_SOURCE_METRICS_CATEGORY, SettingsEnums.DASHBOARD_SUMMARY) .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY); + launchIntentOrSelectProfile(activity, tile, intent, SettingsEnums.DASHBOARD_SUMMARY, + /* listener= */ null); } private DynamicDataObserver createDynamicDataObserver(String method, Uri uri, Preference pref) { @@ -413,7 +427,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } private void launchIntentOrSelectProfile(FragmentActivity activity, Tile tile, Intent intent, - int sourceMetricCategory) { + int sourceMetricCategory, OnCancelListener listener) { if (!isIntentResolvable(intent)) { Log.w(TAG, "Cannot resolve intent, skipping. " + intent); return; @@ -444,7 +458,7 @@ public class DashboardFeatureProviderImpl implements DashboardFeatureProvider { } ProfileSelectDialog.show(activity.getSupportFragmentManager(), tile, - sourceMetricCategory); + sourceMetricCategory, listener); } } diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index dfd931db7e9..4317fc6ef40 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -496,15 +496,15 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment if (mDashboardTilePrefKeys.containsKey(key)) { // Have the key already, will rebind. final Preference preference = screen.findPreference(key); - mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), - forceRoundedIcons, getMetricsCategory(), preference, tile, key, + mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), this, + forceRoundedIcons, preference, tile, key, mPlaceholderPreferenceController.getOrder()); } else { // Don't have this key, add it. final Preference pref = createPreference(tile); final List observers = mDashboardFeatureProvider.bindPreferenceToTileAndGetObservers(getActivity(), - forceRoundedIcons, getMetricsCategory(), pref, tile, key, + this, forceRoundedIcons, pref, tile, key, mPlaceholderPreferenceController.getOrder()); screen.addPreference(pref); registerDynamicDataObservers(observers); diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java index 36cdd422508..b05f23b812b 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java @@ -19,6 +19,7 @@ package com.android.settings.dashboard.profileselector; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; @@ -44,19 +45,23 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen private int mSourceMetricCategory; private Tile mSelectedTile; + private OnCancelListener mOnCancelListener; /** * Display the profile select dialog, adding the fragment to the given FragmentManager. * @param manager The FragmentManager this fragment will be added to. * @param tile The tile for this fragment. * @param sourceMetricCategory The source metric category. + * @param listener The listener listens to the dialog cancelling event. */ - public static void show(FragmentManager manager, Tile tile, int sourceMetricCategory) { + public static void show(FragmentManager manager, Tile tile, int sourceMetricCategory, + OnCancelListener listener) { final ProfileSelectDialog dialog = new ProfileSelectDialog(); final Bundle args = new Bundle(); args.putParcelable(ARG_SELECTED_TILE, tile); args.putInt(ARG_SOURCE_METRIC_CATEGORY, sourceMetricCategory); dialog.setArguments(args); + dialog.mOnCancelListener = listener; dialog.show(manager, "select_profile"); } @@ -91,6 +96,13 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen getActivity().startActivityAsUser(intent, user); } + @Override + public void onCancel(DialogInterface dialog) { + if (mOnCancelListener != null) { + mOnCancelListener.onCancel(dialog); + } + } + public static void updateUserHandlesIfNeeded(Context context, Tile tile) { final List userHandles = tile.userHandle; if (tile.userHandle == null || tile.userHandle.size() <= 1) { diff --git a/src/com/android/settings/homepage/HighlightableMenu.java b/src/com/android/settings/homepage/HighlightableMenu.java new file mode 100644 index 00000000000..6a10e66dd6a --- /dev/null +++ b/src/com/android/settings/homepage/HighlightableMenu.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.homepage; + +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_HIGHLIGHTABLE_MENU_KEY; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; + +import android.annotation.XmlRes; +import android.content.Context; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Log; + +import com.android.settings.core.PreferenceXmlParserUtils; +import com.android.settings.core.PreferenceXmlParserUtils.MetadataFlag; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Class for mapping highlightable menu keys and preference keys + */ +public class HighlightableMenu { + private static final String TAG = "HighlightableMenu"; + + /** + * Map from highlightable menu key to preference key. + */ + private static final Map MENU_TO_PREFERENCE_KEY_MAP; + + /** + * Map from old menu key to current key string id. + */ + private static final Map MENU_KEY_COMPAT_MAP; + + private static boolean sXmlParsed; + + static { + MENU_TO_PREFERENCE_KEY_MAP = new ArrayMap<>(); + MENU_KEY_COMPAT_MAP = new ArrayMap<>(); + + // Manual mapping for platform compatibility, e.g. + // MENU_KEY_COMPAT_MAP.put("top_level_apps_and_notifs", R.string.menu_key_apps); + } + + /** Parses the highlightable menu keys from xml */ + public static synchronized void fromXml(Context context, @XmlRes int xmlResId) { + if (sXmlParsed) { + return; + } + + Log.d(TAG, "parsing highlightable menu from xml"); + final List preferenceMetadata; + try { + preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId, + MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_HIGHLIGHTABLE_MENU_KEY); + } catch (IOException | XmlPullParserException e) { + Log.e(TAG, "Failed to parse preference xml for getting highlightable menu keys", e); + return; + } + + for (Bundle metadata : preferenceMetadata) { + final String menuKey = metadata.getString(METADATA_HIGHLIGHTABLE_MENU_KEY); + if (TextUtils.isEmpty(menuKey)) { + continue; + } + final String prefKey = metadata.getString(METADATA_KEY); + if (TextUtils.isEmpty(prefKey)) { + Log.w(TAG, "Highlightable menu requires android:key but it's missing in xml: " + + menuKey); + continue; + } + MENU_TO_PREFERENCE_KEY_MAP.put(menuKey, prefKey); + } + + if (MENU_TO_PREFERENCE_KEY_MAP.isEmpty()) { + return; + } + + sXmlParsed = true; + MENU_KEY_COMPAT_MAP.forEach((compatMenuKey, keyId) -> { + final String prefKey = lookupPreferenceKey(context.getString(keyId)); + if (prefKey != null) { + MENU_TO_PREFERENCE_KEY_MAP.put(compatMenuKey, prefKey); + } + }); + } + + /** Manually adds a preference as the menu key for Injection */ + public static synchronized void addMenuKey(String key) { + Log.d(TAG, "add menu key: " + key); + MENU_TO_PREFERENCE_KEY_MAP.put(key, key); + } + + /** Looks up the preference key by a specified menu key */ + public static String lookupPreferenceKey(String menuKey) { + return MENU_TO_PREFERENCE_KEY_MAP.get(menuKey); + } +} diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 3e9b9d059f5..471b1a4d1e2 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -16,13 +16,15 @@ package com.android.settings.homepage; +import static android.provider.Settings.ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK; +import static android.provider.Settings.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI; +import static android.provider.Settings.EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY; + import android.animation.LayoutTransition; import android.app.ActivityManager; -import android.app.PendingIntent; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Bundle; import android.text.TextUtils; import android.util.FeatureFlagUtils; @@ -36,16 +38,16 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; -import androidx.window.embedding.SplitController; import com.android.settings.R; import com.android.settings.Settings; +import com.android.settings.SettingsActivity; import com.android.settings.Utils; import com.android.settings.accounts.AvatarViewMixin; -import com.android.settings.core.CategoryMixin; -import com.android.settings.core.FeatureFlags; import com.android.settings.activityembedding.ActivityEmbeddingRulesController; import com.android.settings.activityembedding.ActivityEmbeddingUtils; +import com.android.settings.core.CategoryMixin; +import com.android.settings.core.FeatureFlags; import com.android.settings.homepage.contextualcards.ContextualCardsFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; @@ -64,6 +66,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements // An alias class name of SettingsHomepageActivity. public static final String ALIAS_DEEP_LINK = "com.android.settings.DeepLinkHomepageActivity"; + private static final int DEFAULT_HIGHLIGHT_MENU_KEY = R.string.menu_key_network; private static final long HOMEPAGE_LOADING_TIMEOUT_MS = 300; private View mHomepageView; @@ -120,7 +123,11 @@ public class SettingsHomepageActivity extends FragmentActivity implements showFragment(new ContextualCardsFragment(), R.id.contextual_cards_content); } } - showFragment(new TopLevelSettings(), R.id.main_content); + final Fragment fragment = new TopLevelSettings(); + fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, + getHighlightMenuKey()); + showFragment(fragment, R.id.main_content); + ((FrameLayout) findViewById(R.id.main_content)) .getLayoutTransition().enableTransitionType(LayoutTransition.CHANGING); @@ -132,9 +139,13 @@ public class SettingsHomepageActivity extends FragmentActivity implements protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - // When it's large screen 2-pane and Settings app is in background. Receiving a Intent - // in this Activity will not finish nor onCreate. setIntent here for this case. + // When it's large screen 2-pane and Settings app is in the background, receiving an Intent + // will not recreate this activity. Update the intent for this case. setIntent(intent); + reloadHighlightMenuKey(); + if (isFinishing()) { + return; + } // Launch the intent from deep link for large screen devices. launchDeepLinkIntentToRight(); } @@ -180,12 +191,12 @@ public class SettingsHomepageActivity extends FragmentActivity implements final Intent intent = getIntent(); if (intent == null || !TextUtils.equals(intent.getAction(), - android.provider.Settings.ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK)) { + ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK)) { return; } final String intentUriString = intent.getStringExtra( - android.provider.Settings.EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI); + EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI); if (TextUtils.isEmpty(intentUriString)) { Log.e(TAG, "No EXTRA_SETTINGS_LARGE_SCREEN_DEEP_LINK_INTENT_URI to deep link"); finish(); @@ -236,6 +247,29 @@ public class SettingsHomepageActivity extends FragmentActivity implements startActivity(targetIntent); } + private String getHighlightMenuKey() { + final Intent intent = getIntent(); + if (intent != null && TextUtils.equals(intent.getAction(), + ACTION_SETTINGS_LARGE_SCREEN_DEEP_LINK)) { + final String menuKey = intent.getStringExtra( + EXTRA_SETTINGS_LARGE_SCREEN_HIGHLIGHT_MENU_KEY); + if (!TextUtils.isEmpty(menuKey)) { + return menuKey; + } + } + return getString(DEFAULT_HIGHLIGHT_MENU_KEY); + } + + private void reloadHighlightMenuKey() { + final TopLevelSettings fragment = + (TopLevelSettings) getSupportFragmentManager().findFragmentById(R.id.main_content); + if (fragment != null) { + fragment.getArguments().putString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY, + getHighlightMenuKey()); + fragment.reloadHighlightMenuKey(); + } + } + private void initHomepageContainer() { final View view = findViewById(R.id.homepage_container); // Prevent inner RecyclerView gets focus and invokes scrolling. diff --git a/src/com/android/settings/homepage/TopLevelSettings.java b/src/com/android/settings/homepage/TopLevelSettings.java index 681ea5111c7..a819b870540 100644 --- a/src/com/android/settings/homepage/TopLevelSettings.java +++ b/src/com/android/settings/homepage/TopLevelSettings.java @@ -21,20 +21,27 @@ import static com.android.settingslib.search.SearchIndexable.MOBILE; import android.app.settings.SettingsEnums; import android.content.Context; +import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; import androidx.fragment.app.Fragment; import androidx.preference.Preference; import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; import com.android.settings.R; +import com.android.settings.SettingsActivity; import com.android.settings.Utils; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.support.SupportPreferenceController; +import com.android.settings.widget.HighlightableTopLevelPreferenceAdapter; import com.android.settingslib.core.instrumentation.Instrumentable; import com.android.settingslib.search.SearchIndexable; @@ -43,6 +50,11 @@ public class TopLevelSettings extends DashboardFragment implements PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { private static final String TAG = "TopLevelSettings"; + private static final String SAVED_HIGHLIGHTED_PREF = "highlighted_pref"; + + private HighlightableTopLevelPreferenceAdapter mTopLevelAdapter; + + private String mHighlightedPreferenceKey; public TopLevelSettings() { final Bundle args = new Bundle(); @@ -69,6 +81,7 @@ public class TopLevelSettings extends DashboardFragment implements @Override public void onAttach(Context context) { super.onAttach(context); + HighlightableMenu.fromXml(context, getPreferenceScreenResId()); use(SupportPreferenceController.class).setActivity(getActivity()); } @@ -83,6 +96,12 @@ public class TopLevelSettings extends DashboardFragment implements return this; } + @Override + public boolean onPreferenceTreeClick(Preference preference) { + setHighlightPreferenceKey(preference.getKey()); + return super.onPreferenceTreeClick(preference); + } + @Override public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) { new SubSettingLauncher(getActivity()) @@ -96,6 +115,20 @@ public class TopLevelSettings extends DashboardFragment implements return true; } + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + if (icicle != null) { + mHighlightedPreferenceKey = icicle.getString(SAVED_HIGHLIGHTED_PREF); + } + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putString(SAVED_HIGHLIGHTED_PREF, mHighlightedPreferenceKey); + } + @Override public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { super.onCreatePreferences(savedInstanceState, rootKey); @@ -118,12 +151,78 @@ public class TopLevelSettings extends DashboardFragment implements } } + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + highlightPreferenceIfNeeded(); + } + + @Override + public void highlightPreferenceIfNeeded() { + if (mTopLevelAdapter != null) { + mTopLevelAdapter.requestHighlight(); + } + } + + /** Highlight a preference with specified key */ + public void setHighlightPreferenceKey(String prefKey) { + if (mTopLevelAdapter != null) { + mHighlightedPreferenceKey = prefKey; + mTopLevelAdapter.highlightPreference(prefKey, /* scrollNeeded= */ false); + } + } + + /** Highlight the previous preference */ + public void restorePreviousHighlight() { + if (mTopLevelAdapter != null) { + mTopLevelAdapter.restorePreviousHighlight(); + } + } + @Override protected boolean shouldForceRoundedIcon() { return getContext().getResources() .getBoolean(R.bool.config_force_rounded_icon_TopLevelSettings); } + @Override + protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { + if (!ActivityEmbeddingUtils.isEmbeddingActivityEnabled(getContext())) { + return super.onCreateAdapter(preferenceScreen); + } + + if (TextUtils.isEmpty(mHighlightedPreferenceKey)) { + mHighlightedPreferenceKey = getHighlightPrefKeyFromArguments(); + } + + Log.d(TAG, "onCreateAdapter, pref key: " + mHighlightedPreferenceKey); + mTopLevelAdapter = new HighlightableTopLevelPreferenceAdapter(preferenceScreen, + getListView(), mHighlightedPreferenceKey); + return mTopLevelAdapter; + } + + void reloadHighlightMenuKey() { + if (mTopLevelAdapter == null) { + return; + } + + mHighlightedPreferenceKey = getHighlightPrefKeyFromArguments(); + Log.d(TAG, "reloadHighlightMenuKey, pref key: " + mHighlightedPreferenceKey); + mTopLevelAdapter.highlightPreference(mHighlightedPreferenceKey, /* scrollNeeded= */ true); + } + + private String getHighlightPrefKeyFromArguments() { + final Bundle arguments = getArguments(); + final String menuKey = arguments.getString(SettingsActivity.EXTRA_FRAGMENT_ARG_KEY); + final String prefKey = HighlightableMenu.lookupPreferenceKey(menuKey); + if (TextUtils.isEmpty(prefKey)) { + Log.e(TAG, "Invalid highlight menu key: " + menuKey); + } else { + Log.d(TAG, "Menu key: " + menuKey); + } + return prefKey; + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.top_level_settings) { diff --git a/src/com/android/settings/search/SearchFeatureProviderImpl.java b/src/com/android/settings/search/SearchFeatureProviderImpl.java index 508d37d7e6a..6f909709058 100644 --- a/src/com/android/settings/search/SearchFeatureProviderImpl.java +++ b/src/com/android/settings/search/SearchFeatureProviderImpl.java @@ -50,7 +50,7 @@ public class SearchFeatureProviderImpl implements SearchFeatureProvider { if (isSettingsPackage || isAllowlistedPackage) { return; } - throw new SecurityException("Search result intents must be called with from a " + throw new SecurityException("Search result intents must be called with from an " + "allowlisted package."); } diff --git a/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java new file mode 100644 index 00000000000..eeaad214a21 --- /dev/null +++ b/src/com/android/settings/widget/HighlightableTopLevelPreferenceAdapter.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.util.Log; +import android.util.TypedValue; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceGroupAdapter; +import androidx.preference.PreferenceViewHolder; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.Utils; +import com.android.settings.activityembedding.ActivityEmbeddingUtils; + +/** + * Adapter for highlighting top level preferences + */ +public class HighlightableTopLevelPreferenceAdapter extends PreferenceGroupAdapter { + + private static final String TAG = "HighlightableTopLevelAdapter"; + + static final long DELAY_HIGHLIGHT_DURATION_MILLIS = 100L; + + @VisibleForTesting + final int mHighlightColor; + final int mTitleColorNormal; + final int mTitleColorHighlight; + final int mSummaryColorNormal; + final int mSummaryColorHighlight; + final int mIconColorNormal; + final int mIconColorHighlight; + + private final Context mContext; + private final RecyclerView mRecyclerView; + private final int mNormalBackgroundRes; + private String mHighlightKey; + private String mPreviousHighlightKey; + private int mHighlightPosition = RecyclerView.NO_POSITION; + private boolean mHighlightNeeded; + private boolean mScrolled; + + public HighlightableTopLevelPreferenceAdapter(PreferenceGroup preferenceGroup, + RecyclerView recyclerView, String key) { + super(preferenceGroup); + mRecyclerView = recyclerView; + mHighlightKey = key; + mContext = preferenceGroup.getContext(); + final TypedValue outValue = new TypedValue(); + mContext.getTheme().resolveAttribute(android.R.attr.selectableItemBackground, + outValue, true /* resolveRefs */); + mNormalBackgroundRes = outValue.resourceId; + mHighlightColor = Utils.getColorAttrDefaultColor(mContext, + com.android.internal.R.attr.colorAccentSecondaryVariant); + mTitleColorNormal = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorPrimary); + mTitleColorHighlight = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorPrimaryInverse); + mSummaryColorNormal = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorSecondary); + mSummaryColorHighlight = Utils.getColorAttrDefaultColor(mContext, + android.R.attr.textColorSecondaryInverse); + mIconColorNormal = Utils.getHomepageIconColor(mContext); + mIconColorHighlight = Utils.getHomepageIconColorHighlight(mContext); + } + + @Override + public void onBindViewHolder(PreferenceViewHolder holder, int position) { + super.onBindViewHolder(holder, position); + updateBackground(holder, position); + } + + @VisibleForTesting + void updateBackground(PreferenceViewHolder holder, int position) { + if (!isHighlightNeeded()) { + removeHighlightBackground(holder); + return; + } + + if (position == mHighlightPosition + && mHighlightKey != null + && TextUtils.equals(mHighlightKey, getItem(position).getKey())) { + // This position should be highlighted. + addHighlightBackground(holder); + } else { + removeHighlightBackground(holder); + } + } + + /** + * A function can highlight a specific setting in recycler view. + */ + public void requestHighlight() { + if (mRecyclerView == null || TextUtils.isEmpty(mHighlightKey)) { + return; + } + + if (TextUtils.isEmpty(mHighlightKey)) { + // De-highlight previous preference. + final int previousPosition = mHighlightPosition; + mHighlightPosition = RecyclerView.NO_POSITION; + mScrolled = true; + if (previousPosition >= 0) { + notifyItemChanged(previousPosition); + } + return; + } + + final int position = getPreferenceAdapterPosition(mHighlightKey); + if (position < 0) { + return; + } + + final boolean highlightNeeded = isHighlightNeeded(); + if (highlightNeeded) { + scrollToPositionIfNeeded(position); + } + + // Turn on/off highlight when screen split mode is changed. + if (highlightNeeded != mHighlightNeeded) { + Log.d(TAG, "Highlight change needed: " + highlightNeeded); + mHighlightNeeded = highlightNeeded; + mHighlightPosition = position; + notifyItemChanged(position); + return; + } + + if (position == mHighlightPosition) { + return; + } + + final int previousPosition = mHighlightPosition; + mHighlightPosition = position; + Log.d(TAG, "Request highlight position " + position); + Log.d(TAG, "Is highlight needed: " + highlightNeeded); + if (!highlightNeeded) { + return; + } + + // Highlight preference. + notifyItemChanged(position); + + // De-highlight previous preference. + if (previousPosition >= 0) { + notifyItemChanged(previousPosition); + } + } + + /** + * A function that highlights a setting by specifying a preference key. Usually used whenever a + * preference is clicked. + */ + public void highlightPreference(String key, boolean scrollNeeded) { + mPreviousHighlightKey = mHighlightKey; + mHighlightKey = key; + mScrolled = !scrollNeeded; + requestHighlight(); + } + + /** + * A function that restores the previous highlighted setting. + */ + public void restorePreviousHighlight() { + mHighlightKey = mPreviousHighlightKey; + requestHighlight(); + } + + private void scrollToPositionIfNeeded(int position) { + if (mScrolled || position < 0) { + return; + } + + // Only when the recyclerView is loaded, it can be scrolled + final View view = mRecyclerView.getChildAt(position); + if (view == null) { + mRecyclerView.postDelayed(() -> scrollToPositionIfNeeded(position), + DELAY_HIGHLIGHT_DURATION_MILLIS); + return; + } + + mScrolled = true; + Log.d(TAG, "Scroll to position " + position); + // Scroll to the top to reset the position. + mRecyclerView.nestedScrollBy(0, -mRecyclerView.getHeight()); + + final int scrollY = view.getTop(); + if (scrollY > 0) { + mRecyclerView.nestedScrollBy(0, scrollY); + } + } + + private void addHighlightBackground(PreferenceViewHolder holder) { + final View v = holder.itemView; + v.setBackgroundColor(mHighlightColor); + ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorHighlight); + ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorHighlight); + final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); + if (drawable != null) { + drawable.setTint(mIconColorHighlight); + } + } + + private void removeHighlightBackground(PreferenceViewHolder holder) { + final View v = holder.itemView; + v.setBackgroundResource(mNormalBackgroundRes); + ((TextView) v.findViewById(android.R.id.title)).setTextColor(mTitleColorNormal); + ((TextView) v.findViewById(android.R.id.summary)).setTextColor(mSummaryColorNormal); + final Drawable drawable = ((ImageView) v.findViewById(android.R.id.icon)).getDrawable(); + if (drawable != null) { + drawable.setTint(mIconColorNormal); + } + } + + private boolean isHighlightNeeded() { + return ActivityEmbeddingUtils.isTwoPaneResolution(mContext); + } +} diff --git a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java index a827284a088..cccca9c9ea5 100644 --- a/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AccountDetailDashboardFragmentTest.java @@ -43,7 +43,6 @@ import android.os.UserManager; import androidx.fragment.app.FragmentActivity; import androidx.preference.Preference; -import com.android.internal.logging.nano.MetricsProto; import com.android.settings.dashboard.DashboardFeatureProviderImpl; import com.android.settings.testutils.shadow.ShadowAccountManager; import com.android.settings.testutils.shadow.ShadowUserManager; @@ -151,9 +150,9 @@ public class AccountDetailDashboardFragmentTest { final FragmentActivity activity = Robolectric.setupActivity(FragmentActivity.class); final Preference preference = new Preference(mContext); - dashboardFeatureProvider.bindPreferenceToTileAndGetObservers(activity, - false /* forceRoundedIcon */, MetricsProto.MetricsEvent.DASHBOARD_SUMMARY, - preference, tile, null /* key */, Preference.DEFAULT_ORDER); + dashboardFeatureProvider.bindPreferenceToTileAndGetObservers(activity, mFragment, + false /* forceRoundedIcon */, preference, tile, null /* key */, + Preference.DEFAULT_ORDER); assertThat(preference.getKey()).isEqualTo(tile.getKey(mContext)); preference.performClick(); diff --git a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java index 5d6fdf98f1b..e7c99c873b1 100644 --- a/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/DashboardFeatureProviderImplTest.java @@ -114,6 +114,7 @@ public class DashboardFeatureProviderImplTest { private Bundle mSwitchMetaData; private DashboardFeatureProviderImpl mImpl; private boolean mForceRoundedIcon; + private DashboardFragment mFragment; @Before public void setUp() { @@ -144,6 +145,7 @@ public class DashboardFeatureProviderImplTest { .thenReturn(new ResolveInfo()); mFeatureFactory = FakeFeatureFactory.setupForTest(); mImpl = new DashboardFeatureProviderImpl(mContext); + mFragment = new TestFragment(); } @Test @@ -159,8 +161,8 @@ public class DashboardFeatureProviderImplTest { doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565))) .when(tile).getIcon(any(Context.class)); mActivityInfo.metaData.putString(SettingsActivity.META_DATA_KEY_FRAGMENT_CLASS, "HI"); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.settings_label)); assertThat(preference.getSummary()) @@ -180,8 +182,8 @@ public class DashboardFeatureProviderImplTest { doReturn(Icon.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565))) .when(tile).getIcon(any(Context.class)); final List observers = mImpl.bindPreferenceToTileAndGetObservers( - mActivity, mForceRoundedIcon, MetricsEvent.SETTINGS_GESTURES, preference, tile, - null /* key*/, Preference.DEFAULT_ORDER); + mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key*/, + Preference.DEFAULT_ORDER); assertThat(preference.getTitle()).isEqualTo(mContext.getText(R.string.settings_label)); assertThat(preference.getSummary()) @@ -198,8 +200,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10); final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getFragment()).isNull(); assertThat(preference.getOnPreferenceClickListener()).isNotNull(); @@ -214,8 +216,8 @@ public class DashboardFeatureProviderImplTest { tile.userHandle.add(mock(UserHandle.class)); tile.userHandle.add(mock(UserHandle.class)); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); preference.getOnPreferenceClickListener().onPreferenceClick(null); verify(mActivity).getSupportFragmentManager(); @@ -231,8 +233,8 @@ public class DashboardFeatureProviderImplTest { when(mActivity.getSystemService(Context.USER_SERVICE)) .thenReturn(mUserManager); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); preference.getOnPreferenceClickListener().onPreferenceClick(null); verify(mFeatureFactory.metricsFeatureProvider).logStartedIntent( @@ -250,8 +252,8 @@ public class DashboardFeatureProviderImplTest { tile.userHandle = new ArrayList<>(); tile.userHandle.add(mock(UserHandle.class)); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); preference.getOnPreferenceClickListener().onPreferenceClick(null); verify(mFeatureFactory.metricsFeatureProvider).logStartedIntent( any(Intent.class), @@ -263,8 +265,8 @@ public class DashboardFeatureProviderImplTest { @Test public void bindPreference_nullPreference_shouldIgnore() { final Tile tile = mock(Tile.class); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, null, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + null /* keys */, tile, "123", Preference.DEFAULT_ORDER); verifyZeroInteractions(tile); } @@ -273,8 +275,8 @@ public class DashboardFeatureProviderImplTest { public void bindPreference_withNullKeyNullPriority_shouldGenerateKeyAndPriority() { final Preference preference = new Preference(RuntimeEnvironment.application); final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, null /* key */, Preference.DEFAULT_ORDER); assertThat(preference.getKey()).isNotNull(); @@ -288,9 +290,8 @@ public class DashboardFeatureProviderImplTest { final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, - Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, null /* key */, Preference.DEFAULT_ORDER); assertThat(preference.getSummary()).isNull(); } @@ -304,8 +305,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_SUMMARY_URI, uriString); final List observers = mImpl.bindPreferenceToTileAndGetObservers( - mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, - null /*key */, Preference.DEFAULT_ORDER); + mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */, + Preference.DEFAULT_ORDER); assertThat(preference.getSummary()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY); assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString); @@ -320,8 +321,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString(TileUtils.META_DATA_PREFERENCE_TITLE_URI, uriString); final List observers = mImpl.bindPreferenceToTileAndGetObservers( - mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, - null /*key */, Preference.DEFAULT_ORDER); + mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */, + Preference.DEFAULT_ORDER); assertThat(preference.getTitle()).isEqualTo(ShadowTileUtils.MOCK_SUMMARY); assertThat(observers.get(0).getUri().toString()).isEqualTo(uriString); @@ -336,9 +337,8 @@ public class DashboardFeatureProviderImplTest { final Bundle bundle = new Bundle(); bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, false); ShadowTileUtils.setResultBundle(bundle); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, - Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, null /* key */, Preference.DEFAULT_ORDER); preference.callChangeListener(false); @@ -358,9 +358,8 @@ public class DashboardFeatureProviderImplTest { final Bundle bundle = new Bundle(); bundle.putBoolean(EXTRA_SWITCH_SET_CHECKED_ERROR, true); ShadowTileUtils.setResultBundle(bundle); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, null /*key */, - Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, null /* key */, Preference.DEFAULT_ORDER); preference.callChangeListener(true); @@ -378,8 +377,8 @@ public class DashboardFeatureProviderImplTest { final Tile tile = new ProviderTile(mProviderInfo, CategoryKey.CATEGORY_HOMEPAGE, mSwitchMetaData); final List observers = mImpl.bindPreferenceToTileAndGetObservers( - mActivity, mForceRoundedIcon, MetricsEvent.VIEW_UNKNOWN, preference, tile, - null /*key */, Preference.DEFAULT_ORDER); + mActivity, mFragment, mForceRoundedIcon, preference, tile, null /* key */, + Preference.DEFAULT_ORDER); ShadowTileUtils.setProviderChecked(false); observers.get(0).onDataChanged(); @@ -397,9 +396,8 @@ public class DashboardFeatureProviderImplTest { final Preference preference = new Preference(RuntimeEnvironment.application); mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key"); final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, null /* key */, - Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, null /* key */, Preference.DEFAULT_ORDER); assertThat(preference.getKey()).isEqualTo(tile.getKey(mContext)); } @@ -483,8 +481,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10); final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", baseOrder); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", baseOrder); assertThat(preference.getOrder()).isEqualTo(tile.getOrder() + baseOrder); } @@ -496,8 +494,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, 10); final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); mActivityInfo.metaData.putInt(META_DATA_KEY_ORDER, testOrder); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getOrder()).isEqualTo(testOrder); } @@ -508,8 +506,8 @@ public class DashboardFeatureProviderImplTest { final Tile tile = new ActivityTile(mActivityInfo, CategoryKey.CATEGORY_HOMEPAGE); mActivityInfo.metaData.putString(META_DATA_KEY_ORDER, "hello"); - mImpl.bindPreferenceToTileAndGetObservers(mActivity, mForceRoundedIcon, - MetricsEvent.VIEW_UNKNOWN, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(mActivity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); assertThat(preference.getOrder()).isEqualTo(Preference.DEFAULT_ORDER); } @@ -522,8 +520,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString(META_DATA_PREFERENCE_KEYHINT, "key"); mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction"); tile.userHandle = null; - mImpl.bindPreferenceToTileAndGetObservers(activity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(activity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); preference.performClick(); ShadowActivity shadowActivity = Shadows.shadowOf(activity); @@ -546,8 +544,8 @@ public class DashboardFeatureProviderImplTest { mActivityInfo.metaData.putString("com.android.settings.intent.action", "TestAction"); tile.userHandle = null; - mImpl.bindPreferenceToTileAndGetObservers(activity, mForceRoundedIcon, - MetricsEvent.SETTINGS_GESTURES, preference, tile, "123", Preference.DEFAULT_ORDER); + mImpl.bindPreferenceToTileAndGetObservers(activity, mFragment, mForceRoundedIcon, + preference, tile, "123", Preference.DEFAULT_ORDER); preference.performClick(); final ShadowActivity.IntentForResult launchIntent = @@ -669,4 +667,22 @@ public class DashboardFeatureProviderImplTest { assertThat(argument.getValue().getIdentifier()).isEqualTo(0); verify(mActivity, never()).getSupportFragmentManager(); } + + private static class TestFragment extends DashboardFragment { + + @Override + public int getMetricsCategory() { + return MetricsEvent.SETTINGS_GESTURES; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.gestures; + } + + @Override + protected String getLogTag() { + return "TestFragment"; + } + } } From 72a25f16ee937670b58f969186f26980b83d3e68 Mon Sep 17 00:00:00 2001 From: ykhung Date: Tue, 12 Oct 2021 16:32:18 +0800 Subject: [PATCH 3/4] Add try-catch to avoid IllegalStateException in the data parsing if the BatteryStats service is crashed, we will provide the default instance for all modules in the battery settings, but it will encounter the IllegalStateException if we try to parse its history content (reference: ag/15926630) Bug: 201729939 Test: make SettingsRoboTests Change-Id: Ifebb48551e371e7c4e121969cb92cf95aa026812 --- .../batterytip/detectors/HighUsageDetector.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java index 4b3f2df3a7f..8126dcc2a31 100644 --- a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java @@ -21,6 +21,7 @@ import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME; import android.content.Context; import android.os.BatteryUsageStats; import android.os.UidBatteryConsumer; +import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -41,6 +42,8 @@ import java.util.concurrent.TimeUnit; * {@link BatteryTipDetector} since it need the most up-to-date {@code visibleTips} */ public class HighUsageDetector implements BatteryTipDetector { + private static final String TAG = "HighUsageDetector"; + private BatteryTipPolicy mPolicy; private BatteryUsageStats mBatteryUsageStats; private final BatteryInfo mBatteryInfo; @@ -113,6 +116,10 @@ public class HighUsageDetector implements BatteryTipDetector { @VisibleForTesting void parseBatteryData() { - mBatteryInfo.parseBatteryHistory(mDataParser); + try { + mBatteryInfo.parseBatteryHistory(mDataParser); + } catch (IllegalStateException e) { + Log.e(TAG, "parseBatteryData() failed", e); + } } } From 1770a04730d6db87aa8a70b3b52b6043a01db486 Mon Sep 17 00:00:00 2001 From: ykhung Date: Tue, 12 Oct 2021 19:43:39 +0800 Subject: [PATCH 4/4] Extend the SeekBar left/start padding to avoid touch region too small old padding: https://screenshot.googleplex.com/3t57cJfM2HCd6bc new padding: https://screenshot.googleplex.com/5yuzj5oQvvKvY7W Bug: 202295741 Test: make SettingsRoboTests Change-Id: Ia4453261c22919e325a971237efc79b4887362ec --- res/layout/preference_widget_seekbar_settings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layout/preference_widget_seekbar_settings.xml b/res/layout/preference_widget_seekbar_settings.xml index 29b4106d64a..c4e1506c767 100644 --- a/res/layout/preference_widget_seekbar_settings.xml +++ b/res/layout/preference_widget_seekbar_settings.xml @@ -61,7 +61,7 @@ android:id="@*android:id/seekbar" android:layout_gravity="center_vertical" android:layout_width="match_parent" - android:paddingStart="0dp" + android:paddingStart="12dp" android:paddingEnd="12dp" android:layout_height="48dp"/>