diff --git a/res/values/strings.xml b/res/values/strings.xml index 4b2c67344dc..1c58a90ea95 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4842,6 +4842,28 @@ Your tablet was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your tablet was used for about %1$s since last full charge.\n\n Total usage: Your device was used heavily and this consumed a lot of battery. Your battery is behaving normally.\n\n Your device was used for about %1$s since last full charge.\n\n Total usage: + + + Restrict %1$d app + Restrict %1$d apps + + + + %1$d recently restricted + %1$d apps recently restricted + + + + %1$s has high battery usage + %2$d apps have high battery usage + + + App changes are in progress + + + To save battery, you can stop this app from running in the background when it’s not being used. + + Restrict Smart battery manager diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java index b51474defaa..6d9aaabb810 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -34,6 +34,9 @@ import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController. import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; + +import java.util.List; /** * Dialog Fragment to show action dialog for each anomaly @@ -84,6 +87,23 @@ public class BatteryTipDialogFragment extends InstrumentedDialogFragment impleme .setView(view) .setPositiveButton(android.R.string.ok, null) .create(); + case BatteryTip.TipType.APP_RESTRICTION: + final RestrictAppTip restrictAppTip = (RestrictAppTip) mBatteryTip; + final RecyclerView restrictionView = (RecyclerView) LayoutInflater.from( + context).inflate(R.layout.recycler_view, null); + final List restrictedAppList = restrictAppTip.getRestrictAppList(); + final int num = restrictedAppList.size(); + restrictionView.setLayoutManager(new LinearLayoutManager(context)); + restrictionView.setAdapter(new HighUsageAdapter(context, restrictedAppList)); + + return new AlertDialog.Builder(context) + .setTitle(context.getResources().getQuantityString( + R.plurals.battery_tip_restrict_title, num, num)) + .setMessage(getString(R.string.battery_tip_restrict_app_dialog_message)) + .setView(restrictionView) + .setPositiveButton(R.string.battery_tip_restrict_app_dialog_ok, this) + .setNegativeButton(android.R.string.cancel, null) + .create(); default: throw new IllegalArgumentException("unknown type " + mBatteryTip.getType()); } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java index ced34616879..a61584168bc 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoader.java @@ -26,6 +26,7 @@ import com.android.settings.fuelgauge.batterytip.detectors.EarlyWarningDetector; import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector; import com.android.settings.fuelgauge.batterytip.detectors.LowBatteryDetector; import com.android.settings.fuelgauge.batterytip.detectors.SmartBatteryDetector; +import com.android.settings.fuelgauge.batterytip.detectors.RestrictAppDetector; import com.android.settings.fuelgauge.batterytip.detectors.SummaryDetector; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip; @@ -70,6 +71,7 @@ public class BatteryTipLoader extends AsyncLoader> { tips.add(new SmartBatteryDetector(policy, context.getContentResolver()).detect()); tips.add(new EarlyWarningDetector(policy, context).detect()); tips.add(new SummaryDetector(policy).detect()); + tips.add(new RestrictAppDetector(policy).detect()); Collections.sort(tips); return tips; diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java index 60aa6c8832a..6c129d8a9be 100644 --- a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java +++ b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java @@ -77,7 +77,9 @@ public class HighUsageAdapter extends RecyclerView.Adapter highUsageApps = new ArrayList<>(); + return new RestrictAppTip( + highUsageApps.isEmpty() ? BatteryTip.StateType.INVISIBLE : BatteryTip.StateType.NEW, + highUsageApps); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java new file mode 100644 index 00000000000..054b6e19421 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.fuelgauge.batterytip.tips; + +import android.content.Context; +import android.content.res.Resources; +import android.os.Parcel; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.fuelgauge.batterytip.AppInfo; + +import java.util.List; + +/** + * Tip to suggest user to restrict some bad apps + */ +public class RestrictAppTip extends BatteryTip { + private List mRestrictAppList; + + public RestrictAppTip(@StateType int state, List highUsageApps) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = highUsageApps; + } + + @VisibleForTesting + RestrictAppTip(Parcel in) { + super(in); + mRestrictAppList = in.createTypedArrayList(AppInfo.CREATOR); + } + + @Override + public CharSequence getTitle(Context context) { + final int num = mRestrictAppList.size(); + return context.getResources().getQuantityString( + mState == StateType.HANDLED + ? R.plurals.battery_tip_restrict_handled_title + : R.plurals.battery_tip_restrict_title, + num, num); + } + + @Override + public CharSequence getSummary(Context context) { + final int num = mRestrictAppList.size(); + final CharSequence appLabel = num > 0 ? Utils.getApplicationLabel(context, + mRestrictAppList.get(0).packageName) : ""; + return mState == StateType.HANDLED + ? context.getString(R.string.battery_tip_restrict_handled_summary) + : context.getResources().getQuantityString(R.plurals.battery_tip_restrict_summary, + num, appLabel, num); + } + + @Override + public int getIconId() { + return mState == StateType.HANDLED + ? R.drawable.ic_perm_device_information_green_24dp + : R.drawable.ic_battery_alert_24dp; + } + + @Override + public void updateState(BatteryTip tip) { + mState = tip.mState; + } + + public List getRestrictAppList() { + return mRestrictAppList; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeTypedList(mRestrictAppList); + } + + public static final Creator CREATOR = new Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new RestrictAppTip(in); + } + + public BatteryTip[] newArray(int size) { + return new RestrictAppTip[size]; + } + }; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java index ddee31461c3..a5815016275 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java @@ -27,7 +27,9 @@ import android.text.format.DateUtils; import com.android.settings.R; import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; import com.android.settings.testutils.shadow.ShadowRuntimePermissionPresenter; @@ -55,6 +57,7 @@ public class BatteryTipDialogFragmentTest { private BatteryTipDialogFragment mDialogFragment; private Context mContext; private HighUsageTip mHighUsageTip; + private RestrictAppTip mRestrictedAppTip; @Before public void setUp() { @@ -67,6 +70,7 @@ public class BatteryTipDialogFragmentTest { highUsageTips.add(new AppInfo.Builder().setScreenOnTimeMs(SCREEN_TIME_MS).setPackageName( PACKAGE_NAME).build()); mHighUsageTip = new HighUsageTip(SCREEN_TIME_MS, highUsageTips); + mRestrictedAppTip = new RestrictAppTip(BatteryTip.StateType.NEW, highUsageTips); } @Test @@ -82,5 +86,19 @@ public class BatteryTipDialogFragmentTest { mContext.getString(R.string.battery_tip_dialog_message, "1h")); } + @Test + public void testOnCreateDialog_restrictAppTip_fireRestrictAppDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictedAppTip); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Restrict 1 app"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_restrict_app_dialog_message)); + } + } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java index 83b32258009..09e67edbdbc 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipLoaderTest.java @@ -51,6 +51,7 @@ import java.util.List; public class BatteryTipLoaderTest { private static final int[] TIP_ORDER = { BatteryTip.TipType.SMART_BATTERY_MANAGER, + BatteryTip.TipType.APP_RESTRICTION, BatteryTip.TipType.HIGH_DEVICE_USAGE, BatteryTip.TipType.BATTERY_SAVER, BatteryTip.TipType.LOW_BATTERY, diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java new file mode 100644 index 00000000000..e1dea17c1f6 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.settings.fuelgauge.batterytip.tips; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.os.Parcel; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.testutils.SettingsRobolectricTestRunner; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class RestrictAppTipTest { + private static final String PACKAGE_NAME = "com.android.app"; + private static final String DISPLAY_NAME = "app"; + + private Context mContext; + private RestrictAppTip mNewBatteryTip; + private RestrictAppTip mHandledBatteryTip; + private List mUsageAppList; + @Mock + private ApplicationInfo mApplicationInfo; + @Mock + private PackageManager mPackageManager; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + doReturn(mPackageManager).when(mContext).getPackageManager(); + doReturn(mApplicationInfo).when(mPackageManager).getApplicationInfo(PACKAGE_NAME, + PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_ANY_USER); + doReturn(DISPLAY_NAME).when(mApplicationInfo).loadLabel(mPackageManager); + + mUsageAppList = new ArrayList<>(); + mUsageAppList.add(new AppInfo.Builder() + .setPackageName(PACKAGE_NAME) + .build()); + mNewBatteryTip = new RestrictAppTip(BatteryTip.StateType.NEW, mUsageAppList); + mHandledBatteryTip = new RestrictAppTip(BatteryTip.StateType.HANDLED, mUsageAppList); + } + + @Test + public void testParcelable() { + Parcel parcel = Parcel.obtain(); + mNewBatteryTip.writeToParcel(parcel, mNewBatteryTip.describeContents()); + parcel.setDataPosition(0); + + final RestrictAppTip parcelTip = new RestrictAppTip(parcel); + + assertThat(parcelTip.getType()).isEqualTo(BatteryTip.TipType.APP_RESTRICTION); + assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW); + final AppInfo app = parcelTip.getRestrictAppList().get(0); + assertThat(app.packageName).isEqualTo(PACKAGE_NAME); + } + + @Test + public void testGetTitle_stateNew_showRestrictTitle() { + assertThat(mNewBatteryTip.getTitle(mContext)).isEqualTo("Restrict 1 app"); + } + + @Test + public void testGetTitle_stateHandled_showHandledTitle() { + assertThat(mHandledBatteryTip.getTitle(mContext)).isEqualTo("1 recently restricted"); + } + + @Test + public void testGetSummary_stateNew_showRestrictSummary() { + assertThat(mNewBatteryTip.getSummary(mContext)).isEqualTo( + "app has high battery usage"); + } + + @Test + public void testGetSummary_stateHandled_showHandledSummary() { + assertThat(mHandledBatteryTip.getSummary(mContext)).isEqualTo( + "App changes are in progress"); + } +}