From cb9374e294ceae9182641fe51fda025a7d595249 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 24 May 2023 09:50:16 +0800 Subject: [PATCH 1/5] Improve the loading time of DataSaverSummary Use AppListRepository instead of ApplicationsState can achieve better performance. Fix: 280280596 Test: Manually on Data Saver page Test: Unit test Change-Id: I98cbae50500b90e7e7f6101fb064003339b082c1 --- .../settings/datausage/DataSaverBackend.java | 6 +- .../settings/datausage/DataSaverSummary.kt | 99 ++++++---------- .../datausage/DataSaverSummaryTest.kt | 109 ++++++++++++++++++ 3 files changed, 150 insertions(+), 64 deletions(-) create mode 100644 tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt diff --git a/src/com/android/settings/datausage/DataSaverBackend.java b/src/com/android/settings/datausage/DataSaverBackend.java index e47ecbdc997..6a392341122 100644 --- a/src/com/android/settings/datausage/DataSaverBackend.java +++ b/src/com/android/settings/datausage/DataSaverBackend.java @@ -196,8 +196,10 @@ public class DataSaverBackend { public interface Listener { void onDataSaverChanged(boolean isDataSaving); - void onAllowlistStatusChanged(int uid, boolean isAllowlisted); + /** This is called when allow list status is changed. */ + default void onAllowlistStatusChanged(int uid, boolean isAllowlisted) {} - void onDenylistStatusChanged(int uid, boolean isDenylisted); + /** This is called when deny list status is changed. */ + default void onDenylistStatusChanged(int uid, boolean isDenylisted) {} } } diff --git a/src/com/android/settings/datausage/DataSaverSummary.kt b/src/com/android/settings/datausage/DataSaverSummary.kt index 1d9cbb73a66..7f38900bbd2 100644 --- a/src/com/android/settings/datausage/DataSaverSummary.kt +++ b/src/com/android/settings/datausage/DataSaverSummary.kt @@ -15,33 +15,36 @@ */ package com.android.settings.datausage -import android.app.Application import android.app.settings.SettingsEnums import android.content.Context +import android.net.NetworkPolicyManager import android.os.Bundle +import android.os.UserHandle import android.telephony.SubscriptionManager import android.widget.Switch +import androidx.annotation.VisibleForTesting import androidx.lifecycle.lifecycleScope import androidx.preference.Preference import com.android.settings.R import com.android.settings.SettingsActivity import com.android.settings.SettingsPreferenceFragment -import com.android.settings.applications.AppStateBaseBridge -import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState import com.android.settings.search.BaseSearchIndexProvider import com.android.settings.widget.SettingsMainSwitchBar -import com.android.settingslib.applications.ApplicationsState import com.android.settingslib.search.SearchIndexable import com.android.settingslib.spa.framework.util.formatString +import com.android.settingslib.spaprivileged.model.app.AppListRepository +import com.android.settingslib.spaprivileged.model.app.AppListRepositoryImpl +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext @SearchIndexable class DataSaverSummary : SettingsPreferenceFragment() { private lateinit var switchBar: SettingsMainSwitchBar private lateinit var dataSaverBackend: DataSaverBackend private lateinit var unrestrictedAccess: Preference - private var dataUsageBridge: AppStateDataUsageBridge? = null - private var session: ApplicationsState.Session? = null // Flag used to avoid infinite loop due if user switch it on/off too quick. private var switching = false @@ -72,27 +75,15 @@ class DataSaverSummary : SettingsPreferenceFragment() { override fun onResume() { super.onResume() - dataSaverBackend.refreshAllowlist() - dataSaverBackend.refreshDenylist() dataSaverBackend.addListener(dataSaverBackendListener) - dataUsageBridge?.resume(/* forceLoadAllApps= */ true) - ?: viewLifecycleOwner.lifecycleScope.launch { - val applicationsState = ApplicationsState.getInstance( - requireContext().applicationContext as Application - ) - dataUsageBridge = AppStateDataUsageBridge( - applicationsState, dataUsageBridgeCallbacks, dataSaverBackend - ) - session = - applicationsState.newSession(applicationsStateCallbacks, settingsLifecycle) - dataUsageBridge?.resume(/* forceLoadAllApps= */ true) - } + viewLifecycleOwner.lifecycleScope.launch { + unrestrictedAccess.summary = getUnrestrictedSummary(requireContext()) + } } override fun onPause() { super.onPause() dataSaverBackend.remListener(dataSaverBackendListener) - dataUsageBridge?.pause() } private fun onSwitchChanged(isChecked: Boolean) { @@ -115,52 +106,36 @@ class DataSaverSummary : SettingsPreferenceFragment() { switching = false } } - - override fun onAllowlistStatusChanged(uid: Int, isAllowlisted: Boolean) {} - - override fun onDenylistStatusChanged(uid: Int, isDenylisted: Boolean) {} - } - - private val dataUsageBridgeCallbacks = AppStateBaseBridge.Callback { - updateUnrestrictedAccessSummary() - } - - private val applicationsStateCallbacks = object : ApplicationsState.Callbacks { - override fun onRunningStateChanged(running: Boolean) {} - - override fun onPackageListChanged() {} - - override fun onRebuildComplete(apps: ArrayList?) {} - - override fun onPackageIconChanged() {} - - override fun onPackageSizeChanged(packageName: String?) {} - - override fun onAllSizesComputed() { - updateUnrestrictedAccessSummary() - } - - override fun onLauncherInfoChanged() { - updateUnrestrictedAccessSummary() - } - - override fun onLoadEntriesCompleted() {} - } - - private fun updateUnrestrictedAccessSummary() { - if (!isAdded || isFinishingOrDestroyed) return - val allApps = session?.allApps ?: return - val count = allApps.count { - ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(it) && - (it.extraInfo as? DataUsageState)?.isDataSaverAllowlisted == true - } - unrestrictedAccess.summary = - resources.formatString(R.string.data_saver_unrestricted_summary, "count" to count) } companion object { private const val KEY_UNRESTRICTED_ACCESS = "unrestricted_access" + @VisibleForTesting + suspend fun getUnrestrictedSummary( + context: Context, + appListRepository: AppListRepository = + AppListRepositoryImpl(context.applicationContext), + ) = context.formatString( + R.string.data_saver_unrestricted_summary, + "count" to getAllowCount(context.applicationContext, appListRepository), + ) + + private suspend fun getAllowCount(context: Context, appListRepository: AppListRepository) = + withContext(Dispatchers.IO) { + coroutineScope { + val appsDeferred = async { + appListRepository.loadAndFilterApps( + userId = UserHandle.myUserId(), + isSystemApp = false, + ) + } + val uidsAllowed = NetworkPolicyManager.from(context) + .getUidsWithPolicy(NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND) + appsDeferred.await().count { app -> app.uid in uidsAllowed } + } + } + private fun Context.isDataSaverVisible(): Boolean = resources.getBoolean(R.bool.config_show_data_saver) diff --git a/tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt b/tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt new file mode 100644 index 00000000000..3c88d8e875b --- /dev/null +++ b/tests/spa_unit/src/com/android/settings/datausage/DataSaverSummaryTest.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2023 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.datausage + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.net.NetworkPolicyManager +import android.net.NetworkPolicyManager.POLICY_ALLOW_METERED_BACKGROUND +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.settings.datausage.DataSaverSummary.Companion.getUnrestrictedSummary +import com.android.settingslib.spaprivileged.model.app.AppListRepository +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Spy +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.Mockito.`when` as whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@RunWith(AndroidJUnit4::class) +class DataSaverSummaryTest { + @get:Rule + val mockito: MockitoRule = MockitoJUnit.rule() + + @Spy + private val context: Context = ApplicationProvider.getApplicationContext() + + @Mock + private lateinit var networkPolicyManager: NetworkPolicyManager + + @Before + fun setUp() { + whenever(context.applicationContext).thenReturn(context) + whenever(NetworkPolicyManager.from(context)).thenReturn(networkPolicyManager) + } + + @Test + fun getUnrestrictedSummary_whenTwoAppsAllowed() = runTest { + whenever( + networkPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND) + ).thenReturn(intArrayOf(APP1.uid, APP2.uid)) + + val summary = + getUnrestrictedSummary(context = context, appListRepository = FakeAppListRepository) + + assertThat(summary) + .isEqualTo("2 apps allowed to use unrestricted data when Data Saver is on") + } + + @Test + fun getUnrestrictedSummary_whenNoAppsAllowed() = runTest { + whenever( + networkPolicyManager.getUidsWithPolicy(POLICY_ALLOW_METERED_BACKGROUND) + ).thenReturn(intArrayOf()) + + val summary = + getUnrestrictedSummary(context = context, appListRepository = FakeAppListRepository) + + assertThat(summary) + .isEqualTo("0 apps allowed to use unrestricted data when Data Saver is on") + } + + private companion object { + val APP1 = ApplicationInfo().apply { uid = 10001 } + val APP2 = ApplicationInfo().apply { uid = 10002 } + val APP3 = ApplicationInfo().apply { uid = 10003 } + + object FakeAppListRepository : AppListRepository { + override suspend fun loadApps( + userId: Int, + loadInstantApps: Boolean, + matchAnyUserForAdmin: Boolean, + ) = emptyList() + + override fun showSystemPredicate( + userIdFlow: Flow, + showSystemFlow: Flow, + ): Flow<(app: ApplicationInfo) -> Boolean> = flowOf { false } + + override fun getSystemPackageNamesBlocking(userId: Int): Set = emptySet() + + override suspend fun loadAndFilterApps(userId: Int, isSystemApp: Boolean) = + listOf(APP1, APP2, APP3) + } + } +} \ No newline at end of file From ce56dcc30bb5adb1e609f5f681ee49ce189b6d32 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Wed, 24 May 2023 19:41:20 +0800 Subject: [PATCH 2/5] Fix crash of PictureInPicture This follows change I3115cf1b99a305efef192a0dcf3e809eb7903d0a PackageManager.getPackageInfoAsUser() will throw exceptions when the package is too large which is a known issue to PackageManager but very low priority given resourcing constraints. As per the PackageManager team suggestion, catch the exception on the app side to alleviate the impact to the PictureInPicture & App info page. Fix: 283076353 Fix: 283354211 Test: Unit test Change-Id: Iad2bf9fbfca6ee7f604fec1c4afa1b9382f6ec7e --- .../spa/app/specialaccess/PictureInPicture.kt | 59 ++++++++++++------- .../app/specialaccess/PictureInPictureTest.kt | 32 ++++++++++ 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt index 9fc358b5cf8..5ed361560d7 100644 --- a/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt +++ b/src/com/android/settings/spa/app/specialaccess/PictureInPicture.kt @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager.GET_ACTIVITIES import android.content.pm.PackageManager.PackageInfoFlags +import android.util.Log import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState import com.android.settings.R @@ -56,26 +57,21 @@ class PictureInPictureListModel(private val context: Context) : private val packageManager = context.packageManager override fun transform(userIdFlow: Flow, appListFlow: Flow>) = - userIdFlow.map(::getPictureInPicturePackages).combine(appListFlow) { - pictureInPicturePackages, - appList -> - appList.map { app -> - createPictureInPictureRecord( - app = app, - isSupport = app.packageName in pictureInPicturePackages, - ) + userIdFlow.map(::getPictureInPicturePackages) + .combine(appListFlow) { pictureInPicturePackages, appList -> + appList.map { app -> + createPictureInPictureRecord( + app = app, + isSupport = app.packageName in pictureInPicturePackages, + ) + } } - } - override fun transformItem(app: ApplicationInfo): PictureInPictureRecord { - return createPictureInPictureRecord( - app = app, - isSupport = app.installed && - packageManager - .getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) - .supportsPictureInPicture(), - ) - } + override fun transformItem(app: ApplicationInfo) = createPictureInPictureRecord( + app = app, + isSupport = app.installed && + getPackageAndActivityInfo(app)?.supportsPictureInPicture() == true, + ) private fun createPictureInPictureRecord(app: ApplicationInfo, isSupport: Boolean) = PictureInPictureRecord( @@ -103,13 +99,36 @@ class PictureInPictureListModel(private val context: Context) : } private fun getPictureInPicturePackages(userId: Int): Set = - packageManager - .getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId) + getPackageAndActivityInfoList(userId) .filter { it.supportsPictureInPicture() } .map { it.packageName } .toSet() + private fun getPackageAndActivityInfo(app: ApplicationInfo): PackageInfo? = try { + packageManager.getPackageInfoAsUser(app.packageName, GET_ACTIVITIES_FLAGS, app.userId) + } catch (e: Exception) { + // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause + // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture + // packages, we need to catch the exception to alleviate the impact before PackageManager + // fixing this issue or provide a better api. + Log.e(TAG, "Exception while getPackageInfoAsUser", e) + null + } + + private fun getPackageAndActivityInfoList(userId: Int): List = try { + packageManager.getInstalledPackagesAsUser(GET_ACTIVITIES_FLAGS, userId) + } catch (e: Exception) { + // Query PackageManager.getPackageInfoAsUser() with GET_ACTIVITIES_FLAGS could cause + // exception sometimes. Since we reply on this flag to retrieve the Picture In Picture + // packages, we need to catch the exception to alleviate the impact before PackageManager + // fixing this issue or provide a better api. + Log.e(TAG, "Exception while getInstalledPackagesAsUser", e) + emptyList() + } + companion object { + private const val TAG = "PictureInPictureListModel" + private fun PackageInfo.supportsPictureInPicture() = activities?.any(ActivityInfo::supportsPictureInPicture) ?: false diff --git a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt index f90d63947d0..fb0fb698045 100644 --- a/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt +++ b/tests/spa_unit/src/com/android/settings/spa/app/specialaccess/PictureInPictureTest.kt @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo import android.content.pm.PackageManager import android.content.pm.PackageManager.PackageInfoFlags +import android.os.DeadSystemRuntimeException import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.settings.R @@ -100,6 +101,23 @@ class PictureInPictureTest { assertThat(record.isSupport).isTrue() } + @Test + fun transform_getInstalledPackagesAsUserThrowsException_treatAsNotSupported() = runTest { + whenever(packageManager.getInstalledPackagesAsUser(any(), anyInt())) + .thenThrow(DeadSystemRuntimeException()) + + val recordListFlow = listModel.transform( + userIdFlow = flowOf(USER_ID), + appListFlow = flowOf(listOf(PICTURE_IN_PICTURE_APP)), + ) + + val recordList = recordListFlow.first() + assertThat(recordList).hasSize(1) + val record = recordList[0] + assertThat(record.app).isSameInstanceAs(PICTURE_IN_PICTURE_APP) + assertThat(record.isSupport).isFalse() + } + @Test fun transformItem() { whenever( @@ -114,6 +132,20 @@ class PictureInPictureTest { assertThat(record.isSupport).isTrue() } + @Test + fun transformItem_getPackageInfoAsUserThrowsException_treatAsNotSupported() { + whenever( + packageManager.getPackageInfoAsUser( + eq(PICTURE_IN_PICTURE_PACKAGE_NAME), any(), eq(USER_ID) + ) + ).thenThrow(DeadSystemRuntimeException()) + + val record = listModel.transformItem(PICTURE_IN_PICTURE_APP) + + assertThat(record.app).isSameInstanceAs(PICTURE_IN_PICTURE_APP) + assertThat(record.isSupport).isFalse() + } + @Test fun filter_isSupport() = runTest { val record = createRecord(isSupport = true) From 71dd391e4dd0a7b62283a740c7c431e1cacb7509 Mon Sep 17 00:00:00 2001 From: Diya Bera Date: Wed, 24 May 2023 10:15:52 -0700 Subject: [PATCH 3/5] Fingerprint Error Dialog Add "Try Again" for UNABLE_TO_PROCESS error. Change default title string. Custom strings for Settings and Setup. Bug: 280786940 Test: Try to enroll fingerprint, trigger a corresponding error and check the error dialog Change-Id: I5975d169e1d33cff87f58fb8882261bb1bd3520e --- res/values/strings.xml | 14 +++- .../FingerprintEnrollEnrolling.java | 3 +- .../FingerprintEnrollFindSensor.java | 3 +- .../fingerprint/FingerprintErrorDialog.java | 66 +++++++++++++++++-- 4 files changed, 76 insertions(+), 10 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c19d3e3a1eb..ed2b77d5871 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1029,13 +1029,21 @@ Touch the sensor on the back of your phone. Use your index finger. - Enrollment was not completed + Can\u2019t complete fingerprint setup + + You can try again now or set up your fingerprint later in Settings. + + You can try again now or set up your fingerprint later. Fingerprint setup timed out + + You can set up your fingerprint later in Settings. + + Something went wrong. You can set up your fingerprint later in Settings. - You can set up your fingerprint later in Settings. + You can set up your fingerprint later. - Fingerprint enrollment didn\'t work. Try again or use a different finger. + Something went wrong. You can set up your fingerprint later. Add another diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 938c075cb70..7e764059b9a 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -505,7 +505,8 @@ public class FingerprintEnrollEnrolling extends BiometricsEnrollEnrolling { // showErrorDialog() will cause onWindowFocusChanged(false), set mIsCanceled to false // before showErrorDialog() to prevent that another error dialog is triggered again. mIsCanceled = true; - FingerprintErrorDialog.showErrorDialog(this, errorMsgId); + FingerprintErrorDialog.showErrorDialog(this, errorMsgId, + this instanceof SetupFingerprintEnrollEnrolling); cancelEnrollment(); stopIconAnimation(); stopListenOrientationEvent(); diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java index e54403170eb..aadc9329e4c 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollFindSensor.java @@ -329,7 +329,8 @@ public class FingerprintEnrollFindSensor extends BiometricEnrollBase implements if (mNextClicked && errMsgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { proceedToEnrolling(false /* cancelEnrollment */); } else { - FingerprintErrorDialog.showErrorDialog(this, errMsgId); + FingerprintErrorDialog.showErrorDialog(this, errMsgId, + this instanceof SetupFingerprintEnrollFindSensor); } } diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java index f0a87a4a889..55745d043f6 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintErrorDialog.java @@ -18,11 +18,14 @@ package com.android.settings.biometrics.fingerprint; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_FINISHED; import static com.android.settings.biometrics.BiometricEnrollBase.RESULT_TIMEOUT; +import static com.android.settings.biometrics.fingerprint.FingerprintEnrollEnrolling.KEY_STATE_CANCELED; import android.app.Activity; import android.app.Dialog; import android.app.settings.SettingsEnums; +import android.content.Intent; import android.hardware.biometrics.BiometricConstants; +import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; @@ -48,6 +51,8 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { final CharSequence errorTitle = getArguments().getCharSequence(KEY_ERROR_TITLE); final int errMsgId = getArguments().getInt(KEY_ERROR_ID); final boolean wasTimeout = errMsgId == BiometricConstants.BIOMETRIC_ERROR_TIMEOUT; + final boolean showTryAgain = errMsgId + == BiometricFingerprintConstants.FINGERPRINT_ERROR_UNABLE_TO_PROCESS; builder.setTitle(errorTitle) .setMessage(errorString) @@ -64,12 +69,33 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { } activity.finish(); }); + + if (showTryAgain) { + builder.setPositiveButton( + R.string.security_settings_fingerprint_enroll_dialog_try_again, + (dialog, which) -> { + dialog.dismiss(); + final Activity activity = getActivity(); + final Intent intent = activity.getIntent(); + intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT); + intent.putExtra(KEY_STATE_CANCELED, false); + activity.startActivity(intent); + activity.finish(); + }) + .setNegativeButton(R.string.security_settings_fingerprint_enroll_dialog_ok, + (dialog, which) -> { + dialog.dismiss(); + final Activity activity = getActivity(); + activity.setResult(RESULT_FINISHED); + activity.finish(); + }); + } final AlertDialog dialog = builder.create(); dialog.setCanceledOnTouchOutside(false); return dialog; } - public static void showErrorDialog(BiometricEnrollBase host, int errMsgId) { + public static void showErrorDialog(BiometricEnrollBase host, int errMsgId, boolean isSetup) { if (host.isFinishing()) { return; } @@ -77,12 +103,39 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { if (fragmentManager.isDestroyed() || fragmentManager.isStateSaved()) { return; } - CharSequence errMsg = host.getText(getErrorMessage(errMsgId)); + CharSequence errMsg; + if (isSetup) { + errMsg = host.getText(getSetupErrorMessage(errMsgId)); + } else { + errMsg = host.getText(getErrorMessage(errMsgId)); + } final CharSequence errTitle = host.getText(getErrorTitle(errMsgId)); final FingerprintErrorDialog dialog = newInstance(errMsg, errTitle, errMsgId); dialog.show(fragmentManager, FingerprintErrorDialog.class.getName()); } + /** + * Gets dialog message as error id inside {@link FingerprintManager} + */ + public static int getSetupErrorMessage(int errMsgId) { + switch (errMsgId) { + case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: + // This message happens when the underlying crypto layer decides to revoke + // the enrollment auth token. + return R.string + .security_settings_fingerprint_enroll_error_timeout_dialog_message_setup; + case FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION: + return R.string.security_settings_fingerprint_bad_calibration; + case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS: + return R.string + .security_settings_fingerprint_enroll_error_unable_to_process_message_setup; + default: + // There's nothing specific to tell the user about. Ask them to try again. + return R.string + .security_settings_fingerprint_enroll_error_generic_dialog_message_setup; + } + } + /** * Gets dialog message as error id inside {@link FingerprintManager} */ @@ -94,6 +147,9 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { return R.string.security_settings_fingerprint_enroll_error_timeout_dialog_message; case FingerprintManager.FINGERPRINT_ERROR_BAD_CALIBRATION: return R.string.security_settings_fingerprint_bad_calibration; + case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS: + return R.string + .security_settings_fingerprint_enroll_error_unable_to_process_message; default: // There's nothing specific to tell the user about. Ask them to try again. return R.string.security_settings_fingerprint_enroll_error_generic_dialog_message; @@ -105,11 +161,11 @@ public class FingerprintErrorDialog extends InstrumentedDialogFragment { */ public static int getErrorTitle(int errMsgId) { switch (errMsgId) { - case FingerprintManager.FINGERPRINT_ERROR_UNABLE_TO_PROCESS: + case FingerprintManager.FINGERPRINT_ERROR_TIMEOUT: + return R.string.security_settings_fingerprint_enroll_error_dialog_title; + default: return R.string .security_settings_fingerprint_enroll_error_unable_to_process_dialog_title; - default: - return R.string.security_settings_fingerprint_enroll_error_dialog_title; } } From 97a1fc643e23158f22316ec5a517bbcce278ab79 Mon Sep 17 00:00:00 2001 From: tom hsu Date: Wed, 24 May 2023 16:06:47 +0800 Subject: [PATCH 4/5] [Settings] Use background thread to get VoNr state. - TelephonyManager#isVoNrEnabled may not send back the result in time, so use different thread and timer to prevent ANR. Bug: 282876876 Test: atest passed Change-Id: Ie6cc0f8a459f5ebed930e457dacb9b742df70fad --- ...NrAdvancedCallingPreferenceController.java | 25 +++++++++++++++++-- ...vancedCallingPreferenceControllerTest.java | 1 + 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.java b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.java index b585ecec6a4..5786dfc99cd 100644 --- a/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.java +++ b/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceController.java @@ -17,6 +17,8 @@ package com.android.settings.network.telephony; import android.content.Context; +import android.os.Handler; +import android.os.Looper; import android.os.PersistableBundle; import android.telephony.CarrierConfigManager; import android.telephony.SubscriptionManager; @@ -33,9 +35,10 @@ import com.android.internal.telephony.util.ArrayUtils; import com.android.settingslib.core.lifecycle.LifecycleObserver; import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; +import com.android.settingslib.utils.ThreadUtils; /** - * Preference controller for "Enhanced 4G LTE" + * Preference controller for "Voice over NR". */ public class NrAdvancedCallingPreferenceController extends TelephonyTogglePreferenceController implements LifecycleObserver, OnStart, OnStop { @@ -50,8 +53,11 @@ public class NrAdvancedCallingPreferenceController extends TelephonyTogglePrefer private boolean mIsVonrVisibleFromCarrierConfig = false; private boolean mIsNrEnableFromCarrierConfig = false; private boolean mHas5gCapability = false; + private boolean mIsVoNrEnabled = false; private Integer mCallState; + private Handler mHandler = new Handler(Looper.getMainLooper()); + public NrAdvancedCallingPreferenceController(Context context, String key) { super(context, key); mTelephonyManager = context.getSystemService(TelephonyManager.class); @@ -94,6 +100,8 @@ public class NrAdvancedCallingPreferenceController extends TelephonyTogglePrefer CarrierConfigManager.KEY_CARRIER_NR_AVAILABILITIES_INT_ARRAY); mIsNrEnableFromCarrierConfig = !ArrayUtils.isEmpty(nrAvailabilities); + updateVoNrState(); + Log.d(TAG, "mHas5gCapability: " + mHas5gCapability + ",mIsNrEnabledFromCarrierConfig: " + mIsNrEnableFromCarrierConfig + ",mIsVonrEnabledFromCarrierConfig: " + mIsVonrEnabledFromCarrierConfig @@ -162,7 +170,7 @@ public class NrAdvancedCallingPreferenceController extends TelephonyTogglePrefer @Override public boolean isChecked() { - return mTelephonyManager.isVoNrEnabled(); + return mIsVoNrEnabled; } @VisibleForTesting @@ -174,6 +182,19 @@ public class NrAdvancedCallingPreferenceController extends TelephonyTogglePrefer return isCallStateIdle(); } + private void updateVoNrState() { + ThreadUtils.postOnBackgroundThread(() -> { + boolean result = mTelephonyManager.isVoNrEnabled(); + if (result != mIsVoNrEnabled) { + Log.i(TAG, "VoNr state : " + result); + mIsVoNrEnabled = result; + mHandler.post(() -> { + updateState(mPreference); + }); + } + }); + } + private class PhoneCallStateTelephonyCallback extends TelephonyCallback implements TelephonyCallback.CallStateListener { diff --git a/tests/unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.java b/tests/unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.java index 9d4082ecff6..053fb4d8916 100644 --- a/tests/unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/network/telephony/NrAdvancedCallingPreferenceControllerTest.java @@ -186,6 +186,7 @@ public class NrAdvancedCallingPreferenceControllerTest { doReturn(true).when(mTelephonyManager).isVoNrEnabled(); mPreference.setChecked(false); + mController.init(SUB_ID); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isTrue(); From 20c5966bf4e81c2a7b790e379c7103a23fa1262c Mon Sep 17 00:00:00 2001 From: Yi-Ling Chuang Date: Thu, 25 May 2023 12:17:54 +0800 Subject: [PATCH 5/5] Add a footer message for battery cycle count Bug: 276399056 Test: rebuild Change-Id: I3aea252ace55edbde117698c17f1b4e0ab601b89 --- res/values/strings.xml | 2 ++ res/xml/battery_info.xml | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/res/values/strings.xml b/res/values/strings.xml index 650ba3ef469..c494bff903f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5509,6 +5509,8 @@ < %1$s + + Due to quality inspections before shipping, the cycle count may not be zero on first use Process Stats diff --git a/res/xml/battery_info.xml b/res/xml/battery_info.xml index 186b4fe3ebf..8e3c31f22aa 100644 --- a/res/xml/battery_info.xml +++ b/res/xml/battery_info.xml @@ -42,4 +42,9 @@ settings:controller="com.android.settings.deviceinfo.batteryinfo.BatteryCycleCountPreferenceController" settings:enableCopying="true"/> +