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
This commit is contained in:
@@ -706,7 +706,7 @@ public class NetworkProviderSettings extends RestrictedDashboardFragment
|
|||||||
forget(mSelectedWifiEntry);
|
forget(mSelectedWifiEntry);
|
||||||
return true;
|
return true;
|
||||||
case MENU_ID_SHARE:
|
case MENU_ID_SHARE:
|
||||||
WifiDppUtils.showLockScreen(getContext(),
|
WifiDppUtils.showLockScreenForWifiSharing(getContext(),
|
||||||
() -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry));
|
() -> launchWifiDppConfiguratorActivity(mSelectedWifiEntry));
|
||||||
return true;
|
return true;
|
||||||
case MENU_ID_MODIFY:
|
case MENU_ID_MODIFY:
|
||||||
|
@@ -57,7 +57,8 @@ public class AddDevicePreferenceController2 extends BasePreferenceController {
|
|||||||
@Override
|
@Override
|
||||||
public boolean handlePreferenceTreeClick(Preference preference) {
|
public boolean handlePreferenceTreeClick(Preference preference) {
|
||||||
if (KEY_ADD_DEVICE.equals(preference.getKey())) {
|
if (KEY_ADD_DEVICE.equals(preference.getKey())) {
|
||||||
WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner());
|
WifiDppUtils.showLockScreenForWifiSharing(mContext,
|
||||||
|
() -> launchWifiDppConfiguratorQrCodeScanner());
|
||||||
return true; /* click is handled */
|
return true; /* click is handled */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -980,7 +980,8 @@ public class WifiDetailPreferenceController2 extends AbstractPreferenceControlle
|
|||||||
* Share the wifi network with QR code.
|
* Share the wifi network with QR code.
|
||||||
*/
|
*/
|
||||||
private void shareNetwork() {
|
private void shareNetwork() {
|
||||||
WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity());
|
WifiDppUtils.showLockScreenForWifiSharing(mContext,
|
||||||
|
() -> launchWifiDppConfiguratorActivity());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
|
|
||||||
package com.android.settings.wifi.dpp;
|
package com.android.settings.wifi.dpp;
|
||||||
|
|
||||||
|
import android.annotation.NonNull;
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.KeyguardManager;
|
import android.app.KeyguardManager;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@@ -33,6 +35,9 @@ import android.os.Vibrator;
|
|||||||
import android.security.keystore.KeyGenParameterSpec;
|
import android.security.keystore.KeyGenParameterSpec;
|
||||||
import android.security.keystore.KeyProperties;
|
import android.security.keystore.KeyProperties;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.android.settings.R;
|
import com.android.settings.R;
|
||||||
import com.android.settings.Utils;
|
import com.android.settings.Utils;
|
||||||
@@ -58,6 +63,8 @@ import javax.crypto.SecretKey;
|
|||||||
* @see WifiQrCode
|
* @see WifiQrCode
|
||||||
*/
|
*/
|
||||||
public class WifiDppUtils {
|
public class WifiDppUtils {
|
||||||
|
private static final String TAG = "WifiDppUtils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The fragment tag specified to FragmentManager for container activities to manage fragments.
|
* 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 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.
|
* 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
|
* Shows authentication screen to confirm credentials (pin, pattern or password) for the current
|
||||||
* user of the device.
|
* 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
|
* @param successRunnable The {@code Runnable} which will be executed if the user does not setup
|
||||||
* device security or if lock screen is unlocked
|
* device security or if lock screen is unlocked
|
||||||
*/
|
*/
|
||||||
public static void showLockScreen(Context context, Runnable successRunnable) {
|
public static void showLockScreen(@NonNull Context context, @NonNull Runnable successRunnable) {
|
||||||
final KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(
|
KeyguardManager keyguardManager = context.getSystemService(KeyguardManager.class);
|
||||||
Context.KEYGUARD_SERVICE);
|
if (keyguardManager == null || !keyguardManager.isKeyguardSecure()) {
|
||||||
|
|
||||||
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 {
|
|
||||||
successRunnable.run();
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -123,7 +123,7 @@ public class WifiTetherSSIDPreferenceController extends WifiTetherBasePreference
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void shareHotspotNetwork(Intent intent) {
|
private void shareHotspotNetwork(Intent intent) {
|
||||||
WifiDppUtils.showLockScreen(mContext, () -> {
|
WifiDppUtils.showLockScreenForWifiSharing(mContext, () -> {
|
||||||
mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
|
mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN,
|
||||||
SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_HOTSPOT_QR_CODE,
|
SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_HOTSPOT_QR_CODE,
|
||||||
SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
|
SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR,
|
||||||
|
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user