From 87bc65c9e41f45735de578665e33d0a2a65f00b5 Mon Sep 17 00:00:00 2001 From: menghanli Date: Tue, 25 Apr 2023 13:34:51 +0800 Subject: [PATCH 01/13] Fix the gradient drawable orientation is not mirrored for RTL alignment Root cause: The orientation of the gradient drawable follows the view alignment. It should consider the text alignment. Solution: Use getLayoutDirectionFromLocale# to get the text alignment to set the correct orientation of the gradient drawable. Bug: 278950655 Test: Manual test for RTL and LTR language Change-Id: If94af75172ace137ac5168763f4cb47951f9e943 Merged-In: If94af75172ace137ac5168763f4cb47951f9e943 (cherry picked from commit 95fbb04106d084aa8fa5753a7f19971a19693765) --- .../settings/accessibility/PaletteListPreference.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/com/android/settings/accessibility/PaletteListPreference.java b/src/com/android/settings/accessibility/PaletteListPreference.java index 4e10b936485..eec8b5a6cde 100644 --- a/src/com/android/settings/accessibility/PaletteListPreference.java +++ b/src/com/android/settings/accessibility/PaletteListPreference.java @@ -34,6 +34,7 @@ import android.widget.TextView; import androidx.annotation.ColorInt; import androidx.annotation.IntDef; +import androidx.core.text.TextUtilsCompat; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; @@ -49,6 +50,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; +import java.util.Locale; import java.util.stream.Collectors; /** Preference that easier preview by matching name to color. */ @@ -156,10 +158,11 @@ public final class PaletteListPreference extends Preference { mGradientColors.set(Position.END, color); final GradientDrawable gradientDrawable = new GradientDrawable(); + final Locale locale = Locale.getDefault(); final Orientation orientation = - rootView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL - ? Orientation.RIGHT_LEFT - : Orientation.LEFT_RIGHT; + TextUtilsCompat.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL + ? Orientation.RIGHT_LEFT + : Orientation.LEFT_RIGHT; gradientDrawable.setOrientation(orientation); gradientDrawable.setColors(Ints.toArray(mGradientColors), Floats.toArray(mGradientOffsets)); From cd74f76a438dc0f771cac6175ecd7375d9eec668 Mon Sep 17 00:00:00 2001 From: Edgar Wang Date: Thu, 6 Apr 2023 18:00:30 +0800 Subject: [PATCH 02/13] Remove restricted icon in SettingsMainSwitchBar - display disable status directly - click disabled MainSwitchBar will show devicepolicy dialog - make sure BT and location use MainSwitchBar correctly Bug: 260406688 Test: manual Change-Id: I8bb57213eec6dd5e991af580e1a06ebf8a867c28 --- .../settings/bluetooth/BluetoothEnabler.java | 1 - .../location/LocationSwitchBarController.java | 7 ++- .../widget/SettingsMainSwitchBar.java | 32 +++------- .../LocationSwitchBarControllerTest.java | 17 ++++- .../widget/SettingsMainSwitchBarTest.java | 63 +++++++++++++++++++ 5 files changed, 94 insertions(+), 26 deletions(-) create mode 100644 tests/robotests/src/com/android/settings/widget/SettingsMainSwitchBarTest.java diff --git a/src/com/android/settings/bluetooth/BluetoothEnabler.java b/src/com/android/settings/bluetooth/BluetoothEnabler.java index 483e46af595..de49ce1ef52 100644 --- a/src/com/android/settings/bluetooth/BluetoothEnabler.java +++ b/src/com/android/settings/bluetooth/BluetoothEnabler.java @@ -223,7 +223,6 @@ public final class BluetoothEnabler implements SwitchWidgetController.OnSwitchCh mSwitchController.setDisabledByAdmin(admin); if (admin != null) { mSwitchController.setChecked(false); - mSwitchController.setEnabled(false); } return admin != null; } diff --git a/src/com/android/settings/location/LocationSwitchBarController.java b/src/com/android/settings/location/LocationSwitchBarController.java index c84014f04d8..b150b1d93c0 100644 --- a/src/com/android/settings/location/LocationSwitchBarController.java +++ b/src/com/android/settings/location/LocationSwitchBarController.java @@ -15,6 +15,7 @@ package com.android.settings.location; import android.content.Context; import android.os.UserHandle; +import android.os.UserManager; import android.widget.Switch; import com.android.settings.widget.SettingsMainSwitchBar; @@ -75,8 +76,12 @@ public class LocationSwitchBarController implements OnMainSwitchChangeListener, // only, it would be re-enabled again if the switch bar is not disabled. if (!hasBaseUserRestriction && admin != null) { mSwitchBar.setDisabledByAdmin(admin); + } else if (restricted) { + RestrictedLockUtils.EnforcedAdmin enforcedAdmin = RestrictedLockUtils.EnforcedAdmin + .createDefaultEnforcedAdminWithRestriction(UserManager.DISALLOW_SHARE_LOCATION); + mSwitchBar.setDisabledByAdmin(enforcedAdmin); } else { - mSwitchBar.setEnabled(!restricted); + mSwitchBar.setEnabled(true); } if (enabled != mSwitchBar.isChecked()) { diff --git a/src/com/android/settings/widget/SettingsMainSwitchBar.java b/src/com/android/settings/widget/SettingsMainSwitchBar.java index 5ad16d79f99..8b8a9bae494 100644 --- a/src/com/android/settings/widget/SettingsMainSwitchBar.java +++ b/src/com/android/settings/widget/SettingsMainSwitchBar.java @@ -21,14 +21,12 @@ import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.content.Context; import android.util.AttributeSet; import android.view.View; -import android.widget.ImageView; import android.widget.Switch; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.widget.MainSwitchBar; -import com.android.settingslib.widget.R; /** * A {@link MainSwitchBar} with a customized Switch and provides the metrics feature. @@ -47,7 +45,6 @@ public class SettingsMainSwitchBar extends MainSwitchBar { boolean onBeforeCheckedChanged(Switch switchView, boolean isChecked); } - private ImageView mRestrictedIcon; private EnforcedAdmin mEnforcedAdmin; private boolean mDisabledByAdmin; @@ -74,14 +71,6 @@ public class SettingsMainSwitchBar extends MainSwitchBar { mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); addOnSwitchChangeListener((switchView, isChecked) -> logMetrics(isChecked)); - - mRestrictedIcon = findViewById(R.id.restricted_icon); - mRestrictedIcon.setOnClickListener((View v) -> { - if (mDisabledByAdmin) { - RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, mEnforcedAdmin); - onRestrictedIconClick(); - } - }); } /** @@ -95,12 +84,9 @@ public class SettingsMainSwitchBar extends MainSwitchBar { mDisabledByAdmin = true; mTextView.setEnabled(false); mSwitch.setEnabled(false); - mSwitch.setVisibility(View.GONE); - mRestrictedIcon.setVisibility(View.VISIBLE); } else { mDisabledByAdmin = false; mSwitch.setVisibility(View.VISIBLE); - mRestrictedIcon.setVisibility(View.GONE); setEnabled(isEnabled()); } } @@ -120,11 +106,12 @@ public class SettingsMainSwitchBar extends MainSwitchBar { @Override public boolean performClick() { - return getDelegatingView().performClick(); - } + if (mDisabledByAdmin) { + performRestrictedClick(); + return true; + } - protected void onRestrictedIconClick() { - mMetricsFeatureProvider.clicked(mMetricsCategory, "switch_bar|restricted"); + return mSwitch.performClick(); } @Override @@ -157,11 +144,12 @@ public class SettingsMainSwitchBar extends MainSwitchBar { mMetricsCategory = category; } - private View getDelegatingView() { - return mDisabledByAdmin ? mRestrictedIcon : mSwitch; - } - private void logMetrics(boolean isChecked) { mMetricsFeatureProvider.changed(mMetricsCategory, "switch_bar", isChecked ? 1 : 0); } + + private void performRestrictedClick() { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), mEnforcedAdmin); + mMetricsFeatureProvider.clicked(mMetricsCategory, "switch_bar|restricted"); + } } diff --git a/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java index 348f64d975d..50f618f5ffe 100644 --- a/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationSwitchBarControllerTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; +import android.os.UserManager; import android.provider.Settings; import android.widget.Switch; @@ -108,14 +109,26 @@ public class LocationSwitchBarControllerTest { verify(mSwitchBar).setDisabledByAdmin(admin); } + @Test + public void onLocationModeChanged_Restricted_shouldDisableSwitchByAdmin() { + final RestrictedLockUtils.EnforcedAdmin admin = RestrictedLockUtils.EnforcedAdmin + .createDefaultEnforcedAdminWithRestriction(UserManager.DISALLOW_SHARE_LOCATION); + doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); + doReturn(false).when(mEnabler).hasShareLocationRestriction(anyInt()); + + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, true); + + verify(mSwitchBar).setDisabledByAdmin(admin); + } + @Test public void onLocationModeChanged_Restricted_shouldDisableSwitch() { doReturn(null).when(mEnabler).getShareLocationEnforcedAdmin(anyInt()); doReturn(true).when(mEnabler).hasShareLocationRestriction(anyInt()); - mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, true); + mController.onLocationModeChanged(Settings.Secure.LOCATION_MODE_BATTERY_SAVING, false); - verify(mSwitchBar).setEnabled(false); + verify(mSwitchBar).setEnabled(true); } @Test diff --git a/tests/robotests/src/com/android/settings/widget/SettingsMainSwitchBarTest.java b/tests/robotests/src/com/android/settings/widget/SettingsMainSwitchBarTest.java new file mode 100644 index 00000000000..dc69d8e0dfb --- /dev/null +++ b/tests/robotests/src/com/android/settings/widget/SettingsMainSwitchBarTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.widget; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.widget.TextView; + +import com.android.settingslib.R; +import com.android.settingslib.RestrictedLockUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class SettingsMainSwitchBarTest { + + private SettingsMainSwitchBar mMainSwitchBar; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + final Context context = RuntimeEnvironment.application; + mMainSwitchBar = new SettingsMainSwitchBar(context); + } + + @Test + public void disabledByAdmin_shouldBeDisabled() { + mMainSwitchBar.setDisabledByAdmin(new RestrictedLockUtils.EnforcedAdmin()); + + TextView title = (TextView) mMainSwitchBar.findViewById(R.id.switch_text); + assertThat(title.isEnabled()).isFalse(); + assertThat(mMainSwitchBar.getSwitch().isEnabled()).isFalse(); + } + + @Test + public void disabledByAdmin_setNull_shouldBeEnabled() { + mMainSwitchBar.setDisabledByAdmin(null); + + TextView title = (TextView) mMainSwitchBar.findViewById(R.id.switch_text); + assertThat(title.isEnabled()).isTrue(); + assertThat(mMainSwitchBar.getSwitch().isEnabled()).isTrue(); + } +} From 766ec49e8d941fc69a01ae8235c6424d98105c43 Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Thu, 20 Apr 2023 16:07:27 -0400 Subject: [PATCH 03/13] Fix channel panel UI on 3 button nav devices Test: edit panel on 3 button & gesture nav devices Fixes: 278710039 Change-Id: I45b7e7c942578c4f49be8a0d193893cf872ff36e --- res/layout/notification_channel_panel.xml | 1 + .../app/ChannelPanelActivity.java | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/res/layout/notification_channel_panel.xml b/res/layout/notification_channel_panel.xml index 14e10cc94f5..7e944ac7e74 100644 --- a/res/layout/notification_channel_panel.xml +++ b/res/layout/notification_channel_panel.xml @@ -19,6 +19,7 @@ android:layout_height="@dimen/notif_channel_panel_max_height" android:maxHeight="@dimen/notif_channel_panel_max_height" android:background="@drawable/settings_panel_background" + android:fitsSystemWindows="true" android:orientation="vertical" android:layout_width="match_parent"> diff --git a/src/com/android/settings/notification/app/ChannelPanelActivity.java b/src/com/android/settings/notification/app/ChannelPanelActivity.java index 9889183d4d7..dda1c65869c 100644 --- a/src/com/android/settings/notification/app/ChannelPanelActivity.java +++ b/src/com/android/settings/notification/app/ChannelPanelActivity.java @@ -28,10 +28,13 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsControllerCompat; import androidx.fragment.app.FragmentActivity; import androidx.fragment.app.FragmentManager; import com.android.settings.R; +import com.android.settings.Utils; import com.android.settings.core.SubSettingLauncher; import com.android.settingslib.core.lifecycle.HideNonSystemOverlayMixin; @@ -99,7 +102,7 @@ public class ChannelPanelActivity extends FragmentActivity { findViewById(R.id.done).setOnClickListener(v -> finish()); findViewById(R.id.see_more).setOnClickListener(v -> launchFullSettings()); - + setupNavigationBar(); mPanelFragment = callingIntent.hasExtra(Settings.EXTRA_CONVERSATION_ID) ? new ConversationNotificationSettings() : new ChannelNotificationSettings(); @@ -107,4 +110,27 @@ public class ChannelPanelActivity extends FragmentActivity { fragmentManager.beginTransaction().replace( android.R.id.list_container, mPanelFragment).commit(); } + + /** + * Adjust bottom edge and color. + */ + private void setupNavigationBar() { + // Extend the panel all the way to the bottom of the screen, as opposed to sitting on top of + // the navigation bar. + ViewCompat.setOnApplyWindowInsetsListener(getWindow().getDecorView(), + (v, windowInsets) -> { + v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), 0); + return windowInsets; // propagate down to panel layout root element + }); + + // When using 3-button navigation in light mode, the system picks white navigation buttons + // which are not sufficiently contrasted from the panel background. + WindowInsetsControllerCompat windowInsetsController = + ViewCompat.getWindowInsetsController(getWindow().getDecorView()); + + if (windowInsetsController != null) { + boolean forceNavigationButtonsDark = !Utils.isNightMode(this); + windowInsetsController.setAppearanceLightNavigationBars(forceNavigationButtonsDark); + } + } } From 99f1d5448df2d2f28fa9843e5367176d4526f40c Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Fri, 21 Apr 2023 20:10:03 +0000 Subject: [PATCH 04/13] DO NOT MERGE Hide provider from this list if autofill provider Since we are merging the two lists we should hide the provider from this list to avoid duplicates. Test: local & unit Bug: 279205251 Change-Id: I70ec4a4bda13bdcd5fd8f82f6ba6045e94d5daa9 --- ...CredentialManagerPreferenceController.java | 21 +++++++++++++++++++ ...entialManagerPreferenceControllerTest.java | 19 +++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index c627df3e198..62768748650 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -348,6 +348,10 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl Map> groupedInfos = new HashMap<>(); for (CredentialProviderInfo cpi : mServices) { String packageName = cpi.getServiceInfo().packageName; + if (isProviderHiddenBecauseOfAutofill(packageName)) { + continue; + } + if (!groupedInfos.containsKey(packageName)) { groupedInfos.put(packageName, new ArrayList<>()); } @@ -551,6 +555,23 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl return new NewProviderConfirmationDialogFragment(host, packageName, appName); } + /** If the provider is also the autofill provider then hide it. */ + @VisibleForTesting + public boolean isProviderHiddenBecauseOfAutofill(String packageName) { + final String autofillService = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE, + getUser()); + if (autofillService == null || TextUtils.isEmpty(autofillService)) { + return false; + } + if (packageName == null || TextUtils.isEmpty(packageName)) { + return false; + } + + return autofillService.startsWith(packageName); + } + @VisibleForTesting void completeEnableProviderDialogBox( int whichButton, String packageName, boolean setActivityResult) { diff --git a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java index 2a2aaee9494..3145cce1ac6 100644 --- a/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/credentials/CredentialManagerPreferenceControllerTest.java @@ -122,6 +122,25 @@ public class CredentialManagerPreferenceControllerTest { assertThat(controller.getAvailabilityStatus()).isEqualTo(AVAILABLE); } + @Test + public void verifyHiddenIfAutofillSelectedProvider() { + CredentialManagerPreferenceController controller = + createControllerWithServices(Collections.emptyList()); + + // Set the autofill provider. + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.AUTOFILL_SERVICE, "com.example.test/AutofillClass", + UserHandle.myUserId()); + + // Verify the error cases + assertThat(controller.isProviderHiddenBecauseOfAutofill(null)).isFalse(); + assertThat(controller.isProviderHiddenBecauseOfAutofill("")).isFalse(); + assertThat(controller.isProviderHiddenBecauseOfAutofill("test")).isFalse(); + + // Verify the example. + assertThat(controller.isProviderHiddenBecauseOfAutofill("com.example.test")).isTrue(); + } + @Test public void displayPreference_noServices_noPreferencesAdded_useAutofillUri() { Settings.Secure.putStringForUser( From 688b023d09a649bdf176f3cf00b781fe5f372634 Mon Sep 17 00:00:00 2001 From: Becca Hughes Date: Thu, 20 Apr 2023 16:04:22 +0000 Subject: [PATCH 05/13] DO NOT MERGE Listen for updates to autofill/credman provider The provider for both of these are in Settings and this will refresh the CredMan section of the page if either of these change. Test: make Bug: 278919696 Change-Id: Ia82c3533540e52aaa26a68e0a27f0e496728b87f --- ...CredentialManagerPreferenceController.java | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java index c627df3e198..58c5a2cbe0d 100644 --- a/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java +++ b/src/com/android/settings/applications/credentials/CredentialManagerPreferenceController.java @@ -23,6 +23,7 @@ import android.annotation.Nullable; import android.app.Activity; import android.app.Dialog; import android.content.ComponentName; +import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -33,9 +34,11 @@ import android.content.res.Resources; import android.credentials.CredentialManager; import android.credentials.CredentialProviderInfo; import android.credentials.SetEnabledProvidersException; +import android.database.ContentObserver; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.OutcomeReceiver; import android.os.UserHandle; import android.provider.DeviceConfig; @@ -59,10 +62,12 @@ import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.content.PackageMonitor; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.BasePreferenceController; import com.android.settings.dashboard.DashboardFragment; +import com.android.settingslib.utils.ThreadUtils; import java.util.ArrayList; import java.util.HashMap; @@ -89,10 +94,12 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl private final Executor mExecutor; private final Map mPrefs = new HashMap<>(); // key is package name private final List mPendingServiceInfos = new ArrayList<>(); + private final Handler mHandler = new Handler(); private @Nullable FragmentManager mFragmentManager = null; private @Nullable Delegate mDelegate = null; private @Nullable String mFlagOverrideForTest = null; + private @Nullable PreferenceScreen mPreferenceScreen = null; public CredentialManagerPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); @@ -103,6 +110,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl mExecutor = ContextCompat.getMainExecutor(mContext); mCredentialManager = getCredentialManager(context, preferenceKey.equals("credentials_test")); + new SettingContentObserver(mHandler).register(context.getContentResolver()); } private @Nullable CredentialManager getCredentialManager(Context context, boolean isTest) { @@ -241,6 +249,25 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl update(); } + private void update() { + if (mCredentialManager == null) { + return; + } + + setAvailableServices( + mCredentialManager.getCredentialProviderServices( + getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY), + null); + } + + private void updateFromExternal() { + update(); + + if (mPreferenceScreen != null) { + displayPreference(mPreferenceScreen); + } + } + @VisibleForTesting void setAvailableServices( List availableServices, @@ -276,6 +303,7 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl // Since the UI is being cleared, clear any refs. mPrefs.clear(); + mPreferenceScreen = screen; PreferenceGroup group = screen.findPreference(getPreferenceKey()); Context context = screen.getContext(); mPrefs.putAll(buildPreferenceList(context, group)); @@ -655,20 +683,6 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl } }; - /** - * Update the data in this UI. - */ - private void update() { - if (mCredentialManager == null) { - return; - } - - setAvailableServices( - mCredentialManager.getCredentialProviderServices( - getUser(), CredentialManager.PROVIDER_FILTER_USER_PROVIDERS_ONLY), - null); - } - /** Dialog fragment parent class. */ private abstract static class CredentialManagerDialogFragment extends DialogFragment implements DialogInterface.OnClickListener { @@ -790,4 +804,28 @@ public class CredentialManagerPreferenceController extends BasePreferenceControl getDialogHost().onDialogClick(which); } } + + /** Updates the list if setting content changes. */ + private final class SettingContentObserver extends ContentObserver { + + private final Uri mAutofillService = + Settings.Secure.getUriFor(Settings.Secure.AUTOFILL_SERVICE); + + private final Uri mCredentialService = + Settings.Secure.getUriFor(Settings.Secure.CREDENTIAL_SERVICE); + + public SettingContentObserver(Handler handler) { + super(handler); + } + + public void register(ContentResolver contentResolver) { + contentResolver.registerContentObserver(mAutofillService, false, this, getUser()); + contentResolver.registerContentObserver(mCredentialService, false, this, getUser()); + } + + @Override + public void onChange(boolean selfChange, Uri uri) { + updateFromExternal(); + } + } } From 54d1cfdae0cefe758b80f06017dd8f0f847ff7c7 Mon Sep 17 00:00:00 2001 From: Grace Cheng Date: Thu, 23 Feb 2023 23:05:36 +0000 Subject: [PATCH 06/13] =?UTF-8?q?Fix=20flicker=20of=20=E2=80=9Ctouch=20to?= =?UTF-8?q?=20unlock=20anytime=E2=80=9D=20toggle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes flicker of toggle by setting not visible when launching confirm pin/pattern/password or enrollment prior to displaying FingerprintSettings. Also cleans up FingerprintSettings and security_settings_fingerprint.xml to better separate SFPS and non-SFPS logic and rename methods for clarity Test: (manual) navigate to sfps fp settings and observe toggle doesn’t flicker Test: make RunSettingsRoboTests ROBOTEST_FILTER=FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=FingerprintSettingsUnlockCategoryControllerTest Fixes: 263843645 Change-Id: Ide3a666fc31b926ac14645dc22d090f77d81f901 --- res/xml/security_settings_fingerprint.xml | 17 ++- .../fingerprint/FingerprintSettings.java | 133 ++++++++++------- ...ireScreenOnToAuthPreferenceController.java | 2 +- .../FingerprintUnlockCategoryController.java | 60 ++++++++ ...sEnrolledCategoryPreferenceController.java | 59 ++++++++ ...creenOnToAuthPreferenceControllerTest.java | 10 +- ...tSettingsUnlockCategoryControllerTest.java | 135 ++++++++++++++++++ 7 files changed, 357 insertions(+), 59 deletions(-) create mode 100644 src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java create mode 100644 src/com/android/settings/biometrics/fingerprint/FingerprintsEnrolledCategoryPreferenceController.java create mode 100644 tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsUnlockCategoryControllerTest.java diff --git a/res/xml/security_settings_fingerprint.xml b/res/xml/security_settings_fingerprint.xml index a4ce545a7ff..0156ef97ac6 100644 --- a/res/xml/security_settings_fingerprint.xml +++ b/res/xml/security_settings_fingerprint.xml @@ -19,10 +19,21 @@ xmlns:settings="http://schemas.android.com/apk/res-auto" android:title="@string/security_settings_fingerprint_preference_title"> + + + + + + settings:controller="com.android.settings.biometrics.fingerprint.FingerprintUnlockCategoryController" + settings:isPreferenceVisible="false"> + + + diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java index 40a719a35d4..a5e5f579c02 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettings.java @@ -171,8 +171,12 @@ public class FingerprintSettings extends SubSettings { private static final String KEY_IS_ENROLLING = "is_enrolled"; private static final String KEY_REQUIRE_SCREEN_ON_TO_AUTH = "security_settings_require_screen_on_to_auth"; + private static final String KEY_FINGERPRINTS_ENROLLED_CATEGORY = + "security_settings_fingerprints_enrolled"; private static final String KEY_FINGERPRINT_UNLOCK_CATEGORY = "security_settings_fingerprint_unlock_category"; + private static final String KEY_FINGERPRINT_UNLOCK_FOOTER = + "security_settings_fingerprint_footer"; private static final int MSG_REFRESH_FINGERPRINT_TEMPLATES = 1000; private static final int MSG_FINGER_AUTH_SUCCESS = 1001; @@ -189,10 +193,15 @@ public class FingerprintSettings extends SubSettings { protected static final boolean DEBUG = false; private List mControllers; + private FingerprintUnlockCategoryController + mFingerprintUnlockCategoryPreferenceController; private FingerprintSettingsRequireScreenOnToAuthPreferenceController mRequireScreenOnToAuthPreferenceController; + private Preference mAddFingerprintPreference; private RestrictedSwitchPreference mRequireScreenOnToAuthPreference; + private PreferenceCategory mFingerprintsEnrolledCategory; private PreferenceCategory mFingerprintUnlockCategory; + private PreferenceCategory mFingerprintUnlockFooter; private FingerprintManager mFingerprintManager; private FingerprintUpdater mFingerprintUpdater; @@ -259,9 +268,6 @@ public class FingerprintSettings extends SubSettings { } private void updateDialog() { - if (isSfps()) { - setRequireScreenOnToAuthVisibility(); - } RenameDialog renameDialog = (RenameDialog) getFragmentManager(). findFragmentByTag(RenameDialog.class.getName()); if (renameDialog != null) { @@ -277,7 +283,8 @@ public class FingerprintSettings extends SubSettings { case MSG_REFRESH_FINGERPRINT_TEMPLATES: removeFingerprintPreference(msg.arg1); updateAddPreference(); - retryFingerprint(); + updateFingerprintUnlockCategoryVisibility(); + updatePreferences(); break; case MSG_FINGER_AUTH_SUCCESS: highlightFingerprintItem(msg.arg1); @@ -423,6 +430,9 @@ public class FingerprintSettings extends SubSettings { addFirstFingerprint(null); } } + final PreferenceScreen root = getPreferenceScreen(); + root.removeAll(); + addPreferencesFromResource(getPreferenceScreenResId()); updateFooterColumns(activity); } @@ -512,48 +522,33 @@ public class FingerprintSettings extends SubSettings { */ private PreferenceScreen createPreferenceHierarchy() { PreferenceScreen root = getPreferenceScreen(); - if (root != null) { - root.removeAll(); - } - final String fpPrefKey = addFingerprintItemPreferences(root); - if (isSfps()) { - scrollToPreference(fpPrefKey); - } - addPreferencesFromResource(getPreferenceScreenResId()); - mRequireScreenOnToAuthPreference = findPreference(KEY_REQUIRE_SCREEN_ON_TO_AUTH); - mFingerprintUnlockCategory = findPreference(KEY_FINGERPRINT_UNLOCK_CATEGORY); - for (AbstractPreferenceController controller : mControllers) { - ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId); - } - mRequireScreenOnToAuthPreference.setChecked( - mRequireScreenOnToAuthPreferenceController.isChecked()); - mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( - (preference, newValue) -> { - boolean isChecked = ((SwitchPreference) preference).isChecked(); - mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked); - return true; - }); - mFingerprintUnlockCategory.setVisible(false); - if (isSfps()) { - setRequireScreenOnToAuthVisibility(); - } + addFingerprintPreferences(root); setPreferenceScreen(root); return root; } - private void setRequireScreenOnToAuthVisibility() { - int fingerprintsEnrolled = mFingerprintManager.getEnrolledFingerprints(mUserId).size(); - final boolean removalInProgress = mRemovalSidecar.inProgress(); - // Removing last remaining fingerprint - if (fingerprintsEnrolled == 0 && removalInProgress) { - mFingerprintUnlockCategory.setVisible(false); - } else { - mFingerprintUnlockCategory.setVisible(true); + private void addFingerprintPreferences(PreferenceGroup root) { + final String fpPrefKey = addFingerprintItemPreferences(root); + if (isSfps()) { + scrollToPreference(fpPrefKey); + addFingerprintUnlockCategory(); } + for (AbstractPreferenceController controller : mControllers) { + if (controller instanceof FingerprintSettingsPreferenceController) { + ((FingerprintSettingsPreferenceController) controller).setUserId(mUserId); + } else if (controller instanceof FingerprintUnlockCategoryController) { + ((FingerprintUnlockCategoryController) controller).setUserId(mUserId); + } + } + createFooterPreference(root); } private String addFingerprintItemPreferences(PreferenceGroup root) { - root.removeAll(); + mFingerprintsEnrolledCategory = findPreference(KEY_FINGERPRINTS_ENROLLED_CATEGORY); + if (mFingerprintsEnrolledCategory != null) { + mFingerprintsEnrolledCategory.removeAll(); + } + String keyToReturn = KEY_FINGERPRINT_ADD; final List items = mFingerprintManager.getEnrolledFingerprints(mUserId); final int fingerprintCount = items.size(); @@ -576,22 +571,46 @@ public class FingerprintSettings extends SubSettings { if (mFingerprintsRenaming.containsKey(item.getBiometricId())) { pref.setTitle(mFingerprintsRenaming.get(item.getBiometricId())); } - root.addPreference(pref); + mFingerprintsEnrolledCategory.addPreference(pref); pref.setOnPreferenceChangeListener(this); } - - Preference addPreference = new Preference(root.getContext()); - addPreference.setKey(KEY_FINGERPRINT_ADD); - addPreference.setTitle(R.string.fingerprint_add_title); - addPreference.setIcon(R.drawable.ic_add_24dp); - root.addPreference(addPreference); - addPreference.setOnPreferenceChangeListener(this); - updateAddPreference(); - createFooterPreference(root); - + mAddFingerprintPreference = findPreference(KEY_FINGERPRINT_ADD); + setupAddFingerprintPreference(); return keyToReturn; } + private void setupAddFingerprintPreference() { + mAddFingerprintPreference.setOnPreferenceChangeListener(this); + updateAddPreference(); + } + + private void addFingerprintUnlockCategory() { + mFingerprintUnlockCategory = findPreference(KEY_FINGERPRINT_UNLOCK_CATEGORY); + setupFingerprintUnlockCategoryPreferences(); + updateFingerprintUnlockCategoryVisibility(); + } + + private void updateFingerprintUnlockCategoryVisibility() { + final boolean mFingerprintUnlockCategoryAvailable = + mFingerprintUnlockCategoryPreferenceController.isAvailable(); + if (mFingerprintUnlockCategory.isVisible() != mFingerprintUnlockCategoryAvailable) { + mFingerprintUnlockCategory.setVisible( + mFingerprintUnlockCategoryAvailable); + } + } + + private void setupFingerprintUnlockCategoryPreferences() { + mRequireScreenOnToAuthPreference = findPreference(KEY_REQUIRE_SCREEN_ON_TO_AUTH); + mRequireScreenOnToAuthPreference.setChecked( + mRequireScreenOnToAuthPreferenceController.isChecked()); + mRequireScreenOnToAuthPreference.setOnPreferenceChangeListener( + (preference, newValue) -> { + final boolean isChecked = ((SwitchPreference) preference).isChecked(); + mRequireScreenOnToAuthPreferenceController.setChecked(!isChecked); + return true; + }); + } + private void updateAddPreference() { if (getActivity() == null) { return; // Activity went away @@ -612,8 +631,8 @@ public class FingerprintSettings extends SubSettings { final boolean removalInProgress = mRemovalSidecar.inProgress(); CharSequence maxSummary = tooMany ? getContext().getString(R.string.fingerprint_add_max, max) : ""; - addPreference.setSummary(maxSummary); - addPreference.setEnabled(!tooMany && !removalInProgress && mToken != null); + mAddFingerprintPreference.setSummary(maxSummary); + mAddFingerprintPreference.setEnabled(!tooMany && !removalInProgress && mToken != null); } private void createFooterPreference(PreferenceGroup root) { @@ -621,6 +640,10 @@ public class FingerprintSettings extends SubSettings { if (context == null) { return; } + mFingerprintUnlockFooter = findPreference(KEY_FINGERPRINT_UNLOCK_FOOTER); + if (mFingerprintUnlockFooter != null) { + mFingerprintUnlockFooter.removeAll(); + } for (int i = 0; i < mFooterColumns.size(); ++i) { final FooterColumn column = mFooterColumns.get(i); final FooterPreference footer = new FooterPreference.Builder(context) @@ -634,7 +657,7 @@ public class FingerprintSettings extends SubSettings { footer.setLearnMoreText(column.mLearnMoreOverrideText); } } - root.addPreference(footer); + mFingerprintUnlockFooter.addPreference(footer); } } @@ -815,11 +838,17 @@ public class FingerprintSettings extends SubSettings { private List buildPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); + mFingerprintUnlockCategoryPreferenceController = + new FingerprintUnlockCategoryController( + context, + KEY_FINGERPRINT_UNLOCK_CATEGORY + ); mRequireScreenOnToAuthPreferenceController = new FingerprintSettingsRequireScreenOnToAuthPreferenceController( context, KEY_REQUIRE_SCREEN_ON_TO_AUTH ); + controllers.add(mFingerprintUnlockCategoryPreferenceController); controllers.add(mRequireScreenOnToAuthPreferenceController); return controllers; } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java index 52e6d135abc..87396dd91d5 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceController.java @@ -94,7 +94,7 @@ public class FingerprintSettingsRequireScreenOnToAuthPreferenceController && mFingerprintManager.isHardwareDetected() && mFingerprintManager.isPowerbuttonFps()) { return mFingerprintManager.hasEnrolledTemplates(getUserId()) - ? AVAILABLE : DISABLED_DEPENDENT_SETTING; + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; } else { return UNSUPPORTED_ON_DEVICE; } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java new file mode 100644 index 00000000000..674a0dfa758 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintUnlockCategoryController.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.biometrics.fingerprint; + +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; + +/** + * Preference controller that controls the fingerprint unlock features to be shown / be hidden. + */ +public class FingerprintUnlockCategoryController extends BasePreferenceController { + private static final String TAG = "FingerprintUnlockCategoryPreferenceController"; + + private int mUserId; + @VisibleForTesting + protected FingerprintManager mFingerprintManager; + + public FingerprintUnlockCategoryController(Context context, String key) { + super(context, key); + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); + } + + @Override + public int getAvailabilityStatus() { + if (mFingerprintManager != null + && mFingerprintManager.isHardwareDetected() + && mFingerprintManager.isPowerbuttonFps()) { + return mFingerprintManager.hasEnrolledTemplates(getUserId()) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + public void setUserId(int userId) { + mUserId = userId; + } + + protected int getUserId() { + return mUserId; + } +} diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintsEnrolledCategoryPreferenceController.java b/src/com/android/settings/biometrics/fingerprint/FingerprintsEnrolledCategoryPreferenceController.java new file mode 100644 index 00000000000..53d53797515 --- /dev/null +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintsEnrolledCategoryPreferenceController.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.biometrics.fingerprint; + +import android.content.Context; +import android.hardware.fingerprint.FingerprintManager; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.Utils; +import com.android.settings.core.BasePreferenceController; + +/** + * Preference controller that controls the enrolled fingerprints to be shown / be hidden. + */ +public class FingerprintsEnrolledCategoryPreferenceController extends BasePreferenceController { + private static final String TAG = "FingerprintsEnrolledCategoryPreferenceController"; + + private int mUserId; + @VisibleForTesting + protected FingerprintManager mFingerprintManager; + + public FingerprintsEnrolledCategoryPreferenceController(Context context, String key) { + super(context, key); + mFingerprintManager = Utils.getFingerprintManagerOrNull(context); + } + + @Override + public int getAvailabilityStatus() { + if (mFingerprintManager != null + && mFingerprintManager.isHardwareDetected()) { + return mFingerprintManager.hasEnrolledTemplates(getUserId()) + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } else { + return UNSUPPORTED_ON_DEVICE; + } + } + + public void setUserId(int userId) { + mUserId = userId; + } + + protected int getUserId() { + return mUserId; + } +} diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java index b6df62eeff7..ebfa6d5d62d 100644 --- a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest.java @@ -17,7 +17,7 @@ package com.android.settings.biometrics.fingerprint; import static com.android.settings.core.BasePreferenceController.AVAILABLE; -import static com.android.settings.core.BasePreferenceController.DISABLED_DEPENDENT_SETTING; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; import static com.google.common.truth.Truth.assertThat; @@ -103,15 +103,15 @@ public class FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest { } @Test - public void isAvailable_isDisabled_whenSfpsHardwareDetected_AndNoEnrolledFingerprints() { + public void isUnavailable_isDisabled_whenSfpsHardwareDetected_AndNoEnrolledFingerprints() { assertThat(mController.isAvailable()).isEqualTo(false); assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); configure_hardwareDetected_isSfps_hasEnrolledTemplates( true /* isHardwareDetected */, true /* isPowerbuttonFps */, false /* hasEnrolledTemplates */); - assertThat(mController.isAvailable()).isEqualTo(true); - assertThat(mController.getAvailabilityStatus()).isEqualTo(DISABLED_DEPENDENT_SETTING); + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); } @Test @@ -122,7 +122,7 @@ public class FingerprintSettingsRequireScreenOnToAuthPreferenceControllerTest { false /* isHardwareDetected */, true /* isPowerbuttonFps */, true /* hasEnrolledTemplates */); - assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.isAvailable()).isEqualTo(false); assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); } diff --git a/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsUnlockCategoryControllerTest.java b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsUnlockCategoryControllerTest.java new file mode 100644 index 00000000000..7b6a70ed7ef --- /dev/null +++ b/tests/robotests/src/com/android/settings/biometrics/fingerprint/FingerprintSettingsUnlockCategoryControllerTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.biometrics.fingerprint; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.hardware.fingerprint.FingerprintManager; + +import com.android.settings.testutils.shadow.ShadowUtils; +import com.android.settingslib.RestrictedSwitchPreference; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowUtils.class}) +public class FingerprintSettingsUnlockCategoryControllerTest { + + @Mock + private FingerprintManager mFingerprintManager; + @Mock + private PackageManager mPackageManager; + @Mock + private RestrictedSwitchPreference mPreference; + + private Context mContext; + private FingerprintSettingsRequireScreenOnToAuthPreferenceController mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + when(mContext.getSystemService(eq(Context.FINGERPRINT_SERVICE))).thenReturn( + mFingerprintManager); + when(mContext.getPackageManager()).thenReturn(mPackageManager); + + mController = spy(new FingerprintSettingsRequireScreenOnToAuthPreferenceController(mContext, + "test_key")); + ReflectionHelpers.setField(mController, "mFingerprintManager", mFingerprintManager); + } + + @After + public void tearDown() { + ShadowUtils.reset(); + } + + @Test + public void isAvailable_isEnabled_whenSfpsHardwareDetected_AndHasEnrolledFingerprints() { + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + true /* isHardwareDetected */, + true /* isPowerbuttonFps */, + true /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isEqualTo(true); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + public void isUnavailable_isDisabled_whenSfpsHardwareDetected_AndNoEnrolledFingerprints() { + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + true /* isHardwareDetected */, + true /* isPowerbuttonFps */, + false /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void isUnavailable_whenHardwareNotDetected() { + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + false /* isHardwareDetected */, + true /* isPowerbuttonFps */, + true /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isEqualTo(false); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void isUnavailable_onNonSfpsDevice() { + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + configure_hardwareDetected_isSfps_hasEnrolledTemplates( + true /* isHardwareDetected */, + false /* isPowerbuttonFps */, + true /* hasEnrolledTemplates */); + assertThat(mController.isAvailable()).isFalse(); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + private void configure_hardwareDetected_isSfps_hasEnrolledTemplates( + boolean isHardwareDetected, boolean isPowerbuttonFps, boolean hasEnrolledTemplates) { + when(mFingerprintManager.isHardwareDetected()).thenReturn(isHardwareDetected); + when(mFingerprintManager.isPowerbuttonFps()).thenReturn(isPowerbuttonFps); + when(mFingerprintManager.hasEnrolledTemplates(anyInt())).thenReturn(hasEnrolledTemplates); + } + +} From fa5ea719de2816e2dedc91159d286ab3cac96c40 Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Thu, 27 Apr 2023 03:46:50 +0000 Subject: [PATCH 07/13] Fix AppEntry.extraInfo ClassCastException AppEntry's extraInfo is an Object class, force cast it to Boolean may cause ClassCastException. Bug: 279639370 Bug: 279333773 Test: local test Change-Id: Ie135d9a49fdac6623f6b29a13ddf83896a911712 Merged-In: Iccbe9b669011c5061f1c8b3998632f05fbd8a02f --- src/com/android/settings/applications/AppStateLocaleBridge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/android/settings/applications/AppStateLocaleBridge.java b/src/com/android/settings/applications/AppStateLocaleBridge.java index 2fea004778d..a50d61724d8 100644 --- a/src/com/android/settings/applications/AppStateLocaleBridge.java +++ b/src/com/android/settings/applications/AppStateLocaleBridge.java @@ -83,7 +83,7 @@ public class AppStateLocaleBridge extends AppStateBaseBridge { Log.d(TAG, "[" + entry.info.packageName + "]" + " has No extra info."); return false; } - return (Boolean) entry.extraInfo; + return entry.extraInfo == Boolean.TRUE; } }; From 1aaad53e092c7784b4726523dc2bd495a9a7cae5 Mon Sep 17 00:00:00 2001 From: Wesley Wang Date: Thu, 27 Apr 2023 13:36:45 +0800 Subject: [PATCH 08/13] Update battery saver footer string Bug: 279825606 Test: make SettingsRoboTests Change-Id: I8fb05d9db8037ab6b3c8be57b2891c8f022f1440 --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index b38552f61f0..d32c656369e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5164,7 +5164,7 @@ Battery Saver on - Learn more about Battery saver + Learn more about Battery Saver Some features may be limited From 8c50edd1e71b6a89fb41f0f4bf8bca84e649780e Mon Sep 17 00:00:00 2001 From: Weng Su Date: Wed, 26 Apr 2023 18:40:58 +0800 Subject: [PATCH 09/13] Fixed speed selection lost when changing password in Wi-Fi hotspot settings - Use current SoftApConfiguration to build new SoftApConfiguration to keep unchanged properties Bug: 279717060 Test: manual test atest -c WifiTetherSettingsTest Change-Id: I3ee7960b13b339445cc79c3d440f5c56c78c015f --- .../wifi/tether/WifiTetherSettings.java | 17 ++++++---- .../wifi/tether/WifiTetherSettingsTest.java | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/com/android/settings/wifi/tether/WifiTetherSettings.java b/src/com/android/settings/wifi/tether/WifiTetherSettings.java index 2774be66585..4ce59b9b53f 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSettings.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSettings.java @@ -80,11 +80,14 @@ public class WifiTetherSettings extends RestrictedDashboardFragment @VisibleForTesting SettingsMainSwitchBar mMainSwitchBar; private WifiTetherSwitchBarController mSwitchBarController; - private WifiTetherSSIDPreferenceController mSSIDPreferenceController; - private WifiTetherPasswordPreferenceController mPasswordPreferenceController; + @VisibleForTesting + WifiTetherSSIDPreferenceController mSSIDPreferenceController; + @VisibleForTesting + WifiTetherPasswordPreferenceController mPasswordPreferenceController; private WifiTetherSecurityPreferenceController mSecurityPreferenceController; private WifiTetherMaximizeCompatibilityPreferenceController mMaxCompatibilityPrefController; - private WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController; + @VisibleForTesting + WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController; private boolean mUnavailable; private WifiRestriction mWifiRestriction; @@ -269,10 +272,12 @@ public class WifiTetherSettings extends RestrictedDashboardFragment setLoading(restarting, false); } - private SoftApConfiguration buildNewConfig() { - SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(); + @VisibleForTesting + SoftApConfiguration buildNewConfig() { + SoftApConfiguration currentConfig = mWifiTetherViewModel.getSoftApConfiguration(); + SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder(currentConfig); int securityType = (mWifiTetherViewModel.isSpeedFeatureAvailable()) - ? mWifiTetherViewModel.getSoftApConfiguration().getSecurityType() + ? currentConfig.getSecurityType() : mSecurityPreferenceController.getSecurityType(); configBuilder.setSsid(mSSIDPreferenceController.getSSID()); if (securityType != SoftApConfiguration.SECURITY_TYPE_OPEN) { diff --git a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java index 0a54c888d58..fe663abbc28 100644 --- a/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java +++ b/tests/robotests/src/com/android/settings/wifi/tether/WifiTetherSettingsTest.java @@ -16,10 +16,12 @@ package com.android.settings.wifi.tether; +import static android.net.wifi.SoftApConfiguration.SECURITY_TYPE_WPA3_SAE; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static com.android.settings.wifi.WifiUtils.setCanShowWifiHotspotCached; +import static com.android.settings.wifi.repository.WifiHotspotRepository.BAND_2GHZ_5GHZ_6GHZ; import static com.android.settings.wifi.tether.WifiTetherSettings.KEY_WIFI_HOTSPOT_SECURITY; import static com.android.settings.wifi.tether.WifiTetherSettings.KEY_WIFI_HOTSPOT_SPEED; @@ -41,6 +43,7 @@ import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.TetheringManager; +import android.net.wifi.SoftApConfiguration; import android.net.wifi.WifiManager; import android.os.Bundle; import android.os.UserManager; @@ -84,6 +87,8 @@ public class WifiTetherSettingsTest { private static final int XML_RES = R.xml.wifi_tether_settings; private static final String[] WIFI_REGEXS = {"wifi_regexs"}; + private static final String SSID = "ssid"; + private static final String PASSWORD = "password"; @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @@ -117,6 +122,12 @@ public class WifiTetherSettingsTest { private LiveData mSpeedSummary; @Mock private SettingsMainSwitchBar mMainSwitchBar; + @Mock + private WifiTetherSSIDPreferenceController mSSIDPreferenceController; + @Mock + private WifiTetherPasswordPreferenceController mPasswordPreferenceController; + @Mock + private WifiTetherAutoOffPreferenceController mWifiTetherAutoOffPreferenceController; private WifiTetherSettings mSettings; @@ -143,6 +154,12 @@ public class WifiTetherSettingsTest { mSettings = spy(new WifiTetherSettings(mWifiRestriction)); mSettings.mMainSwitchBar = mMainSwitchBar; + mSettings.mSSIDPreferenceController = mSSIDPreferenceController; + when(mSSIDPreferenceController.getSSID()).thenReturn(SSID); + mSettings.mPasswordPreferenceController = mPasswordPreferenceController; + when(mPasswordPreferenceController.getPasswordValidated(anyInt())).thenReturn(PASSWORD); + mSettings.mWifiTetherAutoOffPreferenceController = mWifiTetherAutoOffPreferenceController; + when(mWifiTetherAutoOffPreferenceController.isEnabled()).thenReturn(true); mSettings.mWifiTetherViewModel = mWifiTetherViewModel; when(mSettings.findPreference(KEY_WIFI_HOTSPOT_SECURITY)).thenReturn(mWifiHotspotSecurity); when(mSettings.findPreference(KEY_WIFI_HOTSPOT_SPEED)).thenReturn(mWifiHotspotSpeed); @@ -326,6 +343,22 @@ public class WifiTetherSettingsTest { verify(mSettings).setLoading(true, false); } + @Test + public void buildNewConfig_speedFeatureIsAvailableAndPasswordChanged_bandShouldNotBeLost() { + String newPassword = "new" + PASSWORD; + SoftApConfiguration currentConfig = new SoftApConfiguration.Builder() + .setPassphrase(PASSWORD, SECURITY_TYPE_WPA3_SAE) + .setBand(BAND_2GHZ_5GHZ_6GHZ) + .build(); + when(mWifiTetherViewModel.getSoftApConfiguration()).thenReturn(currentConfig); + when(mWifiTetherViewModel.isSpeedFeatureAvailable()).thenReturn(true); + when(mPasswordPreferenceController.getPasswordValidated(anyInt())).thenReturn(newPassword); + + SoftApConfiguration newConfig = mSettings.buildNewConfig(); + + assertThat(newConfig.getBand()).isEqualTo(currentConfig.getBand()); + } + @Test public void onRestartingChanged_restartingFalse_setLoadingFalse() { doNothing().when(mSettings).setLoading(anyBoolean(), anyBoolean()); From fa5f8d3b0a39296e2eb096fac111a404500f803f Mon Sep 17 00:00:00 2001 From: menghanli Date: Thu, 27 Apr 2023 17:09:31 +0800 Subject: [PATCH 10/13] Fix "Open " starts from end for RTL language Root cause: TextView space uses "wrap_content" to not match the parent width Solution: Use "match_parent" instead Bug: 279646115 Test: anual test for RTL and LTR language Change-Id: If4091958e5ab4323c881732a7b38814dc93db4a1 --- res/layout/accessibility_launch_activity_preference.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layout/accessibility_launch_activity_preference.xml b/res/layout/accessibility_launch_activity_preference.xml index 5ec6bea4690..679120663ef 100644 --- a/res/layout/accessibility_launch_activity_preference.xml +++ b/res/layout/accessibility_launch_activity_preference.xml @@ -34,7 +34,7 @@ Date: Thu, 27 Apr 2023 17:22:08 +0800 Subject: [PATCH 11/13] Change drop down strings to "View by ..." https://screenshot.googleplex.com/8J4KQW9FKB9yjsL.png Bug: 279654750 Fix: 279654750 Test: manual Change-Id: Ia66db0910b318b1e819960d08294a4303497b3e7 --- res/values/strings.xml | 4 ++-- .../batteryusage/BatteryUsageBreakdownController.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index b38552f61f0..a512c177066 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5472,9 +5472,9 @@ Screen time for %s - By apps + View by apps - By systems + View by systems < %1$s diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java index c272a499194..b262dee93ab 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBreakdownController.java @@ -177,8 +177,8 @@ public class BatteryUsageBreakdownController extends BasePreferenceController mAppListPreferenceGroup.setOrderingAsAdded(false); mSpinnerPreference.initializeSpinner( new String[]{ - mPrefContext.getString(R.string.battery_usage_spinner_by_apps), - mPrefContext.getString(R.string.battery_usage_spinner_by_systems) + mPrefContext.getString(R.string.battery_usage_spinner_view_by_apps), + mPrefContext.getString(R.string.battery_usage_spinner_view_by_systems) }, new AdapterView.OnItemSelectedListener() { @Override From c9994c0a54746808743867138e2a289bd5d9444c Mon Sep 17 00:00:00 2001 From: Zoey Chen Date: Thu, 27 Apr 2023 09:44:17 +0000 Subject: [PATCH 12/13] [Regional Preferences]] Update the string of header Bug: 279549042 Test: local test Change-Id: I4983618c91f0e5ea83c4095491be7bb4433f6d2b --- res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index b38552f61f0..1515a50f48e 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -361,7 +361,7 @@ Language may differ from languages available in the app. Some apps may not support this setting. - Only apps that support language selection are shown here. + Set the language for each app. Your system, apps, and websites use the first supported language from your preferred languages. From b2fd7b20c7187efb0757660589155082936da627 Mon Sep 17 00:00:00 2001 From: Julia Tuttle Date: Wed, 26 Apr 2023 22:09:10 -0400 Subject: [PATCH 13/13] Update FSI permission strings Change them to better align with the other "Special app access" screens. Bug: 277938620 Test: none Change-Id: I0083a207a9c8cf98d1b5a9927a1f8bf024832697 --- res/values/strings.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index b38552f61f0..14e869e987f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -9139,13 +9139,13 @@ Can access all files - Show full screen notifications + Full screen notifications - Allow app to show full screen notifications when the device is locked + Allow full screen notifications from this app - Allow the app to show notifications that take up the full screen when the device is locked. Apps may use these to highlight alarms, incoming calls, or other urgent notifications. + Allow this app to show notifications that take up the full screen when the device is locked. Apps may use these to highlight alarms, incoming calls, or other urgent notifications. Media management apps