diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ea4f6ffb2ee..4546a54cbbc 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -273,10 +273,6 @@
-
-
-
-
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 03e23190649..f193a3180f9 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -13404,8 +13404,10 @@
W+ network
SIM
-
+
DOWNLOADED SIM
+
+ DOWNLOADED SIMS
Active
diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
index aeb5e9f2fcc..44990ffa3ef 100644
--- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
+++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java
@@ -16,21 +16,33 @@
package com.android.settings.fuelgauge;
+import android.app.AppGlobals;
+import android.app.AppOpsManager;
import android.app.backup.BackupDataInputStream;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupHelper;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
import android.os.IDeviceIdleController;
import android.os.RemoteException;
import android.os.ParcelFileDescriptor;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
+import com.android.settings.fuelgauge.BatteryOptimizeUtils.AppUsageState;
+
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
/** An implementation to backup and restore battery configurations. */
public final class BatteryBackupHelper implements BackupHelper {
@@ -39,13 +51,24 @@ public final class BatteryBackupHelper implements BackupHelper {
private static final String DEVICE_IDLE_SERVICE = "deviceidle";
private static final boolean DEBUG = false;
- @VisibleForTesting
- static final CharSequence DELIMITER = ":";
- @VisibleForTesting
+ // Only the owner can see all apps.
+ private static final int RETRIEVE_FLAG_ADMIN =
+ PackageManager.MATCH_ANY_USER |
+ PackageManager.MATCH_DISABLED_COMPONENTS |
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+ private static final int RETRIEVE_FLAG =
+ PackageManager.MATCH_DISABLED_COMPONENTS |
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
+
+ static final CharSequence DELIMITER = ",";
+ static final CharSequence DELIMITER_MODE = "|";
static final String KEY_FULL_POWER_LIST = "full_power_list";
+ static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list";
@VisibleForTesting
IDeviceIdleController mIDeviceIdleController;
+ @VisibleForTesting
+ IPackageManager mIPackageManager;
private final Context mContext;
@@ -57,10 +80,13 @@ public final class BatteryBackupHelper implements BackupHelper {
public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
ParcelFileDescriptor newState) {
if (!isOwner()) {
- Log.w(TAG, "ignore the backup process for non-owner");
+ Log.w(TAG, "ignore performBackup() for non-owner");
return;
}
- backupFullPowerList(getIDeviceIdleController(), data);
+ final List allowlistedApps = backupFullPowerList(data);
+ if (allowlistedApps != null) {
+ backupOptimizationMode(data, allowlistedApps);
+ }
}
@Override
@@ -72,33 +98,57 @@ public final class BatteryBackupHelper implements BackupHelper {
public void writeNewStateDescription(ParcelFileDescriptor newState) {
}
- private void backupFullPowerList(
- IDeviceIdleController deviceIdleService, BackupDataOutput data) {
+ private List backupFullPowerList(BackupDataOutput data) {
final long timestamp = System.currentTimeMillis();
String[] allowlistedApps;
try {
- allowlistedApps = deviceIdleService.getFullPowerWhitelist();
+ allowlistedApps = getIDeviceIdleController().getFullPowerWhitelist();
} catch (RemoteException e) {
Log.e(TAG, "backupFullPowerList() failed", e);
- return;
+ return null;
}
// Ignores unexpected emptty result case.
if (allowlistedApps == null || allowlistedApps.length == 0) {
Log.w(TAG, "no data found in the getFullPowerList()");
- return;
+ return new ArrayList<>();
}
+
debugLog("allowlistedApps:" + Arrays.toString(allowlistedApps));
final String allowedApps = String.join(DELIMITER, allowlistedApps);
- final byte[] allowedAppsBytes = allowedApps.getBytes();
- try {
- data.writeEntityHeader(KEY_FULL_POWER_LIST, allowedAppsBytes.length);
- data.writeEntityData(allowedAppsBytes, allowedAppsBytes.length);
- } catch (IOException e) {
- Log.e(TAG, "backup getFullPowerList() failed", e);
- return;
- }
+ writeBackupData(data, KEY_FULL_POWER_LIST, allowedApps);
Log.d(TAG, String.format("backup getFullPowerList() size=%d in %d/ms",
allowlistedApps.length, (System.currentTimeMillis() - timestamp)));
+ return Arrays.asList(allowlistedApps);
+ }
+
+ @VisibleForTesting
+ void backupOptimizationMode(BackupDataOutput data, List allowlistedApps) {
+ final long timestamp = System.currentTimeMillis();
+ final List applications = getInstalledApplications();
+ if (applications == null || applications.isEmpty()) {
+ Log.w(TAG, "no data found in the getInstalledApplications()");
+ return;
+ }
+ final StringBuilder builder = new StringBuilder();
+ final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class);
+ // Converts application into the AppUsageState.
+ for (ApplicationInfo info : applications) {
+ final int mode = appOps.checkOpNoThrow(
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName);
+ final AppUsageState state = BatteryOptimizeUtils.getAppUsageState(
+ mode, allowlistedApps.contains(info.packageName));
+ // Ignores default optimized or unknown state.
+ if (state == AppUsageState.OPTIMIZED || state == AppUsageState.UNKNOWN) {
+ continue;
+ }
+ final String packageOptimizeMode = info.packageName + DELIMITER_MODE + state;
+ builder.append(packageOptimizeMode + DELIMITER);
+ debugLog(packageOptimizeMode);
+ }
+
+ writeBackupData(data, KEY_OPTIMIZATION_LIST, builder.toString());
+ Log.d(TAG, String.format("backup getInstalledApplications() size=%d in %d/ms",
+ applications.size(), (System.currentTimeMillis() - timestamp)));
}
// Provides an opportunity to inject mock IDeviceIdleController for testing.
@@ -111,10 +161,58 @@ public final class BatteryBackupHelper implements BackupHelper {
return mIDeviceIdleController;
}
+ private IPackageManager getIPackageManager() {
+ if (mIPackageManager != null) {
+ return mIPackageManager;
+ }
+ mIPackageManager = AppGlobals.getPackageManager();
+ return mIPackageManager;
+ }
+
+ private List getInstalledApplications() {
+ final List applications = new ArrayList<>();
+ final UserManager um = mContext.getSystemService(UserManager.class);
+ for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) {
+ try {
+ @SuppressWarnings("unchecked")
+ final ParceledListSlice infoList =
+ getIPackageManager().getInstalledApplications(
+ userInfo.isAdmin() ? RETRIEVE_FLAG_ADMIN : RETRIEVE_FLAG,
+ userInfo.id);
+ if (infoList != null) {
+ applications.addAll(infoList.getList());
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "getInstalledApplications() is failed", e);
+ return null;
+ }
+ }
+ // Removes the application which is disabled by the system.
+ for (int index = applications.size() - 1; index >= 0; index--) {
+ final ApplicationInfo info = applications.get(index);
+ if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER
+ && !info.enabled) {
+ applications.remove(index);
+ }
+ }
+ return applications;
+ }
+
private void debugLog(String debugContent) {
if (DEBUG) Log.d(TAG, debugContent);
}
+ private static void writeBackupData(
+ BackupDataOutput data, String dataKey, String dataContent) {
+ final byte[] dataContentBytes = dataContent.getBytes();
+ try {
+ data.writeEntityHeader(dataKey, dataContentBytes.length);
+ data.writeEntityData(dataContentBytes, dataContentBytes.length);
+ } catch (IOException e) {
+ Log.e(TAG, "writeBackupData() is failed for " + dataKey, e);
+ }
+ }
+
private static boolean isOwner() {
return UserHandle.myUserId() == UserHandle.USER_OWNER;
}
diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
index 4a560402667..0be90600ba7 100644
--- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
+++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java
@@ -59,20 +59,25 @@ public class BatteryOptimizeUtils {
mAllowListed = mPowerAllowListBackend.isAllowlisted(mPackageName);
}
- public AppUsageState getAppUsageState() {
- refreshState();
- if (!mAllowListed && mMode == AppOpsManager.MODE_IGNORED) {
+ /** Gets the {@link AppUsageState} based on mode and allowed list. */
+ public static AppUsageState getAppUsageState(int mode, boolean isAllowListed) {
+ if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) {
return AppUsageState.RESTRICTED;
- } else if (mAllowListed && mMode == AppOpsManager.MODE_ALLOWED) {
+ } else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
return AppUsageState.UNRESTRICTED;
- } else if (!mAllowListed && mMode == AppOpsManager.MODE_ALLOWED) {
+ } else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
return AppUsageState.OPTIMIZED;
} else {
- Log.d(TAG, "get unknown app usage state.");
return AppUsageState.UNKNOWN;
}
}
+ /** Gets the current {@link AppUsageState}. */
+ public AppUsageState getAppUsageState() {
+ refreshState();
+ return getAppUsageState(mMode, mAllowListed);
+ }
+
public void setAppUsageState(AppUsageState state) {
switch (state) {
case RESTRICTED:
diff --git a/src/com/android/settings/network/NetworkProviderCallsSmsController.java b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
index f83418ba551..c8b1c49b7bf 100644
--- a/src/com/android/settings/network/NetworkProviderCallsSmsController.java
+++ b/src/com/android/settings/network/NetworkProviderCallsSmsController.java
@@ -24,6 +24,7 @@ import android.telephony.ServiceState;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.LifecycleObserver;
@@ -44,12 +45,14 @@ public class NetworkProviderCallsSmsController extends AbstractPreferenceControl
private static final String TAG = "NetworkProviderCallsSmsController";
private static final String KEY = "calls_and_sms";
+ private static final String RTL_MARK = "\u200F";
private UserManager mUserManager;
private SubscriptionManager mSubscriptionManager;
private SubscriptionsChangeListener mSubscriptionsChangeListener;
private TelephonyManager mTelephonyManager;
private RestrictedPreference mPreference;
+ private boolean mIsRtlMode;
/**
* The summary text and click behavior of the "Calls & SMS" item on the
@@ -61,6 +64,8 @@ public class NetworkProviderCallsSmsController extends AbstractPreferenceControl
mUserManager = context.getSystemService(UserManager.class);
mSubscriptionManager = context.getSystemService(SubscriptionManager.class);
mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+ mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
if (lifecycle != null) {
mSubscriptionsChangeListener = new SubscriptionsChangeListener(context, this);
lifecycle.addObserver(this);
@@ -121,6 +126,10 @@ public class NetworkProviderCallsSmsController extends AbstractPreferenceControl
if (subInfo != subs.get(subs.size() - 1)) {
summary.append(", ");
}
+
+ if (mIsRtlMode) {
+ summary.insert(0, RTL_MARK).insert(summary.length(), RTL_MARK);
+ }
}
return summary;
}
@@ -152,12 +161,12 @@ public class NetworkProviderCallsSmsController extends AbstractPreferenceControl
}
@VisibleForTesting
- protected int getDefaultVoiceSubscriptionId(){
+ protected int getDefaultVoiceSubscriptionId() {
return SubscriptionManager.getDefaultVoiceSubscriptionId();
}
@VisibleForTesting
- protected int getDefaultSmsSubscriptionId(){
+ protected int getDefaultSmsSubscriptionId() {
return SubscriptionManager.getDefaultSmsSubscriptionId();
}
diff --git a/src/com/android/settings/network/NetworkProviderDownloadedSimsCategoryController.java b/src/com/android/settings/network/NetworkProviderDownloadedSimsCategoryController.java
index 199740fd202..f7c70f25a90 100644
--- a/src/com/android/settings/network/NetworkProviderDownloadedSimsCategoryController.java
+++ b/src/com/android/settings/network/NetworkProviderDownloadedSimsCategoryController.java
@@ -3,9 +3,11 @@ package com.android.settings.network;
import android.content.Context;
import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceScreen;
+import com.android.settings.R;
import com.android.settings.widget.PreferenceCategoryController;
import com.android.settingslib.core.lifecycle.Lifecycle;
import com.android.settingslib.core.lifecycle.LifecycleObserver;
@@ -15,6 +17,7 @@ public class NetworkProviderDownloadedSimsCategoryController extends
private static final String KEY_PREFERENCE_CATEGORY_DOWNLOADED_SIM =
"provider_model_downloaded_sim_category";
+ private PreferenceCategory mPreferenceCategory;
private NetworkProviderDownloadedSimListController mNetworkProviderDownloadedSimListController;
public NetworkProviderDownloadedSimsCategoryController(Context context, String key) {
@@ -44,9 +47,19 @@ public class NetworkProviderDownloadedSimsCategoryController extends
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
- PreferenceCategory preferenceCategory = screen.findPreference(
+ mPreferenceCategory = screen.findPreference(
KEY_PREFERENCE_CATEGORY_DOWNLOADED_SIM);
- preferenceCategory.setVisible(isAvailable());
+ mPreferenceCategory.setVisible(isAvailable());
mNetworkProviderDownloadedSimListController.displayPreference(screen);
}
+
+ @Override
+ public void updateState(Preference preference) {
+ super.updateState(preference);
+ int count = mPreferenceCategory.getPreferenceCount();
+ String title = mContext.getString(count > 1
+ ? R.string.downloaded_sims_category_title
+ : R.string.downloaded_sim_category_title);
+ mPreferenceCategory.setTitle(title);
+ }
}
diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java
index 35a34d471a8..fefae9ee98f 100644
--- a/src/com/android/settings/network/NetworkProviderSettings.java
+++ b/src/com/android/settings/network/NetworkProviderSettings.java
@@ -710,6 +710,11 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment
Log.i(TAG, "onWifiStateChanged called with wifi state: " + wifiState);
}
+ if (isFinishingOrDestroyed()) {
+ Log.w(TAG, "onWifiStateChanged shouldn't run when fragment is finishing or destroyed");
+ return;
+ }
+
switch (wifiState) {
case WifiManager.WIFI_STATE_ENABLED:
updateWifiEntryPreferences();
@@ -981,6 +986,11 @@ public class NetworkProviderSettings extends RestrictedSettingsFragment
private String getSavedNetworkSettingsSummaryText(
int numSavedNetworks, int numSavedSubscriptions) {
+ if (getResources() == null) {
+ Log.w(TAG, "getSavedNetworkSettingsSummaryText shouldn't run if resource is not ready");
+ return null;
+ }
+
if (numSavedSubscriptions == 0) {
return getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
numSavedNetworks, numSavedNetworks);
diff --git a/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java b/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java
index 36b19ba1e60..16ac8f7ab4e 100644
--- a/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java
+++ b/src/com/android/settings/network/telephony/CallsDefaultSubscriptionController.java
@@ -46,7 +46,7 @@ public class CallsDefaultSubscriptionController extends DefaultSubscriptionContr
@Override
public CharSequence getSummary() {
if (Utils.isProviderModelEnabled(mContext)) {
- return MobileNetworkUtils.getPreferredStatus(mContext, mManager, true);
+ return MobileNetworkUtils.getPreferredStatus(isRtlMode(), mContext, mManager, true);
} else {
return super.getSummary();
}
diff --git a/src/com/android/settings/network/telephony/DefaultSubscriptionController.java b/src/com/android/settings/network/telephony/DefaultSubscriptionController.java
index 4fb6cff75d0..d21d584a71c 100644
--- a/src/com/android/settings/network/telephony/DefaultSubscriptionController.java
+++ b/src/com/android/settings/network/telephony/DefaultSubscriptionController.java
@@ -26,6 +26,7 @@ import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
+import android.view.View;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
@@ -62,11 +63,14 @@ public abstract class DefaultSubscriptionController extends TelephonyBasePrefere
private static final ComponentName PSTN_CONNECTION_SERVICE_COMPONENT =
new ComponentName("com.android.phone",
"com.android.services.telephony.TelephonyConnectionService");
+ private boolean mIsRtlMode;
public DefaultSubscriptionController(Context context, String preferenceKey) {
super(context, preferenceKey);
mManager = context.getSystemService(SubscriptionManager.class);
mChangeListener = new SubscriptionsChangeListener(context, this);
+ mIsRtlMode = context.getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_RTL;
}
public void init(Lifecycle lifecycle) {
@@ -285,4 +289,8 @@ public abstract class DefaultSubscriptionController extends TelephonyBasePrefere
refreshSummary(mPreference);
}
}
+
+ boolean isRtlMode() {
+ return mIsRtlMode;
+ }
}
diff --git a/src/com/android/settings/network/telephony/MobileNetworkUtils.java b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
index 18984848b2f..6e5d4b7b8eb 100644
--- a/src/com/android/settings/network/telephony/MobileNetworkUtils.java
+++ b/src/com/android/settings/network/telephony/MobileNetworkUtils.java
@@ -99,6 +99,7 @@ public class MobileNetworkUtils {
"esim.enable_esim_system_ui_by_default";
private static final String LEGACY_ACTION_CONFIGURE_PHONE_ACCOUNT =
"android.telecom.action.CONNECTION_SERVICE_CONFIGURE";
+ private static final String RTL_MARK = "\u200F";
// The following constants are used to draw signal icon.
public static final int NO_CELL_DATA_TYPE_ICON = 0;
@@ -922,7 +923,7 @@ public class MobileNetworkUtils {
/**
* Returns preferred status of Calls & SMS separately when Provider Model is enabled.
*/
- public static CharSequence getPreferredStatus(Context context,
+ public static CharSequence getPreferredStatus(boolean isRtlMode, Context context,
SubscriptionManager subscriptionManager, boolean isPreferredCallStatus) {
final List subs = SubscriptionUtil.getActiveSubscriptions(
subscriptionManager);
@@ -956,6 +957,10 @@ public class MobileNetworkUtils {
if (subInfo != subs.get(subs.size() - 1)) {
summary.append(", ");
}
+
+ if (isRtlMode) {
+ summary.insert(0, RTL_MARK).insert(summary.length(), RTL_MARK);
+ }
}
return summary;
} else {
diff --git a/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java b/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java
index ca8c0f6c5d3..a73c62116a9 100644
--- a/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java
+++ b/src/com/android/settings/network/telephony/SmsDefaultSubscriptionController.java
@@ -62,7 +62,7 @@ public class SmsDefaultSubscriptionController extends DefaultSubscriptionControl
@Override
public CharSequence getSummary() {
if (Utils.isProviderModelEnabled(mContext)) {
- return MobileNetworkUtils.getPreferredStatus(mContext, mManager, false);
+ return MobileNetworkUtils.getPreferredStatus(isRtlMode(), mContext, mManager, false);
} else {
return super.getSummary();
}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
index 87aa8122caf..47fa59e6271 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java
@@ -27,11 +27,21 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.app.backup.BackupDataOutput;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
import android.os.IDeviceIdleController;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
+
+import java.util.Arrays;
+import java.util.List;
import org.junit.After;
import org.junit.Before;
@@ -57,13 +67,23 @@ public final class BatteryBackupHelperTest {
private BackupDataOutput mBackupDataOutput;
@Mock
private IDeviceIdleController mDeviceController;
+ @Mock
+ private IPackageManager mIPackageManager;
+ @Mock
+ private AppOpsManager mAppOpsManager;
+ @Mock
+ private UserManager mUserManager;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = spy(RuntimeEnvironment.application);
+ doReturn(mContext).when(mContext).getApplicationContext();
+ doReturn(mAppOpsManager).when(mContext).getSystemService(AppOpsManager.class);
+ doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
mBatteryBackupHelper = new BatteryBackupHelper(mContext);
mBatteryBackupHelper.mIDeviceIdleController = mDeviceController;
+ mBatteryBackupHelper.mIPackageManager = mIPackageManager;
}
@After
@@ -135,6 +155,78 @@ public final class BatteryBackupHelperTest {
verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt());
}
+ @Test
+ public void backupOptimizationMode_nullInstalledApps_ignoreBackupOptimization()
+ throws Exception {
+ final UserInfo userInfo =
+ new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0);
+ doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt());
+ doThrow(new RuntimeException())
+ .when(mIPackageManager)
+ .getInstalledApplications(anyInt(), anyInt());
+
+ mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, null);
+
+ verify(mBackupDataOutput, never()).writeEntityHeader(anyString(), anyInt());
+ }
+
+ @Test
+ public void backupOptimizationMode_backupOptimizationMode() throws Exception {
+ final String packageName1 = "com.android.testing.1";
+ final String packageName2 = "com.android.testing.2";
+ final String packageName3 = "com.android.testing.3";
+ final List allowlistedApps = Arrays.asList(packageName1);
+ createTestingData(packageName1, packageName2, packageName3);
+
+ mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, allowlistedApps);
+
+ final String expectedResult =
+ packageName1 + "|UNRESTRICTED," + packageName2 + "|RESTRICTED,";
+ final byte[] expectedBytes = expectedResult.getBytes();
+ verify(mBackupDataOutput).writeEntityHeader(
+ BatteryBackupHelper.KEY_OPTIMIZATION_LIST, expectedBytes.length);
+ verify(mBackupDataOutput).writeEntityData(expectedBytes, expectedBytes.length);
+ }
+
+ private void createTestingData(
+ String packageName1, String packageName2, String packageName3) throws Exception {
+ // Sets the getInstalledApplications() method for testing.
+ final UserInfo userInfo =
+ new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0);
+ doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt());
+ final ApplicationInfo applicationInfo1 = new ApplicationInfo();
+ applicationInfo1.enabled = true;
+ applicationInfo1.uid = 1;
+ applicationInfo1.packageName = packageName1;
+ final ApplicationInfo applicationInfo2 = new ApplicationInfo();
+ applicationInfo2.enabled = false;
+ applicationInfo2.uid = 2;
+ applicationInfo2.packageName = packageName2;
+ applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER;
+ final ApplicationInfo applicationInfo3 = new ApplicationInfo();
+ applicationInfo3.enabled = false;
+ applicationInfo3.uid = 3;
+ applicationInfo3.packageName = packageName3;
+ applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
+ doReturn(new ParceledListSlice(
+ Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3)))
+ .when(mIPackageManager)
+ .getInstalledApplications(anyInt(), anyInt());
+ // Sets the AppOpsManager for checkOpNoThrow() method.
+ doReturn(AppOpsManager.MODE_ALLOWED)
+ .when(mAppOpsManager)
+ .checkOpNoThrow(
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+ applicationInfo1.uid,
+ applicationInfo1.packageName);
+ doReturn(AppOpsManager.MODE_IGNORED)
+ .when(mAppOpsManager)
+ .checkOpNoThrow(
+ AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
+ applicationInfo2.uid,
+ applicationInfo2.packageName);
+ }
+
@Implements(UserHandle.class)
public static class ShadowUserHandle {
// Sets the default as thte OWNER role.