From ab0cde6bad5005e5c7fba3e9e17ca6b6408a09ff Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Fri, 12 Jan 2018 13:52:20 -0800 Subject: [PATCH] Add app restrict tip and detector 1. Add RestrictAppTip with two state(new and handled) 2. Add RestrictAppDetector, and future cl will hook up it with anomaly database 3. Add related dialog in BatteryTipDialogFragment Bug: 72385333 Test: RunSettingsRoboTests Change-Id: Ic10efc6387150e62b6c6ad8d4c0d16ff75564fac --- res/values/strings.xml | 22 ++++ .../batterytip/BatteryTipDialogFragment.java | 20 ++++ .../batterytip/BatteryTipLoader.java | 2 + .../batterytip/HighUsageAdapter.java | 4 +- .../detectors/RestrictAppDetector.java | 46 ++++++++ .../batterytip/tips/RestrictAppTip.java | 99 ++++++++++++++++ .../BatteryTipDialogFragmentTest.java | 18 +++ .../batterytip/BatteryTipLoaderTest.java | 1 + .../batterytip/tips/RestrictAppTipTest.java | 111 ++++++++++++++++++ 9 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java create mode 100644 src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java create mode 100644 tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTipTest.java 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"); + } +}