diff --git a/res/values/strings.xml b/res/values/strings.xml index 49d950d9e0b..cae56078d8f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4842,6 +4842,41 @@ 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 + + + + Restrict app? + Restrict %1$d apps? + + + To save battery, you can stop this app from running in the background when it’s not being used. + + Restrict + + Remove restriction for %1$s? + + This app will be able to use battery in the background. This may cause your battery to be used up faster. + + Remove + + Not now Smart battery manager diff --git a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java index de027a3a199..e073456db82 100644 --- a/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java +++ b/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java @@ -46,6 +46,8 @@ import com.android.settings.Utils; import com.android.settings.applications.LayoutPreference; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.fuelgauge.anomaly.AnomalyUtils; +import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settings.fuelgauge.anomaly.Anomaly; import com.android.settings.fuelgauge.anomaly.AnomalyDialogFragment; @@ -69,7 +71,7 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements ButtonActionDialogFragment.AppButtonsDialogListener, AnomalyDialogFragment.AnomalyDialogListener, LoaderManager.LoaderCallbacks>, - BackgroundActivityPreferenceController.WarningConfirmationListener { + BatteryTipPreferenceController.BatteryTipListener { public static final String TAG = "AdvancedPowerUsageDetail"; public static final String EXTRA_UID = "extra_uid"; @@ -373,8 +375,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment implements } @Override - public void onLimitBackgroundActivity() { - mBackgroundActivityPreferenceController.setRestricted( + public void onBatteryTipHandled(BatteryTip batteryTip) { + mBackgroundActivityPreferenceController.updateSummary( findPreference(mBackgroundActivityPreferenceController.getPreferenceKey())); } } diff --git a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java index 01e41825074..21bd4b73181 100644 --- a/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java +++ b/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceController.java @@ -14,27 +14,22 @@ 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; import android.support.v7.preference.Preference; -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.fuelgauge.batterytip.AppInfo; +import com.android.settings.fuelgauge.batterytip.BatteryTipDialogFragment; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; import com.android.settings.wrapper.DevicePolicyManagerWrapper; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.fuelgauge.PowerWhitelistBackend; @@ -99,15 +94,6 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo return mTargetPackage != null; } - /** - * Called from the warning dialog, if the user decides to go ahead and disable background - * activity for this package - */ - public void setRestricted(Preference preference) { - mBatteryUtils.setForceAppStandby(mUid, mTargetPackage, AppOpsManager.MODE_IGNORED); - updateSummary(preference); - } - @Override public String getPreferenceKey() { return KEY_BACKGROUND_ACTIVITY; @@ -119,20 +105,13 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo final int mode = mAppOpsManager .checkOpNoThrow(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, mUid, mTargetPackage); final boolean restricted = mode == AppOpsManager.MODE_IGNORED; - if (!restricted) { - showDialog(); - return false; - } - mBatteryUtils.setForceAppStandby(mUid, mTargetPackage, AppOpsManager.MODE_ALLOWED); - updateSummary(preference); - return true; + showDialog(restricted); } return false; } - @VisibleForTesting - void updateSummary(Preference preference) { + public void updateSummary(Preference preference) { if (mPowerWhitelistBackend.isWhitelisted(mTargetPackage)) { preference.setSummary(R.string.background_activity_summary_whitelisted); return; @@ -150,42 +129,16 @@ public class BackgroundActivityPreferenceController extends AbstractPreferenceCo } @VisibleForTesting - void showDialog() { - final WarningDialogFragment dialogFragment = new WarningDialogFragment(); + void showDialog(boolean restricted) { + final AppInfo appInfo = new AppInfo.Builder() + .setPackageName(mTargetPackage) + .build(); + BatteryTip tip = restricted + ? new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo) + : new RestrictAppTip(BatteryTip.StateType.NEW, appInfo); + + final BatteryTipDialogFragment dialogFragment = BatteryTipDialogFragment.newInstance(tip); dialogFragment.setTargetFragment(mFragment, 0 /* requestCode */); dialogFragment.show(mFragment.getFragmentManager(), TAG); } - - 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(); - } - } } diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java index b51474defaa..66ce3caad01 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragment.java @@ -34,6 +34,10 @@ 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 com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; + +import java.util.List; /** * Dialog Fragment to show action dialog for each anomaly @@ -84,6 +88,39 @@ 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 List restrictedAppList = restrictAppTip.getRestrictAppList(); + final int num = restrictedAppList.size(); + + final AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(context.getResources().getQuantityString( + R.plurals.battery_tip_restrict_app_dialog_title, num, num)) + .setMessage(getString(R.string.battery_tip_restrict_app_dialog_message)) + .setPositiveButton(R.string.battery_tip_restrict_app_dialog_ok, this) + .setNegativeButton(android.R.string.cancel, null); + + // TODO(b/72385333): consider building dialog with 5+ apps when strings are done + if (num > 1) { + final RecyclerView restrictionView = (RecyclerView) LayoutInflater.from( + context).inflate(R.layout.recycler_view, null); + restrictionView.setLayoutManager(new LinearLayoutManager(context)); + restrictionView.setAdapter(new HighUsageAdapter(context, restrictedAppList)); + builder.setView(restrictionView); + } + + return builder.create(); + case BatteryTip.TipType.REMOVE_APP_RESTRICTION: + final UnrestrictAppTip unrestrictAppTip = (UnrestrictAppTip) mBatteryTip; + final CharSequence name = Utils.getApplicationLabel(context, + unrestrictAppTip.getPackageName()); + + return new AlertDialog.Builder(context) + .setTitle(getString(R.string.battery_tip_unrestrict_app_dialog_title, name)) + .setMessage(R.string.battery_tip_unrestrict_app_dialog_message) + .setPositiveButton(R.string.battery_tip_unrestrict_app_dialog_ok, this) + .setNegativeButton(R.string.battery_tip_unrestrict_app_dialog_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/BatteryTipUtils.java b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java index 5781afd61f2..5eec3229f80 100644 --- a/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java +++ b/src/com/android/settings/fuelgauge/batterytip/BatteryTipUtils.java @@ -17,12 +17,17 @@ package com.android.settings.fuelgauge.batterytip; import android.app.Fragment; +import android.content.Context; import com.android.settings.SettingsActivity; import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction; import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction; +import com.android.settings.fuelgauge.batterytip.actions.RestrictAppAction; import com.android.settings.fuelgauge.batterytip.actions.SmartBatteryAction; +import com.android.settings.fuelgauge.batterytip.actions.UnrestrictAppAction; import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; /** * Utility class for {@link BatteryTip} @@ -42,7 +47,11 @@ public class BatteryTipUtils { case BatteryTip.TipType.SMART_BATTERY_MANAGER: return new SmartBatteryAction(settingsActivity, fragment); case BatteryTip.TipType.BATTERY_SAVER: - return new BatterySaverAction(settingsActivity.getApplicationContext()); + return new BatterySaverAction(settingsActivity); + case BatteryTip.TipType.APP_RESTRICTION: + return new RestrictAppAction(settingsActivity, (RestrictAppTip) batteryTip); + case BatteryTip.TipType.REMOVE_APP_RESTRICTION: + return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip); default: return null; } 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 appInfos = mRestrictAppTip.getRestrictAppList(); + + for (int i = 0, size = appInfos.size(); i < size; i++) { + final String packageName = appInfos.get(i).packageName; + // Force app standby, then app can't run in the background + mBatteryUtils.setForceAppStandby(mBatteryUtils.getPackageUid(packageName), packageName, + AppOpsManager.MODE_IGNORED); + } + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java b/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java new file mode 100644 index 00000000000..16812f69afa --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/actions/UnrestrictAppAction.java @@ -0,0 +1,48 @@ +/* + * 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.actions; + +import android.app.AppOpsManager; +import android.content.Context; + +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip; + +/** + * Action to clear the restriction to the app + */ +public class UnrestrictAppAction extends BatteryTipAction { + private UnrestrictAppTip mUnRestrictAppTip; + private BatteryUtils mBatteryUtils; + + public UnrestrictAppAction(Context context, UnrestrictAppTip tip) { + super(context); + mUnRestrictAppTip = tip; + mBatteryUtils = BatteryUtils.getInstance(context); + } + + /** + * Handle the action when user clicks positive button + */ + @Override + public void handlePositiveAction() { + final String packageName = mUnRestrictAppTip.getPackageName(); + // Clear force app standby, then app can run in the background + mBatteryUtils.setForceAppStandby(mBatteryUtils.getPackageUid(packageName), packageName, + AppOpsManager.MODE_ALLOWED); + } +} diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java new file mode 100644 index 00000000000..46e241a24d5 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/detectors/RestrictAppDetector.java @@ -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 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/BatteryTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java index 09ebc4b5b14..59cd5ee4226 100644 --- a/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java +++ b/src/com/android/settings/fuelgauge/batterytip/tips/BatteryTip.java @@ -50,7 +50,8 @@ public abstract class BatteryTip implements Comparable, Parcelable { TipType.SMART_BATTERY_MANAGER, TipType.APP_RESTRICTION, TipType.REDUCED_BATTERY, - TipType.LOW_BATTERY}) + TipType.LOW_BATTERY, + TipType.REMOVE_APP_RESTRICTION}) public @interface TipType { int SMART_BATTERY_MANAGER = 0; int APP_RESTRICTION = 1; @@ -59,6 +60,7 @@ public abstract class BatteryTip implements Comparable, Parcelable { int REDUCED_BATTERY = 4; int LOW_BATTERY = 5; int SUMMARY = 6; + int REMOVE_APP_RESTRICTION = 7; } private static final String KEY_PREFIX = "key_battery_tip"; 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..1d84d7fb94b --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/RestrictAppTip.java @@ -0,0 +1,106 @@ +/* + * 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.ArrayList; +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 restrictApps) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = restrictApps; + } + + public RestrictAppTip(@StateType int state, AppInfo appInfo) { + super(TipType.APP_RESTRICTION, state, true /* showDialog */); + mRestrictAppList = new ArrayList<>(); + mRestrictAppList.add(appInfo); + } + + @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/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java new file mode 100644 index 00000000000..ec67f6a47b1 --- /dev/null +++ b/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTip.java @@ -0,0 +1,84 @@ +/* + * 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.os.Parcel; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.settings.fuelgauge.batterytip.AppInfo; + +/** + * Tip to suggest user to remove app restriction. This is the empty tip and it is only used in + * {@link com.android.settings.fuelgauge.AdvancedPowerUsageDetail} to create dialog. + */ +public class UnrestrictAppTip extends BatteryTip { + private AppInfo mAppInfo; + + public UnrestrictAppTip(@StateType int state, AppInfo appInfo) { + super(TipType.REMOVE_APP_RESTRICTION, state, true /* showDialog */); + mAppInfo = appInfo; + } + + @VisibleForTesting + UnrestrictAppTip(Parcel in) { + super(in); + mAppInfo = in.readParcelable(getClass().getClassLoader()); + } + + @Override + public CharSequence getTitle(Context context) { + // Don't need title since this is an empty tip + return null; + } + + @Override + public CharSequence getSummary(Context context) { + // Don't need summary since this is an empty tip + return null; + } + + @Override + public int getIconId() { + return 0; + } + + public String getPackageName() { + return mAppInfo.packageName; + } + + @Override + public void updateState(BatteryTip tip) { + mState = tip.mState; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeParcelable(mAppInfo, flags); + } + + public static final Creator CREATOR = new Creator() { + public BatteryTip createFromParcel(Parcel in) { + return new UnrestrictAppTip(in); + } + + public BatteryTip[] newArray(int size) { + return new UnrestrictAppTip[size]; + } + }; +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java index 30fdccb23ec..0cfe135d9c7 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BackgroundActivityPreferenceControllerTest.java @@ -142,18 +142,17 @@ public class BackgroundActivityPreferenceControllerTest { mController.handlePreferenceTreeClick(mPreference); - verify(mController).showDialog(); + verify(mController).showDialog(false /* restrict */); } @Test - public void testHandlePreferenceTreeClick_unRestrictApp_setModeAllowed() { + public void testHandlePreferenceTreeClick_unRestrictApp_showDialog() { doReturn(AppOpsManager.MODE_IGNORED).when(mAppOpsManager).checkOpNoThrow(anyInt(), anyInt(), anyString()); mController.handlePreferenceTreeClick(mPreference); - verify(mBatteryUtils).setForceAppStandby(UID_LOW_SDK, LOW_SDK_PACKAGE, - AppOpsManager.MODE_ALLOWED); + verify(mController).showDialog(true /* restrict */); } @Test @@ -211,17 +210,4 @@ public class BackgroundActivityPreferenceControllerTest { public void testIsAvailable_ReturnTrue() { assertThat(mController.isAvailable()).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(); - } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java index fe907510117..6bc6ee71664 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryUtilsTest.java @@ -32,6 +32,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -523,4 +524,27 @@ public class BatteryUtilsTest { public void testIsLegacyApp_SdkLargerOrEqualThanO_ReturnFalse() { assertThat(mBatteryUtils.isLegacyApp(HIGH_SDK_PACKAGE)).isFalse(); } + + @Test + public void testSetForceAppStandby_forcePreOApp_forceTwoRestrictions() { + mBatteryUtils.setForceAppStandby(UID, LOW_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + + // Restrict both OP_RUN_IN_BACKGROUND and OP_RUN_ANY_IN_BACKGROUND + verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID, LOW_SDK_PACKAGE, + AppOpsManager.MODE_IGNORED); + verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID, LOW_SDK_PACKAGE, + AppOpsManager.MODE_IGNORED); + } + + @Test + public void testSetForceAppStandby_forceOApp_forceOneRestriction() { + mBatteryUtils.setForceAppStandby(UID, HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + + // Don't restrict OP_RUN_IN_BACKGROUND because it is already been restricted for O app + verify(mAppOpsManager, never()).setMode(AppOpsManager.OP_RUN_IN_BACKGROUND, UID, + HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + // Restrict OP_RUN_ANY_IN_BACKGROUND + verify(mAppOpsManager).setMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, UID, + HIGH_SDK_PACKAGE, AppOpsManager.MODE_IGNORED); + } } 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..ec7238449dd 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/BatteryTipDialogFragmentTest.java @@ -27,10 +27,13 @@ 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.fuelgauge.batterytip.tips.UnrestrictAppTip; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.SettingsRobolectricTestRunner; -import com.android.settings.testutils.shadow.ShadowRuntimePermissionPresenter; +import com.android.settings.testutils.shadow.ShadowUtils; import org.junit.Before; import org.junit.Test; @@ -47,14 +50,18 @@ import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION, - shadows = ShadowRuntimePermissionPresenter.class) + shadows = ShadowUtils.class) public class BatteryTipDialogFragmentTest { private static final String PACKAGE_NAME = "com.android.app"; + private static final String DISPLAY_NAME = "app"; private static final long SCREEN_TIME_MS = DateUtils.HOUR_IN_MILLIS; private BatteryTipDialogFragment mDialogFragment; private Context mContext; private HighUsageTip mHighUsageTip; + private RestrictAppTip mRestrictedOneAppTip; + private RestrictAppTip mRestrictAppsTip; + private UnrestrictAppTip mUnrestrictAppTip; @Before public void setUp() { @@ -64,9 +71,22 @@ public class BatteryTipDialogFragmentTest { FakeFeatureFactory.setupForTest(); List highUsageTips = new ArrayList<>(); - highUsageTips.add(new AppInfo.Builder().setScreenOnTimeMs(SCREEN_TIME_MS).setPackageName( - PACKAGE_NAME).build()); + final AppInfo appInfo = new AppInfo.Builder() + .setScreenOnTimeMs(SCREEN_TIME_MS) + .setPackageName(PACKAGE_NAME) + .build(); + highUsageTips.add(appInfo); mHighUsageTip = new HighUsageTip(SCREEN_TIME_MS, highUsageTips); + + final List restrictApps = new ArrayList<>(); + restrictApps.add(appInfo); + mRestrictedOneAppTip = new RestrictAppTip(BatteryTip.StateType.NEW, + new ArrayList<>(restrictApps)); + restrictApps.add(appInfo); + mRestrictAppsTip = new RestrictAppTip(BatteryTip.StateType.NEW, + new ArrayList<>(restrictApps)); + + mUnrestrictAppTip = new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo); } @Test @@ -82,5 +102,49 @@ public class BatteryTipDialogFragmentTest { mContext.getString(R.string.battery_tip_dialog_message, "1h")); } + @Test + public void testOnCreateDialog_restrictOneAppTip_fireRestrictOneAppDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictedOneAppTip); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Restrict app?"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_restrict_app_dialog_message)); + } + + @Test + public void testOnCreateDialog_restrictAppsTip_fireRestrictAppsDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mRestrictAppsTip); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Restrict 2 apps?"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_restrict_app_dialog_message)); + assertThat(shadowDialog.getView()).isNotNull(); + } + + @Test + public void testOnCreateDialog_unRestrictAppTip_fireUnRestrictDialog() { + mDialogFragment = BatteryTipDialogFragment.newInstance(mUnrestrictAppTip); + ShadowUtils.setApplicationLabel(PACKAGE_NAME, DISPLAY_NAME); + + FragmentTestUtil.startFragment(mDialogFragment); + + final AlertDialog dialog = (AlertDialog) ShadowDialog.getLatestDialog(); + ShadowAlertDialog shadowDialog = shadowOf(dialog); + + assertThat(shadowDialog.getTitle()).isEqualTo("Remove restriction for app?"); + assertThat(shadowDialog.getMessage()).isEqualTo( + mContext.getString(R.string.battery_tip_unrestrict_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/actions/RestrictAppActionTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppActionTest.java new file mode 100644 index 00000000000..47785d555b3 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/actions/RestrictAppActionTest.java @@ -0,0 +1,83 @@ +/* + * 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.actions; + +import static org.mockito.Matchers.anyInt; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.verify; + +import android.app.AppOpsManager; +import android.content.Context; + +import com.android.settings.TestConfig; +import com.android.settings.fuelgauge.BatteryUtils; +import com.android.settings.fuelgauge.batterytip.AppInfo; +import com.android.settings.fuelgauge.batterytip.tips.BatteryTip; +import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip; +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 RestrictAppActionTest { + private static final String PACKAGE_NAME_1 = "com.android.app1"; + private static final String PACKAGE_NAME_2 = "com.android.app2"; + + @Mock + private BatteryUtils mBatteryUtils; + private Context mContext; + private RestrictAppAction mRestrictAppAction; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = RuntimeEnvironment.application; + final List mAppInfos = new ArrayList<>(); + mAppInfos.add(new AppInfo.Builder() + .setPackageName(PACKAGE_NAME_1) + .build()); + mAppInfos.add(new AppInfo.Builder() + .setPackageName(PACKAGE_NAME_2) + .build()); + + mRestrictAppAction = new RestrictAppAction(mContext, new RestrictAppTip( + BatteryTip.StateType.NEW, mAppInfos)); + mRestrictAppAction.mBatteryUtils = mBatteryUtils; + } + + @Test + public void testHandlePositiveAction() { + mRestrictAppAction.handlePositiveAction(); + + verify(mBatteryUtils).setForceAppStandby(anyInt(), eq(PACKAGE_NAME_1), + eq(AppOpsManager.MODE_IGNORED)); + verify(mBatteryUtils).setForceAppStandby(anyInt(), eq(PACKAGE_NAME_2), + eq(AppOpsManager.MODE_IGNORED)); + } + + +} 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"); + } +} diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java new file mode 100644 index 00000000000..a83a1587b64 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/UnrestrictAppTipTest.java @@ -0,0 +1,61 @@ +/* + * 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 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.MockitoAnnotations; +import org.robolectric.annotation.Config; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class UnrestrictAppTipTest { + private static final String PACKAGE_NAME = "com.android.app"; + + private UnrestrictAppTip mBatteryTip; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + AppInfo appInfo = new AppInfo.Builder() + .setPackageName(PACKAGE_NAME) + .build(); + mBatteryTip = new UnrestrictAppTip(BatteryTip.StateType.NEW, appInfo); + } + + @Test + public void testParcelable() { + Parcel parcel = Parcel.obtain(); + mBatteryTip.writeToParcel(parcel, mBatteryTip.describeContents()); + parcel.setDataPosition(0); + + final UnrestrictAppTip parcelTip = new UnrestrictAppTip(parcel); + + assertThat(parcelTip.getType()).isEqualTo(BatteryTip.TipType.REMOVE_APP_RESTRICTION); + assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW); + assertThat(parcelTip.getPackageName()).isEqualTo(PACKAGE_NAME); + } +} diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java index 06910862d3b..b4169913a8d 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowUtils.java @@ -27,6 +27,9 @@ import com.android.settings.wrapper.FingerprintManagerWrapper; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import java.util.HashMap; +import java.util.Map; + @Implements(Utils.class) public class ShadowUtils { @@ -34,6 +37,7 @@ public class ShadowUtils { private static boolean sIsUserAMonkey; private static boolean sIsDemoUser; private static ComponentName sDeviceOwnerComponentName; + private static Map sAppNameMap; @Implementation public static int enforceSameOwner(Context context, int userId) { @@ -89,4 +93,19 @@ public class ShadowUtils { public static int getManagedProfileId(UserManager um, int parentUserId) { return UserHandle.USER_NULL; } + + @Implementation + public static CharSequence getApplicationLabel(Context context, String packageName) { + if (sAppNameMap != null) { + return sAppNameMap.get(packageName); + } + return null; + } + + public static void setApplicationLabel(String packageName, String appLabel) { + if (sAppNameMap == null) { + sAppNameMap = new HashMap<>(); + } + sAppNameMap.put(packageName, appLabel); + } }