Files
packages_apps_Settings/src/com/android/settings/fuelgauge/AdvancedPowerUsageDetail.java
Zaiyue Xue 65cfcbe486 Only log the names of the app packages which are installed from Google Play Store in battery usage app optimization page
This is to meet the Guidelines for Logging Package Names: go/package-name-logging-guidance

Bug: 267430574
Fix: 267430574
Test: presubmit
Change-Id: Icf10000f5dcf87e45175d4005af8365191ad444d
2023-02-02 16:51:13 +08:00

514 lines
22 KiB
Java

/*
* Copyright (C) 2017 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;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.backup.BackupManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.applications.appinfo.AppButtonsPreferenceController;
import com.android.settings.applications.appinfo.ButtonActionDialogFragment;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryHistEntry;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Power usage detail fragment for each app, this fragment contains
*
* 1. Detail battery usage information for app(i.e. usage time, usage amount)
* 2. Battery related controls for app(i.e uninstall, force stop)
*/
public class AdvancedPowerUsageDetail extends DashboardFragment implements
ButtonActionDialogFragment.AppButtonsDialogListener,
SelectorWithWidgetPreference.OnClickListener {
public static final String TAG = "AdvancedPowerDetail";
public static final String EXTRA_UID = "extra_uid";
public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
public static final String EXTRA_FOREGROUND_TIME = "extra_foreground_time";
public static final String EXTRA_BACKGROUND_TIME = "extra_background_time";
public static final String EXTRA_SCREEN_ON_TIME = "extra_screen_on_time";
public static final String EXTRA_SLOT_TIME = "extra_slot_time";
public static final String EXTRA_LABEL = "extra_label";
public static final String EXTRA_ICON_ID = "extra_icon_id";
public static final String EXTRA_POWER_USAGE_PERCENT = "extra_power_usage_percent";
public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount";
private static final String KEY_PREF_HEADER = "header_view";
private static final String KEY_PREF_UNRESTRICTED = "unrestricted_pref";
private static final String KEY_PREF_OPTIMIZED = "optimized_pref";
private static final String KEY_PREF_RESTRICTED = "restricted_pref";
private static final String KEY_FOOTER_PREFERENCE = "app_usage_footer_preference";
private static final String PACKAGE_NAME_NONE = "none";
private static final String HEADER_SUMMARY_FORMAT = "%s\n(%s)";
private static final int REQUEST_UNINSTALL = 0;
private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@VisibleForTesting
LayoutPreference mHeaderPreference;
@VisibleForTesting
ApplicationsState mState;
@VisibleForTesting
ApplicationsState.AppEntry mAppEntry;
@VisibleForTesting
BatteryOptimizeUtils mBatteryOptimizeUtils;
@VisibleForTesting
FooterPreference mFooterPreference;
@VisibleForTesting
SelectorWithWidgetPreference mRestrictedPreference;
@VisibleForTesting
SelectorWithWidgetPreference mOptimizePreference;
@VisibleForTesting
SelectorWithWidgetPreference mUnrestrictedPreference;
@VisibleForTesting
@BatteryOptimizeUtils.OptimizationMode
int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
@VisibleForTesting
BackupManager mBackupManager;
@VisibleForTesting
StringBuilder mLogStringBuilder;
private AppButtonsPreferenceController mAppButtonsPreferenceController;
// A wrapper class to carry LaunchBatteryDetailPage required arguments.
private static final class LaunchBatteryDetailPageArgs {
private String mUsagePercent;
private String mPackageName;
private String mAppLabel;
private String mSlotInformation;
private int mUid;
private int mIconId;
private int mConsumedPower;
private long mForegroundTimeMs;
private long mBackgroundTimeMs;
private long mScreenOnTimeMs;
private boolean mIsUserEntry;
}
/** Launches battery details page for an individual battery consumer. */
public static void startBatteryDetailPage(
Activity caller, InstrumentedPreferenceFragment fragment,
BatteryDiffEntry diffEntry, String usagePercent, String slotInformation) {
startBatteryDetailPage(
caller, fragment.getMetricsCategory(), diffEntry, usagePercent, slotInformation,
/*showTimeInformation=*/ true);
}
/** Launches battery details page for an individual battery consumer fragment. */
public static void startBatteryDetailPage(
Context context, int sourceMetricsCategory,
BatteryDiffEntry diffEntry, String usagePercent, String slotInformation,
boolean showTimeInformation) {
final BatteryHistEntry histEntry = diffEntry.mBatteryHistEntry;
final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
// configure the launch argument.
launchArgs.mUsagePercent = usagePercent;
launchArgs.mPackageName = diffEntry.getPackageName();
launchArgs.mAppLabel = diffEntry.getAppLabel();
launchArgs.mSlotInformation = slotInformation;
launchArgs.mUid = (int) histEntry.mUid;
launchArgs.mIconId = diffEntry.getAppIconId();
launchArgs.mConsumedPower = (int) diffEntry.mConsumePower;
if (showTimeInformation) {
launchArgs.mForegroundTimeMs = diffEntry.mForegroundUsageTimeInMs;
launchArgs.mBackgroundTimeMs = diffEntry.mBackgroundUsageTimeInMs;
launchArgs.mScreenOnTimeMs = diffEntry.mScreenOnTimeInMs;
}
launchArgs.mIsUserEntry = histEntry.isUserEntry();
startBatteryDetailPage(context, sourceMetricsCategory, launchArgs);
}
/** Launches battery details page for an individual battery consumer. */
public static void startBatteryDetailPage(Activity caller,
InstrumentedPreferenceFragment fragment, BatteryEntry entry, String usagePercent) {
final LaunchBatteryDetailPageArgs launchArgs = new LaunchBatteryDetailPageArgs();
// configure the launch argument.
launchArgs.mUsagePercent = usagePercent;
launchArgs.mPackageName = entry.getDefaultPackageName();
launchArgs.mAppLabel = entry.getLabel();
launchArgs.mUid = entry.getUid();
launchArgs.mIconId = entry.mIconId;
launchArgs.mConsumedPower = (int) entry.getConsumedPower();
launchArgs.mIsUserEntry = entry.isUserEntry();
startBatteryDetailPage(caller, fragment.getMetricsCategory(), launchArgs);
}
private static void startBatteryDetailPage(
Context context, int sourceMetricsCategory, LaunchBatteryDetailPageArgs launchArgs) {
final Bundle args = new Bundle();
if (launchArgs.mPackageName == null) {
// populate data for system app
args.putString(EXTRA_LABEL, launchArgs.mAppLabel);
args.putInt(EXTRA_ICON_ID, launchArgs.mIconId);
args.putString(EXTRA_PACKAGE_NAME, null);
} else {
// populate data for normal app
args.putString(EXTRA_PACKAGE_NAME, launchArgs.mPackageName);
}
args.putInt(EXTRA_UID, launchArgs.mUid);
args.putLong(EXTRA_BACKGROUND_TIME, launchArgs.mBackgroundTimeMs);
args.putLong(EXTRA_FOREGROUND_TIME, launchArgs.mForegroundTimeMs);
args.putLong(EXTRA_SCREEN_ON_TIME, launchArgs.mScreenOnTimeMs);
args.putString(EXTRA_SLOT_TIME, launchArgs.mSlotInformation);
args.putString(EXTRA_POWER_USAGE_PERCENT, launchArgs.mUsagePercent);
args.putInt(EXTRA_POWER_USAGE_AMOUNT, launchArgs.mConsumedPower);
final int userId = launchArgs.mIsUserEntry ? ActivityManager.getCurrentUser()
: UserHandle.getUserId(launchArgs.mUid);
new SubSettingLauncher(context)
.setDestination(AdvancedPowerUsageDetail.class.getName())
.setTitleRes(R.string.battery_details_title)
.setArguments(args)
.setSourceMetricsCategory(sourceMetricsCategory)
.setUserHandle(new UserHandle(userId))
.launch();
}
/**
* Start packageName's battery detail page.
*/
public static void startBatteryDetailPage(
Activity caller, Instrumentable instrumentable, String packageName,
UserHandle userHandle) {
final Bundle args = new Bundle(3);
final PackageManager packageManager = caller.getPackageManager();
args.putString(EXTRA_PACKAGE_NAME, packageName);
args.putString(EXTRA_POWER_USAGE_PERCENT, Utils.formatPercentage(0));
try {
args.putInt(EXTRA_UID, packageManager.getPackageUid(packageName, 0 /* no flag */));
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Cannot find package: " + packageName, e);
}
new SubSettingLauncher(caller)
.setDestination(AdvancedPowerUsageDetail.class.getName())
.setTitleRes(R.string.battery_details_title)
.setArguments(args)
.setSourceMetricsCategory(instrumentable.getMetricsCategory())
.setUserHandle(userHandle)
.launch();
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mState = ApplicationsState.getInstance(getActivity().getApplication());
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
onCreateForTriState(packageName);
mHeaderPreference = findPreference(KEY_PREF_HEADER);
if (packageName != null) {
mAppEntry = mState.getEntry(packageName, UserHandle.myUserId());
}
}
@Override
public void onResume() {
super.onResume();
initHeader();
mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode();
initPreferenceForTriState(getContext());
mExecutor.execute(() -> {
String packageName =
getLoggingPackageName(getContext(), mBatteryOptimizeUtils.getPackageName());
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
.action(
getContext(),
SettingsEnums.OPEN_APP_BATTERY_USAGE,
packageName);
});
mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode);
}
@Override
public void onPause() {
super.onPause();
final int selectedPreference = getSelectedPreference();
notifyBackupManager();
mLogStringBuilder.append(", onPause mode = ").append(selectedPreference);
logMetricCategory(selectedPreference);
BatteryHistoricalLogUtil.writeLog(
getContext().getApplicationContext(),
Action.MANUAL,
BatteryHistoricalLogUtil.getPackageNameWithUserId(
mBatteryOptimizeUtils.getPackageName(), UserHandle.myUserId()),
mLogStringBuilder.toString());
Log.d(TAG, "Leave with mode: " + selectedPreference);
}
@VisibleForTesting
void notifyBackupManager() {
if (mOptimizationMode != mBatteryOptimizeUtils.getAppOptimizationMode()) {
final BackupManager backupManager = mBackupManager != null
? mBackupManager : new BackupManager(getContext());
backupManager.dataChanged();
}
}
@VisibleForTesting
void initHeader() {
final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
final Activity context = getActivity();
final Bundle bundle = getArguments();
EntityHeaderController controller = EntityHeaderController
.newInstance(context, this, appSnippet)
.setRecyclerView(getListView(), getSettingsLifecycle())
.setButtonActions(EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE);
if (mAppEntry == null) {
controller.setLabel(bundle.getString(EXTRA_LABEL));
final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
if (iconId == 0) {
controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
} else {
controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
}
} else {
mState.ensureIcon(mAppEntry);
controller.setLabel(mAppEntry);
controller.setIcon(mAppEntry);
controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
}
controller.setSummary(getHeaderSummary(bundle));
controller.done(context, true /* rebindActions */);
}
@VisibleForTesting
void initPreferenceForTriState(Context context) {
final String stateString;
final String footerString;
if (!mBatteryOptimizeUtils.isValidPackageName()) {
// Present optimized only string when the package name is invalid.
stateString = context.getString(R.string.manager_battery_usage_optimized_only);
footerString = context.getString(
R.string.manager_battery_usage_footer_limited, stateString);
} else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) {
// Present unrestricted only string when the package is system or default active app.
stateString = context.getString(R.string.manager_battery_usage_unrestricted_only);
footerString = context.getString(
R.string.manager_battery_usage_footer_limited, stateString);
} else {
// Present default string to normal app.
footerString = context.getString(R.string.manager_battery_usage_footer);
}
mFooterPreference.setTitle(footerString);
final Intent helpIntent = HelpUtils.getHelpIntent(context, context.getString(
R.string.help_url_app_usage_settings), /*backupContext=*/ "");
if (helpIntent != null) {
mFooterPreference.setLearnMoreAction(v ->
startActivityForResult(helpIntent, /*requestCode=*/ 0));
mFooterPreference.setLearnMoreText(
context.getString(R.string.manager_battery_usage_link_a11y));
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.FUELGAUGE_POWER_USAGE_DETAIL;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.power_usage_detail;
}
@Override
protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
final List<AbstractPreferenceController> controllers = new ArrayList<>();
final Bundle bundle = getArguments();
final int uid = bundle.getInt(EXTRA_UID, 0);
final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);
mAppButtonsPreferenceController = new AppButtonsPreferenceController(
(SettingsActivity) getActivity(), this, getSettingsLifecycle(),
packageName, mState, REQUEST_UNINSTALL, REQUEST_REMOVE_DEVICE_ADMIN);
controllers.add(mAppButtonsPreferenceController);
controllers.add(new UnrestrictedPreferenceController(context, uid, packageName));
controllers.add(new OptimizedPreferenceController(context, uid, packageName));
controllers.add(new RestrictedPreferenceController(context, uid, packageName));
return controllers;
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (mAppButtonsPreferenceController != null) {
mAppButtonsPreferenceController.handleActivityResult(requestCode, resultCode, data);
}
}
@Override
public void handleDialogClick(int id) {
if (mAppButtonsPreferenceController != null) {
mAppButtonsPreferenceController.handleDialogClick(id);
}
}
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference selected) {
final String selectedKey = selected.getKey();
updatePreferenceState(mUnrestrictedPreference, selectedKey);
updatePreferenceState(mOptimizePreference, selectedKey);
updatePreferenceState(mRestrictedPreference, selectedKey);
mBatteryOptimizeUtils.setAppUsageState(getSelectedPreference(), Action.APPLY);
}
private void updatePreferenceState(SelectorWithWidgetPreference preference,
String selectedKey) {
preference.setChecked(selectedKey.equals(preference.getKey()));
}
private void logMetricCategory(int selectedKey) {
if (selectedKey == mOptimizationMode) {
return;
}
int metricCategory = 0;
switch (selectedKey) {
case BatteryOptimizeUtils.MODE_UNRESTRICTED:
metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_UNRESTRICTED;
break;
case BatteryOptimizeUtils.MODE_OPTIMIZED:
metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_OPTIMIZED;
break;
case BatteryOptimizeUtils.MODE_RESTRICTED:
metricCategory = SettingsEnums.ACTION_APP_BATTERY_USAGE_RESTRICTED;
break;
}
if (metricCategory == 0) {
return;
}
int finalMetricCategory = metricCategory;
mExecutor.execute(() -> {
String packageName =
getLoggingPackageName(getContext(), mBatteryOptimizeUtils.getPackageName());
FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider()
.action(
/* attribution */ SettingsEnums.OPEN_APP_BATTERY_USAGE,
/* action */ finalMetricCategory,
/* pageId */ SettingsEnums.OPEN_APP_BATTERY_USAGE,
packageName,
getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT));
});
}
private void onCreateForTriState(String packageName) {
mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED);
mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED);
mRestrictedPreference = findPreference(KEY_PREF_RESTRICTED);
mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE);
mUnrestrictedPreference.setOnClickListener(this);
mOptimizePreference.setOnClickListener(this);
mRestrictedPreference.setOnClickListener(this);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(
getContext(), getArguments().getInt(EXTRA_UID), packageName);
}
private int getSelectedPreference() {
if (mRestrictedPreference.isChecked()) {
return BatteryOptimizeUtils.MODE_RESTRICTED;
} else if (mUnrestrictedPreference.isChecked()) {
return BatteryOptimizeUtils.MODE_UNRESTRICTED;
} else if (mOptimizePreference.isChecked()) {
return BatteryOptimizeUtils.MODE_OPTIMIZED;
} else {
return BatteryOptimizeUtils.MODE_UNKNOWN;
}
}
private CharSequence getHeaderSummary(Bundle bundle) {
final long foregroundTimeMs = bundle.getLong(EXTRA_FOREGROUND_TIME);
final long backgroundTimeMs = bundle.getLong(EXTRA_BACKGROUND_TIME);
final long screenOnTimeInMs = bundle.getLong(EXTRA_SCREEN_ON_TIME);
final String slotTime = bundle.getString(EXTRA_SLOT_TIME, null);
final String usageSummary = BatteryUtils.buildBatteryUsageTimeSummary(getContext(),
/* isSystem= */ false, foregroundTimeMs, backgroundTimeMs, screenOnTimeInMs);
if (usageSummary.isEmpty()) {
return getText(R.string.battery_usage_without_time);
} else {
CharSequence slotSummary = slotTime == null
? getText(R.string.battery_usage_since_last_full_charge) : slotTime;
return String.format("%s\n(%s)", usageSummary, slotSummary);
}
}
private static String getLoggingPackageName(Context context, String originalPackingName) {
return BatteryUtils.isAppInstalledFromGooglePlayStore(context, originalPackingName)
? originalPackingName : PACKAGE_NAME_NONE;
}
}