diff --git a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java index 487e254a929..69ae9a735e3 100644 --- a/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java +++ b/src/com/android/settings/biometrics/combination/BiometricsSettingsBase.java @@ -21,6 +21,7 @@ import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRIN import static com.android.settings.password.ChooseLockPattern.RESULT_FINISHED; +import android.app.Activity; import android.content.Context; import android.content.Intent; import android.hardware.biometrics.SensorProperties; @@ -179,6 +180,12 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { } mFaceManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { + final Activity activity = getActivity(); + if (activity == null || activity.isFinishing()) { + Log.e(getLogTag(), "Stop during generating face unlock challenge" + + " because activity is null or finishing"); + return; + } try { final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId, challenge); @@ -215,6 +222,12 @@ public abstract class BiometricsSettingsBase extends DashboardFragment { } mFingerprintManager.generateChallenge(mUserId, (sensorId, userId, challenge) -> { + final Activity activity = getActivity(); + if (activity == null || activity.isFinishing()) { + Log.e(getLogTag(), "Stop during generating fingerprint challenge" + + " because activity is null or finishing"); + return; + } try { final byte[] token = requestGatekeeperHat(context, mGkPwHandle, mUserId, challenge); diff --git a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java index 0a0e2081a8f..985c8b713dc 100644 --- a/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java +++ b/src/com/android/settings/connecteddevice/stylus/StylusDevicesController.java @@ -45,6 +45,7 @@ import androidx.preference.SwitchPreference; import com.android.settings.R; import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.dashboard.profileselector.UserAdapter; +import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.AbstractPreferenceController; @@ -59,7 +60,8 @@ import java.util.List; * This class adds stylus preferences. */ public class StylusDevicesController extends AbstractPreferenceController implements - Preference.OnPreferenceClickListener, LifecycleObserver, OnResume { + Preference.OnPreferenceClickListener, Preference.OnPreferenceChangeListener, + LifecycleObserver, OnResume { @VisibleForTesting static final String KEY_STYLUS = "device_stylus"; @@ -138,11 +140,15 @@ public class StylusDevicesController extends AbstractPreferenceController implem return pref; } - private SwitchPreference createOrUpdateHandwritingPreference(SwitchPreference preference) { - SwitchPreference pref = preference == null ? new SwitchPreference(mContext) : preference; + private PrimarySwitchPreference createOrUpdateHandwritingPreference( + PrimarySwitchPreference preference) { + PrimarySwitchPreference pref = preference == null ? new PrimarySwitchPreference(mContext) + : preference; pref.setKey(KEY_HANDWRITING); pref.setTitle(mContext.getString(R.string.stylus_textfield_handwriting)); pref.setIcon(R.drawable.ic_text_fields_alt); + // Using a two-target preference, clicking will send an intent and change will toggle. + pref.setOnPreferenceChangeListener(this); pref.setOnPreferenceClickListener(this); pref.setChecked(Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.STYLUS_HANDWRITING_ENABLED, @@ -165,7 +171,6 @@ public class StylusDevicesController extends AbstractPreferenceController implem @Override public boolean onPreferenceClick(Preference preference) { String key = preference.getKey(); - switch (key) { case KEY_DEFAULT_NOTES: PackageManager pm = mContext.getPackageManager(); @@ -181,20 +186,13 @@ public class StylusDevicesController extends AbstractPreferenceController implem } break; case KEY_HANDWRITING: - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.STYLUS_HANDWRITING_ENABLED, - ((SwitchPreference) preference).isChecked() ? 1 : 0); - - if (((SwitchPreference) preference).isChecked()) { - InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); - InputMethodInfo inputMethod = imm.getCurrentInputMethodInfo(); - if (inputMethod == null) break; - - Intent handwritingIntent = - inputMethod.createStylusHandwritingSettingsActivityIntent(); - if (handwritingIntent != null) { - mContext.startActivity(handwritingIntent); - } + InputMethodManager imm = mContext.getSystemService(InputMethodManager.class); + InputMethodInfo inputMethod = imm.getCurrentInputMethodInfo(); + if (inputMethod == null) break; + Intent handwritingIntent = + inputMethod.createStylusHandwritingSettingsActivityIntent(); + if (handwritingIntent != null) { + mContext.startActivity(handwritingIntent); } break; case KEY_IGNORE_BUTTON: @@ -206,6 +204,19 @@ public class StylusDevicesController extends AbstractPreferenceController implem return true; } + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + String key = preference.getKey(); + switch (key) { + case KEY_HANDWRITING: + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.STYLUS_HANDWRITING_ENABLED, + (boolean) newValue ? 1 : 0); + break; + } + return true; + } + @Override public final void displayPreference(PreferenceScreen screen) { mPreferencesContainer = (PreferenceCategory) screen.findPreference(getPreferenceKey()); @@ -233,7 +244,7 @@ public class StylusDevicesController extends AbstractPreferenceController implem mPreferencesContainer.addPreference(notesPref); } - SwitchPreference currHandwritingPref = mPreferencesContainer.findPreference( + PrimarySwitchPreference currHandwritingPref = mPreferencesContainer.findPreference( KEY_HANDWRITING); Preference handwritingPref = createOrUpdateHandwritingPreference(currHandwritingPref); if (currHandwritingPref == null) { @@ -328,5 +339,4 @@ public class StylusDevicesController extends AbstractPreferenceController implem return false; } - } diff --git a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java index 2ce0757f77b..4781f5623ae 100644 --- a/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java +++ b/tests/robotests/src/com/android/settings/biometrics/combination/CombinedBiometricProfileSettingsTest.java @@ -43,6 +43,7 @@ import android.hardware.face.FaceManager; import android.hardware.fingerprint.FingerprintManager; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.os.Bundle; +import android.util.AndroidRuntimeException; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -58,7 +59,10 @@ import androidx.preference.PreferenceScreen; import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; +import com.android.settings.biometrics.BiometricStatusPreferenceController; import com.android.settings.biometrics.BiometricsSplitScreenDialog; +import com.android.settings.biometrics.face.FaceStatusPreferenceController; +import com.android.settings.biometrics.fingerprint.FingerprintStatusPreferenceController; import com.android.settings.password.ChooseLockSettingsHelper; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowFragment; @@ -68,7 +72,6 @@ import com.android.settingslib.core.AbstractPreferenceController; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -86,7 +89,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -@Ignore @RunWith(RobolectricTestRunner.class) @Config(shadows = {ShadowSettingsPreferenceFragment.class, ShadowUtils.class, ShadowFragment.class}) public class CombinedBiometricProfileSettingsTest { @@ -104,6 +106,10 @@ public class CombinedBiometricProfileSettingsTest { @Mock private BiometricSettingsAppPreferenceController mBiometricSettingsAppPreferenceController; @Mock + private FingerprintStatusPreferenceController mFingerprintStatusPreferenceController; + @Mock + private FaceStatusPreferenceController mFaceStatusPreferenceController; + @Mock private FaceManager mFaceManager; @Mock private FragmentTransaction mFragmentTransaction; @@ -128,6 +134,29 @@ public class CombinedBiometricProfileSettingsTest { List controllerList = new ArrayList<>(); controllerList.add(mBiometricSettingsAppPreferenceController); preferenceControllers.put(BiometricSettingsAppPreferenceController.class, controllerList); + controllerList.add(mFingerprintStatusPreferenceController); + preferenceControllers.put(FingerprintStatusPreferenceController.class, controllerList); + controllerList.add(mFaceStatusPreferenceController); + preferenceControllers.put(FaceStatusPreferenceController.class, controllerList); + + doAnswer(invocation -> { + final Preference preference = invocation.getArgument(0); + return preference.getKey().equals(mFragment.getFingerprintPreferenceKey()); + }).when(mFingerprintStatusPreferenceController) + .setPreferenceTreeClickLauncher(any(), any()); + doAnswer(invocation -> { + final Preference preference = invocation.getArgument(0); + return preference.getKey().equals(mFragment.getFingerprintPreferenceKey()); + }).when(mFingerprintStatusPreferenceController).handlePreferenceTreeClick(any()); + doAnswer(invocation -> { + final Preference preference = invocation.getArgument(0); + return preference.getKey().equals(mFragment.getFacePreferenceKey()); + }).when(mFaceStatusPreferenceController) + .setPreferenceTreeClickLauncher(any(), any()); + doAnswer(invocation -> { + final Preference preference = invocation.getArgument(0); + return preference.getKey().equals(mFragment.getFacePreferenceKey()); + }).when(mFaceStatusPreferenceController).handlePreferenceTreeClick(any()); doAnswer(invocation -> { final CharSequence key = invocation.getArgument(0); @@ -164,7 +193,7 @@ public class CombinedBiometricProfileSettingsTest { preference.setKey(mFragment.getFingerprintPreferenceKey()); mFragment.onPreferenceTreeClick(preference); - verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick( + verify(mFingerprintStatusPreferenceController).handlePreferenceTreeClick( mPreferenceCaptor.capture()); List capturedPreferences = mPreferenceCaptor.getAllValues(); @@ -224,7 +253,7 @@ public class CombinedBiometricProfileSettingsTest { mFragment.onActivityResult(CONFIRM_REQUEST, RESULT_FINISHED, new Intent().putExtra(ChooseLockSettingsHelper.EXTRA_KEY_GK_PW_HANDLE, 1L)); - verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick( + verify(mFingerprintStatusPreferenceController).handlePreferenceTreeClick( mPreferenceCaptor.capture()); List capturedPreferences = mPreferenceCaptor.getAllValues(); assertThat(capturedPreferences.size()).isEqualTo(1); @@ -254,7 +283,7 @@ public class CombinedBiometricProfileSettingsTest { preference.setKey(mFragment.getFacePreferenceKey()); mFragment.onPreferenceTreeClick(preference); - verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick( + verify(mFaceStatusPreferenceController).handlePreferenceTreeClick( mPreferenceCaptor.capture()); List capturedPreferences = mPreferenceCaptor.getAllValues(); assertThat(capturedPreferences.size()).isEqualTo(1); @@ -313,7 +342,7 @@ public class CombinedBiometricProfileSettingsTest { preference.setKey(mFragment.getFacePreferenceKey()); mFragment.onPreferenceTreeClick(preference); - verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick( + verify(mFaceStatusPreferenceController).handlePreferenceTreeClick( mPreferenceCaptor.capture()); List capturedPreferences = mPreferenceCaptor.getAllValues(); assertThat(capturedPreferences.size()).isEqualTo(1); @@ -323,7 +352,7 @@ public class CombinedBiometricProfileSettingsTest { @Test public void testClickFingerprintUnlock_inMultiWindow_withoutEnrolledFp_showsDialog() { testClickFingerprintUnlock(true /* isInMultiWindow */, false /* hasEnrolledFingerprint */); - verifyShowsDialogAfterClickingUnlock(); + verifyShowsDialogAfterClickingUnlock(mFragment.getFingerprintPreferenceKey()); } @Test @@ -380,7 +409,7 @@ public class CombinedBiometricProfileSettingsTest { @Test public void testClickFaceUnlock_inMultiWindow_withoutEnrolledFp_showsDialog() { testClickFaceUnlock(true /* isInMultiWindow */, false /*hasEnrolledFace*/); - verifyShowsDialogAfterClickingUnlock(); + verifyShowsDialogAfterClickingUnlock(mFragment.getFacePreferenceKey()); } @Test @@ -424,8 +453,11 @@ public class CombinedBiometricProfileSettingsTest { } private void verifyNoDialogAfterClickingUnlock(String preferenceKey) { - verify(mBiometricSettingsAppPreferenceController).handlePreferenceTreeClick( - mPreferenceCaptor.capture()); + final BiometricStatusPreferenceController controller = + preferenceKey.equals(mFragment.getFacePreferenceKey()) + ? mFaceStatusPreferenceController + : mFingerprintStatusPreferenceController; + verify(controller).handlePreferenceTreeClick(mPreferenceCaptor.capture()); List capturedPreferences = mPreferenceCaptor.getAllValues(); assertThat(capturedPreferences).hasSize(1); assertThat(capturedPreferences.get(0).getKey()).isEqualTo(preferenceKey); @@ -433,12 +465,77 @@ public class CombinedBiometricProfileSettingsTest { eq(BiometricsSplitScreenDialog.class.getName())); } - private void verifyShowsDialogAfterClickingUnlock() { - verify(mBiometricSettingsAppPreferenceController, never()).handlePreferenceTreeClick(any()); + private void verifyShowsDialogAfterClickingUnlock(String preferenceKey) { + final BiometricStatusPreferenceController controller = + preferenceKey.equals(mFragment.getFacePreferenceKey()) + ? mFaceStatusPreferenceController + : mFingerprintStatusPreferenceController; + verify(controller, never()).handlePreferenceTreeClick(any()); verify(mFragmentTransaction).add(any(), eq(BiometricsSplitScreenDialog.class.getName())); } + @Test + public void testNoCrashIfDetachActivityDuringGeneratingChallengeThroughFaceManager() { + doAnswer(invocation -> { + final FaceManager.GenerateChallengeCallback callback = + invocation.getArgument(1); + mFragment.onPause(); + mFragment.onStop(); + mFragment.onDestroy(); + mFragment.onDetach(); + doReturn(null).when(mFragment).getActivity(); + callback.onGenerateChallengeResult(0, 0, 1L); + return null; + }).when(mFaceManager).generateChallenge(anyInt(), any()); + doThrow(new IllegalStateException("Test")).when(mFragment).requestGatekeeperHat( + any(), anyLong(), anyInt(), anyLong()); + FragmentManager fragmentManager = mock(FragmentManager.class); + + // Start fragment + mFragment.onAttach(mContext); + mFragment.onCreate(null); + mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY); + mFragment.onResume(); + + // User clicks on "Face Unlock" + final Preference preference = new Preference(mContext); + preference.setKey(mFragment.getFacePreferenceKey()); + mFragment.onPreferenceTreeClick(preference); + + verify(mFragment, never()).launchChooseOrConfirmLock(); + } + + @Test + public void testNoCrashIfDetachActivityDuringGeneratingChallengeThroughFingerprintManager() { + doAnswer(invocation -> { + final FingerprintManager.GenerateChallengeCallback callback = + invocation.getArgument(1); + mFragment.onPause(); + mFragment.onStop(); + mFragment.onDestroy(); + mFragment.onDetach(); + doReturn(null).when(mFragment).getActivity(); + callback.onChallengeGenerated(0, 0, 1L); + return null; + }).when(mFingerprintManager).generateChallenge(anyInt(), any()); + doThrow(new IllegalStateException("Test")).when(mFragment).requestGatekeeperHat( + any(), anyLong(), anyInt(), anyLong()); + + // Start fragment + mFragment.onAttach(mContext); + mFragment.onCreate(null); + mFragment.onCreateView(LayoutInflater.from(mContext), mock(ViewGroup.class), Bundle.EMPTY); + mFragment.onResume(); + + // User clicks on "Fingerprint Unlock" + final Preference preference = new Preference(mContext); + preference.setKey(mFragment.getFingerprintPreferenceKey()); + mFragment.onPreferenceTreeClick(preference); + + verify(mFragment, never()).launchChooseOrConfirmLock(); + } + /** * a test fragment that initializes PreferenceScreen for testing. */ @@ -492,7 +589,9 @@ public class CombinedBiometricProfileSettingsTest { @Override protected void launchChooseOrConfirmLock() { - // do nothing + if (getActivity() == null) { + throw new AndroidRuntimeException("TestFailed"); + } } } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java index 3c459de3792..9538092c361 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/stylus/StylusDevicesControllerTest.java @@ -57,6 +57,7 @@ import androidx.test.core.app.ApplicationProvider; import com.android.settings.R; import com.android.settings.dashboard.profileselector.UserAdapter; +import com.android.settingslib.PrimarySwitchPreference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.core.lifecycle.Lifecycle; @@ -403,9 +404,10 @@ public class StylusDevicesControllerTest { Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1); showScreen(mController); - SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); - assertThat(handwritingPref.isChecked()).isEqualTo(true); + assertThat(handwritingPref.getCheckedState()).isEqualTo(true); } @Test @@ -414,9 +416,10 @@ public class StylusDevicesControllerTest { Settings.Secure.STYLUS_HANDWRITING_ENABLED, 0); showScreen(mController); - SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); - assertThat(handwritingPref.isChecked()).isEqualTo(false); + assertThat(handwritingPref.getCheckedState()).isEqualTo(false); } @Test @@ -424,21 +427,20 @@ public class StylusDevicesControllerTest { Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.STYLUS_HANDWRITING_ENABLED, 0); showScreen(mController); - SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); - handwritingPref.performClick(); + handwritingPref.callChangeListener(true); - assertThat(handwritingPref.isChecked()).isEqualTo(true); assertThat(Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.STYLUS_HANDWRITING_ENABLED, -1)).isEqualTo(1); } @Test - public void handwritingPreference_startsHandwritingSettingsOnClickIfChecked() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.STYLUS_HANDWRITING_ENABLED, 0); + public void handwritingPreference_startsHandwritingSettingsOnClick() { showScreen(mController); - SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); handwritingPref.performClick(); @@ -447,11 +449,23 @@ public class StylusDevicesControllerTest { } @Test - public void handwritingPreference_doesNotStartHandwritingSettingsOnClickIfNotChecked() { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1); + public void handwritingPreference_doesNotStartHandwritingSettingsOnChange() { showScreen(mController); - SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); + + handwritingPref.callChangeListener(true); + + verify(mInputMethodInfo, times(0)).createStylusHandwritingSettingsActivityIntent(); + verify(mContext, times(0)).startActivity(any()); + } + + @Test + public void handwritingPreference_doesNotCreateIntentIfNoInputMethod() { + when(mImm.getCurrentInputMethodInfo()).thenReturn(null); + showScreen(mController); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); handwritingPref.performClick(); @@ -463,14 +477,12 @@ public class StylusDevicesControllerTest { public void handwritingPreference_doesNotStartHandwritingSettingsIfNoIntent() { when(mInputMethodInfo.createStylusHandwritingSettingsActivityIntent()) .thenReturn(null); - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1); showScreen(mController); - SwitchPreference handwritingPref = (SwitchPreference) mPreferenceContainer.getPreference(1); + PrimarySwitchPreference handwritingPref = + (PrimarySwitchPreference) mPreferenceContainer.getPreference(1); handwritingPref.performClick(); - verify(mInputMethodInfo, times(0)).createStylusHandwritingSettingsActivityIntent(); verify(mContext, times(0)).startActivity(any()); }