diff --git a/res/drawable/homepage_app_bar_background.xml b/res/drawable/homepage_app_bar_background.xml new file mode 100644 index 00000000000..6872ab77a6f --- /dev/null +++ b/res/drawable/homepage_app_bar_background.xml @@ -0,0 +1,24 @@ + + + + + + \ No newline at end of file diff --git a/res/layout/search_bar_two_pane_version.xml b/res/layout/search_bar_two_pane_version.xml index ede57226699..9ce220c8c5f 100644 --- a/res/layout/search_bar_two_pane_version.xml +++ b/res/layout/search_bar_two_pane_version.xml @@ -21,8 +21,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" - android:layout_marginEnd="@dimen/search_bar_margin" - android:layout_marginVertical="@dimen/search_bar_margin"> + android:layout_marginEnd="@dimen/search_bar_margin"> + android:layout_marginHorizontal="@dimen/homepage_app_bar_margin_horizontal_two_pane" + android:padding="@dimen/homepage_app_bar_padding_two_pane" + android:orientation="horizontal" + android:background="@drawable/homepage_app_bar_background"> - + - + - - - - - - \ No newline at end of file + diff --git a/res/layout/settings_homepage_container.xml b/res/layout/settings_homepage_container.xml index 3de5a8a2dc4..89d40d08b44 100644 --- a/res/layout/settings_homepage_container.xml +++ b/res/layout/settings_homepage_container.xml @@ -34,6 +34,11 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + + + + + + + diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 3d616a83f74..b88d01d12b9 100755 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -152,6 +152,12 @@ 8dp 16dp 24dp + 32dp + 6dp + + 10dp + + 18dp 16dp diff --git a/src/com/android/settings/DefaultRingtonePreference.java b/src/com/android/settings/DefaultRingtonePreference.java index 914c4b214d1..824a5a05dc1 100644 --- a/src/com/android/settings/DefaultRingtonePreference.java +++ b/src/com/android/settings/DefaultRingtonePreference.java @@ -46,6 +46,11 @@ public class DefaultRingtonePreference extends RingtonePreference { @Override protected void onSaveRingtone(Uri ringtoneUri) { + if (ringtoneUri == null) { + setActualDefaultRingtoneUri(ringtoneUri); + return; + } + String mimeType = getContext().getContentResolver().getType(ringtoneUri); if (mimeType == null) { Log.e(TAG, "onSaveRingtone for URI:" + ringtoneUri diff --git a/src/com/android/settings/applications/AppStateBaseBridge.java b/src/com/android/settings/applications/AppStateBaseBridge.java index d7f253b6c0b..a4bdabf43e3 100644 --- a/src/com/android/settings/applications/AppStateBaseBridge.java +++ b/src/com/android/settings/applications/AppStateBaseBridge.java @@ -52,7 +52,6 @@ public abstract class AppStateBaseBridge implements ApplicationsState.Callbacks public void resume(boolean forceLoadAllApps) { mForceLoadAllApps = forceLoadAllApps; - mHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ALL); if (mForceLoadAllApps) { mAppSession.onResume(); } else { diff --git a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java index 9cb5a3f851a..708f8b7004d 100644 --- a/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java +++ b/src/com/android/settings/applications/manageapplications/ResetAppsHelper.java @@ -38,6 +38,7 @@ import android.util.Log; import androidx.appcompat.app.AlertDialog; import com.android.settings.R; +import com.android.settings.fuelgauge.BatteryOptimizeUtils; import java.util.Arrays; import java.util.List; @@ -152,6 +153,7 @@ public class ResetAppsHelper implements DialogInterface.OnClickListener, } catch (RemoteException e) { } mAom.resetAllModes(); + BatteryOptimizeUtils.resetAppOptimizationMode(mContext, mIPm, mAom); final int[] restrictedUids = mNpm.getUidsWithPolicy( POLICY_REJECT_METERED_BACKGROUND); final int currentUserId = ActivityManager.getCurrentUser(); diff --git a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java index f5e21ddc1b2..9860240b289 100644 --- a/src/com/android/settings/fuelgauge/BatteryBackupHelper.java +++ b/src/com/android/settings/fuelgauge/BatteryBackupHelper.java @@ -24,16 +24,13 @@ 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.Build; import android.os.IDeviceIdleController; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; -import android.os.UserManager; +import android.util.ArraySet; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -53,22 +50,13 @@ public final class BatteryBackupHelper implements BackupHelper { private static final String DEVICE_IDLE_SERVICE = "deviceidle"; private static final boolean DEBUG = Build.TYPE.equals("userdebug"); - // 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 String DELIMITER = ","; static final String DELIMITER_MODE = ":"; static final String KEY_FULL_POWER_LIST = "full_power_list"; static final String KEY_OPTIMIZATION_LIST = "optimization_mode_list"; @VisibleForTesting - List mTestApplicationInfoList = null; + ArraySet mTestApplicationInfoList = null; @VisibleForTesting PowerAllowlistBackend mPowerAllowlistBackend; @@ -146,7 +134,7 @@ public final class BatteryBackupHelper implements BackupHelper { @VisibleForTesting void backupOptimizationMode(BackupDataOutput data, List allowlistedApps) { final long timestamp = System.currentTimeMillis(); - final List applications = getInstalledApplications(); + final ArraySet applications = getInstalledApplications(); if (applications == null || applications.isEmpty()) { Log.w(TAG, "no data found in the getInstalledApplications()"); return; @@ -269,36 +257,11 @@ public final class BatteryBackupHelper implements BackupHelper { || powerAllowlistBackend.isDefaultActiveApp(packageName); } - private List getInstalledApplications() { + private ArraySet getInstalledApplications() { if (mTestApplicationInfoList != null) { return mTestApplicationInfoList; } - 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; + return BatteryOptimizeUtils.getInstalledApplications(mContext, getIPackageManager()); } private void debugLog(String debugContent) { diff --git a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java index 9306a34df67..dbfacc6907c 100644 --- a/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java +++ b/src/com/android/settings/fuelgauge/BatteryOptimizeUtils.java @@ -19,7 +19,15 @@ package com.android.settings.fuelgauge; import android.annotation.IntDef; import android.app.AppOpsManager; 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.AsyncTask; +import android.os.UserHandle; +import android.os.UserManager; +import android.util.ArraySet; import android.util.Log; import androidx.annotation.VisibleForTesting; @@ -43,6 +51,15 @@ public class BatteryOptimizeUtils { private final String mPackageName; private final int mUid; + // If current user is admin, match apps from all users. Otherwise, only match the currect user. + 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; + // Optimization modes. static final int MODE_UNKNOWN = 0; static final int MODE_RESTRICTED = 1; @@ -90,28 +107,20 @@ public class BatteryOptimizeUtils { return getAppOptimizationMode(mMode, mAllowListed); } + /** Resets optimization mode for all applications. */ + public static void resetAppOptimizationMode( + Context context, IPackageManager ipm, AppOpsManager aom) { + resetAppOptimizationMode(context, ipm, aom, + PowerAllowlistBackend.getInstance(context), BatteryUtils.getInstance(context)); + } + /** Sets the {@link OptimizationMode} for associated app. */ public void setAppUsageState(@OptimizationMode int mode) { if (getAppOptimizationMode(mMode, mAllowListed) == mode) { Log.w(TAG, "set the same optimization mode for: " + mPackageName); return; } - - AsyncTask.execute(() -> { - switch (mode) { - case MODE_RESTRICTED: - setAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false); - break; - case MODE_UNRESTRICTED: - setAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true); - break; - case MODE_OPTIMIZED: - setAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); - break; - default: - Log.d(TAG, "set unknown app optimization mode."); - } - }); + setAppUsageStateInternal(mode, mUid, mPackageName, mBatteryUtils, mPowerAllowListBackend); } /** @@ -126,25 +135,111 @@ public class BatteryOptimizeUtils { */ public boolean isSystemOrDefaultApp() { mPowerAllowListBackend.refreshList(); + return isSystemOrDefaultApp(mPowerAllowListBackend, mPackageName); + } - return mPowerAllowListBackend.isSysAllowlisted(mPackageName) - || mPowerAllowListBackend.isDefaultActiveApp(mPackageName); + /** + * Gets the list of installed applications. + */ + public static ArraySet getInstalledApplications( + Context context, IPackageManager ipm) { + final ArraySet applications = new ArraySet<>(); + final UserManager um = context.getSystemService(UserManager.class); + for (UserInfo userInfo : um.getProfiles(UserHandle.myUserId())) { + try { + @SuppressWarnings("unchecked") + final ParceledListSlice infoList = ipm.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. + applications.removeIf( + info -> info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER + && !info.enabled); + return applications; + } + + @VisibleForTesting + static void resetAppOptimizationMode( + Context context, IPackageManager ipm, AppOpsManager aom, + PowerAllowlistBackend allowlistBackend, BatteryUtils batteryUtils) { + final ArraySet applications = getInstalledApplications(context, ipm); + if (applications == null || applications.isEmpty()) { + Log.w(TAG, "no data found in the getInstalledApplications()"); + return; + } + + allowlistBackend.refreshList(); + // Resets optimization mode for each application. + for (ApplicationInfo info : applications) { + final int mode = aom.checkOpNoThrow( + AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, info.uid, info.packageName); + @OptimizationMode + final int optimizationMode = getAppOptimizationMode( + mode, allowlistBackend.isAllowlisted(info.packageName)); + // Ignores default optimized/unknown state or system/default apps. + if (optimizationMode == MODE_OPTIMIZED + || optimizationMode == MODE_UNKNOWN + || isSystemOrDefaultApp(allowlistBackend, info.packageName)) { + continue; + } + + // Resets to the default mode: MODE_OPTIMIZED. + setAppUsageStateInternal(MODE_OPTIMIZED, info.uid, info.packageName, batteryUtils, + allowlistBackend); + } } String getPackageName() { return mPackageName == null ? UNKNOWN_PACKAGE : mPackageName; } - private void setAppOptimizationMode(int appStandbyMode, boolean allowListed) { + private static boolean isSystemOrDefaultApp( + PowerAllowlistBackend powerAllowlistBackend, String packageName) { + return powerAllowlistBackend.isSysAllowlisted(packageName) + || powerAllowlistBackend.isDefaultActiveApp(packageName); + } + + private static void setAppUsageStateInternal( + @OptimizationMode int mode, int uid, String packageName, BatteryUtils batteryUtils, + PowerAllowlistBackend powerAllowlistBackend) { + if (mode == MODE_UNKNOWN) { + Log.d(TAG, "set unknown app optimization mode."); + return; + } + + // MODE_RESTRICTED = AppOpsManager.MODE_IGNORED + !allowListed + // MODE_UNRESTRICTED = AppOpsManager.MODE_ALLOWED + allowListed + // MODE_OPTIMIZED = AppOpsManager.MODE_ALLOWED + !allowListed + final int appOpsManagerMode = + mode == MODE_RESTRICTED ? AppOpsManager.MODE_IGNORED : AppOpsManager.MODE_ALLOWED; + final boolean allowListed = mode == MODE_UNRESTRICTED; + + AsyncTask.execute(() -> { + setAppOptimizationModeInternal(appOpsManagerMode, allowListed, uid, packageName, + batteryUtils, powerAllowlistBackend); + }); + } + + private static void setAppOptimizationModeInternal( + int appStandbyMode, boolean allowListed, int uid, String packageName, + BatteryUtils batteryUtils, PowerAllowlistBackend powerAllowlistBackend) { try { - mBatteryUtils.setForceAppStandby(mUid, mPackageName, appStandbyMode); + batteryUtils.setForceAppStandby(uid, packageName, appStandbyMode); if (allowListed) { - mPowerAllowListBackend.addApp(mPackageName); + powerAllowlistBackend.addApp(packageName); } else { - mPowerAllowListBackend.removeApp(mPackageName); + powerAllowlistBackend.removeApp(packageName); } } catch (Exception e) { - Log.e(TAG, "set OPTIMIZED failed for " + mPackageName, e); + Log.e(TAG, "set OPTIMIZATION MODE failed for " + packageName, e); } } @@ -154,8 +249,6 @@ public class BatteryOptimizeUtils { mMode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mPackageName); Log.d(TAG, String.format("refresh %s state, allowlisted = %s, mode = %d", - mPackageName, - mAllowListed, - mMode)); + mPackageName, mAllowListed, mMode)); } } diff --git a/src/com/android/settings/homepage/SettingsHomepageActivity.java b/src/com/android/settings/homepage/SettingsHomepageActivity.java index 86b123be49b..c7bd3b5712e 100644 --- a/src/com/android/settings/homepage/SettingsHomepageActivity.java +++ b/src/com/android/settings/homepage/SettingsHomepageActivity.java @@ -89,7 +89,6 @@ public class SettingsHomepageActivity extends FragmentActivity implements private TopLevelSettings mMainFragment; private View mHomepageView; - private View mAppBar; private View mSuggestionView; private View mTwoPaneSuggestionView; private CategoryMixin mCategoryMixin; @@ -174,8 +173,7 @@ public class SettingsHomepageActivity extends FragmentActivity implements mSplitController = SplitController.getInstance(); mIsTwoPane = mSplitController.isActivityEmbedded(this); - mAppBar = findViewById(R.id.app_bar_container); - mAppBar.setMinimumHeight(getSearchBoxHeight()); + updateAppBarMinHeight(); initHomepageContainer(); updateHomepageAppBar(); updateHomepageBackground(); @@ -491,12 +489,15 @@ public class SettingsHomepageActivity extends FragmentActivity implements if (!mIsEmbeddingActivityEnabled) { return; } + updateAppBarMinHeight(); if (mIsTwoPane) { findViewById(R.id.homepage_app_bar_regular_phone_view).setVisibility(View.GONE); findViewById(R.id.homepage_app_bar_two_pane_view).setVisibility(View.VISIBLE); + findViewById(R.id.suggestion_container_two_pane).setVisibility(View.VISIBLE); } else { findViewById(R.id.homepage_app_bar_regular_phone_view).setVisibility(View.VISIBLE); findViewById(R.id.homepage_app_bar_two_pane_view).setVisibility(View.GONE); + findViewById(R.id.suggestion_container_two_pane).setVisibility(View.GONE); } } @@ -507,19 +508,20 @@ public class SettingsHomepageActivity extends FragmentActivity implements if (mIsTwoPane) { int padding = getResources().getDimensionPixelSize( R.dimen.homepage_padding_horizontal_two_pane); - mAppBar.setPaddingRelative(padding, 0, padding, 0); mMainFragment.setPaddingHorizontal(padding); } else { - mAppBar.setPaddingRelative(0, 0, 0, 0); mMainFragment.setPaddingHorizontal(0); } mMainFragment.updatePreferencePadding(mIsTwoPane); } - private int getSearchBoxHeight() { + private void updateAppBarMinHeight() { final int searchBarHeight = getResources().getDimensionPixelSize(R.dimen.search_bar_height); - final int searchBarMargin = getResources().getDimensionPixelSize(R.dimen.search_bar_margin); - return searchBarHeight + searchBarMargin * 2; + final int margin = getResources().getDimensionPixelSize( + mIsEmbeddingActivityEnabled && mIsTwoPane + ? R.dimen.homepage_app_bar_padding_two_pane + : R.dimen.search_bar_margin); + findViewById(R.id.app_bar_container).setMinimumHeight(searchBarHeight + margin * 2); } private static class SuggestionFragCreator implements FragmentCreator { diff --git a/src/com/android/settings/wfd/WifiDisplayPreferenceController.java b/src/com/android/settings/wfd/WifiDisplayPreferenceController.java index a83a56b5945..b15396b7ed2 100644 --- a/src/com/android/settings/wfd/WifiDisplayPreferenceController.java +++ b/src/com/android/settings/wfd/WifiDisplayPreferenceController.java @@ -19,6 +19,7 @@ package com.android.settings.wfd; import android.content.Context; import android.media.MediaRouter; import android.media.MediaRouter.RouteInfo; +import android.text.TextUtils; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; @@ -87,7 +88,12 @@ public class WifiDisplayPreferenceController extends BasePreferenceController im final MediaRouter.RouteInfo route = mRouter.getRouteAt(i); if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY) && route.isSelected() && !route.isConnecting()) { - summary = mContext.getString(R.string.wifi_display_status_connected); + CharSequence status = route.getStatus(); + if (!TextUtils.isEmpty(status)) { + summary = status; + } else { + summary = mContext.getString(R.string.wifi_display_status_connected); + } break; } } diff --git a/src/com/android/settings/wfd/WifiDisplaySettings.java b/src/com/android/settings/wfd/WifiDisplaySettings.java index 19b6c5457d7..8562e5fd68b 100755 --- a/src/com/android/settings/wfd/WifiDisplaySettings.java +++ b/src/com/android/settings/wfd/WifiDisplaySettings.java @@ -38,6 +38,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.provider.Settings; +import android.text.TextUtils; import android.util.Slog; import android.util.TypedValue; import android.view.Menu; @@ -678,7 +679,12 @@ public final class WifiDisplaySettings extends SettingsPreferenceFragment implem if (route.isConnecting()) { setSummary(R.string.wifi_display_status_connecting); } else { - setSummary(R.string.wifi_display_status_connected); + CharSequence status = route.getStatus(); + if (!TextUtils.isEmpty(status)) { + setSummary(status); + } else { + setSummary(R.string.wifi_display_status_connected); + } } } else { if (isEnabled()) { diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java index ca1797ac9e9..14bbeea7187 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryBackupHelperTest.java @@ -25,6 +25,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; @@ -48,6 +49,7 @@ import android.os.IDeviceIdleController; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; +import android.util.ArraySet; import com.android.settingslib.fuelgauge.PowerAllowlistBackend; @@ -193,7 +195,7 @@ public final class BatteryBackupHelperTest { doReturn(Arrays.asList(userInfo)).when(mUserManager).getProfiles(anyInt()); doThrow(new RuntimeException()) .when(mIPackageManager) - .getInstalledApplications(anyInt(), anyInt()); + .getInstalledApplications(anyLong(), anyInt()); mBatteryBackupHelper.backupOptimizationMode(mBackupDataOutput, null); @@ -369,7 +371,7 @@ public final class BatteryBackupHelperTest { doReturn(new ParceledListSlice( Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3))) .when(mIPackageManager) - .getInstalledApplications(anyInt(), anyInt()); + .getInstalledApplications(anyLong(), anyInt()); // Sets the AppOpsManager for checkOpNoThrow() method. doReturn(AppOpsManager.MODE_ALLOWED) .when(mAppOpsManager) @@ -384,7 +386,7 @@ public final class BatteryBackupHelperTest { applicationInfo2.uid, applicationInfo2.packageName); mBatteryBackupHelper.mTestApplicationInfoList = - Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3); + new ArraySet<>(Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3)); } @Implements(UserHandle.class) diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java index c154f426b30..b223d0bc755 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryOptimizeUtilsTest.java @@ -22,26 +22,43 @@ import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRIC import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.app.AppOpsManager; 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.UserManager; +import android.util.ArraySet; import com.android.settingslib.fuelgauge.PowerAllowlistBackend; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import java.util.Arrays; import java.util.concurrent.TimeUnit; @RunWith(RobolectricTestRunner.class) @@ -50,9 +67,11 @@ public class BatteryOptimizeUtilsTest { private static final int UID = 12345; private static final String PACKAGE_NAME = "com.android.app"; - @Mock BatteryUtils mMockBatteryUtils; - @Mock AppOpsManager mMockAppOpsManager; - @Mock PowerAllowlistBackend mMockBackend; + @Mock private BatteryUtils mMockBatteryUtils; + @Mock private AppOpsManager mMockAppOpsManager; + @Mock private PowerAllowlistBackend mMockBackend; + @Mock private IPackageManager mMockIPackageManager; + @Mock private UserManager mMockUserManager; private Context mContext; private BatteryOptimizeUtils mBatteryOptimizeUtils; @@ -68,6 +87,7 @@ public class BatteryOptimizeUtilsTest { // Sets the default mode as MODE_RESTRICTED. mBatteryOptimizeUtils.mMode = AppOpsManager.MODE_IGNORED; mBatteryOptimizeUtils.mAllowListed = false; + doReturn(mMockUserManager).when(mContext).getSystemService(UserManager.class); } @Test @@ -135,9 +155,7 @@ public class BatteryOptimizeUtilsTest { mBatteryOptimizeUtils.setAppUsageState(MODE_RESTRICTED); TimeUnit.SECONDS.sleep(1); - verify(mMockBatteryUtils).setForceAppStandby(UID, - PACKAGE_NAME, AppOpsManager.MODE_IGNORED); - verify(mMockBackend).removeApp(PACKAGE_NAME); + verifySetAppOptimizationMode(AppOpsManager.MODE_IGNORED, /* allowListed */ false); } @Test @@ -145,9 +163,7 @@ public class BatteryOptimizeUtilsTest { mBatteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED); TimeUnit.SECONDS.sleep(1); - verify(mMockBatteryUtils).setForceAppStandby(UID, - PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); - verify(mMockBackend).addApp(PACKAGE_NAME); + verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ true); } @Test @@ -155,9 +171,7 @@ public class BatteryOptimizeUtilsTest { mBatteryOptimizeUtils.setAppUsageState(MODE_OPTIMIZED); TimeUnit.SECONDS.sleep(1); - verify(mMockBatteryUtils).setForceAppStandby(UID, - PACKAGE_NAME, AppOpsManager.MODE_ALLOWED); - verify(mMockBackend).removeApp(PACKAGE_NAME); + verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); } @Test @@ -172,4 +186,148 @@ public class BatteryOptimizeUtilsTest { verifyZeroInteractions(mMockBackend); verifyZeroInteractions(mMockBatteryUtils); } + + @Test + public void testGetInstalledApplications_returnEmptyArray() { + assertTrue(BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager) + .isEmpty()); + } + + @Test + public void testGetInstalledApplications_returnNull() throws Exception { + final UserInfo userInfo = + new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0); + doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt()); + doThrow(new RuntimeException()) + .when(mMockIPackageManager) + .getInstalledApplications(anyLong(), anyInt()); + + assertNull(BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager)); + } + + @Test + public void testGetInstalledApplications_returnInstalledApps() throws Exception { + final UserInfo userInfo = + new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0); + doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt()); + + final ApplicationInfo applicationInfo1 = new ApplicationInfo(); + applicationInfo1.enabled = true; + applicationInfo1.uid = 1; + final ApplicationInfo applicationInfo2 = new ApplicationInfo(); + applicationInfo2.enabled = false; + applicationInfo2.uid = 2; + applicationInfo2.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; + final ApplicationInfo applicationInfo3 = new ApplicationInfo(); + applicationInfo3.enabled = false; + applicationInfo3.uid = 3; + applicationInfo3.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED; + final ApplicationInfo applicationInfo4 = new ApplicationInfo(); + applicationInfo4.enabled = true; + applicationInfo4.uid = 4; + applicationInfo4.enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; + doReturn(new ParceledListSlice( + Arrays.asList(applicationInfo1, applicationInfo2, applicationInfo3, applicationInfo4))) + .when(mMockIPackageManager) + .getInstalledApplications(anyLong(), anyInt()); + + final ArraySet applications = + BatteryOptimizeUtils.getInstalledApplications(mContext, mMockIPackageManager); + assertThat(applications.size()).isEqualTo(3); + // applicationInfo3 should be filtered. + assertTrue(applications.contains(applicationInfo1)); + assertTrue(applications.contains(applicationInfo2)); + assertFalse(applications.contains(applicationInfo3)); + assertTrue(applications.contains(applicationInfo4)); + } + + @Test + public void testResetAppOptimizationMode_Optimized_verifyAction() throws Exception { + runTestForResetWithMode( + AppOpsManager.MODE_ALLOWED, /* allowListed */ false, + /* isSystemOrDefaultApp */ false); + + verifyZeroInteractions(mMockBatteryUtils); + + final InOrder inOrder = inOrder(mMockBackend); + inOrder.verify(mMockBackend).refreshList(); + inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME); + verifyNoMoreInteractions(mMockBackend); + } + + @Test + public void testResetAppOptimizationMode_SystemOrDefault_verifyAction() throws Exception { + runTestForResetWithMode( + AppOpsManager.MODE_ALLOWED, /* allowListed */ true, + /* isSystemOrDefaultApp */ true); + + verifyZeroInteractions(mMockBatteryUtils); + + final InOrder inOrder = inOrder(mMockBackend); + inOrder.verify(mMockBackend).refreshList(); + inOrder.verify(mMockBackend).isAllowlisted(PACKAGE_NAME); + inOrder.verify(mMockBackend).isSysAllowlisted(PACKAGE_NAME); + verifyNoMoreInteractions(mMockBackend); + } + + @Test + public void testResetAppOptimizationMode_Restricted_verifyAction() throws Exception { + runTestForResetWithMode( + AppOpsManager.MODE_IGNORED, /* allowListed */ false, + /* isSystemOrDefaultApp */ false); + + verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); + } + + @Test + public void testResetAppOptimizationMode_Unrestricted_verifyAction() throws Exception { + runTestForResetWithMode( + AppOpsManager.MODE_ALLOWED, /* allowListed */ true, + /* isSystemOrDefaultApp */ false); + + verifySetAppOptimizationMode(AppOpsManager.MODE_ALLOWED, /* allowListed */ false); + } + + private void runTestForResetWithMode( + int appStandbyMode, boolean allowListed, boolean isSystemOrDefaultApp) + throws Exception { + final UserInfo userInfo = + new UserInfo(/*userId=*/ 0, /*userName=*/ "google", /*flag=*/ 0); + doReturn(Arrays.asList(userInfo)).when(mMockUserManager).getProfiles(anyInt()); + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.uid = UID; + applicationInfo.packageName = PACKAGE_NAME; + applicationInfo.enabled = true; + doReturn(new ParceledListSlice( + Arrays.asList(applicationInfo))) + .when(mMockIPackageManager) + .getInstalledApplications(anyLong(), anyInt()); + + doReturn(appStandbyMode) + .when(mMockAppOpsManager) + .checkOpNoThrow(anyInt(), anyInt(), anyString()); + doReturn(allowListed) + .when(mMockBackend) + .isAllowlisted(anyString()); + doReturn(isSystemOrDefaultApp) + .when(mMockBackend) + .isSysAllowlisted(anyString()); + doReturn(isSystemOrDefaultApp) + .when(mMockBackend) + .isDefaultActiveApp(anyString()); + + BatteryOptimizeUtils.resetAppOptimizationMode( + mContext, mMockIPackageManager, mMockAppOpsManager, mMockBackend, + mMockBatteryUtils); + TimeUnit.SECONDS.sleep(1); + } + + private void verifySetAppOptimizationMode(int appStandbyMode, boolean allowListed) { + verify(mMockBatteryUtils).setForceAppStandby(UID, PACKAGE_NAME, appStandbyMode); + if (allowListed) { + verify(mMockBackend).addApp(PACKAGE_NAME); + } else { + verify(mMockBackend).removeApp(PACKAGE_NAME); + } + } }