From e5d19b38cbf1f8d8237a92d1a27fc474776496b4 Mon Sep 17 00:00:00 2001 From: Weng Su Date: Fri, 13 Dec 2024 08:44:05 +0800 Subject: [PATCH] Skip authentication if device was unlocked recently - Sync the same behavior from SystemUI to Settings Bug: 365611488 Flag: EXEMPT bugfix Test: Manual testing atest -c WifiNetworkDetailsFragmentTest \ WifiDetailPreferenceController2Test \ WifiTetherSSIDPreferenceControllerTest \ com.android.settings.wifi.dpp.WifiDppUtilsTest atest -c com.android.settings.spa.wifi.dpp.WifiDppUtilsTest Change-Id: Ie3e8374b1fdbbc61e9e5bbf0f5162b18ba1452f3 --- .../network/NetworkProviderSettings.java | 2 +- .../AddDevicePreferenceController2.java | 3 +- .../WifiDetailPreferenceController2.java | 3 +- .../settings/wifi/dpp/WifiDppUtils.java | 121 ++++++++++++------ .../WifiTetherSSIDPreferenceController.java | 2 +- .../settings/spa/wifi/dpp/WifiDppUtilsTest.kt | 118 +++++++++++++++++ 6 files changed, 204 insertions(+), 45 deletions(-) create mode 100644 tests/spa_unit/src/com/android/settings/spa/wifi/dpp/WifiDppUtilsTest.kt diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java index c776987856e..1fc91014f10 100644 --- a/src/com/android/settings/network/NetworkProviderSettings.java +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -706,7 +706,7 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment 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 8f9741a6b34..4ffe279d6d4 100644 --- a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java +++ b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java @@ -57,7 +57,8 @@ public class AddDevicePreferenceController2 extends BasePreferenceController { @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 a8d7f417a4a..ecddecfce74 100644 --- a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java +++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java @@ -980,7 +980,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle * 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 23a6a5423e4..24ab496cc3f 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.os.Vibrator; 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 @@ import javax.crypto.SecretKey; * @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 @@ public class WifiDppUtils { 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 @@ public class WifiDppUtils { * 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 1bcff1ea45e..d2d26ab84fa 100644 --- a/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java +++ b/src/com/android/settings/wifi/tether/WifiTetherSSIDPreferenceController.java @@ -123,7 +123,7 @@ public class WifiTetherSSIDPreferenceController extends WifiTetherBasePreference } 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 00000000000..31ee9e6a2c7 --- /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() + private val cipher = mock() + private var mockKeyguardManager = mock() + 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()) + + try { + WifiDppUtils.showLockScreenForWifiSharing(context, runnable) + } catch (_: Exception) {} + + verify(runnable, never()).run() + } +}