diff --git a/res/layout/sfps_enroll_find_sensor_layout.xml b/res/layout/sfps_enroll_find_sensor_layout.xml new file mode 100644 index 00000000000..fe74e583e89 --- /dev/null +++ b/res/layout/sfps_enroll_find_sensor_layout.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + diff --git a/res/raw/fingerprint_edu_lottie.json b/res/raw/fingerprint_edu_lottie.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/res/raw/fingerprint_edu_lottie_portrait.json b/res/raw/fingerprint_edu_lottie_portrait.json new file mode 100644 index 00000000000..e69de29bb2d diff --git a/res/values/strings.xml b/res/values/strings.xml index 3994678a335..0b7a3d5fccb 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -13328,6 +13328,11 @@ Allow access to wallet from lock screen and quick settings + + Show QR Code Scanner + + Allow access to QR code scanner from lock screen + Show device controls diff --git a/res/xml/security_lockscreen_settings.xml b/res/xml/security_lockscreen_settings.xml index 82cb8609114..5796f2ac553 100644 --- a/res/xml/security_lockscreen_settings.xml +++ b/res/xml/security_lockscreen_settings.xml @@ -51,6 +51,12 @@ android:summary="@string/lockscreen_privacy_wallet_summary" settings:controller="com.android.settings.display.WalletPrivacyPreferenceController" /> + + props = fingerprintManager.getSensorPropertiesInternal(); mCanAssumeUdfps = props != null && props.size() == 1 && props.get(0).isAnyUdfpsType(); + mCanAssumeSidefps = props != null && props.size() == 1 && props.get(0).isAnySidefpsType(); setContentView(getContentView()); mFooterBarMixin = getLayout().getMixin(FooterBarMixin.class); mFooterBarMixin.setSecondaryButton( @@ -72,6 +79,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements .build() ); + listenOrientationEvent(); + if (mCanAssumeUdfps) { setHeaderText(R.string.security_settings_udfps_enroll_find_sensor_title); setDescriptionText(R.string.security_settings_udfps_enroll_find_sensor_message); @@ -90,6 +99,28 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements lottieAnimationView.setAnimation(R.raw.udfps_edu_a11y_lottie); } + } else if (mCanAssumeSidefps) { + setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); + setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message); + final LottieAnimationView lottieAnimationView = findViewById(R.id.illustration_lottie); + final LottieAnimationView lottieAnimationViewPortrait = + findViewById(R.id.illustration_lottie_portrait); + final int rotation = getApplicationContext().getDisplay().getRotation(); + switch(rotation) { + case Surface.ROTATION_90: + lottieAnimationView.setVisibility(View.GONE); + lottieAnimationViewPortrait.setVisibility(View.VISIBLE); + break; + case Surface.ROTATION_270: + lottieAnimationView.setVisibility(View.GONE); + lottieAnimationViewPortrait.setVisibility(View.VISIBLE); + lottieAnimationViewPortrait.setRotation(180); + break; + default: + lottieAnimationView.setVisibility(View.VISIBLE); + lottieAnimationViewPortrait.setVisibility(View.GONE); + break; + } } else { setHeaderText(R.string.security_settings_fingerprint_enroll_find_sensor_title); setDescriptionText(R.string.security_settings_fingerprint_enroll_find_sensor_message); @@ -145,6 +176,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements protected int getContentView() { if (mCanAssumeUdfps) { return R.layout.udfps_enroll_find_sensor_layout; + } else if (mCanAssumeSidefps) { + return R.layout.sfps_enroll_find_sensor_layout; } return R.layout.fingerprint_enroll_find_sensor; } @@ -220,6 +253,7 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements @Override protected void onDestroy() { + stopListenOrientationEvent(); super.onDestroy(); if (mAnimation != null) { mAnimation.stopAnimation(); @@ -297,4 +331,37 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements public int getMetricsCategory() { return SettingsEnums.FINGERPRINT_FIND_SENSOR; } + + private void listenOrientationEvent() { + if (!mCanAssumeSidefps) { + // Do nothing if the device doesn't support SideFPS. + return; + } + mOrientationEventListener = new OrientationEventListener(this) { + @Override + public void onOrientationChanged(int orientation) { + final int currentRotation = getDisplay().getRotation(); + if ((mPreviousRotation == Surface.ROTATION_90 + && currentRotation == Surface.ROTATION_270) || ( + mPreviousRotation == Surface.ROTATION_270 + && currentRotation == Surface.ROTATION_90)) { + mPreviousRotation = currentRotation; + recreate(); + } + } + }; + mOrientationEventListener.enable(); + mPreviousRotation = getDisplay().getRotation(); + } + + private void stopListenOrientationEvent() { + if (!mCanAssumeSidefps) { + // Do nothing if the device doesn't support SideFPS. + return; + } + if (mOrientationEventListener != null) { + mOrientationEventListener.disable(); + } + mOrientationEventListener = null; + } } diff --git a/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java b/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java index 704d00b4b2c..402982fc4ea 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileFragmentBridge.java @@ -22,7 +22,6 @@ import com.android.settings.accounts.AccountDashboardFragment; import com.android.settings.applications.manageapplications.ManageApplications; import com.android.settings.deviceinfo.StorageDashboardFragment; import com.android.settings.location.LocationServices; -import com.android.settings.location.RecentLocationAccessSeeAllFragment; import java.util.Map; @@ -43,8 +42,6 @@ public class ProfileFragmentBridge { ProfileSelectAccountFragment.class.getName()); FRAGMENT_MAP.put(ManageApplications.class.getName(), ProfileSelectManageApplications.class.getName()); - FRAGMENT_MAP.put(RecentLocationAccessSeeAllFragment.class.getName(), - ProfileSelectRecentLocationAccessFragment.class.getName()); FRAGMENT_MAP.put(LocationServices.class.getName(), ProfileSelectLocationServicesFragment.class.getName()); FRAGMENT_MAP.put(StorageDashboardFragment.class.getName(), diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectRecentLocationAccessFragment.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectRecentLocationAccessFragment.java deleted file mode 100644 index 3cb77c545a4..00000000000 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectRecentLocationAccessFragment.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.dashboard.profileselector; - -import android.os.Bundle; - -import androidx.fragment.app.Fragment; - -import com.android.settings.location.RecentLocationAccessSeeAllFragment; - -/** - * Recent location request page for personal/managed profile. - */ -public class ProfileSelectRecentLocationAccessFragment extends ProfileSelectFragment { - - @Override - public Fragment[] getFragments() { - final Bundle workOnly = new Bundle(); - workOnly.putInt(EXTRA_PROFILE, ProfileType.WORK); - final Fragment workFragment = new RecentLocationAccessSeeAllFragment(); - workFragment.setArguments(workOnly); - - final Bundle personalOnly = new Bundle(); - personalOnly.putInt(EXTRA_PROFILE, ProfileType.PERSONAL); - final Fragment personalFragment = new RecentLocationAccessSeeAllFragment(); - personalFragment.setArguments(personalOnly); - return new Fragment[]{ - personalFragment, //0 - workFragment - }; - } -} diff --git a/src/com/android/settings/display/QRCodeScannerPreferenceController.java b/src/com/android/settings/display/QRCodeScannerPreferenceController.java new file mode 100644 index 00000000000..16e594a62d5 --- /dev/null +++ b/src/com/android/settings/display/QRCodeScannerPreferenceController.java @@ -0,0 +1,108 @@ +/* + * 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.display; + +import static android.provider.Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING; + +import static androidx.lifecycle.Lifecycle.Event.ON_START; +import static androidx.lifecycle.Lifecycle.Event.ON_STOP; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; +import android.provider.Settings; + +import androidx.lifecycle.OnLifecycleEvent; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.TogglePreferenceController; + +/** + * Preference controller for enabling/disabling QR code scanner button on lock screen. + */ +public class QRCodeScannerPreferenceController extends TogglePreferenceController { + private static final String SETTING_KEY = Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER; + private final ContentObserver mSettingsObserver; + private final ContentResolver mContentResolver; + private Preference mPreference; + + public QRCodeScannerPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + mContentResolver = context.getContentResolver(); + mSettingsObserver = new ContentObserver(null) { + @Override + public void onChange(boolean selfChange, Uri uri) { + updateState(mPreference); + } + }; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + /** Called when activity starts being displayed to user. */ + @OnLifecycleEvent(ON_START) + public void onStart() { + mContentResolver.registerContentObserver( + Settings.Global.getUriFor(SHOW_QR_CODE_SCANNER_SETTING), false, + mSettingsObserver); + } + + /** Called when activity stops being displayed to user. */ + @OnLifecycleEvent(ON_STOP) + public void onStop() { + mContentResolver.unregisterContentObserver(mSettingsObserver); + } + + @Override + public boolean isChecked() { + return Settings.Secure.getInt(mContext.getContentResolver(), SETTING_KEY, 0) != 0; + } + + @Override + public boolean setChecked(boolean isChecked) { + return Settings.Secure.putInt(mContext.getContentResolver(), SETTING_KEY, + isChecked ? 1 : 0); + } + + @Override + public int getAvailabilityStatus() { + return isScannerActivityAvailable() ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + refreshSummary(preference); + } + + @Override + public int getSliceHighlightMenuRes() { + return R.string.menu_key_display; + } + + private boolean isScannerActivityAvailable() { + return Settings.Secure.getString(mContext.getContentResolver(), + SHOW_QR_CODE_SCANNER_SETTING) != null; + } +} diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java b/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java index a8417fb8936..e27b28c8238 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllFragment.java @@ -24,7 +24,6 @@ import android.view.MenuItem; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; -import com.android.settings.dashboard.profileselector.ProfileSelectFragment; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settingslib.search.SearchIndexable; @@ -52,13 +51,8 @@ public class RecentLocationAccessSeeAllFragment extends DashboardFragment { @Override public void onAttach(Context context) { super.onAttach(context); - final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE); - mController = use(RecentLocationAccessSeeAllPreferenceController.class); mController.init(this); - if (profileType != 0) { - mController.setProfileType(profileType); - } } @Override diff --git a/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java b/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java index eba6271f5cf..bca4486f01c 100644 --- a/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java +++ b/src/com/android/settings/location/RecentLocationAccessSeeAllPreferenceController.java @@ -40,7 +40,6 @@ public class RecentLocationAccessSeeAllPreferenceController private final RecentAppOpsAccess mRecentLocationAccesses; private boolean mShowSystem = false; private Preference mPreference; - private int mType = ProfileSelectFragment.ProfileType.ALL; public RecentLocationAccessSeeAllPreferenceController(Context context, String key) { super(context, key); @@ -68,7 +67,8 @@ public class RecentLocationAccessSeeAllPreferenceController final List recentLocationAccesses = new ArrayList<>(); for (RecentAppOpsAccess.Access access : mRecentLocationAccesses.getAppListSorted( mShowSystem)) { - if (isRequestMatchesProfileType(userManager, access, mType)) { + if (isRequestMatchesProfileType( + userManager, access, ProfileSelectFragment.ProfileType.ALL)) { recentLocationAccesses.add(access); } } @@ -89,15 +89,6 @@ public class RecentLocationAccessSeeAllPreferenceController } } - /** - * Initialize {@link ProfileSelectFragment.ProfileType} of the controller - * - * @param type {@link ProfileSelectFragment.ProfileType} of the controller. - */ - public void setProfileType(@ProfileSelectFragment.ProfileType int type) { - mType = type; - } - /** * Set the value of {@link #mShowSystem}. */ diff --git a/tests/robotests/src/com/android/settings/display/QRCodeScannerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/display/QRCodeScannerPreferenceControllerTest.java new file mode 100644 index 00000000000..548d65fb332 --- /dev/null +++ b/tests/robotests/src/com/android/settings/display/QRCodeScannerPreferenceControllerTest.java @@ -0,0 +1,106 @@ +/* + * 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.display; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.UNSUPPORTED_ON_DEVICE; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.ContentResolver; +import android.content.Context; +import android.provider.Settings; + +import androidx.preference.Preference; + +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; + +@RunWith(RobolectricTestRunner.class) +public class QRCodeScannerPreferenceControllerTest { + private static final String TEST_KEY = "test_key"; + private static final String SETTING_KEY = Settings.Secure.LOCK_SCREEN_SHOW_QR_CODE_SCANNER; + private static final String DEFAULT_COMPONENT = + Settings.Secure.SHOW_QR_CODE_SCANNER_SETTING; + + private Context mContext; + private ContentResolver mContentResolver; + private QRCodeScannerPreferenceController mController; + + @Mock + private Preference mPreference; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mContentResolver = mContext.getContentResolver(); + mController = new QRCodeScannerPreferenceController(mContext, TEST_KEY); + } + + @Test + public void isChecked_SettingIs1_returnTrue() { + Settings.Secure.putInt(mContentResolver, SETTING_KEY, 1); + + assertThat(mController.isChecked()).isTrue(); + } + + @Test + public void isChecked_SettingIs0_returnFalse() { + Settings.Secure.putInt(mContentResolver, SETTING_KEY, 0); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void isChecked_SettingIsNotSet_returnFalse() { + Settings.Secure.putString(mContentResolver, SETTING_KEY, null); + + assertThat(mController.isChecked()).isFalse(); + } + + @Test + public void setChecked_true_SettingIsNot0() { + mController.setChecked(true); + + assertThat(Settings.Secure.getInt(mContentResolver, SETTING_KEY, 0)).isNotEqualTo(0); + } + + @Test + public void setChecked_false_SettingIs0() { + mController.setChecked(false); + + assertThat(Settings.Secure.getInt(mContentResolver, SETTING_KEY, 0)).isEqualTo(0); + } + + @Test + public void getAvailabilityStatus_defaultComponentNotSet() { + Settings.Secure.putString(mContext.getContentResolver(), DEFAULT_COMPONENT, null); + assertThat(mController.getAvailabilityStatus()).isEqualTo(UNSUPPORTED_ON_DEVICE); + } + + @Test + public void getAvailabilityStatus_defaultComponentSet() { + Settings.Secure.putString(mContext.getContentResolver(), DEFAULT_COMPONENT, "abc"); + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } +}