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
This commit is contained in:
jackqdyulei
2018-01-12 13:52:20 -08:00
parent 36952523bb
commit ab0cde6bad
9 changed files with 322 additions and 1 deletions

View File

@@ -4842,6 +4842,28 @@
<string name="battery_tip_dialog_message" product="tablet">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 <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
<!-- Message for battery tip dialog to show the status about the battery [CHAR LIMIT=NONE] -->
<string name="battery_tip_dialog_message" product="device">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 <xliff:g id="hour">%1$s</xliff:g> since last full charge.\n\n Total usage:</string>
<!-- Title for restricted app preference, showing how many app need to be restricted [CHAR LIMIT=NONE] -->
<plurals name="battery_tip_restrict_title">
<item quantity="one">Restrict %1$d app</item>
<item quantity="other">Restrict %1$d apps</item>
</plurals>
<!-- Title for restricted app preference, showing how many app been restricted [CHAR LIMIT=NONE] -->
<plurals name="battery_tip_restrict_handled_title">
<item quantity="one">%1$d recently restricted</item>
<item quantity="other">%1$d apps recently restricted</item>
</plurals>
<!-- Summary for restricted app preference, showing the impact of the apps [CHAR LIMIT=NONE] -->
<plurals name="battery_tip_restrict_summary">
<item quantity="one">%1$s has high battery usage</item>
<item quantity="other">%2$d apps have high battery usage</item>
</plurals>
<!-- Summary for restricted app preference, showing the impact of the apps [CHAR LIMIT=NONE] -->
<string name="battery_tip_restrict_handled_summary">App changes are in progress</string>
<!-- Message for battery tip dialog to show the restrict app list [CHAR LIMIT=NONE] -->
<string name="battery_tip_restrict_app_dialog_message">To save battery, you can stop this app from running in the background when its not being used.</string>
<!-- OK button for battery tip dialog to show the restrict app list [CHAR LIMIT=NONE] -->
<string name="battery_tip_restrict_app_dialog_ok">Restrict</string>
<!-- Title for the smart battery manager preference [CHAR LIMIT=NONE] -->
<string name="smart_battery_manager_title">Smart battery manager</string>

View File

@@ -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<AppInfo> 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());
}

View File

@@ -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<List<BatteryTip>> {
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;

View File

@@ -77,7 +77,9 @@ public class HighUsageAdapter extends RecyclerView.Adapter<HighUsageAdapter.View
Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, app.packageName,
UserHandle.myUserId()));
holder.appName.setText(Utils.getApplicationLabel(mContext, app.packageName));
holder.appTime.setText(Utils.formatElapsedTime(mContext, app.screenOnTimeMs, false));
if (app.screenOnTimeMs != 0) {
holder.appTime.setText(Utils.formatElapsedTime(mContext, app.screenOnTimeMs, false));
}
}
@Override

View File

@@ -0,0 +1,46 @@
/*
* 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.detectors;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
import java.util.ArrayList;
import java.util.List;
/**
* Detector whether to show summary tip. This detector should be executed as the last
* {@link BatteryTipDetector} since it need the most up-to-date {@code visibleTips}
*/
public class RestrictAppDetector implements BatteryTipDetector {
private BatteryTipPolicy mPolicy;
public RestrictAppDetector(BatteryTipPolicy policy) {
mPolicy = policy;
}
@Override
public BatteryTip detect() {
// TODO(b/70570352): Detect restrict apps here, get data from database
final List<AppInfo> highUsageApps = new ArrayList<>();
return new RestrictAppTip(
highUsageApps.isEmpty() ? BatteryTip.StateType.INVISIBLE : BatteryTip.StateType.NEW,
highUsageApps);
}
}

View File

@@ -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<AppInfo> mRestrictAppList;
public RestrictAppTip(@StateType int state, List<AppInfo> 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<AppInfo> 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];
}
};
}

View File

@@ -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));
}
}

View File

@@ -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,

View File

@@ -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<AppInfo> 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");
}
}