Loading res/values/strings.xml +6 −0 Original line number Diff line number Diff line Loading @@ -4630,6 +4630,12 @@ <string name="background_activity_summary_off">App\'s background activity is limited when not in use</string> <!-- Summary for the background activity when it is disabled [CHAR_LIMIT=120] --> <string name="background_activity_summary_disabled">App not allowed to run in background</string> <!-- TODO: Pending UX review. Summary for the background activity when it is whitlisted [CHAR_LIMIT=120] --> <string name="background_activity_summary_whitelisted">App can not be optimized for battery use</string> <!-- TODO: Pending UX review. Title for the warning dialog to show to the user when limiting background activity for an app --> <string name="background_activity_warning_dialog_title">Limit background activity?</string> <!-- TODO: Pending UX review. Text for the warning dialog to show to the user when limiting background activity for an app --> <string name="background_activity_warning_dialog_text">If you limit background activity for an app, it may misbehave</string> <!-- Title for the screen usage in power use UI [CHAR_LIMIT=60] --> <string name="device_screen_usage">Screen usage since full charge</string> Loading src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +12 −2 Original line number Diff line number Diff line Loading @@ -68,7 +68,8 @@ import java.util.List; public class AdvancedPowerUsageDetail extends DashboardFragment implements ButtonActionDialogFragment.AppButtonsDialogListener, AnomalyDialogFragment.AnomalyDialogListener, LoaderManager.LoaderCallbacks<List<Anomaly>> { LoaderManager.LoaderCallbacks<List<Anomaly>>, BackgroundActivityPreferenceController.WarningConfirmationListener { public static final String TAG = "AdvancedPowerUsageDetail"; public static final String EXTRA_UID = "extra_uid"; Loading Loading @@ -109,6 +110,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements @VisibleForTesting AnomalySummaryPreferenceController mAnomalySummaryPreferenceController; private AppButtonsPreferenceController mAppButtonsPreferenceController; private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController; private DevicePolicyManagerWrapper mDpm; private UserManager mUserManager; Loading Loading @@ -319,7 +321,9 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements final int uid = bundle.getInt(EXTRA_UID, 0); final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); controllers.add(new BackgroundActivityPreferenceController(context, uid)); mBackgroundActivityPreferenceController = new BackgroundActivityPreferenceController( context, this, uid, packageName); controllers.add(mBackgroundActivityPreferenceController); controllers.add(new BatteryOptimizationPreferenceController( (SettingsActivity) getActivity(), this, packageName)); mAppButtonsPreferenceController = new AppButtonsPreferenceController( Loading Loading @@ -364,4 +368,10 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements public void onLoaderReset(Loader<List<Anomaly>> loader) { } @Override public void onLimitBackgroundActivity() { mBackgroundActivityPreferenceController.setUnchecked( findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); } } src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java +93 −28 Original line number Diff line number Diff line Loading @@ -14,12 +14,17 @@ package com.android.settings.fuelgauge; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.Dialog; import android.app.Fragment; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; import android.support.v14.preference.SwitchPreference; Loading @@ -29,6 +34,7 @@ import android.util.Log; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.enterprise.DevicePolicyManagerWrapper; import com.android.settings.enterprise.DevicePolicyManagerWrapperImpl; import com.android.settingslib.core.AbstractPreferenceController; Loading @@ -45,54 +51,72 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo private final PackageManager mPackageManager; private final AppOpsManager mAppOpsManager; private final UserManager mUserManager; private final String[] mPackages; private final int mUid; @VisibleForTesting DevicePolicyManagerWrapper mDpm; private Fragment mFragment; private String mTargetPackage; private boolean mIsPreOApp; private PowerWhitelistBackend mPowerWhitelistBackend; public BackgroundActivityPreferenceController(Context context, Fragment fragment, int uid, String packageName) { this(context, fragment, uid, packageName, PowerWhitelistBackend.getInstance()); } public BackgroundActivityPreferenceController(Context context, int uid) { @VisibleForTesting BackgroundActivityPreferenceController(Context context, Fragment fragment, int uid, String packageName, PowerWhitelistBackend backend) { super(context); mPowerWhitelistBackend = backend; mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mDpm = new DevicePolicyManagerWrapperImpl( (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE)); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mUid = uid; mPackages = mPackageManager.getPackagesForUid(mUid); mFragment = fragment; mTargetPackage = packageName; mIsPreOApp = isLegacyApp(packageName); } @Override public void updateState(Preference preference) { final int mode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage); .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage); final boolean whitelisted = mPowerWhitelistBackend.isWhitelisted(mTargetPackage); // Set checked or not before we may set it disabled if (mode != AppOpsManager.MODE_ERRORED) { final boolean checked = mode != AppOpsManager.MODE_IGNORED; final boolean checked = whitelisted || mode != AppOpsManager.MODE_IGNORED; ((SwitchPreference) preference).setChecked(checked); } if (mode == AppOpsManager.MODE_ERRORED if (whitelisted || mode == AppOpsManager.MODE_ERRORED || Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mTargetPackage)) { preference.setEnabled(false); } else { preference.setEnabled(true); } updateSummary(preference); } @Override public boolean isAvailable() { if (mPackages == null) { return false; } for (final String packageName : mPackages) { if (isLegacyApp(packageName)) { mTargetPackage = packageName; return true; } return mTargetPackage != null; } return false; /** * Called from the warning dialog, if the user decides to go ahead and disable background * activity for this package */ public void setUnchecked(Preference preference) { if (mIsPreOApp) { mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage, AppOpsManager.MODE_IGNORED); } mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage, AppOpsManager.MODE_IGNORED); ((SwitchPreference) preference).setChecked(false); updateSummary(preference); } @Override Loading @@ -102,19 +126,23 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo @Override public boolean onPreferenceChange(Preference preference, Object newValue) { boolean switchOn = (Boolean) newValue; final boolean switchOn = (Boolean) newValue; if (!switchOn) { final WarningDialogFragment dialogFragment = new WarningDialogFragment(); dialogFragment.setTargetFragment(mFragment, 0); dialogFragment.show(mFragment.getFragmentManager(), TAG); return false; } if (mIsPreOApp) { mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage, switchOn ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); AppOpsManager.MODE_ALLOWED); } mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage, AppOpsManager.MODE_ALLOWED); updateSummary(preference); return true; } @VisibleForTesting String getTargetPackage() { return mTargetPackage; } @VisibleForTesting boolean isLegacyApp(final String packageName) { try { Loading @@ -131,8 +159,12 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo @VisibleForTesting void updateSummary(Preference preference) { if (mPowerWhitelistBackend.isWhitelisted(mTargetPackage)) { preference.setSummary(R.string.background_activity_summary_whitelisted); return; } final int mode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage); .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage); if (mode == AppOpsManager.MODE_ERRORED) { preference.setSummary(R.string.background_activity_summary_disabled); Loading @@ -142,4 +174,37 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo : R.string.background_activity_summary_off); } } interface WarningConfirmationListener { void onLimitBackgroundActivity(); } /** * Warning dialog to show to the user as turning off background activity can lead to * apps misbehaving as their background task scheduling guarantees will no longer be honored. */ public static class WarningDialogFragment extends InstrumentedDialogFragment { @Override public int getMetricsCategory() { // TODO (b/65494831): add metric id return 0; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final WarningConfirmationListener listener = (WarningConfirmationListener) getTargetFragment(); return new AlertDialog.Builder(getContext()) .setTitle(R.string.background_activity_warning_dialog_title) .setMessage(R.string.background_activity_warning_dialog_text) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.onLimitBackgroundActivity(); } }) .setNegativeButton(R.string.dlg_cancel, null) .create(); } } } tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java +79 −55 Original line number Diff line number Diff line Loading @@ -16,14 +16,25 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.UserManager; import android.support.v14.preference.SwitchPreference; import android.widget.Button; import com.android.settings.R; import com.android.settings.TestConfig; Loading @@ -38,22 +49,17 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.robolectric.shadows.ShadowAlertDialog; import org.robolectric.shadows.ShadowDialog; import org.robolectric.util.FragmentTestUtil; @RunWith(RobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BackgroundActivityPreferenceControllerTest { private static final int UID_NORMAL = 1234; private static final int UID_SPECIAL = 2345; private static final int UID_LOW_SDK = 1234; private static final int UID_HIGH_SDK = 3456; private static final String HIGH_SDK_PACKAGE = "com.android.package.high"; private static final String LOW_SDK_PACKAGE = "com.android.package.low"; private static final String[] PACKAGES_NORMAL = {LOW_SDK_PACKAGE}; private static final String[] PACKAGES_SPECIAL = {HIGH_SDK_PACKAGE, LOW_SDK_PACKAGE}; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; Loading @@ -71,6 +77,10 @@ public class BackgroundActivityPreferenceControllerTest { private DevicePolicyManager mDevicePolicyManager; @Mock private DevicePolicyManagerWrapper mDevicePolicyManagerWrapper; @Mock private AdvancedPowerUsageDetail mFragment; @Mock private PowerWhitelistBackend mPowerWhitelistBackend; private BackgroundActivityPreferenceController mController; private SwitchPreference mPreference; private Context mShadowContext; Loading @@ -85,19 +95,19 @@ public class BackgroundActivityPreferenceControllerTest { when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( mDevicePolicyManager); when(mPackageManager.getPackagesForUid(UID_NORMAL)).thenReturn(PACKAGES_NORMAL); when(mPackageManager.getPackagesForUid(UID_SPECIAL)).thenReturn(PACKAGES_SPECIAL); when(mPackageManager.getApplicationInfo(HIGH_SDK_PACKAGE, PackageManager.GET_META_DATA)) .thenReturn(mHighApplicationInfo); when(mPackageManager.getApplicationInfo(LOW_SDK_PACKAGE, PackageManager.GET_META_DATA)) .thenReturn(mLowApplicationInfo); when(mPowerWhitelistBackend.isWhitelisted(LOW_SDK_PACKAGE)).thenReturn(false); mHighApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; mLowApplicationInfo.targetSdkVersion = Build.VERSION_CODES.L; mPreference = new SwitchPreference(mShadowContext); mController = spy(new BackgroundActivityPreferenceController(mContext, UID_NORMAL)); mController.isAvailable(); mController = spy(new BackgroundActivityPreferenceController( mContext, mFragment, UID_LOW_SDK, LOW_SDK_PACKAGE, mPowerWhitelistBackend)); mController.mDpm = mDevicePolicyManagerWrapper; } Loading @@ -105,49 +115,66 @@ public class BackgroundActivityPreferenceControllerTest { public void testOnPreferenceChange_TurnOnCheck_MethodInvoked() { mController.onPreferenceChange(mPreference, true); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, mController.getTargetPackage(), AppOpsManager.MODE_ALLOWED); verify(mController).updateSummary(mPreference); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); assertThat(mPreference.getSummary()) .isEqualTo(mShadowContext.getText(R.string.background_activity_summary_on)); } @Test public void testOnPreferenceChange_TurnOffCheck_MethodInvoked() { mController.onPreferenceChange(mPreference, false); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, mController.getTargetPackage(), AppOpsManager.MODE_IGNORED); verify(mController).updateSummary(mPreference); public void testOnPreferenceChange_TurnOnCheckHighSDK_MethodInvoked() { mController = new BackgroundActivityPreferenceController(mContext, mFragment, UID_HIGH_SDK, HIGH_SDK_PACKAGE, mPowerWhitelistBackend); mController.onPreferenceChange(mPreference, true); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_HIGH_SDK, HIGH_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); verify(mAppOpsManager, never()).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_HIGH_SDK, HIGH_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); assertThat(mPreference.getSummary()) .isEqualTo(mShadowContext.getText(R.string.background_activity_summary_on)); } @Test public void testUpdateState_CheckOn_SetCheckedTrue() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_DEFAULT); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isTrue(); assertThat(mPreference.isEnabled()).isTrue(); verify(mController).updateSummary(mPreference); } @Test public void testUpdateState_CheckOff_SetCheckedFalse() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_IGNORED); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_IGNORED); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isFalse(); assertThat(mPreference.isEnabled()).isTrue(); verify(mController).updateSummary(mPreference); } @Test public void testUpdateState_whitelisted() { when(mPowerWhitelistBackend.isWhitelisted(LOW_SDK_PACKAGE)).thenReturn(true); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isTrue(); assertThat(mPreference.isEnabled()).isFalse(); assertThat(mPreference.getSummary()).isEqualTo( mShadowContext.getText(R.string.background_activity_summary_whitelisted)); } @Test public void testUpdateSummary_modeError_showSummaryDisabled() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_ERRORED); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_ERRORED); final CharSequence expectedSummary = mShadowContext.getText( R.string.background_activity_summary_disabled); mController.updateSummary(mPreference); Loading @@ -157,9 +184,8 @@ public class BackgroundActivityPreferenceControllerTest { @Test public void testUpdateSummary_modeDefault_showSummaryOn() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_DEFAULT); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_DEFAULT); final CharSequence expectedSummary = mShadowContext.getText( R.string.background_activity_summary_on); Loading @@ -170,9 +196,8 @@ public class BackgroundActivityPreferenceControllerTest { @Test public void testUpdateSummary_modeIgnored_showSummaryOff() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_IGNORED); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_IGNORED); final CharSequence expectedSummary = mShadowContext.getText( R.string.background_activity_summary_off); Loading @@ -182,31 +207,30 @@ public class BackgroundActivityPreferenceControllerTest { } @Test public void testIsPackageAvailable_SdkLowerThanO_ReturnTrue() { public void testIsLegacyApp_SdkLowerThanO_ReturnTrue() { assertThat(mController.isLegacyApp(LOW_SDK_PACKAGE)).isTrue(); } @Test public void testIsPackageAvailable_SdkLargerOrEqualThanO_ReturnFalse() { public void testIsLegacyApp_SdkLargerOrEqualThanO_ReturnFalse() { assertThat(mController.isLegacyApp(HIGH_SDK_PACKAGE)).isFalse(); } @Test public void testMultiplePackages_ReturnStatusForTargetPackage() { mController = new BackgroundActivityPreferenceController(mContext, UID_SPECIAL); mController.mDpm = mDevicePolicyManagerWrapper; when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_SPECIAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_ALLOWED); when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_SPECIAL, HIGH_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_IGNORED); final boolean available = mController.isAvailable(); mController.updateState(mPreference); public void testIsAvailable_ReturnTrue() { assertThat(mController.isAvailable()).isTrue(); } assertThat(available).isTrue(); // Should get status from LOW_SDK_PACKAGE assertThat(mPreference.isChecked()).isTrue(); @Test public void testWarningDialog() { BackgroundActivityPreferenceController.WarningDialogFragment dialogFragment = new BackgroundActivityPreferenceController.WarningDialogFragment(); dialogFragment.setTargetFragment(mFragment, 0); FragmentTestUtil.startFragment(dialogFragment); final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); ShadowAlertDialog shadowDialog = shadowOf(dialog); final Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); shadowDialog.clickOn(okButton.getId()); verify(mFragment).onLimitBackgroundActivity(); } } Loading
res/values/strings.xml +6 −0 Original line number Diff line number Diff line Loading @@ -4630,6 +4630,12 @@ <string name="background_activity_summary_off">App\'s background activity is limited when not in use</string> <!-- Summary for the background activity when it is disabled [CHAR_LIMIT=120] --> <string name="background_activity_summary_disabled">App not allowed to run in background</string> <!-- TODO: Pending UX review. Summary for the background activity when it is whitlisted [CHAR_LIMIT=120] --> <string name="background_activity_summary_whitelisted">App can not be optimized for battery use</string> <!-- TODO: Pending UX review. Title for the warning dialog to show to the user when limiting background activity for an app --> <string name="background_activity_warning_dialog_title">Limit background activity?</string> <!-- TODO: Pending UX review. Text for the warning dialog to show to the user when limiting background activity for an app --> <string name="background_activity_warning_dialog_text">If you limit background activity for an app, it may misbehave</string> <!-- Title for the screen usage in power use UI [CHAR_LIMIT=60] --> <string name="device_screen_usage">Screen usage since full charge</string> Loading
src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +12 −2 Original line number Diff line number Diff line Loading @@ -68,7 +68,8 @@ import java.util.List; public class AdvancedPowerUsageDetail extends DashboardFragment implements ButtonActionDialogFragment.AppButtonsDialogListener, AnomalyDialogFragment.AnomalyDialogListener, LoaderManager.LoaderCallbacks<List<Anomaly>> { LoaderManager.LoaderCallbacks<List<Anomaly>>, BackgroundActivityPreferenceController.WarningConfirmationListener { public static final String TAG = "AdvancedPowerUsageDetail"; public static final String EXTRA_UID = "extra_uid"; Loading Loading @@ -109,6 +110,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements @VisibleForTesting AnomalySummaryPreferenceController mAnomalySummaryPreferenceController; private AppButtonsPreferenceController mAppButtonsPreferenceController; private BackgroundActivityPreferenceController mBackgroundActivityPreferenceController; private DevicePolicyManagerWrapper mDpm; private UserManager mUserManager; Loading Loading @@ -319,7 +321,9 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements final int uid = bundle.getInt(EXTRA_UID, 0); final String packageName = bundle.getString(EXTRA_PACKAGE_NAME); controllers.add(new BackgroundActivityPreferenceController(context, uid)); mBackgroundActivityPreferenceController = new BackgroundActivityPreferenceController( context, this, uid, packageName); controllers.add(mBackgroundActivityPreferenceController); controllers.add(new BatteryOptimizationPreferenceController( (SettingsActivity) getActivity(), this, packageName)); mAppButtonsPreferenceController = new AppButtonsPreferenceController( Loading Loading @@ -364,4 +368,10 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements public void onLoaderReset(Loader<List<Anomaly>> loader) { } @Override public void onLimitBackgroundActivity() { mBackgroundActivityPreferenceController.setUnchecked( findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); } }
src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java +93 −28 Original line number Diff line number Diff line Loading @@ -14,12 +14,17 @@ package com.android.settings.fuelgauge; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.Dialog; import android.app.Fragment; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; import android.os.UserManager; import android.support.annotation.VisibleForTesting; import android.support.v14.preference.SwitchPreference; Loading @@ -29,6 +34,7 @@ import android.util.Log; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.PreferenceControllerMixin; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.enterprise.DevicePolicyManagerWrapper; import com.android.settings.enterprise.DevicePolicyManagerWrapperImpl; import com.android.settingslib.core.AbstractPreferenceController; Loading @@ -45,54 +51,72 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo private final PackageManager mPackageManager; private final AppOpsManager mAppOpsManager; private final UserManager mUserManager; private final String[] mPackages; private final int mUid; @VisibleForTesting DevicePolicyManagerWrapper mDpm; private Fragment mFragment; private String mTargetPackage; private boolean mIsPreOApp; private PowerWhitelistBackend mPowerWhitelistBackend; public BackgroundActivityPreferenceController(Context context, Fragment fragment, int uid, String packageName) { this(context, fragment, uid, packageName, PowerWhitelistBackend.getInstance()); } public BackgroundActivityPreferenceController(Context context, int uid) { @VisibleForTesting BackgroundActivityPreferenceController(Context context, Fragment fragment, int uid, String packageName, PowerWhitelistBackend backend) { super(context); mPowerWhitelistBackend = backend; mPackageManager = context.getPackageManager(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); mDpm = new DevicePolicyManagerWrapperImpl( (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE)); mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE); mUid = uid; mPackages = mPackageManager.getPackagesForUid(mUid); mFragment = fragment; mTargetPackage = packageName; mIsPreOApp = isLegacyApp(packageName); } @Override public void updateState(Preference preference) { final int mode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage); .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage); final boolean whitelisted = mPowerWhitelistBackend.isWhitelisted(mTargetPackage); // Set checked or not before we may set it disabled if (mode != AppOpsManager.MODE_ERRORED) { final boolean checked = mode != AppOpsManager.MODE_IGNORED; final boolean checked = whitelisted || mode != AppOpsManager.MODE_IGNORED; ((SwitchPreference) preference).setChecked(checked); } if (mode == AppOpsManager.MODE_ERRORED if (whitelisted || mode == AppOpsManager.MODE_ERRORED || Utils.isProfileOrDeviceOwner(mUserManager, mDpm, mTargetPackage)) { preference.setEnabled(false); } else { preference.setEnabled(true); } updateSummary(preference); } @Override public boolean isAvailable() { if (mPackages == null) { return false; } for (final String packageName : mPackages) { if (isLegacyApp(packageName)) { mTargetPackage = packageName; return true; } return mTargetPackage != null; } return false; /** * Called from the warning dialog, if the user decides to go ahead and disable background * activity for this package */ public void setUnchecked(Preference preference) { if (mIsPreOApp) { mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage, AppOpsManager.MODE_IGNORED); } mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage, AppOpsManager.MODE_IGNORED); ((SwitchPreference) preference).setChecked(false); updateSummary(preference); } @Override Loading @@ -102,19 +126,23 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo @Override public boolean onPreferenceChange(Preference preference, Object newValue) { boolean switchOn = (Boolean) newValue; final boolean switchOn = (Boolean) newValue; if (!switchOn) { final WarningDialogFragment dialogFragment = new WarningDialogFragment(); dialogFragment.setTargetFragment(mFragment, 0); dialogFragment.show(mFragment.getFragmentManager(), TAG); return false; } if (mIsPreOApp) { mAppOpsManager.setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage, switchOn ? AppOpsManager.MODE_ALLOWED : AppOpsManager.MODE_IGNORED); AppOpsManager.MODE_ALLOWED); } mAppOpsManager.setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage, AppOpsManager.MODE_ALLOWED); updateSummary(preference); return true; } @VisibleForTesting String getTargetPackage() { return mTargetPackage; } @VisibleForTesting boolean isLegacyApp(final String packageName) { try { Loading @@ -131,8 +159,12 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo @VisibleForTesting void updateSummary(Preference preference) { if (mPowerWhitelistBackend.isWhitelisted(mTargetPackage)) { preference.setSummary(R.string.background_activity_summary_whitelisted); return; } final int mode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, mUid, mTargetPackage); .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage); if (mode == AppOpsManager.MODE_ERRORED) { preference.setSummary(R.string.background_activity_summary_disabled); Loading @@ -142,4 +174,37 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo : R.string.background_activity_summary_off); } } interface WarningConfirmationListener { void onLimitBackgroundActivity(); } /** * Warning dialog to show to the user as turning off background activity can lead to * apps misbehaving as their background task scheduling guarantees will no longer be honored. */ public static class WarningDialogFragment extends InstrumentedDialogFragment { @Override public int getMetricsCategory() { // TODO (b/65494831): add metric id return 0; } @Override public Dialog onCreateDialog(Bundle savedInstanceState) { final WarningConfirmationListener listener = (WarningConfirmationListener) getTargetFragment(); return new AlertDialog.Builder(getContext()) .setTitle(R.string.background_activity_warning_dialog_title) .setMessage(R.string.background_activity_warning_dialog_text) .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { listener.onLimitBackgroundActivity(); } }) .setNegativeButton(R.string.dlg_cancel, null) .create(); } } }
tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java +79 −55 Original line number Diff line number Diff line Loading @@ -16,14 +16,25 @@ package com.android.settings.fuelgauge; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.app.AlertDialog; import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.DialogInterface; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.os.Build; import android.os.UserManager; import android.support.v14.preference.SwitchPreference; import android.widget.Button; import com.android.settings.R; import com.android.settings.TestConfig; Loading @@ -38,22 +49,17 @@ import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import org.robolectric.shadows.ShadowAlertDialog; import org.robolectric.shadows.ShadowDialog; import org.robolectric.util.FragmentTestUtil; @RunWith(RobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) public class BackgroundActivityPreferenceControllerTest { private static final int UID_NORMAL = 1234; private static final int UID_SPECIAL = 2345; private static final int UID_LOW_SDK = 1234; private static final int UID_HIGH_SDK = 3456; private static final String HIGH_SDK_PACKAGE = "com.android.package.high"; private static final String LOW_SDK_PACKAGE = "com.android.package.low"; private static final String[] PACKAGES_NORMAL = {LOW_SDK_PACKAGE}; private static final String[] PACKAGES_SPECIAL = {HIGH_SDK_PACKAGE, LOW_SDK_PACKAGE}; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; Loading @@ -71,6 +77,10 @@ public class BackgroundActivityPreferenceControllerTest { private DevicePolicyManager mDevicePolicyManager; @Mock private DevicePolicyManagerWrapper mDevicePolicyManagerWrapper; @Mock private AdvancedPowerUsageDetail mFragment; @Mock private PowerWhitelistBackend mPowerWhitelistBackend; private BackgroundActivityPreferenceController mController; private SwitchPreference mPreference; private Context mShadowContext; Loading @@ -85,19 +95,19 @@ public class BackgroundActivityPreferenceControllerTest { when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager); when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE)).thenReturn( mDevicePolicyManager); when(mPackageManager.getPackagesForUid(UID_NORMAL)).thenReturn(PACKAGES_NORMAL); when(mPackageManager.getPackagesForUid(UID_SPECIAL)).thenReturn(PACKAGES_SPECIAL); when(mPackageManager.getApplicationInfo(HIGH_SDK_PACKAGE, PackageManager.GET_META_DATA)) .thenReturn(mHighApplicationInfo); when(mPackageManager.getApplicationInfo(LOW_SDK_PACKAGE, PackageManager.GET_META_DATA)) .thenReturn(mLowApplicationInfo); when(mPowerWhitelistBackend.isWhitelisted(LOW_SDK_PACKAGE)).thenReturn(false); mHighApplicationInfo.targetSdkVersion = Build.VERSION_CODES.O; mLowApplicationInfo.targetSdkVersion = Build.VERSION_CODES.L; mPreference = new SwitchPreference(mShadowContext); mController = spy(new BackgroundActivityPreferenceController(mContext, UID_NORMAL)); mController.isAvailable(); mController = spy(new BackgroundActivityPreferenceController( mContext, mFragment, UID_LOW_SDK, LOW_SDK_PACKAGE, mPowerWhitelistBackend)); mController.mDpm = mDevicePolicyManagerWrapper; } Loading @@ -105,49 +115,66 @@ public class BackgroundActivityPreferenceControllerTest { public void testOnPreferenceChange_TurnOnCheck_MethodInvoked() { mController.onPreferenceChange(mPreference, true); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, mController.getTargetPackage(), AppOpsManager.MODE_ALLOWED); verify(mController).updateSummary(mPreference); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); assertThat(mPreference.getSummary()) .isEqualTo(mShadowContext.getText(R.string.background_activity_summary_on)); } @Test public void testOnPreferenceChange_TurnOffCheck_MethodInvoked() { mController.onPreferenceChange(mPreference, false); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, mController.getTargetPackage(), AppOpsManager.MODE_IGNORED); verify(mController).updateSummary(mPreference); public void testOnPreferenceChange_TurnOnCheckHighSDK_MethodInvoked() { mController = new BackgroundActivityPreferenceController(mContext, mFragment, UID_HIGH_SDK, HIGH_SDK_PACKAGE, mPowerWhitelistBackend); mController.onPreferenceChange(mPreference, true); verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_HIGH_SDK, HIGH_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); verify(mAppOpsManager, never()).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_HIGH_SDK, HIGH_SDK_PACKAGE, AppOpsManager.MODE_ALLOWED); assertThat(mPreference.getSummary()) .isEqualTo(mShadowContext.getText(R.string.background_activity_summary_on)); } @Test public void testUpdateState_CheckOn_SetCheckedTrue() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_DEFAULT); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_ALLOWED); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isTrue(); assertThat(mPreference.isEnabled()).isTrue(); verify(mController).updateSummary(mPreference); } @Test public void testUpdateState_CheckOff_SetCheckedFalse() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_IGNORED); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_IGNORED); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isFalse(); assertThat(mPreference.isEnabled()).isTrue(); verify(mController).updateSummary(mPreference); } @Test public void testUpdateState_whitelisted() { when(mPowerWhitelistBackend.isWhitelisted(LOW_SDK_PACKAGE)).thenReturn(true); mController.updateState(mPreference); assertThat(mPreference.isChecked()).isTrue(); assertThat(mPreference.isEnabled()).isFalse(); assertThat(mPreference.getSummary()).isEqualTo( mShadowContext.getText(R.string.background_activity_summary_whitelisted)); } @Test public void testUpdateSummary_modeError_showSummaryDisabled() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_ERRORED); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_ERRORED); final CharSequence expectedSummary = mShadowContext.getText( R.string.background_activity_summary_disabled); mController.updateSummary(mPreference); Loading @@ -157,9 +184,8 @@ public class BackgroundActivityPreferenceControllerTest { @Test public void testUpdateSummary_modeDefault_showSummaryOn() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_DEFAULT); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_DEFAULT); final CharSequence expectedSummary = mShadowContext.getText( R.string.background_activity_summary_on); Loading @@ -170,9 +196,8 @@ public class BackgroundActivityPreferenceControllerTest { @Test public void testUpdateSummary_modeIgnored_showSummaryOff() { when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_NORMAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_IGNORED); when(mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID_LOW_SDK, LOW_SDK_PACKAGE)).thenReturn(AppOpsManager.MODE_IGNORED); final CharSequence expectedSummary = mShadowContext.getText( R.string.background_activity_summary_off); Loading @@ -182,31 +207,30 @@ public class BackgroundActivityPreferenceControllerTest { } @Test public void testIsPackageAvailable_SdkLowerThanO_ReturnTrue() { public void testIsLegacyApp_SdkLowerThanO_ReturnTrue() { assertThat(mController.isLegacyApp(LOW_SDK_PACKAGE)).isTrue(); } @Test public void testIsPackageAvailable_SdkLargerOrEqualThanO_ReturnFalse() { public void testIsLegacyApp_SdkLargerOrEqualThanO_ReturnFalse() { assertThat(mController.isLegacyApp(HIGH_SDK_PACKAGE)).isFalse(); } @Test public void testMultiplePackages_ReturnStatusForTargetPackage() { mController = new BackgroundActivityPreferenceController(mContext, UID_SPECIAL); mController.mDpm = mDevicePolicyManagerWrapper; when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_SPECIAL, LOW_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_ALLOWED); when(mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_IN_BACKGROUND, UID_SPECIAL, HIGH_SDK_PACKAGE)) .thenReturn(AppOpsManager.MODE_IGNORED); final boolean available = mController.isAvailable(); mController.updateState(mPreference); public void testIsAvailable_ReturnTrue() { assertThat(mController.isAvailable()).isTrue(); } assertThat(available).isTrue(); // Should get status from LOW_SDK_PACKAGE assertThat(mPreference.isChecked()).isTrue(); @Test public void testWarningDialog() { BackgroundActivityPreferenceController.WarningDialogFragment dialogFragment = new BackgroundActivityPreferenceController.WarningDialogFragment(); dialogFragment.setTargetFragment(mFragment, 0); FragmentTestUtil.startFragment(dialogFragment); final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); ShadowAlertDialog shadowDialog = shadowOf(dialog); final Button okButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); shadowDialog.clickOn(okButton.getId()); verify(mFragment).onLimitBackgroundActivity(); } }