Merge "Skip authentication if device was unlocked recently" into main
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index c776987..1fc9101 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -706,7 +706,7 @@
forget(mSelectedWifiEntry);
return true;
case MENU_ID_SHARE:
- WifiDppUtils.showLockScreen(getContext(),
+ WifiDppUtils.showLockScreenForWifiSharing(getContext(),
() -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry));
return true;
case MENU_ID_MODIFY:
diff --git a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
index 8f9741a..4ffe279 100644
--- a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java
@@ -57,7 +57,8 @@
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (KEY_ADD_DEVICE.equals(preference.getKey())) {
- WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner());
+ WifiDppUtils.showLockScreenForWifiSharing(mContext,
+ () -> launchWifiDppConfiguratorQrCodeScanner());
return true; /* click is handled */
}
diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
index a8d7f41..ecddecf 100644
--- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
+++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java
@@ -980,7 +980,8 @@
* Share the wifi network with QR code.
*/
private void shareNetwork() {
- WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
+ WifiDppUtils.showLockScreenForWifiSharing(mContext,
+ () -> launchWifiDppConfiguratorActivity());
}
/**
diff --git a/src/com/android/settings/wifi/dpp/WifiDppUtils.java b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
index 23a6a54..24ab496 100644
--- a/src/com/android/settings/wifi/dpp/WifiDppUtils.java
+++ b/src/com/android/settings/wifi/dpp/WifiDppUtils.java
@@ -16,6 +16,8 @@
package com.android.settings.wifi.dpp;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
@@ -33,6 +35,9 @@
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.text.TextUtils;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
@@ -58,6 +63,8 @@
* @see WifiQrCode
*/
public class WifiDppUtils {
+ private static final String TAG = "WifiDppUtils";
+
/**
* The fragment tag specified to FragmentManager for container activities to manage fragments.
*/
@@ -109,7 +116,15 @@
private static final Duration VIBRATE_DURATION_QR_CODE_RECOGNITION = Duration.ofMillis(3);
- private static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding";
+ /**
+ * Parameters to check whether the device has been locked recently
+ */
+ @VisibleForTesting
+ public static final String AES_CBC_PKCS7_PADDING = "AES/CBC/PKCS7Padding";
+ @VisibleForTesting
+ public static final String WIFI_SHARING_KEY_ALIAS = "wifi_sharing_auth_key";
+ @VisibleForTesting
+ public static final int WIFI_SHARING_MAX_UNLOCK_SECONDS = 60;
/**
* Returns whether the device support WiFi DPP.
@@ -426,51 +441,75 @@
* Shows authentication screen to confirm credentials (pin, pattern or password) for the current
* user of the device.
*
- * @param context The {@code Context} used to get {@code KeyguardManager} service
+ * @param context The {@code Context} used to get {@code KeyguardManager} service
* @param successRunnable The {@code Runnable} which will be executed if the user does not setup
* device security or if lock screen is unlocked
*/
- public static void showLockScreen(Context context, Runnable successRunnable) {
- final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(
- Context.KEYGUARD_SERVICE);
-
- if (keyguardManager.isKeyguardSecure()) {
- final BiometricPrompt.AuthenticationCallback authenticationCallback =
- new BiometricPrompt.AuthenticationCallback() {
- @Override
- public void onAuthenticationSucceeded(
- BiometricPrompt.AuthenticationResult result) {
- successRunnable.run();
- }
-
- @Override
- public void onAuthenticationError(int errorCode, CharSequence errString) {
- //Do nothing
- }
- };
-
- final int userId = UserHandle.myUserId();
-
- final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
- .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));
-
- if (keyguardManager.isDeviceSecure()) {
- builder.setDeviceCredentialAllowed(true);
- builder.setTextForDeviceCredential(
- null /* title */,
- Utils.getConfirmCredentialStringForUser(
- context, userId, Utils.getCredentialType(context, userId)),
- null /* description */);
- }
-
- final BiometricPrompt bp = builder.build();
- final Handler handler = new Handler(Looper.getMainLooper());
- bp.authenticate(new CancellationSignal(),
- runnable -> handler.post(runnable),
- authenticationCallback);
- } else {
+ public static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable) {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) {
successRunnable.run();
+ return;
}
+ showLockScreen(context, successRunnable, keyguardManager);
+ }
+
+ /**
+ * Shows authentication screen to confirm credentials (pin, pattern or password) for the
+ * current user of the device. But if the device has been unlocked recently, the
+ * authentication screen will be skipped.
+ *
+ * @param context The {@code Context} used to get {@code KeyguardManager} service
+ * @param successRunnable The {@code Runnable} which will be executed if the user does not setup
+ * device security or if lock screen is unlocked
+ */
+ public static void showLockScreenForWifiSharing(@NonNull Context context,
+ @NonNull Runnable successRunnable) {
+ KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
+ if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) {
+ successRunnable.run();
+ return;
+ }
+ if (isUnlockedWithinSeconds(WIFI_SHARING_KEY_ALIAS, WIFI_SHARING_MAX_UNLOCK_SECONDS)) {
+ Log.d(TAG, "Bypassing the lock screen because the device was unlocked recently.");
+ successRunnable.run();
+ return;
+ }
+ showLockScreen(context, successRunnable, keyguardManager);
+ }
+
+ @SuppressLint("MissingPermission")
+ private static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable,
+ @NonNull KeyguardManager keyguardManager) {
+ BiometricPrompt.AuthenticationCallback authenticationCallback =
+ new BiometricPrompt.AuthenticationCallback() {
+ @Override
+ public void onAuthenticationSucceeded(
+ BiometricPrompt.AuthenticationResult result) {
+ successRunnable.run();
+ }
+
+ @Override
+ public void onAuthenticationError(int errorCode, CharSequence errString) {
+ //Do nothing
+ }
+ };
+ int userId = UserHandle.myUserId();
+ BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context)
+ .setTitle(context.getText(R.string.wifi_dpp_lockscreen_title));
+ if (keyguardManager.isDeviceSecure()) {
+ builder.setDeviceCredentialAllowed(true);
+ builder.setTextForDeviceCredential(
+ null /* title */,
+ Utils.getConfirmCredentialStringForUser(
+ context, userId, Utils.getCredentialType(context, userId)),
+ null /* description */);
+ }
+ BiometricPrompt bp = builder.build();
+ Handler handler = new Handler(Looper.getMainLooper());
+ bp.authenticate(new CancellationSignal(),
+ runnable -> handler.post(runnable),
+ authenticationCallback);
}
/**
diff --git a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
index 1bcff1e..d2d26ab 100644
--- a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
+++ b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java
@@ -123,7 +123,7 @@
}
private void shareHotspotNetwork(Intent intent) {
- WifiDppUtils.showLockScreen(mContext, () -> {
+ WifiDppUtils.showLockScreenForWifiSharing(mContext, () -> {
mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_HOTSPOT_QR_CODE,
SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
diff --git a/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt b/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt
new file mode 100644
index 0000000..31ee9e6
--- /dev/null
+++ b/tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2024 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.spa.wifi.dpp
+
+import android.app.KeyguardManager
+import android.content.Context
+import android.hardware.biometrics.BiometricPrompt
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.settings.wifi.dpp.WifiDppUtils
+import java.security.InvalidKeyException
+import java.security.Key
+import javax.crypto.Cipher
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoSession
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.doThrow
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.spy
+import org.mockito.kotlin.stub
+import org.mockito.kotlin.whenever
+import org.mockito.quality.Strictness
+
+@RunWith(AndroidJUnit4::class)
+class WifiDppUtilsTest {
+ private lateinit var mockSession: MockitoSession
+
+ private val runnable = mock<Runnable>()
+ private val cipher = mock<Cipher>()
+ private var mockKeyguardManager = mock<KeyguardManager>()
+ private var context: Context =
+ spy(ApplicationProvider.getApplicationContext()) {
+ on { getSystemService(KeyguardManager::class.java) } doReturn mockKeyguardManager
+ }
+
+ @Before
+ fun setUp() {
+ mockSession =
+ ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .mockStatic(Cipher::class.java)
+ .mockStatic(BiometricPrompt::class.java)
+ .mockStatic(BiometricPrompt.Builder::class.java)
+ .strictness(Strictness.LENIENT)
+ .startMocking()
+ whenever(context.applicationContext).thenReturn(context)
+ }
+
+ @After
+ fun tearDown() {
+ mockSession.finishMocking()
+ }
+
+ @Test
+ fun showLockScreen_notKeyguardSecure_runRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn false }
+
+ WifiDppUtils.showLockScreen(context, runnable)
+
+ verify(runnable).run()
+ }
+
+ @Test
+ fun showLockScreen_isKeyguardSecure_doNotRunRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true }
+
+ try {
+ WifiDppUtils.showLockScreen(context, runnable)
+ } catch (_: Exception) {}
+
+ verify(runnable, never()).run()
+ }
+
+ @Test
+ fun showLockScreenForWifiSharing_deviceUnlockedRecently_runRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true }
+ whenever(Cipher.getInstance(WifiDppUtils.AES_CBC_PKCS7_PADDING)).thenReturn(cipher)
+
+ WifiDppUtils.showLockScreenForWifiSharing(context, runnable)
+
+ verify(runnable).run()
+ }
+
+ @Test
+ fun showLockScreenForWifiSharing_deviceNotUnlockedRecently_doNotRunRunnable() {
+ mockKeyguardManager.stub { on { isKeyguardSecure } doReturn true }
+ whenever(Cipher.getInstance(WifiDppUtils.AES_CBC_PKCS7_PADDING)).thenReturn(cipher)
+ doThrow(InvalidKeyException()).whenever(cipher).init(anyInt(), any<Key>())
+
+ try {
+ WifiDppUtils.showLockScreenForWifiSharing(context, runnable)
+ } catch (_: Exception) {}
+
+ verify(runnable, never()).run()
+ }
+}