Merge 25Q1 (ab/12770256) to aosp-main-future

Bug: 385190204
Merged-In: Iaee6792d1a27be8fa4b443f783a47a3715b6d3a1
Change-Id: I0ac29cecfec526a38cf4a120b8ef704ee7bc01b3
This commit is contained in:
Xin Li
2025-02-26 11:59:26 -08:00
1291 changed files with 58828 additions and 22728 deletions

View File

@@ -26,17 +26,12 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
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;
@@ -47,13 +42,11 @@ import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUt
import com.android.settings.fuelgauge.batteryusage.BatteryDiffEntry;
import com.android.settings.fuelgauge.batteryusage.BatteryEntry;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.EntityHeaderController;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.applications.AppUtils;
import com.android.settingslib.Utils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.core.instrumentation.Instrumentable;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.IntroPreference;
import java.util.ArrayList;
import java.util.List;
@@ -67,9 +60,7 @@ import java.util.concurrent.Executors;
* 2. Battery related controls for app(i.e uninstall, force stop)
*/
public class AdvancedPowerUsageDetail extends DashboardFragment
implements ButtonActionDialogFragment.AppButtonsDialogListener,
Preference.OnPreferenceClickListener,
Preference.OnPreferenceChangeListener {
implements ButtonActionDialogFragment.AppButtonsDialogListener {
public static final String TAG = "AdvancedPowerDetail";
public static final String EXTRA_UID = "extra_uid";
public static final String EXTRA_PACKAGE_NAME = "extra_package_name";
@@ -86,7 +77,8 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
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_ALLOW_BACKGROUND_USAGE = "allow_background_usage";
private static final String KEY_BACKGROUND_USAGE_ALLOWABILITY_CATEGORY =
"background_usage_allowability_category";
private static final int REQUEST_UNINSTALL = 0;
private static final int REQUEST_REMOVE_DEVICE_ADMIN = 1;
@@ -96,11 +88,9 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
private AppButtonsPreferenceController mAppButtonsPreferenceController;
private PowerUsageTimeController mPowerUsageTimeController;
@VisibleForTesting LayoutPreference mHeaderPreference;
@VisibleForTesting ApplicationsState mState;
@VisibleForTesting ApplicationsState.AppEntry mAppEntry;
@VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
@VisibleForTesting PrimarySwitchPreference mAllowBackgroundUsagePreference;
@VisibleForTesting @BatteryOptimizeUtils.OptimizationMode
int mOptimizationMode = BatteryOptimizeUtils.MODE_UNKNOWN;
@@ -242,17 +232,11 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
public void onAttach(Activity activity) {
super.onAttach(activity);
final Bundle bundle = getArguments();
final int uid = bundle.getInt(EXTRA_UID, 0);
final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(getContext(), uid, packageName);
mState = ApplicationsState.getInstance(getActivity().getApplication());
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
onCreateBackgroundUsageState(packageName);
mHeaderPreference = findPreference(KEY_PREF_HEADER);
if (packageName != null) {
mAppEntry = mState.getEntry(packageName, UserHandle.myUserId());
}
@@ -264,7 +248,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
initHeader();
mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode();
initFooter();
mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode);
}
@@ -299,29 +282,26 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
@VisibleForTesting
void initHeader() {
final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
final IntroPreference introPreference = findPreference(KEY_PREF_HEADER);
if (introPreference == null) {
return;
}
final Activity context = getActivity();
final Bundle bundle = getArguments();
EntityHeaderController controller =
EntityHeaderController.newInstance(context, this, appSnippet)
.setButtonActions(
EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE);
if (mAppEntry == null) {
controller.setLabel(bundle.getString(EXTRA_LABEL));
introPreference.setTitle(bundle.getString(EXTRA_LABEL));
final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
if (iconId == 0) {
controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
introPreference.setIcon(context.getPackageManager().getDefaultActivityIcon());
} else {
controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
introPreference.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
}
} else {
mState.ensureIcon(mAppEntry);
controller.setLabel(mAppEntry);
controller.setIcon(mAppEntry);
controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
introPreference.setTitle(mAppEntry.label);
introPreference.setIcon(Utils.getBadgedIcon(context, mAppEntry.info));
}
if (mPowerUsageTimeController != null) {
@@ -337,32 +317,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
anomalyHintPrefKey,
anomalyHintText);
}
controller.done(true /* rebindActions */);
}
@VisibleForTesting
void initFooter() {
final String stateString;
final String detailInfoString;
final Context context = getContext();
if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) {
// Present optimized only string when the package name is invalid.
stateString = context.getString(R.string.manager_battery_usage_optimized_only);
detailInfoString =
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);
detailInfoString =
context.getString(R.string.manager_battery_usage_footer_limited, stateString);
} else {
// Present default string to normal app.
detailInfoString =
context.getString(
R.string.manager_battery_usage_allow_background_usage_summary);
}
mAllowBackgroundUsagePreference.setSummary(detailInfoString);
}
@Override
@@ -384,7 +338,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
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 =
@@ -401,7 +354,12 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
controllers.add(mPowerUsageTimeController);
}
controllers.add(mAppButtonsPreferenceController);
controllers.add(new AllowBackgroundPreferenceController(context, uid, packageName));
controllers.add(
new BackgroundUsageAllowabilityPreferenceController(
context,
/* dashboardFragment= */ this,
KEY_BACKGROUND_USAGE_ALLOWABILITY_CATEGORY,
mBatteryOptimizeUtils));
return controllers;
}
@@ -421,34 +379,6 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
}
}
@Override
public boolean onPreferenceClick(Preference preference) {
if (!(preference instanceof PrimarySwitchPreference)
|| !TextUtils.equals(preference.getKey(), KEY_ALLOW_BACKGROUND_USAGE)) {
return false;
}
PowerBackgroundUsageDetail.startPowerBackgroundUsageDetailPage(
getContext(), getArguments());
return true;
}
@Override
public boolean onPreferenceChange(@NonNull Preference preference, Object newValue) {
if (!(preference instanceof PrimarySwitchPreference)
|| !TextUtils.equals(preference.getKey(), KEY_ALLOW_BACKGROUND_USAGE)) {
return false;
}
if (newValue instanceof Boolean) {
final boolean isAllowBackgroundUsage = (boolean) newValue;
mBatteryOptimizeUtils.setAppUsageState(
isAllowBackgroundUsage
? BatteryOptimizeUtils.MODE_OPTIMIZED
: BatteryOptimizeUtils.MODE_RESTRICTED,
Action.APPLY);
}
return true;
}
private void logMetricCategory(int currentOptimizeMode) {
if (currentOptimizeMode == mOptimizationMode) {
return;
@@ -482,16 +412,4 @@ public class AdvancedPowerUsageDetail extends DashboardFragment
getArguments().getInt(EXTRA_POWER_USAGE_AMOUNT));
});
}
private void onCreateBackgroundUsageState(String packageName) {
mAllowBackgroundUsagePreference = findPreference(KEY_ALLOW_BACKGROUND_USAGE);
if (mAllowBackgroundUsagePreference != null) {
mAllowBackgroundUsagePreference.setOnPreferenceClickListener(this);
mAllowBackgroundUsagePreference.setOnPreferenceChangeListener(this);
}
mBatteryOptimizeUtils =
new BatteryOptimizeUtils(
getContext(), getArguments().getInt(EXTRA_UID), packageName);
}
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2023 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.content.Context;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.PrimarySwitchPreference;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.MainSwitchPreference;
/** Controller to update the app background usage state */
public class AllowBackgroundPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
private static final String TAG = "AllowBackgroundPreferenceController";
@VisibleForTesting static final String KEY_ALLOW_BACKGROUND_USAGE = "allow_background_usage";
@VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
public AllowBackgroundPreferenceController(Context context, int uid, String packageName) {
super(context);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(context, uid, packageName);
}
private void setChecked(Preference preference, boolean checked) {
if (preference instanceof PrimarySwitchPreference) {
((PrimarySwitchPreference) preference).setChecked(checked);
} else if (preference instanceof MainSwitchPreference) {
((MainSwitchPreference) preference).setChecked(checked);
}
}
private void setEnabled(Preference preference, boolean enabled) {
if (preference instanceof PrimarySwitchPreference) {
((PrimarySwitchPreference) preference).setEnabled(enabled);
((PrimarySwitchPreference) preference).setSwitchEnabled(enabled);
} else if (preference instanceof MainSwitchPreference) {
((MainSwitchPreference) preference).setEnabled(enabled);
}
}
@Override
public void updateState(Preference preference) {
setEnabled(preference, mBatteryOptimizeUtils.isOptimizeModeMutable());
final boolean isAllowBackground =
mBatteryOptimizeUtils.getAppOptimizationMode()
!= BatteryOptimizeUtils.MODE_RESTRICTED;
setChecked(preference, isAllowBackground);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_ALLOW_BACKGROUND_USAGE;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
return getPreferenceKey().equals(preference.getKey());
}
}

View File

@@ -0,0 +1,140 @@
/*
* Copyright (C) 2024 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.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.PrimarySwitchPreference;
/** Controller to update the manage battery usage preference in App Battery Usage page */
public class BackgroundUsageAllowabilityPreferenceController extends BasePreferenceController
implements PreferenceControllerMixin {
@VisibleForTesting
static final String KEY_BACKGROUND_USAGE_ALLOWABILITY_SWITCH =
"background_usage_allowability_switch";
private final BatteryOptimizeUtils mBatteryOptimizeUtils;
private final DashboardFragment mDashboardFragment;
@Nullable @VisibleForTesting PrimarySwitchPreference mBackgroundUsageAllowabilityPreference;
public BackgroundUsageAllowabilityPreferenceController(
@NonNull Context context,
@NonNull DashboardFragment dashboardFragment,
@NonNull String preferenceKey,
@NonNull BatteryOptimizeUtils batteryOptimizeUtils) {
super(context, preferenceKey);
mDashboardFragment = dashboardFragment;
mBatteryOptimizeUtils = batteryOptimizeUtils;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(@NonNull Preference preference) {
updatePreferences(mBatteryOptimizeUtils.getAppOptimizationMode());
}
@Override
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
mBackgroundUsageAllowabilityPreference =
screen.findPreference(KEY_BACKGROUND_USAGE_ALLOWABILITY_SWITCH);
initPreferences();
}
@VisibleForTesting
void initPreferences() {
if (mBackgroundUsageAllowabilityPreference == null) {
return;
}
final String stateString;
final String detailInfoString;
boolean isPreferenceEnabled = true;
if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) {
// Present "Optimized" only string if the package name is invalid.
stateString = mContext.getString(R.string.manager_battery_usage_optimized_only);
detailInfoString =
mContext.getString(R.string.manager_battery_usage_footer_limited, stateString);
isPreferenceEnabled = false;
} else if (mBatteryOptimizeUtils.isSystemOrDefaultApp()) {
// Present "Unrestricted" only string if the package is system important apps.
stateString = mContext.getString(R.string.manager_battery_usage_unrestricted_only);
detailInfoString =
mContext.getString(R.string.manager_battery_usage_footer_limited, stateString);
isPreferenceEnabled = false;
} else {
// Present default string to normal app.
detailInfoString =
mContext.getString(
R.string.manager_battery_usage_allow_background_usage_summary);
}
mBackgroundUsageAllowabilityPreference.setEnabled(isPreferenceEnabled);
mBackgroundUsageAllowabilityPreference.setSwitchEnabled(isPreferenceEnabled);
mBackgroundUsageAllowabilityPreference.setSummary(detailInfoString);
if (isPreferenceEnabled) {
mBackgroundUsageAllowabilityPreference.setOnPreferenceClickListener(
preference -> {
PowerBackgroundUsageDetail.startPowerBackgroundUsageDetailPage(
mContext, mDashboardFragment.getArguments());
return true;
});
mBackgroundUsageAllowabilityPreference.setOnPreferenceChangeListener(
(preference, isAllowBackground) -> {
handleBatteryOptimizeModeUpdated(
(boolean) isAllowBackground
? BatteryOptimizeUtils.MODE_OPTIMIZED
: BatteryOptimizeUtils.MODE_RESTRICTED);
return true;
});
}
}
@VisibleForTesting
void handleBatteryOptimizeModeUpdated(int newBatteryOptimizeMode) {
if (mBatteryOptimizeUtils.getAppOptimizationMode() == newBatteryOptimizeMode) {
Log.w(TAG, "ignore same mode for: " + mBatteryOptimizeUtils.getPackageName());
return;
}
mBatteryOptimizeUtils.setAppUsageState(
newBatteryOptimizeMode, BatteryOptimizeHistoricalLogEntry.Action.APPLY);
updatePreferences(newBatteryOptimizeMode);
}
@VisibleForTesting
void updatePreferences(int optimizationMode) {
if (mBackgroundUsageAllowabilityPreference == null) {
return;
}
mBackgroundUsageAllowabilityPreference.setChecked(
optimizationMode != BatteryOptimizeUtils.MODE_RESTRICTED);
}
}

View File

@@ -196,7 +196,9 @@ public final class BatteryBackupHelper implements BackupHelper {
appOptModeMap.containsKey(info.uid)
? (int) appOptModeMap.get(info.uid).getResetOptimizationMode()
: BatteryOptimizeUtils.getAppOptimizationMode(
mode, allowlistedApps.contains(info.packageName));
mode,
allowlistedApps.contains(info.packageName),
/* ignoreUnknownMode= */ false);
// Ignores default optimized/unknown state or system/default apps.
if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
|| optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN

View File

@@ -0,0 +1,126 @@
/*
* Copyright (C) 2024 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.content.Context
import androidx.annotation.VisibleForTesting
import androidx.preference.Preference
import com.android.settings.R
import com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT
import com.android.settingslib.Utils
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.NoOpKeyedObservable
import com.android.settingslib.fuelgauge.BatteryUtils
import com.android.settingslib.metadata.PersistentPreference
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.PreferenceMetadata
import com.android.settingslib.metadata.RangeValue
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.preference.PreferenceBinding
import com.android.settingslib.widget.UsageProgressBarPreference
// LINT.IfChange
class BatteryHeaderPreference :
PersistentPreference<Int>,
PreferenceMetadata,
PreferenceBinding,
PreferenceLifecycleProvider,
RangeValue {
@VisibleForTesting var batteryBroadcastReceiver: BatteryBroadcastReceiver? = null
override val key: String
get() = KEY
override val title: Int
get() = R.string.summary_placeholder
override fun createWidget(context: Context) = UsageProgressBarPreference(context)
override fun bind(preference: Preference, metadata: PreferenceMetadata) {
super.bind(preference, metadata)
preference.isSelectable = false
if (preference is UsageProgressBarPreference) {
quickUpdateHeaderPreference(preference)
}
}
override fun isIndexable(context: Context) = false
override fun onCreate(context: PreferenceLifecycleContext) {
super.onCreate(context)
batteryBroadcastReceiver =
BatteryBroadcastReceiver(context).apply {
setBatteryChangedListener {
if (it != BATTERY_NOT_PRESENT) {
context.notifyPreferenceChange(KEY)
}
}
}
}
override fun onStart(context: PreferenceLifecycleContext) {
super.onStart(context)
batteryBroadcastReceiver?.register()
}
override fun onStop(context: PreferenceLifecycleContext) {
super.onStop(context)
batteryBroadcastReceiver?.unRegister()
}
override fun storage(context: Context): KeyValueStore =
object : NoOpKeyedObservable<String>(), KeyValueStore {
override fun contains(key: String) = BatteryUtils.getBatteryIntent(context) != null
@Suppress("UNCHECKED_CAST")
override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
val batteryIntent = BatteryUtils.getBatteryIntent(context) ?: return null
return Utils.getBatteryLevel(batteryIntent) as T
}
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) =
throw UnsupportedOperationException()
}
override fun getMinValue(context: Context): Int = 0
override fun getMaxValue(context: Context): Int = 100
override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, value: Int?, myUid: Int, callingUid: Int) =
ReadWritePermit.DISALLOW
companion object {
private const val KEY = "battery_header"
private const val BATTERY_MAX_LEVEL: Long = 100L
private fun quickUpdateHeaderPreference(preference: UsageProgressBarPreference) {
val batteryIntent = BatteryUtils.getBatteryIntent(preference.context) ?: return
val batteryLevel: Int = Utils.getBatteryLevel(batteryIntent)
preference.apply {
setUsageSummary(com.android.settings.Utils.formatPercentage(batteryLevel))
setPercent(batteryLevel.toLong(), BATTERY_MAX_LEVEL)
setBottomSummary("")
}
}
}
}
// LINT.ThenChange(BatteryHeaderPreferenceController.java)

View File

@@ -1,5 +1,6 @@
/*
* Copyright (C) 2017 The Android Open Source Project
* Copyright (C) 2024 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
@@ -15,59 +16,77 @@
package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType.BATTERY_NOT_PRESENT;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
import com.android.settingslib.widget.UsageProgressBarPreference;
// LINT.IfChange
/** Controller that update the battery header view */
public class BatteryHeaderPreferenceController extends BasePreferenceController
implements PreferenceControllerMixin, BatteryPreferenceController {
implements PreferenceControllerMixin, LifecycleEventObserver {
private static final String TAG = "BatteryHeaderPreferenceController";
@VisibleForTesting static final String KEY_BATTERY_HEADER = "battery_header";
private static final int BATTERY_MAX_LEVEL = 100;
@VisibleForTesting BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
@VisibleForTesting UsageProgressBarPreference mBatteryUsageProgressBarPref;
private final PowerManager mPowerManager;
private final BatterySettingsFeatureProvider mBatterySettingsFeatureProvider;
private BatteryTip mBatteryTip;
@Nullable @VisibleForTesting BatteryBroadcastReceiver mBatteryBroadcastReceiver;
@Nullable @VisibleForTesting UsageProgressBarPreference mBatteryUsageProgressBarPreference;
public BatteryHeaderPreferenceController(Context context, String key) {
super(context, key);
mPowerManager = context.getSystemService(PowerManager.class);
mBatteryStatusFeatureProvider =
FeatureFactory.getFeatureFactory().getBatteryStatusFeatureProvider();
mBatterySettingsFeatureProvider =
FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider();
}
@Override
public void onStateChanged(@NonNull LifecycleOwner lifecycleOwner,
@NonNull Lifecycle.Event event) {
switch (event) {
case ON_CREATE:
mBatteryBroadcastReceiver = new BatteryBroadcastReceiver(mContext);
mBatteryBroadcastReceiver.setBatteryChangedListener(
type -> {
if (type != BATTERY_NOT_PRESENT) {
quickUpdateHeaderPreference();
}
});
break;
case ON_START:
if (mBatteryBroadcastReceiver != null) {
mBatteryBroadcastReceiver.register();
}
break;
case ON_STOP:
if (mBatteryBroadcastReceiver != null) {
mBatteryBroadcastReceiver.unRegister();
}
break;
default:
break;
}
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mBatteryUsageProgressBarPref = screen.findPreference(getPreferenceKey());
// Set up empty space text first to prevent layout flaky before info loaded.
mBatteryUsageProgressBarPref.setBottomSummary(" ");
mBatteryUsageProgressBarPreference = screen.findPreference(getPreferenceKey());
// Hide the bottom summary from the progress bar.
mBatteryUsageProgressBarPreference.setBottomSummary("");
if (com.android.settings.Utils.isBatteryPresent(mContext)) {
quickUpdateHeaderPreference();
} else {
mBatteryUsageProgressBarPref.setVisible(false);
mBatteryUsageProgressBarPreference.setVisible(false);
}
}
@@ -76,105 +95,23 @@ public class BatteryHeaderPreferenceController extends BasePreferenceController
return AVAILABLE_UNSEARCHABLE;
}
private CharSequence generateLabel(BatteryInfo info) {
if (Utils.containsIncompatibleChargers(mContext, TAG)) {
return mContext.getString(
com.android.settingslib.R.string.battery_info_status_not_charging);
}
if (BatteryUtils.isBatteryDefenderOn(info)
|| FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()
.isExtraDefend()) {
return mContext.getString(
com.android.settingslib.R.string.battery_info_status_charging_on_hold);
}
if (info.remainingLabel != null
&& mBatterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) {
return info.remainingLabel;
}
if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
return info.statusLabel;
}
if (info.pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
final CharSequence wirelessChargingLabel =
mBatterySettingsFeatureProvider.getWirelessChargingLabel(mContext, info);
if (wirelessChargingLabel != null) {
mBatteryUsageProgressBarPref.setBottomSummaryContentDescription(
mBatterySettingsFeatureProvider
.getWirelessChargingContentDescription(mContext, info));
return wirelessChargingLabel;
}
}
if (info.remainingLabel == null) {
return info.statusLabel;
}
if (info.statusLabel != null && !info.discharging) {
// Charging state
if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) {
return info.isFastCharging
? mContext.getString(
R.string.battery_state_and_duration,
info.statusLabel,
info.remainingLabel)
: info.remainingLabel;
}
return mContext.getString(
R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel);
} else if (mPowerManager.isPowerSaveMode()) {
// Power save mode is on
final String powerSaverOn =
mContext.getString(R.string.battery_tip_early_heads_up_done_title);
return mContext.getString(
R.string.battery_state_and_duration, powerSaverOn, info.remainingLabel);
} else if (mBatteryTip != null && mBatteryTip.getType() == BatteryTip.TipType.LOW_BATTERY) {
// Low battery state
final String lowBattery = mContext.getString(R.string.low_battery_summary);
return mContext.getString(
R.string.battery_state_and_duration, lowBattery, info.remainingLabel);
} else {
// Discharging state
return info.remainingLabel;
}
}
public void updateHeaderPreference(BatteryInfo info) {
if (!mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
mBatteryUsageProgressBarPref.setBottomSummary(generateLabel(info));
}
mBatteryUsageProgressBarPref.setUsageSummary(
formatBatteryPercentageText(info.batteryLevel));
mBatteryUsageProgressBarPref.setPercent(info.batteryLevel, BATTERY_MAX_LEVEL);
}
/** Callback which receives text for the summary line. */
public void updateBatteryStatus(String label, BatteryInfo info) {
final CharSequence summary = label != null ? label : generateLabel(info);
mBatteryUsageProgressBarPref.setBottomSummary(summary);
Log.d(TAG, "updateBatteryStatus: " + label + " summary: " + summary);
}
/** Updates {@link UsageProgressBarPreference} information. */
public void quickUpdateHeaderPreference() {
if (mBatteryUsageProgressBarPreference == null) {
return;
}
Intent batteryBroadcast =
com.android.settingslib.fuelgauge.BatteryUtils.getBatteryIntent(mContext);
final int batteryLevel = Utils.getBatteryLevel(batteryBroadcast);
final boolean discharging =
batteryBroadcast.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) == 0;
mBatteryUsageProgressBarPref.setUsageSummary(formatBatteryPercentageText(batteryLevel));
mBatteryUsageProgressBarPref.setPercent(batteryLevel, BATTERY_MAX_LEVEL);
}
/** Update summary when battery tips changed. */
public void updateHeaderByBatteryTips(BatteryTip batteryTip, BatteryInfo batteryInfo) {
mBatteryTip = batteryTip;
if (mBatteryTip != null && batteryInfo != null) {
updateHeaderPreference(batteryInfo);
}
mBatteryUsageProgressBarPreference.setUsageSummary(
formatBatteryPercentageText(batteryLevel));
mBatteryUsageProgressBarPreference.setPercent(batteryLevel, BATTERY_MAX_LEVEL);
}
private CharSequence formatBatteryPercentageText(int batteryLevel) {
return com.android.settings.Utils.formatPercentage(batteryLevel);
}
}
// LINT.ThenChange(BatteryHeaderPreference.kt)

View File

@@ -0,0 +1,63 @@
/*
* Copyright (C) 2024 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.content.Context;
import android.text.TextUtils;
import android.text.method.LinkMovementMethod;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settingslib.widget.GroupSectionDividerMixin;
/** A preference for battery header text. */
public class BatteryHeaderTextPreference extends Preference implements GroupSectionDividerMixin {
private static final String TAG = "BatteryHeaderTextPreference";
@Nullable private CharSequence mText;
@Nullable private CharSequence mContentDescription;
public BatteryHeaderTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
setLayoutResource(R.layout.preference_battery_header_text);
}
@Override
public void onBindViewHolder(PreferenceViewHolder view) {
final TextView textView = (TextView) view.findViewById(R.id.text);
textView.setText(mText);
textView.setMovementMethod(LinkMovementMethod.getInstance());
if (!TextUtils.isEmpty(mContentDescription)) {
textView.setContentDescription(mContentDescription);
}
}
void setText(@Nullable CharSequence text) {
mText = text;
notifyChanged();
}
void setContentDescription(@Nullable CharSequence contentDescription) {
mContentDescription = contentDescription;
notifyChanged();
}
}

View File

@@ -0,0 +1,166 @@
/*
* Copyright (C) 2024 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.content.Context;
import android.os.BatteryManager;
import android.os.PowerManager;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceScreen;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.Utils;
/** Controller that update the battery header view */
public class BatteryHeaderTextPreferenceController extends BasePreferenceController
implements PreferenceControllerMixin, BatteryPreferenceController {
private static final String TAG = "BatteryHeaderTextPreferenceController";
private final PowerManager mPowerManager;
private final BatterySettingsFeatureProvider mBatterySettingsFeatureProvider;
@Nullable private BatteryTip mBatteryTip;
@VisibleForTesting BatteryStatusFeatureProvider mBatteryStatusFeatureProvider;
@Nullable @VisibleForTesting BatteryHeaderTextPreference mBatteryHeaderTextPreference;
public BatteryHeaderTextPreferenceController(Context context, String key) {
super(context, key);
mPowerManager = context.getSystemService(PowerManager.class);
mBatteryStatusFeatureProvider =
FeatureFactory.getFeatureFactory().getBatteryStatusFeatureProvider();
mBatterySettingsFeatureProvider =
FeatureFactory.getFeatureFactory().getBatterySettingsFeatureProvider();
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mBatteryHeaderTextPreference = screen.findPreference(getPreferenceKey());
if (mBatteryHeaderTextPreference != null
&& !com.android.settings.Utils.isBatteryPresent(mContext)) {
mBatteryHeaderTextPreference.setVisible(false);
}
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@NonNull
private CharSequence generateLabel(@NonNull BatteryInfo info) {
if (Utils.containsIncompatibleChargers(mContext, TAG)) {
return mContext.getString(
com.android.settingslib.R.string.battery_info_status_not_charging);
}
if (BatteryUtils.isBatteryDefenderOn(info)
|| FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()
.isExtraDefend()) {
return mContext.getString(
com.android.settingslib.R.string.battery_info_status_charging_on_hold);
}
if (info.remainingLabel != null
&& mBatterySettingsFeatureProvider.isChargingOptimizationMode(mContext)) {
return info.remainingLabel;
}
if (info.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
return info.statusLabel;
}
if (info.pluggedStatus == BatteryManager.BATTERY_PLUGGED_WIRELESS) {
final CharSequence wirelessChargingLabel =
mBatterySettingsFeatureProvider.getWirelessChargingLabel(mContext, info);
if (mBatteryHeaderTextPreference != null && wirelessChargingLabel != null) {
mBatteryHeaderTextPreference.setContentDescription(
mBatterySettingsFeatureProvider
.getWirelessChargingContentDescription(mContext, info));
return wirelessChargingLabel;
}
}
if (info.remainingLabel == null) {
return info.statusLabel;
}
if (info.statusLabel != null && !info.discharging) {
// Charging state
if (com.android.settingslib.fuelgauge.BatteryUtils.isChargingStringV2Enabled()) {
return info.isFastCharging
? mContext.getString(
R.string.battery_state_and_duration,
info.statusLabel,
info.remainingLabel)
: info.remainingLabel;
}
return mContext.getString(
R.string.battery_state_and_duration, info.statusLabel, info.remainingLabel);
} else if (mPowerManager.isPowerSaveMode()) {
// Power save mode is on
final String powerSaverOn =
mContext.getString(R.string.battery_tip_early_heads_up_done_title);
return mContext.getString(
R.string.battery_state_and_duration, powerSaverOn, info.remainingLabel);
} else if (mBatteryTip != null && mBatteryTip.getType() == BatteryTip.TipType.LOW_BATTERY) {
// Low battery state
final String lowBattery = mContext.getString(R.string.low_battery_summary);
return mContext.getString(
R.string.battery_state_and_duration, lowBattery, info.remainingLabel);
} else {
// Discharging state
return info.remainingLabel;
}
}
/** Updates the battery header text with the given BatteryInfo. */
public void updateHeaderPreference(@NonNull BatteryInfo info) {
if (mBatteryHeaderTextPreference != null
&& !mBatteryStatusFeatureProvider.triggerBatteryStatusUpdate(this, info)) {
mBatteryHeaderTextPreference.setText(generateLabel(info));
}
}
/** Callback which updates the battery header text with the given label. */
@Override
public void updateBatteryStatus(String label, BatteryInfo info) {
if (mBatteryHeaderTextPreference == null) {
return;
}
final CharSequence summary = label != null ? label : generateLabel(info);
mBatteryHeaderTextPreference.setText(summary);
Log.d(TAG, "updateBatteryStatus: " + label + " summary: " + summary);
}
/** Update summary when battery tips are changed. */
public void updateHeaderByBatteryTips(
@Nullable BatteryTip batteryTip, @NonNull BatteryInfo batteryInfo) {
mBatteryTip = batteryTip;
if (mBatteryTip != null && batteryInfo != null) {
updateHeaderPreference(batteryInfo);
}
}
}

View File

@@ -0,0 +1,136 @@
/*
* Copyright (C) 2024 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.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.widget.MainSwitchPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
/** Controller to update the app background usage mode state in Allow background usage page */
public class BatteryOptimizationModePreferenceController extends BasePreferenceController
implements PreferenceControllerMixin {
@VisibleForTesting
static final String KEY_BACKGROUND_USAGE_ALLOWABILITY_SWITCH =
"background_usage_allowability_switch";
@VisibleForTesting static final String KEY_OPTIMIZED_PREF = "optimized_preference";
@VisibleForTesting static final String KEY_UNRESTRICTED_PREF = "unrestricted_preference";
private final BatteryOptimizeUtils mBatteryOptimizeUtils;
@Nullable @VisibleForTesting MainSwitchPreference mBackgroundUsageAllowabilityPreference;
@Nullable @VisibleForTesting SelectorWithWidgetPreference mOptimizedPreference;
@Nullable @VisibleForTesting SelectorWithWidgetPreference mUnrestrictedPreference;
public BatteryOptimizationModePreferenceController(
@NonNull Context context,
@NonNull String preferenceKey,
@NonNull BatteryOptimizeUtils batteryOptimizeUtils) {
super(context, preferenceKey);
mBatteryOptimizeUtils = batteryOptimizeUtils;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE;
}
@Override
public void updateState(@NonNull Preference preference) {
updatePreferences(mBatteryOptimizeUtils.getAppOptimizationMode());
}
@Override
public void displayPreference(@NonNull PreferenceScreen screen) {
super.displayPreference(screen);
mBackgroundUsageAllowabilityPreference =
screen.findPreference(KEY_BACKGROUND_USAGE_ALLOWABILITY_SWITCH);
mOptimizedPreference = screen.findPreference(KEY_OPTIMIZED_PREF);
mUnrestrictedPreference = screen.findPreference(KEY_UNRESTRICTED_PREF);
initPreferences();
}
@VisibleForTesting
void initPreferences() {
if (mBackgroundUsageAllowabilityPreference == null
|| mOptimizedPreference == null
|| mUnrestrictedPreference == null) {
return;
}
final boolean isEnabled = mBatteryOptimizeUtils.isOptimizeModeMutable();
mBackgroundUsageAllowabilityPreference.setEnabled(isEnabled);
mOptimizedPreference.setEnabled(isEnabled);
mUnrestrictedPreference.setEnabled(isEnabled);
if (isEnabled) {
mBackgroundUsageAllowabilityPreference.setOnPreferenceChangeListener(
(preference, isAllowBackground) -> {
handleBatteryOptimizeModeUpdated(
(boolean) isAllowBackground
? BatteryOptimizeUtils.MODE_OPTIMIZED
: BatteryOptimizeUtils.MODE_RESTRICTED);
return true;
});
mOptimizedPreference.setOnPreferenceClickListener(
preference -> {
handleBatteryOptimizeModeUpdated(BatteryOptimizeUtils.MODE_OPTIMIZED);
return true;
});
mUnrestrictedPreference.setOnPreferenceClickListener(
preference -> {
handleBatteryOptimizeModeUpdated(BatteryOptimizeUtils.MODE_UNRESTRICTED);
return true;
});
}
}
@VisibleForTesting
void updatePreferences(int optimizationMode) {
if (mBackgroundUsageAllowabilityPreference == null
|| mOptimizedPreference == null
|| mUnrestrictedPreference == null) {
return;
}
final boolean isAllowBackground = optimizationMode != BatteryOptimizeUtils.MODE_RESTRICTED;
mBackgroundUsageAllowabilityPreference.setChecked(isAllowBackground);
mOptimizedPreference.setEnabled(isAllowBackground);
mUnrestrictedPreference.setEnabled(isAllowBackground);
mOptimizedPreference.setChecked(optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED);
mUnrestrictedPreference.setChecked(
optimizationMode == BatteryOptimizeUtils.MODE_UNRESTRICTED);
}
@VisibleForTesting
void handleBatteryOptimizeModeUpdated(int newBatteryOptimizeMode) {
if (mBatteryOptimizeUtils.getAppOptimizationMode() == newBatteryOptimizeMode) {
Log.w(TAG, "ignore same mode for: " + mBatteryOptimizeUtils.getPackageName());
return;
}
mBatteryOptimizeUtils.setAppUsageState(
newBatteryOptimizeMode, BatteryOptimizeHistoricalLogEntry.Action.APPLY);
updatePreferences(newBatteryOptimizeMode);
}
}

View File

@@ -100,7 +100,8 @@ public class BatteryOptimizeUtils {
/** Gets the {@link OptimizationMode} based on mode and allowed list. */
@OptimizationMode
public static int getAppOptimizationMode(int mode, boolean isAllowListed) {
public static int getAppOptimizationMode(
int mode, boolean isAllowListed, boolean ignoreUnknownMode) {
if (!isAllowListed && mode == AppOpsManager.MODE_IGNORED) {
return MODE_RESTRICTED;
} else if (isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
@@ -108,13 +109,15 @@ public class BatteryOptimizeUtils {
} else if (!isAllowListed && mode == AppOpsManager.MODE_ALLOWED) {
return MODE_OPTIMIZED;
} else {
return MODE_UNKNOWN;
// MODE_UNKNOWN = isAllowListed + AppOpsManager.MODE_IGNORED
// Return Unrestricted mode for Unknown mode since it is in allowlist.
return ignoreUnknownMode ? MODE_UNRESTRICTED : MODE_UNKNOWN;
}
}
/** Gets the {@link OptimizationMode} for associated app. */
@OptimizationMode
public int getAppOptimizationMode(boolean refreshList) {
public int getAppOptimizationMode(boolean refreshList, boolean ignoreUnknownMode) {
if (refreshList) {
mPowerAllowListBackend.refreshList();
}
@@ -127,13 +130,13 @@ public class BatteryOptimizeUtils {
String.format(
"refresh %s state, allowlisted = %s, mode = %d",
mPackageName, mAllowListed, mMode));
return getAppOptimizationMode(mMode, mAllowListed);
return getAppOptimizationMode(mMode, mAllowListed, ignoreUnknownMode);
}
/** Gets the {@link OptimizationMode} for associated app. */
@OptimizationMode
public int getAppOptimizationMode() {
return getAppOptimizationMode(true);
return getAppOptimizationMode(/* refreshList= */ true, /* ignoreUnknownMode= */ true);
}
/** Resets optimization mode for all applications. */
@@ -246,10 +249,11 @@ public class BatteryOptimizeUtils {
@OptimizationMode
final int optimizationMode =
getAppOptimizationMode(
mode, allowlistBackend.isAllowlisted(info.packageName, info.uid));
// Ignores default optimized/unknown state or system/default apps.
mode,
allowlistBackend.isAllowlisted(info.packageName, info.uid),
/* ignoreUnknownMode= */ false);
// Ignores default optimized state or system/default apps.
if (optimizationMode == MODE_OPTIMIZED
|| optimizationMode == MODE_UNKNOWN
|| isSystemOrDefaultApp(
context, allowlistBackend, info.packageName, info.uid)) {
continue;
@@ -374,7 +378,8 @@ public class BatteryOptimizeUtils {
"\tStandbyMode: %s, allowListed: %s, mode: %s",
appStandbyMode,
allowListed,
getAppOptimizationMode(appStandbyMode, allowListed));
getAppOptimizationMode(
appStandbyMode, allowListed, /* ignoreUnknownMode= */ false));
}
private static @DataChangeReason int toChangeReason(Action action) {

View File

@@ -72,25 +72,22 @@ public final class BatterySettingsMigrateChecker extends BroadcastReceiver {
Context context,
@BatteryOptimizeUtils.OptimizationMode int optimizationMode,
List<String> allowList) {
allowList.forEach(
packageName -> {
final BatteryOptimizeUtils batteryOptimizeUtils =
BatteryBackupHelper.newBatteryOptimizeUtils(
context,
packageName,
/* testOptimizeUtils */ sBatteryOptimizeUtils);
if (batteryOptimizeUtils == null) {
return;
}
if (batteryOptimizeUtils.getAppOptimizationMode() != optimizationMode) {
Log.w(
TAG,
"Reset " + packageName + " battery mode into " + optimizationMode);
batteryOptimizeUtils.setAppUsageState(
optimizationMode,
BatteryOptimizeHistoricalLogEntry.Action.FORCE_RESET);
}
});
for (String packageName : allowList) {
final BatteryOptimizeUtils batteryOptimizeUtils =
BatteryBackupHelper.newBatteryOptimizeUtils(
context,
packageName,
/* testOptimizeUtils */ sBatteryOptimizeUtils);
if (batteryOptimizeUtils == null) {
continue;
}
if (batteryOptimizeUtils.getAppOptimizationMode() != optimizationMode) {
Log.w(TAG, "Reset " + packageName + " mode into " + optimizationMode);
batteryOptimizeUtils.setAppUsageState(
optimizationMode,
BatteryOptimizeHistoricalLogEntry.Action.FORCE_RESET);
}
}
}
static void verifySaverConfiguration(Context context) {

View File

@@ -347,7 +347,9 @@ public final class BatterySettingsStorage extends ObservableBackupRestoreStorage
appOptModeMap.containsKey(info.uid)
? (int) appOptModeMap.get(info.uid).getResetOptimizationMode()
: BatteryOptimizeUtils.getAppOptimizationMode(
mode, mAllowlistedApps.contains(info.packageName));
mode,
mAllowlistedApps.contains(info.packageName),
/* ignoreUnknownMode= */ false);
// Ignores default optimized/unknown state or system/default apps.
if (optimizationMode == BatteryOptimizeUtils.MODE_OPTIMIZED
|| optimizationMode == BatteryOptimizeUtils.MODE_UNKNOWN

View File

@@ -1,66 +0,0 @@
/*
* Copyright (C) 2021 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.content.Context;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
public class OptimizedPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
private static final String TAG = "OPTIMIZED_PREF";
@VisibleForTesting static final String KEY_OPTIMIZED_PREF = "optimized_preference";
@VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
public OptimizedPreferenceController(Context context, int uid, String packageName) {
super(context);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(context, uid, packageName);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void updateState(Preference preference) {
preference.setEnabled(mBatteryOptimizeUtils.isSelectorPreferenceEnabled());
final boolean isOptimized =
mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()
|| mBatteryOptimizeUtils.getAppOptimizationMode()
== BatteryOptimizeUtils.MODE_OPTIMIZED;
((SelectorWithWidgetPreference) preference).setChecked(isOptimized);
}
@Override
public String getPreferenceKey() {
return KEY_OPTIMIZED_PREF;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
return getPreferenceKey().equals(preference.getKey());
}
}

View File

@@ -24,11 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import androidx.annotation.VisibleForTesting;
@@ -37,15 +33,12 @@ import com.android.settings.core.SubSettingLauncher;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.fuelgauge.batteryusage.AppOptModeSharedPreferencesUtils;
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.Utils;
import com.android.settingslib.applications.ApplicationsState;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.FooterPreference;
import com.android.settingslib.widget.LayoutPreference;
import com.android.settingslib.widget.MainSwitchPreference;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
import com.android.settingslib.widget.IntroPreference;
import java.util.ArrayList;
import java.util.List;
@@ -53,8 +46,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** Allow background usage fragment for each app */
public class PowerBackgroundUsageDetail extends DashboardFragment
implements SelectorWithWidgetPreference.OnClickListener, OnCheckedChangeListener {
public class PowerBackgroundUsageDetail extends DashboardFragment {
private static final String TAG = "PowerBackgroundUsageDetail";
public static final String EXTRA_UID = "extra_uid";
@@ -63,21 +55,15 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
public static final String EXTRA_POWER_USAGE_AMOUNT = "extra_power_usage_amount";
public static final String EXTRA_ICON_ID = "extra_icon_id";
private static final String KEY_PREF_HEADER = "header_view";
private static final String KEY_PREF_UNRESTRICTED = "unrestricted_preference";
private static final String KEY_PREF_OPTIMIZED = "optimized_preference";
private static final String KEY_ALLOW_BACKGROUND_USAGE = "allow_background_usage";
private static final String KEY_FOOTER_PREFERENCE = "app_usage_footer_preference";
private static final String KEY_BATTERY_OPTIMIZATION_MODE_CATEGORY =
"battery_optimization_mode_category";
private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@VisibleForTesting LayoutPreference mHeaderPreference;
@VisibleForTesting ApplicationsState mState;
@VisibleForTesting ApplicationsState.AppEntry mAppEntry;
@VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
@VisibleForTesting SelectorWithWidgetPreference mOptimizePreference;
@VisibleForTesting SelectorWithWidgetPreference mUnrestrictedPreference;
@VisibleForTesting MainSwitchPreference mMainSwitchPreference;
@VisibleForTesting FooterPreference mFooterPreference;
@VisibleForTesting StringBuilder mLogStringBuilder;
@VisibleForTesting @BatteryOptimizeUtils.OptimizationMode
@@ -87,17 +73,11 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
public void onAttach(Activity activity) {
super.onAttach(activity);
final Bundle bundle = getArguments();
final int uid = bundle.getInt(EXTRA_UID, 0);
final String packageName = bundle.getString(EXTRA_PACKAGE_NAME);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(getContext(), uid, packageName);
mState = ApplicationsState.getInstance(getActivity().getApplication());
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final String packageName = getArguments().getString(EXTRA_PACKAGE_NAME);
onCreateBackgroundUsageState(packageName);
mHeaderPreference = findPreference(KEY_PREF_HEADER);
if (packageName != null) {
mAppEntry = mState.getEntry(packageName, UserHandle.myUserId());
}
@@ -107,8 +87,8 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
public void onResume() {
super.onResume();
initHeader();
mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode();
initFooter();
mOptimizationMode = mBatteryOptimizeUtils.getAppOptimizationMode();
mLogStringBuilder = new StringBuilder("onResume mode = ").append(mOptimizationMode);
}
@@ -136,20 +116,6 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
Log.d(TAG, "Leave with mode: " + currentOptimizeMode);
}
@Override
public void onRadioButtonClicked(SelectorWithWidgetPreference selected) {
final String selectedKey = selected == null ? null : selected.getKey();
updateSelectorPreferenceState(mUnrestrictedPreference, selectedKey);
updateSelectorPreferenceState(mOptimizePreference, selectedKey);
mBatteryOptimizeUtils.setAppUsageState(getSelectedPreference(), Action.APPLY);
}
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
mMainSwitchPreference.setChecked(isChecked);
updateSelectorPreference(isChecked);
}
@Override
public int getMetricsCategory() {
return SettingsEnums.FUELGAUGE_POWER_USAGE_MANAGE_BACKGROUND;
@@ -157,14 +123,10 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
@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);
controllers.add(new AllowBackgroundPreferenceController(context, uid, packageName));
controllers.add(new OptimizedPreferenceController(context, uid, packageName));
controllers.add(new UnrestrictedPreferenceController(context, uid, packageName));
final List<AbstractPreferenceController> controllers = new ArrayList<>(1);
controllers.add(
new BatteryOptimizationModePreferenceController(
context, KEY_BATTERY_OPTIMIZATION_MODE_CATEGORY, mBatteryOptimizeUtils));
return controllers;
}
@@ -179,26 +141,6 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
return TAG;
}
@VisibleForTesting
void updateSelectorPreference(boolean isEnabled) {
mOptimizePreference.setEnabled(isEnabled);
mUnrestrictedPreference.setEnabled(isEnabled);
onRadioButtonClicked(isEnabled ? mOptimizePreference : null);
}
@VisibleForTesting
int getSelectedPreference() {
if (!mMainSwitchPreference.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;
}
}
static void startPowerBackgroundUsageDetailPage(Context context, Bundle args) {
new SubSettingLauncher(context)
.setDestination(PowerBackgroundUsageDetail.class.getName())
@@ -209,88 +151,50 @@ public class PowerBackgroundUsageDetail extends DashboardFragment
@VisibleForTesting
void initHeader() {
final View appSnippet = mHeaderPreference.findViewById(R.id.entity_header);
final IntroPreference introPreference = findPreference(KEY_PREF_HEADER);
if (introPreference == null) {
return;
}
final Activity context = getActivity();
final Bundle bundle = getArguments();
EntityHeaderController controller =
EntityHeaderController.newInstance(context, this, appSnippet)
.setButtonActions(
EntityHeaderController.ActionType.ACTION_NONE,
EntityHeaderController.ActionType.ACTION_NONE);
if (mAppEntry == null) {
controller.setLabel(bundle.getString(EXTRA_LABEL));
introPreference.setTitle(bundle.getString(EXTRA_LABEL));
final int iconId = bundle.getInt(EXTRA_ICON_ID, 0);
if (iconId == 0) {
controller.setIcon(context.getPackageManager().getDefaultActivityIcon());
introPreference.setIcon(context.getPackageManager().getDefaultActivityIcon());
} else {
controller.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
introPreference.setIcon(context.getDrawable(bundle.getInt(EXTRA_ICON_ID)));
}
} else {
mState.ensureIcon(mAppEntry);
controller.setLabel(mAppEntry);
controller.setIcon(mAppEntry);
controller.setIsInstantApp(AppUtils.isInstant(mAppEntry.info));
introPreference.setTitle(mAppEntry.label);
introPreference.setIcon(Utils.getBadgedIcon(context, mAppEntry.info));
}
controller.done(true /* rebindActions */);
}
@VisibleForTesting
void initFooter() {
final String stateString;
final String footerString;
final Context context = getContext();
if (mBatteryOptimizeUtils.isDisabledForOptimizeModeOnly()) {
// 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);
final FooterPreference footerPreference = findPreference(KEY_FOOTER_PREFERENCE);
if (footerPreference == null) {
return;
}
mFooterPreference.setTitle(footerString);
final Context context = getContext();
footerPreference.setTitle(context.getString(R.string.manager_battery_usage_footer));
final Intent helpIntent =
HelpUtils.getHelpIntent(
context,
context.getString(R.string.help_url_app_usage_settings),
/* backupContext= */ "");
if (helpIntent != null) {
mFooterPreference.setLearnMoreAction(
footerPreference.setLearnMoreAction(
v -> startActivityForResult(helpIntent, /* requestCode= */ 0));
mFooterPreference.setLearnMoreText(
footerPreference.setLearnMoreText(
context.getString(R.string.manager_battery_usage_link_a11y));
}
}
private void onCreateBackgroundUsageState(String packageName) {
mOptimizePreference = findPreference(KEY_PREF_OPTIMIZED);
mUnrestrictedPreference = findPreference(KEY_PREF_UNRESTRICTED);
mMainSwitchPreference = findPreference(KEY_ALLOW_BACKGROUND_USAGE);
mFooterPreference = findPreference(KEY_FOOTER_PREFERENCE);
mOptimizePreference.setOnClickListener(this);
mUnrestrictedPreference.setOnClickListener(this);
mMainSwitchPreference.addOnSwitchChangeListener(this);
mBatteryOptimizeUtils =
new BatteryOptimizeUtils(
getContext(), getArguments().getInt(EXTRA_UID), packageName);
}
private void updateSelectorPreferenceState(
SelectorWithWidgetPreference preference, String selectedKey) {
preference.setChecked(TextUtils.equals(selectedKey, preference.getKey()));
}
private void logMetricCategory(int currentOptimizeMode) {
if (currentOptimizeMode == mOptimizationMode) {
return;

View File

@@ -16,6 +16,8 @@
package com.android.settings.fuelgauge;
import static com.android.settings.fuelgauge.BatteryOptimizeUtils.MODE_UNRESTRICTED;
import android.Manifest;
import android.content.DialogInterface;
import android.content.pm.ApplicationInfo;
@@ -24,20 +26,20 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerWhitelistManager;
import android.text.TextUtils;
import android.util.Log;
import com.android.internal.app.AlertActivity;
import com.android.internal.app.AlertController;
import com.android.settings.R;
import com.android.settings.fuelgauge.BatteryOptimizeHistoricalLogEntry.Action;
public class RequestIgnoreBatteryOptimizations extends AlertActivity
implements DialogInterface.OnClickListener {
private static final String TAG = "RequestIgnoreBatteryOptimizations";
private static final boolean DEBUG = false;
private PowerWhitelistManager mPowerWhitelistManager;
private String mPackageName;
private ApplicationInfo mApplicationInfo;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -47,8 +49,6 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
android.view.WindowManager.LayoutParams
.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
mPowerWhitelistManager = getSystemService(PowerWhitelistManager.class);
Uri data = getIntent().getData();
if (data == null) {
debugLog(
@@ -56,17 +56,18 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
finish();
return;
}
mPackageName = data.getSchemeSpecificPart();
if (mPackageName == null) {
final String packageName = data.getSchemeSpecificPart();
if (TextUtils.isEmpty(packageName)) {
debugLog(
"No data supplied for IGNORE_BATTERY_OPTIMIZATION_SETTINGS in: " + getIntent());
finish();
return;
}
// Package in Unrestricted mode already ignoring the battery optimizations.
PowerManager power = getSystemService(PowerManager.class);
if (power.isIgnoringBatteryOptimizations(mPackageName)) {
debugLog("Not should prompt, already ignoring optimizations: " + mPackageName);
if (power.isIgnoringBatteryOptimizations(packageName)) {
debugLog("Not should prompt, already ignoring optimizations: " + packageName);
finish();
return;
}
@@ -74,29 +75,28 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
if (getPackageManager()
.checkPermission(
Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,
mPackageName)
packageName)
!= PackageManager.PERMISSION_GRANTED) {
debugLog(
"Requested package "
+ mPackageName
+ packageName
+ " does not hold permission "
+ Manifest.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
finish();
return;
}
ApplicationInfo ai;
try {
ai = getPackageManager().getApplicationInfo(mPackageName, 0);
mApplicationInfo = getPackageManager().getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
debugLog("Requested package doesn't exist: " + mPackageName);
debugLog("Requested package doesn't exist: " + packageName);
finish();
return;
}
final AlertController.AlertParams p = mAlertParams;
final CharSequence appLabel =
ai.loadSafeLabel(
mApplicationInfo.loadSafeLabel(
getPackageManager(),
PackageItemInfo.DEFAULT_MAX_LABEL_SIZE_PX,
PackageItemInfo.SAFE_LABEL_FLAG_TRIM
@@ -114,7 +114,12 @@ public class RequestIgnoreBatteryOptimizations extends AlertActivity
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case BUTTON_POSITIVE:
mPowerWhitelistManager.addToWhitelist(mPackageName);
BatteryOptimizeUtils batteryOptimizeUtils =
new BatteryOptimizeUtils(
getApplicationContext(),
mApplicationInfo.uid,
mApplicationInfo.packageName);
batteryOptimizeUtils.setAppUsageState(MODE_UNRESTRICTED, Action.APPLY);
break;
case BUTTON_NEGATIVE:
break;

View File

@@ -1,66 +0,0 @@
/*
* Copyright (C) 2021 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.content.Context;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settingslib.core.AbstractPreferenceController;
import com.android.settingslib.widget.SelectorWithWidgetPreference;
public class UnrestrictedPreferenceController extends AbstractPreferenceController
implements PreferenceControllerMixin {
private static final String TAG = "UNRESTRICTED_PREF";
@VisibleForTesting static final String KEY_UNRESTRICTED_PREF = "unrestricted_preference";
@VisibleForTesting BatteryOptimizeUtils mBatteryOptimizeUtils;
public UnrestrictedPreferenceController(Context context, int uid, String packageName) {
super(context);
mBatteryOptimizeUtils = new BatteryOptimizeUtils(context, uid, packageName);
}
@Override
public void updateState(Preference preference) {
preference.setEnabled(mBatteryOptimizeUtils.isSelectorPreferenceEnabled());
final boolean isUnrestricted =
mBatteryOptimizeUtils.getAppOptimizationMode()
== BatteryOptimizeUtils.MODE_UNRESTRICTED;
((SelectorWithWidgetPreference) preference).setChecked(isUnrestricted);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_UNRESTRICTED_PREF;
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
return getPreferenceKey().equals(preference.getKey());
}
}

View File

@@ -38,6 +38,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.widget.MainSwitchPreference;
/** Controller to update the battery saver button */
// LINT.IfChange
public class BatterySaverButtonPreferenceController extends TogglePreferenceController
implements LifecycleObserver, OnStart, OnStop, BatterySaverReceiver.BatterySaverListener {
private static final long SWITCH_ANIMATION_DURATION = 350L;
@@ -129,3 +130,4 @@ public class BatterySaverButtonPreferenceController extends TogglePreferenceCont
}
}
}
// LINT.ThenChange(BatterySaverPreference.kt)

View File

@@ -0,0 +1,109 @@
/*
* Copyright (C) 2024 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.batterysaver
import android.content.Context
import android.os.PowerManager
import com.android.settings.R
import com.android.settings.fuelgauge.BatterySaverReceiver
import com.android.settings.fuelgauge.BatterySaverReceiver.BatterySaverListener
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.NoOpKeyedObservable
import com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_SETTINGS
import com.android.settingslib.fuelgauge.BatterySaverUtils
import com.android.settingslib.fuelgauge.BatteryStatus
import com.android.settingslib.fuelgauge.BatteryUtils
import com.android.settingslib.metadata.MainSwitchPreference
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
// LINT.IfChange
class BatterySaverPreference :
MainSwitchPreference(KEY, R.string.battery_saver_master_switch_title),
PreferenceLifecycleProvider {
private var batterySaverReceiver: BatterySaverReceiver? = null
override fun storage(context: Context) = BatterySaverStore(context)
override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override val sensitivityLevel
get() = SensitivityLevel.NO_SENSITIVITY
override fun isEnabled(context: Context) =
!BatteryStatus(BatteryUtils.getBatteryIntent(context)).isPluggedIn
override fun onStart(context: PreferenceLifecycleContext) {
BatterySaverReceiver(context).apply {
batterySaverReceiver = this
setBatterySaverListener(
object : BatterySaverListener {
override fun onPowerSaveModeChanged() {
context.lifecycleScope.launch {
delay(SWITCH_ANIMATION_DURATION)
context.notifyPreferenceChange(KEY)
}
}
override fun onBatteryChanged(pluggedIn: Boolean) =
context.notifyPreferenceChange(KEY)
}
)
setListening(true)
}
}
override fun onStop(context: PreferenceLifecycleContext) {
batterySaverReceiver?.setListening(false)
batterySaverReceiver = null
}
@Suppress("UNCHECKED_CAST")
class BatterySaverStore(private val context: Context) :
NoOpKeyedObservable<String>(), KeyValueStore {
override fun contains(key: String) = key == KEY
override fun <T : Any> getValue(key: String, valueType: Class<T>) =
context.isPowerSaveMode() as T
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
BatterySaverUtils.setPowerSaveMode(
context,
value as Boolean,
/* needFirstTimeWarning= */ false,
SAVER_ENABLED_SETTINGS,
)
}
private fun Context.isPowerSaveMode() =
getSystemService(PowerManager::class.java)?.isPowerSaveMode == true
}
companion object {
private const val KEY = "battery_saver"
private const val SWITCH_ANIMATION_DURATION: Long = 350L
}
}
// LINT.ThenChange(BatterySaverButtonPreferenceController.java)

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2024 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.batterysaver
import android.content.Context
import com.android.settings.R
import com.android.settings.flags.Flags
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator
@ProvidePreferenceScreen
open class BatterySaverScreen : PreferenceScreenCreator {
override val key: String
get() = KEY
override val title: Int
get() = R.string.battery_saver
override val keywords: Int
get() = R.string.keywords_battery_saver
override fun isFlagEnabled(context: Context) = Flags.catalystBatterySaverScreen()
override fun fragmentClass() = BatterySaverSettings::class.java
override fun hasCompleteHierarchy() = false
override fun getPreferenceHierarchy(context: Context) =
preferenceHierarchy(this) { +BatterySaverPreference() order -100 }
companion object {
const val KEY = "battery_saver_screen"
}
}

View File

@@ -17,8 +17,11 @@
package com.android.settings.fuelgauge.batterysaver;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
@@ -93,4 +96,9 @@ public class BatterySaverSettings extends DashboardFragment {
pref.setLearnMoreText(getString(R.string.battery_saver_link_a11y));
}
}
@Override
public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
return BatterySaverScreen.KEY;
}
}

View File

@@ -16,6 +16,7 @@
package com.android.settings.fuelgauge.batterytip;
import android.app.Activity;
import android.content.Context;
import android.os.BadParcelableException;
import android.os.Bundle;
@@ -27,7 +28,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.PreferenceScreen;
import com.android.settings.SettingsActivity;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
@@ -52,7 +52,7 @@ public class BatteryTipPreferenceController extends BasePreferenceController {
private BatteryTipListener mBatteryTipListener;
private List<BatteryTip> mBatteryTips;
private Map<String, BatteryTip> mBatteryTipMap;
private SettingsActivity mSettingsActivity;
private Activity mActivity;
private MetricsFeatureProvider mMetricsFeatureProvider;
private boolean mNeedUpdate;
@VisibleForTesting TipCardPreference mCardPreference;
@@ -66,8 +66,8 @@ public class BatteryTipPreferenceController extends BasePreferenceController {
mNeedUpdate = true;
}
public void setActivity(SettingsActivity activity) {
mSettingsActivity = activity;
public void setActivity(Activity activity) {
mActivity = activity;
}
public void setFragment(InstrumentedPreferenceFragment fragment) {
@@ -126,7 +126,7 @@ public class BatteryTipPreferenceController extends BasePreferenceController {
} else {
final BatteryTipAction action =
BatteryTipUtils.getActionForBatteryTip(
batteryTip, mSettingsActivity, mFragment);
batteryTip, mActivity, mFragment);
if (action != null) {
action.handlePositiveAction(mFragment.getMetricsCategory());
}

View File

@@ -16,16 +16,15 @@
package com.android.settings.fuelgauge.batterytip;
import android.app.Activity;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.Intent;
import android.os.UserHandle;
import android.os.UserManager;
import androidx.annotation.NonNull;
import com.android.internal.util.CollectionUtils;
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
import com.android.settings.fuelgauge.batterytip.actions.OpenBatterySaverAction;
@@ -33,8 +32,6 @@ import com.android.settings.fuelgauge.batterytip.actions.OpenRestrictAppFragment
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.AppLabelPredicate;
import com.android.settings.fuelgauge.batterytip.tips.AppRestrictionPredicate;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip;
@@ -52,7 +49,7 @@ public class BatteryTipUtils {
AppOpsManager appOpsManager, UserManager userManager) {
final List<UserHandle> userHandles = userManager.getUserProfiles();
final List<AppOpsManager.PackageOps> packageOpsList =
appOpsManager.getPackagesForOps(new int[] {AppOpsManager.OP_RUN_ANY_IN_BACKGROUND});
appOpsManager.getPackagesForOps(new int[]{AppOpsManager.OP_RUN_ANY_IN_BACKGROUND});
final List<AppInfo> appInfos = new ArrayList<>();
for (int i = 0, size = CollectionUtils.size(packageOpsList); i < size; i++) {
@@ -65,7 +62,7 @@ public class BatteryTipUtils {
}
if (entry.getMode() != AppOpsManager.MODE_ALLOWED
&& userHandles.contains(
new UserHandle(UserHandle.getUserId(packageOps.getUid())))) {
new UserHandle(UserHandle.getUserId(packageOps.getUid())))) {
appInfos.add(
new AppInfo.Builder()
.setPackageName(packageOps.getPackageName())
@@ -82,34 +79,34 @@ public class BatteryTipUtils {
* Get a corresponding action based on {@code batteryTip}
*
* @param batteryTip used to detect which action to choose
* @param settingsActivity used to populate {@link BatteryTipAction}
* @param fragment used to populate {@link BatteryTipAction}
* @param activity used to populate {@link BatteryTipAction}
* @param fragment used to populate {@link BatteryTipAction}
* @return an action for {@code batteryTip}
*/
public static BatteryTipAction getActionForBatteryTip(
BatteryTip batteryTip,
SettingsActivity settingsActivity,
Activity activity,
InstrumentedPreferenceFragment fragment) {
switch (batteryTip.getType()) {
case BatteryTip.TipType.SMART_BATTERY_MANAGER:
return new SmartBatteryAction(settingsActivity, fragment);
return new SmartBatteryAction(activity, fragment);
case BatteryTip.TipType.BATTERY_SAVER:
case BatteryTip.TipType.LOW_BATTERY:
return new OpenBatterySaverAction(settingsActivity);
return new OpenBatterySaverAction(activity);
case BatteryTip.TipType.APP_RESTRICTION:
if (batteryTip.getState() == BatteryTip.StateType.HANDLED) {
return new OpenRestrictAppFragmentAction(fragment, (RestrictAppTip) batteryTip);
} else {
return new RestrictAppAction(settingsActivity, (RestrictAppTip) batteryTip);
return new RestrictAppAction(activity, (RestrictAppTip) batteryTip);
}
case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip);
return new UnrestrictAppAction(activity, (UnrestrictAppTip) batteryTip);
default:
return null;
}
}
/** Detect and return anomaly apps after {@code timeAfterMs} */
/** Detect and return anomaly apps after {@code timeAfterMs} */
public static List<AppInfo> detectAnomalies(Context context, long timeAfterMs) {
return new ArrayList<>();
}

View File

@@ -16,23 +16,23 @@
package com.android.settings.fuelgauge.batterytip.actions;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import androidx.fragment.app.Fragment;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.fuelgauge.SmartBatterySettings;
import com.android.settingslib.core.instrumentation.Instrumentable;
public class SmartBatteryAction extends BatteryTipAction {
private SettingsActivity mSettingsActivity;
private Activity mActivity;
private Fragment mFragment;
public SmartBatteryAction(SettingsActivity settingsActivity, Fragment fragment) {
super(settingsActivity.getApplicationContext());
mSettingsActivity = settingsActivity;
public SmartBatteryAction(Activity activity, Fragment fragment) {
super(activity.getApplicationContext());
mActivity = activity;
mFragment = fragment;
}
@@ -41,7 +41,7 @@ public class SmartBatteryAction extends BatteryTipAction {
public void handlePositiveAction(int metricsKey) {
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_TIP_OPEN_SMART_BATTERY, metricsKey);
new SubSettingLauncher(mSettingsActivity)
new SubSettingLauncher(mActivity)
.setSourceMetricsCategory(
mFragment instanceof Instrumentable
? ((Instrumentable) mFragment).getMetricsCategory()

View File

@@ -16,6 +16,7 @@
package com.android.settings.fuelgauge.batteryusage;
import android.annotation.Nullable;
import android.content.Context;
import android.text.TextUtils;
import android.view.View;
@@ -37,7 +38,7 @@ class AnomalyAppItemPreference extends PowerGaugePreference {
setLayoutResource(R.layout.anomaly_app_item_preference);
}
void setAnomalyHint(CharSequence anomalyHintText) {
void setAnomalyHint(@Nullable CharSequence anomalyHintText) {
if (!TextUtils.equals(mAnomalyHintText, anomalyHintText)) {
mAnomalyHintText = anomalyHintText;
notifyChanged();

View File

@@ -332,9 +332,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
setBackgroundColor(Color.TRANSPARENT);
mTrapezoidSolidColor = Utils.getColorAccentDefaultColor(context);
mTrapezoidColor = Utils.getDisabled(context, mTrapezoidSolidColor);
mTrapezoidHoverColor =
Utils.getColorAttrDefaultColor(
context, com.android.internal.R.attr.materialColorSecondaryContainer);
mTrapezoidHoverColor = context.getColor(
com.android.internal.R.color.materialColorSecondaryContainer);
// Initializes the divider line paint.
final Resources resources = getContext().getResources();
mDividerWidth = resources.getDimensionPixelSize(R.dimen.chartview_divider_width);

View File

@@ -169,6 +169,7 @@ public class BatteryDiffData {
}
if (packageName != null && hideBackgroundUsageTimeSet.contains(packageName)) {
entry.mBackgroundUsageTimeInMs = 0;
entry.mForegroundServiceUsageTimeInMs = 0;
}
}
}

View File

@@ -51,7 +51,8 @@ final class BatteryOptimizationModeCache {
Pair.create(
BatteryOptimizationMode.forNumber(
batteryOptimizeUtils.getAppOptimizationMode(
/* refreshList= */ false)),
/* refreshList= */ false,
/* ignoreUnknownMode= */ false)),
batteryOptimizeUtils.isOptimizeModeMutable()));
}
final Pair<BatteryOptimizationMode, Boolean> batteryOptimizeModeInfo =

View File

@@ -31,7 +31,6 @@ import android.view.View;
import android.widget.AdapterView;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;
@@ -68,11 +67,11 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
private static final String ROOT_PREFERENCE_KEY = "battery_usage_breakdown";
private static final String FOOTER_PREFERENCE_KEY = "battery_usage_footer";
private static final String SPINNER_PREFERENCE_KEY = "battery_usage_spinner";
private static final String APP_LIST_PREFERENCE_KEY = "app_list";
private static final String PACKAGE_NAME_NONE = "none";
private static final String SLOT_TIMESTAMP = "slot_timestamp";
private static final String ANOMALY_KEY = "anomaly_key";
private static final String KEY_SPINNER_POSITION = "spinner_position";
private static final int ENTRY_PREF_ORDER_OFFSET = 100;
private static final List<BatteryDiffEntry> EMPTY_ENTRY_LIST = new ArrayList<>();
private static int sUiMode = Configuration.UI_MODE_NIGHT_UNDEFINED;
@@ -89,8 +88,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
private SettingsSpinnerAdapter<CharSequence> mSpinnerAdapter;
@VisibleForTesting Context mPrefContext;
@VisibleForTesting PreferenceCategory mRootPreference;
@VisibleForTesting PreferenceGroup mAppListPreferenceGroup;
@VisibleForTesting PreferenceGroup mRootPreferenceGroup;
@VisibleForTesting FooterPreference mFooterPreference;
@VisibleForTesting BatteryDiffData mBatteryDiffData;
@VisibleForTesting String mBatteryUsageBreakdownTitleLastFullChargeText;
@@ -143,7 +141,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
public void onDestroy() {
mHandler.removeCallbacksAndMessages(/* token= */ null);
mPreferenceCache.clear();
mAppListPreferenceGroup.removeAll();
mRootPreferenceGroup.removeAll();
}
@Override
@@ -226,9 +224,8 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPrefContext = screen.getContext();
mRootPreference = screen.findPreference(ROOT_PREFERENCE_KEY);
mRootPreferenceGroup = screen.findPreference(ROOT_PREFERENCE_KEY);
mSpinnerPreference = screen.findPreference(SPINNER_PREFERENCE_KEY);
mAppListPreferenceGroup = screen.findPreference(APP_LIST_PREFERENCE_KEY);
mFooterPreference = screen.findPreference(FOOTER_PREFERENCE_KEY);
mBatteryUsageBreakdownTitleLastFullChargeText =
mPrefContext.getString(
@@ -242,7 +239,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
R.string.battery_usage_less_than_percent_content_description,
formatPercentage);
mAppListPreferenceGroup.setOrderingAsAdded(false);
mRootPreferenceGroup.setOrderingAsAdded(false);
mSpinnerAdapter = new SettingsSpinnerAdapter<>(mPrefContext);
mSpinnerAdapter.addAll(
new String[] {
@@ -328,8 +325,9 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
: mPrefContext.getString(
R.string.battery_usage_breakdown_title_for_slot,
accessibilitySlotTimestamp);
mRootPreference.setTitle(Utils.createAccessibleSequence(displayTitle, accessibilityTitle));
mRootPreference.setVisible(true);
mRootPreferenceGroup.setTitle(
Utils.createAccessibleSequence(displayTitle, accessibilityTitle));
mRootPreferenceGroup.setVisible(true);
}
private void showFooterPreference(boolean isAllBatteryUsageEmpty) {
@@ -350,7 +348,6 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
return;
}
mSpinnerPreference.setVisible(true);
mAppListPreferenceGroup.setVisible(true);
mHandler.post(
() -> {
removeAndCacheAllUnusedPreferences();
@@ -374,7 +371,7 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
}
final long start = System.currentTimeMillis();
final List<BatteryDiffEntry> entries = getBatteryDiffEntries();
int prefIndex = mAppListPreferenceGroup.getPreferenceCount();
int preferenceOrder = ENTRY_PREF_ORDER_OFFSET;
for (BatteryDiffEntry entry : entries) {
boolean isAdded = false;
final String appLabel = entry.getAppLabel();
@@ -384,33 +381,32 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
continue;
}
final String prefKey = entry.getKey();
AnomalyAppItemPreference pref = mAppListPreferenceGroup.findPreference(prefKey);
if (pref != null) {
AnomalyAppItemPreference preference = mRootPreferenceGroup.findPreference(prefKey);
if (preference != null) {
isAdded = true;
} else {
pref = (AnomalyAppItemPreference) mPreferenceCache.get(prefKey);
preference = (AnomalyAppItemPreference) mPreferenceCache.get(prefKey);
}
// Creates new instance if cached preference is not found.
if (pref == null) {
pref = new AnomalyAppItemPreference(mPrefContext);
pref.setKey(prefKey);
mPreferenceCache.put(prefKey, pref);
if (preference == null) {
preference = new AnomalyAppItemPreference(mPrefContext);
preference.setKey(prefKey);
mPreferenceCache.put(prefKey, preference);
}
pref.setIcon(appIcon);
pref.setTitle(appLabel);
pref.setOrder(prefIndex);
pref.setSingleLineTitle(true);
preference.setIcon(appIcon);
preference.setTitle(appLabel);
preference.setOrder(++preferenceOrder);
preference.setSingleLineTitle(true);
// Updates App item preference style
pref.setAnomalyHint(isAnomalyBatteryDiffEntry(entry) ? mAnomalyHintString : null);
preference.setAnomalyHint(isAnomalyBatteryDiffEntry(entry) ? mAnomalyHintString : null);
// Sets the BatteryDiffEntry to preference for launching detailed page.
pref.setBatteryDiffEntry(entry);
pref.setSelectable(entry.validForRestriction());
setPreferencePercentage(pref, entry);
setPreferenceSummary(pref, entry);
preference.setBatteryDiffEntry(entry);
preference.setSelectable(entry.validForRestriction());
setPreferencePercentage(preference, entry);
setPreferenceSummary(preference, entry);
if (!isAdded) {
mAppListPreferenceGroup.addPreference(pref);
mRootPreferenceGroup.addPreference(preference);
}
prefIndex++;
}
Log.d(
TAG,
@@ -424,17 +420,22 @@ public class BatteryUsageBreakdownController extends BasePreferenceController
List<BatteryDiffEntry> entries = getBatteryDiffEntries();
Set<String> entryKeySet = new ArraySet<>(entries.size());
entries.forEach(entry -> entryKeySet.add(entry.getKey()));
final int prefsCount = mAppListPreferenceGroup.getPreferenceCount();
for (int index = prefsCount - 1; index >= 0; index--) {
final Preference pref = mAppListPreferenceGroup.getPreference(index);
if (entryKeySet.contains(pref.getKey())) {
// The pref is still used, don't remove.
final int preferenceCount = mRootPreferenceGroup.getPreferenceCount();
for (int index = preferenceCount - 1; index >= 0; index--) {
final Preference preference = mRootPreferenceGroup.getPreference(index);
if ((preference instanceof SettingsSpinnerPreference)
|| (preference instanceof FooterPreference)) {
// Consider the app preference only and skip others
continue;
}
if (!TextUtils.isEmpty(pref.getKey())) {
mPreferenceCache.put(pref.getKey(), pref);
if (entryKeySet.contains(preference.getKey())) {
// Don't remove the preference if it is still in use
continue;
}
mAppListPreferenceGroup.removePreference(pref);
if (!TextUtils.isEmpty(preference.getKey())) {
mPreferenceCache.put(preference.getKey(), preference);
}
mRootPreferenceGroup.removePreference(preference);
}
}

View File

@@ -20,8 +20,6 @@ import android.app.usage.UsageEvents;
import android.content.Context;
import android.os.AsyncTask;
import android.os.BatteryUsageStats;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
@@ -117,11 +115,9 @@ public final class BatteryUsageDataLoader {
private static void preprocessBatteryUsageSlots(
final Context context, final UserIdsSeries userIdsSeries) {
final long start = System.currentTimeMillis();
final Handler handler = new Handler(Looper.getMainLooper());
final BatteryLevelData batteryLevelData =
DataProcessManager.getBatteryLevelData(
context,
handler,
userIdsSeries,
/* isFromPeriodJob= */ true,
batteryDiffDataMap -> {

View File

@@ -19,8 +19,6 @@ package com.android.settings.fuelgauge.batteryusage;
import android.app.usage.UsageEvents;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.util.ArrayMap;
import android.util.Log;
@@ -82,7 +80,6 @@ public class DataProcessManager {
private final long mLastFullChargeTimestamp;
private final boolean mIsFromPeriodJob;
private final Context mContext;
private final Handler mHandler;
private final UserIdsSeries mUserIdsSeries;
private final OnBatteryDiffDataMapLoadedListener mCallbackFunction;
private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
@@ -123,7 +120,6 @@ public class DataProcessManager {
/** Constructor when there exists battery level data. */
DataProcessManager(
Context context,
Handler handler,
final UserIdsSeries userIdsSeries,
final boolean isFromPeriodJob,
final long rawStartTimestamp,
@@ -132,7 +128,6 @@ public class DataProcessManager {
@NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
@NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
mContext = context.getApplicationContext();
mHandler = handler;
mUserIdsSeries = userIdsSeries;
mIsFromPeriodJob = isFromPeriodJob;
mRawStartTimestamp = rawStartTimestamp;
@@ -145,11 +140,9 @@ public class DataProcessManager {
/** Constructor when there is no battery level data. */
DataProcessManager(
Context context,
Handler handler,
final UserIdsSeries userIdsSeries,
@NonNull final OnBatteryDiffDataMapLoadedListener callbackFunction) {
mContext = context.getApplicationContext();
mHandler = handler;
mUserIdsSeries = userIdsSeries;
mCallbackFunction = callbackFunction;
mIsFromPeriodJob = false;
@@ -444,12 +437,8 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
// Post results back to main thread to refresh UI.
if (mHandler != null && mCallbackFunction != null) {
mHandler.post(
() -> {
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
});
if (mCallbackFunction != null) {
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
}
}
}.execute();
@@ -534,12 +523,8 @@ public class DataProcessManager {
@Override
protected void onPostExecute(final Map<Long, BatteryDiffData> batteryDiffDataMap) {
// Post results back to main thread to refresh UI.
if (mHandler != null && mCallbackFunction != null) {
mHandler.post(
() -> {
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
});
if (mCallbackFunction != null) {
mCallbackFunction.onBatteryDiffDataMapLoaded(batteryDiffDataMap);
}
}
}.execute();
@@ -581,7 +566,6 @@ public class DataProcessManager {
@Nullable
public static BatteryLevelData getBatteryLevelData(
Context context,
@Nullable Handler handler,
final UserIdsSeries userIdsSeries,
final boolean isFromPeriodJob,
final OnBatteryDiffDataMapLoadedListener onBatteryUsageMapLoadedListener) {
@@ -601,7 +585,6 @@ public class DataProcessManager {
final BatteryLevelData batteryLevelData =
getPeriodBatteryLevelData(
context,
handler,
userIdsSeries,
startTimestamp,
lastFullChargeTime,
@@ -621,7 +604,6 @@ public class DataProcessManager {
private static BatteryLevelData getPeriodBatteryLevelData(
Context context,
@Nullable Handler handler,
final UserIdsSeries userIdsSeries,
final long startTimestamp,
final long lastFullChargeTime,
@@ -639,7 +621,6 @@ public class DataProcessManager {
return null;
}
handler = handler != null ? handler : new Handler(Looper.getMainLooper());
final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap =
sFakeBatteryHistoryMap != null
? sFakeBatteryHistoryMap
@@ -650,8 +631,7 @@ public class DataProcessManager {
lastFullChargeTime);
if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) {
Log.d(TAG, "batteryHistoryMap is null in getPeriodBatteryLevelData()");
new DataProcessManager(
context, handler, userIdsSeries, onBatteryDiffDataMapLoadedListener)
new DataProcessManager(context, userIdsSeries, onBatteryDiffDataMapLoadedListener)
.start();
return null;
}
@@ -680,8 +660,7 @@ public class DataProcessManager {
DataProcessor.getLevelDataThroughProcessedHistoryMap(
context, processedBatteryHistoryMap);
if (batteryLevelData == null) {
new DataProcessManager(
context, handler, userIdsSeries, onBatteryDiffDataMapLoadedListener)
new DataProcessManager(context, userIdsSeries, onBatteryDiffDataMapLoadedListener)
.start();
Log.d(TAG, "getBatteryLevelData() returns null");
return null;
@@ -690,7 +669,6 @@ public class DataProcessManager {
// Start the async task to compute diff usage data and load labels and icons.
new DataProcessManager(
context,
handler,
userIdsSeries,
isFromPeriodJob,
startTimestamp,

View File

@@ -31,8 +31,6 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.BatteryManager;
import android.os.BatteryUsageStats;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserManager;
@@ -66,12 +64,6 @@ import java.util.stream.Collectors;
public final class DatabaseUtils {
private static final String TAG = "DatabaseUtils";
private static final String SHARED_PREFS_FILE = "battery_usage_shared_prefs";
private static final boolean EXPLICIT_CLEAR_MEMORY_ENABLED = false;
/** Clear memory threshold for device booting phase. */
private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis();
private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis();
private static final long INVALID_TIMESTAMP = 0L;
static final int DATA_RETENTION_INTERVAL_DAY = 9;
@@ -593,7 +585,6 @@ public final class DatabaseUtils {
String.format(
"sendAppUsageEventData() size=%d in %d/ms",
size, (System.currentTimeMillis() - startTime)));
clearMemory();
return valuesList;
}
@@ -613,7 +604,6 @@ public final class DatabaseUtils {
String.format(
"sendBatteryEventData() in %d/ms",
(System.currentTimeMillis() - startTime)));
clearMemory();
return contentValues;
}
@@ -647,7 +637,6 @@ public final class DatabaseUtils {
String.format(
"sendBatteryEventData() size=%d in %d/ms",
size, (System.currentTimeMillis() - startTime)));
clearMemory();
return valuesList;
}
@@ -681,7 +670,6 @@ public final class DatabaseUtils {
String.format(
"sendBatteryUsageSlotData() size=%d in %d/ms",
size, (System.currentTimeMillis() - startTime)));
clearMemory();
return valuesList;
}
@@ -695,7 +683,6 @@ public final class DatabaseUtils {
final Intent intent = BatteryUtils.getBatteryIntent(context);
if (intent == null) {
Log.e(TAG, "sendBatteryEntryData(): cannot fetch battery intent");
clearMemory();
return null;
}
final int batteryLevel = BatteryStatus.getBatteryLevel(intent);
@@ -796,7 +783,6 @@ public final class DatabaseUtils {
if (isFullChargeStart) {
recordDateTime(context, KEY_LAST_UPLOAD_FULL_CHARGE_TIME);
}
clearMemory();
return valuesList;
}
@@ -992,20 +978,4 @@ public final class DatabaseUtils {
writer.println(String.format("\t\t%s: %s", prefix, results.toString()));
}
}
private static void clearMemory() {
if (!EXPLICIT_CLEAR_MEMORY_ENABLED
|| SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
return;
}
final Handler mainHandler = new Handler(Looper.getMainLooper());
mainHandler.postDelayed(
() -> {
System.gc();
System.runFinalization();
System.gc();
Log.w(TAG, "invoke clearMemory()");
},
CLEAR_MEMORY_DELAYED_MS);
}
}

View File

@@ -214,20 +214,22 @@ public class PowerUsageAdvanced extends PowerUsageBase {
if (!isResumed() || mBatteryLevelData == null) {
return;
}
mBatteryUsageMap =
DataProcessor.generateBatteryUsageMap(
getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null));
Log.d(TAG, "onBatteryDiffDataMapUpdate: " + mBatteryUsageMap);
DataProcessor.loadLabelAndIcon(mBatteryUsageMap);
onSelectedSlotDataUpdated();
detectAnomaly();
logScreenUsageTime();
if (mBatteryChartPreferenceController != null
&& mBatteryLevelData.isEmpty()
&& isBatteryUsageMapNullOrEmpty()) {
// No available battery usage and battery level data.
mBatteryChartPreferenceController.showEmptyChart();
}
mHandler.post(() -> {
mBatteryUsageMap =
DataProcessor.generateBatteryUsageMap(
getContext(), batteryDiffDataMap, mBatteryLevelData.orElse(null));
Log.d(TAG, "onBatteryDiffDataMapUpdate: " + mBatteryUsageMap);
DataProcessor.loadLabelAndIcon(mBatteryUsageMap);
onSelectedSlotDataUpdated();
detectAnomaly();
logScreenUsageTime();
if (mBatteryChartPreferenceController != null
&& mBatteryLevelData.isEmpty()
&& isBatteryUsageMapNullOrEmpty()) {
// No available battery usage and battery level data.
mBatteryChartPreferenceController.showEmptyChart();
}
});
}
private void onSelectedSlotDataUpdated() {
@@ -503,7 +505,6 @@ public class PowerUsageAdvanced extends PowerUsageBase {
public BatteryLevelData loadInBackground() {
return DataProcessManager.getBatteryLevelData(
getContext(),
mHandler,
new UserIdsSeries(getContext(), /* isNonUIRequest= */ false),
/* isFromPeriodJob= */ false,
PowerUsageAdvanced.this::onBatteryDiffDataMapUpdate);

View File

@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.android.settings.fuelgauge.BatteryBroadcastReceiver.BatteryUpdateType;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.database.ContentObserver;
@@ -26,15 +27,16 @@ import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings.Global;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.BatteryHeaderPreferenceController;
import com.android.settings.fuelgauge.BatteryHeaderTextPreferenceController;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryInfoLoader;
import com.android.settings.fuelgauge.BatteryUtils;
@@ -65,7 +67,7 @@ public class PowerUsageSummary extends PowerUsageBase
@VisibleForTesting BatteryUtils mBatteryUtils;
@VisibleForTesting BatteryInfo mBatteryInfo;
@VisibleForTesting BatteryHeaderPreferenceController mBatteryHeaderPreferenceController;
@VisibleForTesting BatteryHeaderTextPreferenceController mBatteryHeaderTextPreferenceController;
@VisibleForTesting BatteryTipPreferenceController mBatteryTipPreferenceController;
@VisibleForTesting boolean mNeedUpdateBatteryTip;
@VisibleForTesting Preference mHelpPreference;
@@ -91,8 +93,8 @@ public class PowerUsageSummary extends PowerUsageBase
@Override
public void onLoadFinished(Loader<BatteryInfo> loader, BatteryInfo batteryInfo) {
mBatteryHeaderPreferenceController.updateHeaderPreference(batteryInfo);
mBatteryHeaderPreferenceController.updateHeaderByBatteryTips(
mBatteryHeaderTextPreferenceController.updateHeaderPreference(batteryInfo);
mBatteryHeaderTextPreferenceController.updateHeaderByBatteryTips(
mBatteryTipPreferenceController.getCurrentBatteryTip(), batteryInfo);
mBatteryInfo = batteryInfo;
}
@@ -114,7 +116,7 @@ public class PowerUsageSummary extends PowerUsageBase
@Override
public void onLoadFinished(Loader<List<BatteryTip>> loader, List<BatteryTip> data) {
mBatteryTipPreferenceController.updateBatteryTips(data);
mBatteryHeaderPreferenceController.updateHeaderByBatteryTips(
mBatteryHeaderTextPreferenceController.updateHeaderByBatteryTips(
mBatteryTipPreferenceController.getCurrentBatteryTip(), mBatteryInfo);
}
@@ -125,9 +127,9 @@ public class PowerUsageSummary extends PowerUsageBase
@Override
public void onAttach(Context context) {
super.onAttach(context);
final SettingsActivity activity = (SettingsActivity) getActivity();
final Activity activity = getActivity();
mBatteryHeaderPreferenceController = use(BatteryHeaderPreferenceController.class);
mBatteryHeaderTextPreferenceController = use(BatteryHeaderTextPreferenceController.class);
mBatteryTipPreferenceController = use(BatteryTipPreferenceController.class);
mBatteryTipPreferenceController.setActivity(activity);
@@ -248,15 +250,6 @@ public class PowerUsageSummary extends PowerUsageBase
mNeedUpdateBatteryTip = icicle == null || mBatteryTipPreferenceController.needUpdate();
}
@Override
protected void restartBatteryStatsLoader(@BatteryUpdateType int refreshType) {
super.restartBatteryStatsLoader(refreshType);
// Update battery header if battery is present.
if (mIsBatteryPresent) {
mBatteryHeaderPreferenceController.quickUpdateHeaderPreference();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -270,4 +263,9 @@ public class PowerUsageSummary extends PowerUsageBase
public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider(R.xml.power_usage_summary);
@Override
public @Nullable String getPreferenceScreenBindingKey(@NonNull Context context) {
return PowerUsageSummaryScreen.KEY;
}
}

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2024 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.batteryusage
import android.content.Context
import com.android.settings.R
import com.android.settings.display.BatteryPercentageSwitchPreference
import com.android.settings.flags.Flags
import com.android.settings.fuelgauge.BatteryHeaderPreference
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceIconProvider
import com.android.settingslib.metadata.ProvidePreferenceScreen
import com.android.settingslib.metadata.preferenceHierarchy
import com.android.settingslib.preference.PreferenceScreenCreator
@ProvidePreferenceScreen
class PowerUsageSummaryScreen :
PreferenceScreenCreator, PreferenceAvailabilityProvider, PreferenceIconProvider {
override val key: String
get() = KEY
override val title: Int
get() = R.string.power_usage_summary_title
override val keywords: Int
get() = R.string.keywords_battery
override fun isFlagEnabled(context: Context) = Flags.catalystPowerUsageSummaryScreen()
override fun hasCompleteHierarchy() = false
override fun fragmentClass() = PowerUsageSummary::class.java
override fun isAvailable(context: Context) =
context.resources.getBoolean(R.bool.config_show_top_level_battery)
override fun getIcon(context: Context): Int =
if (Flags.homepageRevamp()) {
R.drawable.ic_settings_battery_filled
} else {
R.drawable.ic_settings_battery_white
}
override fun getPreferenceHierarchy(context: Context) = preferenceHierarchy(this) {
+BatteryHeaderPreference()
+BatteryPercentageSwitchPreference()
}
companion object {
const val KEY = "power_usage_summary_screen"
}
}