diff --git a/res/values/strings.xml b/res/values/strings.xml index 650ba3ef469..349a4ab5768 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1031,13 +1031,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 @@ -5509,6 +5517,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"/> + diff --git a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java index 9aee087f3e0..f653942f3a9 100644 --- a/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java +++ b/src/com/android/settings/biometrics/fingerprint/FingerprintEnrollEnrolling.java @@ -511,7 +511,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; } } 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/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/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/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 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) 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();