fix: 引入Settings的Module

This commit is contained in:
2024-12-10 14:57:24 +08:00
parent ad8fc8731d
commit df105485bd
6934 changed files with 896168 additions and 2 deletions

View File

@@ -0,0 +1,207 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** Database controls the anomaly logging(e.g. packageName, anomalyType and time) */
public class AnomalyDatabaseHelper extends SQLiteOpenHelper {
private static final String TAG = "BatteryDatabaseHelper";
private static final String DATABASE_NAME = "battery_settings.db";
private static final int DATABASE_VERSION = 5;
@Retention(RetentionPolicy.SOURCE)
@IntDef({State.NEW, State.HANDLED, State.AUTO_HANDLED})
public @interface State {
int NEW = 0;
int HANDLED = 1;
int AUTO_HANDLED = 2;
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({ActionType.RESTRICTION})
public @interface ActionType {
int RESTRICTION = 0;
}
public interface Tables {
String TABLE_ANOMALY = "anomaly";
String TABLE_ACTION = "action";
}
public interface AnomalyColumns {
/** The package name of the anomaly app */
String PACKAGE_NAME = "package_name";
/** The uid of the anomaly app */
String UID = "uid";
/**
* The type of the anomaly app
*
* @see StatsManagerConfig.AnomalyType
*/
String ANOMALY_TYPE = "anomaly_type";
/**
* The state of the anomaly app
*
* @see State
*/
String ANOMALY_STATE = "anomaly_state";
/** The time when anomaly happens */
String TIME_STAMP_MS = "time_stamp_ms";
}
private static final String CREATE_ANOMALY_TABLE =
"CREATE TABLE "
+ Tables.TABLE_ANOMALY
+ "("
+ AnomalyColumns.UID
+ " INTEGER NOT NULL, "
+ AnomalyColumns.PACKAGE_NAME
+ " TEXT, "
+ AnomalyColumns.ANOMALY_TYPE
+ " INTEGER NOT NULL, "
+ AnomalyColumns.ANOMALY_STATE
+ " INTEGER NOT NULL, "
+ AnomalyColumns.TIME_STAMP_MS
+ " INTEGER NOT NULL, "
+ " PRIMARY KEY ("
+ AnomalyColumns.UID
+ ","
+ AnomalyColumns.ANOMALY_TYPE
+ ","
+ AnomalyColumns.ANOMALY_STATE
+ ","
+ AnomalyColumns.TIME_STAMP_MS
+ ")"
+ ")";
public interface ActionColumns {
/** The package name of an app been performed an action */
String PACKAGE_NAME = "package_name";
/** The uid of an app been performed an action */
String UID = "uid";
/**
* The type of user action
*
* @see ActionType
*/
String ACTION_TYPE = "action_type";
/** The time when action been performed */
String TIME_STAMP_MS = "time_stamp_ms";
}
private static final String CREATE_ACTION_TABLE =
"CREATE TABLE "
+ Tables.TABLE_ACTION
+ "("
+ ActionColumns.UID
+ " INTEGER NOT NULL, "
+ ActionColumns.PACKAGE_NAME
+ " TEXT, "
+ ActionColumns.ACTION_TYPE
+ " INTEGER NOT NULL, "
+ ActionColumns.TIME_STAMP_MS
+ " INTEGER NOT NULL, "
+ " PRIMARY KEY ("
+ ActionColumns.ACTION_TYPE
+ ","
+ ActionColumns.UID
+ ","
+ ActionColumns.PACKAGE_NAME
+ ")"
+ ")";
private static AnomalyDatabaseHelper sSingleton;
public static synchronized AnomalyDatabaseHelper getInstance(Context context) {
if (sSingleton == null) {
sSingleton = new AnomalyDatabaseHelper(context.getApplicationContext());
}
return sSingleton;
}
private AnomalyDatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
bootstrapDB(db);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < DATABASE_VERSION) {
Log.w(
TAG,
"Detected schema version '"
+ oldVersion
+ "'. "
+ "Index needs to be rebuilt for schema version '"
+ newVersion
+ "'.");
// We need to drop the tables and recreate them
reconstruct(db);
}
}
@Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(
TAG,
"Detected schema version '"
+ oldVersion
+ "'. "
+ "Index needs to be rebuilt for schema version '"
+ newVersion
+ "'.");
// We need to drop the tables and recreate them
reconstruct(db);
}
public void reconstruct(SQLiteDatabase db) {
dropTables(db);
bootstrapDB(db);
}
private void bootstrapDB(SQLiteDatabase db) {
db.execSQL(CREATE_ANOMALY_TABLE);
db.execSQL(CREATE_ACTION_TABLE);
Log.i(TAG, "Bootstrapped database");
}
private void dropTables(SQLiteDatabase db) {
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_ANOMALY);
db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_ACTION);
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
import android.util.ArraySet;
import androidx.annotation.VisibleForTesting;
import java.util.Objects;
/** Model class stores app info(e.g. package name, type..) that used in battery tip */
public class AppInfo implements Comparable<AppInfo>, Parcelable {
public final String packageName;
/**
* Anomaly type of the app
*
* @see StatsManagerConfig.AnomalyType
*/
public final ArraySet<Integer> anomalyTypes;
public final long screenOnTimeMs;
public final int uid;
private AppInfo(AppInfo.Builder builder) {
packageName = builder.mPackageName;
anomalyTypes = builder.mAnomalyTypes;
screenOnTimeMs = builder.mScreenOnTimeMs;
uid = builder.mUid;
}
@VisibleForTesting
AppInfo(Parcel in) {
packageName = in.readString();
anomalyTypes = (ArraySet<Integer>) in.readArraySet(null /* loader */);
screenOnTimeMs = in.readLong();
uid = in.readInt();
}
@Override
public int compareTo(AppInfo o) {
return Long.compare(screenOnTimeMs, o.screenOnTimeMs);
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
dest.writeArraySet(anomalyTypes);
dest.writeLong(screenOnTimeMs);
dest.writeInt(uid);
}
@Override
public String toString() {
return "packageName="
+ packageName
+ ",anomalyTypes="
+ anomalyTypes
+ ",screenTime="
+ screenOnTimeMs;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof AppInfo)) {
return false;
}
AppInfo other = (AppInfo) obj;
return Objects.equals(anomalyTypes, other.anomalyTypes)
&& uid == other.uid
&& screenOnTimeMs == other.screenOnTimeMs
&& TextUtils.equals(packageName, other.packageName);
}
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
public AppInfo createFromParcel(Parcel in) {
return new AppInfo(in);
}
public AppInfo[] newArray(int size) {
return new AppInfo[size];
}
};
public static final class Builder {
private ArraySet<Integer> mAnomalyTypes = new ArraySet<>();
private String mPackageName;
private long mScreenOnTimeMs;
private int mUid;
public Builder addAnomalyType(int type) {
mAnomalyTypes.add(type);
return this;
}
public Builder setPackageName(String packageName) {
mPackageName = packageName;
return this;
}
public Builder setScreenOnTimeMs(long screenOnTimeMs) {
mScreenOnTimeMs = screenOnTimeMs;
return this;
}
public Builder setUid(int uid) {
mUid = uid;
return this;
}
public AppInfo build() {
return new AppInfo(this);
}
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import static android.database.sqlite.SQLiteDatabase.CONFLICT_IGNORE;
import static android.database.sqlite.SQLiteDatabase.CONFLICT_REPLACE;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.ANOMALY_STATE;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.ANOMALY_TYPE;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.PACKAGE_NAME;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.TIME_STAMP_MS;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns.UID;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.Tables.TABLE_ACTION;
import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.Tables.TABLE_ANOMALY;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.SparseLongArray;
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.ActionColumns;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Database manager for battery data. Now it only contains anomaly data stored in {@link AppInfo}.
*
* <p>This manager may be accessed by multi-threads. All the database related methods are
* synchronized so each operation won't be interfered by other threads.
*/
public class BatteryDatabaseManager {
private static BatteryDatabaseManager sSingleton;
private AnomalyDatabaseHelper mDatabaseHelper;
private BatteryDatabaseManager(Context context) {
mDatabaseHelper = AnomalyDatabaseHelper.getInstance(context);
}
public static synchronized BatteryDatabaseManager getInstance(Context context) {
if (sSingleton == null) {
sSingleton = new BatteryDatabaseManager(context);
}
return sSingleton;
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
public static void setUpForTest(BatteryDatabaseManager batteryDatabaseManager) {
sSingleton = batteryDatabaseManager;
}
/**
* Insert an anomaly log to database.
*
* @param uid the uid of the app
* @param packageName the package name of the app
* @param type the type of the anomaly
* @param anomalyState the state of the anomaly
* @param timestampMs the time when it is happened
* @return {@code true} if insert operation succeed
*/
public synchronized boolean insertAnomaly(
int uid, String packageName, int type, int anomalyState, long timestampMs) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(UID, uid);
values.put(PACKAGE_NAME, packageName);
values.put(ANOMALY_TYPE, type);
values.put(ANOMALY_STATE, anomalyState);
values.put(TIME_STAMP_MS, timestampMs);
return db.insertWithOnConflict(TABLE_ANOMALY, null, values, CONFLICT_IGNORE) != -1;
}
/**
* Query all the anomalies that happened after {@code timestampMsAfter} and with {@code state}.
*/
public synchronized List<AppInfo> queryAllAnomalies(long timestampMsAfter, int state) {
final List<AppInfo> appInfos = new ArrayList<>();
final SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
final String[] projection = {PACKAGE_NAME, ANOMALY_TYPE, UID};
final String orderBy = AnomalyDatabaseHelper.AnomalyColumns.TIME_STAMP_MS + " DESC";
final Map<Integer, AppInfo.Builder> mAppInfoBuilders = new ArrayMap<>();
final String selection = TIME_STAMP_MS + " > ? AND " + ANOMALY_STATE + " = ? ";
final String[] selectionArgs =
new String[] {String.valueOf(timestampMsAfter), String.valueOf(state)};
try (Cursor cursor =
db.query(
TABLE_ANOMALY,
projection,
selection,
selectionArgs,
null /* groupBy */,
null /* having */,
orderBy)) {
while (cursor.moveToNext()) {
final int uid = cursor.getInt(cursor.getColumnIndex(UID));
if (!mAppInfoBuilders.containsKey(uid)) {
final AppInfo.Builder builder =
new AppInfo.Builder()
.setUid(uid)
.setPackageName(
cursor.getString(cursor.getColumnIndex(PACKAGE_NAME)));
mAppInfoBuilders.put(uid, builder);
}
mAppInfoBuilders
.get(uid)
.addAnomalyType(cursor.getInt(cursor.getColumnIndex(ANOMALY_TYPE)));
}
}
for (Integer uid : mAppInfoBuilders.keySet()) {
appInfos.add(mAppInfoBuilders.get(uid).build());
}
return appInfos;
}
public synchronized void deleteAllAnomaliesBeforeTimeStamp(long timestampMs) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
db.delete(
TABLE_ANOMALY, TIME_STAMP_MS + " < ?", new String[] {String.valueOf(timestampMs)});
}
/**
* Update the type of anomalies to {@code state}
*
* @param appInfos represents the anomalies
* @param state which state to update to
*/
public synchronized void updateAnomalies(List<AppInfo> appInfos, int state) {
if (!appInfos.isEmpty()) {
final int size = appInfos.size();
final String[] whereArgs = new String[size];
for (int i = 0; i < size; i++) {
whereArgs[i] = appInfos.get(i).packageName;
}
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(ANOMALY_STATE, state);
db.update(
TABLE_ANOMALY,
values,
PACKAGE_NAME
+ " IN ("
+ TextUtils.join(",", Collections.nCopies(appInfos.size(), "?"))
+ ")",
whereArgs);
}
}
/**
* Query latest timestamps when an app has been performed action {@code type}
*
* @param type of action been performed
* @return {@link SparseLongArray} where key is uid and value is timestamp
*/
public synchronized SparseLongArray queryActionTime(
@AnomalyDatabaseHelper.ActionType int type) {
final SparseLongArray timeStamps = new SparseLongArray();
final SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
final String[] projection = {ActionColumns.UID, ActionColumns.TIME_STAMP_MS};
final String selection = ActionColumns.ACTION_TYPE + " = ? ";
final String[] selectionArgs = new String[] {String.valueOf(type)};
try (Cursor cursor =
db.query(
TABLE_ACTION,
projection,
selection,
selectionArgs,
null /* groupBy */,
null /* having */,
null /* orderBy */)) {
final int uidIndex = cursor.getColumnIndex(ActionColumns.UID);
final int timestampIndex = cursor.getColumnIndex(ActionColumns.TIME_STAMP_MS);
while (cursor.moveToNext()) {
final int uid = cursor.getInt(uidIndex);
final long timeStamp = cursor.getLong(timestampIndex);
timeStamps.append(uid, timeStamp);
}
}
return timeStamps;
}
/** Insert an action, or update it if already existed */
public synchronized boolean insertAction(
@AnomalyDatabaseHelper.ActionType int type,
int uid,
String packageName,
long timestampMs) {
final SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
final ContentValues values = new ContentValues();
values.put(ActionColumns.UID, uid);
values.put(ActionColumns.PACKAGE_NAME, packageName);
values.put(ActionColumns.ACTION_TYPE, type);
values.put(ActionColumns.TIME_STAMP_MS, timestampMs);
return db.insertWithOnConflict(TABLE_ACTION, null, values, CONFLICT_REPLACE) != -1;
}
/** Remove an action */
public synchronized boolean deleteAction(
@AnomalyDatabaseHelper.ActionType int type, int uid, String packageName) {
SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
final String where =
ActionColumns.ACTION_TYPE
+ " = ? AND "
+ ActionColumns.UID
+ " = ? AND "
+ ActionColumns.PACKAGE_NAME
+ " = ? ";
final String[] whereArgs =
new String[] {
String.valueOf(type), String.valueOf(uid), String.valueOf(packageName)
};
return db.delete(TABLE_ACTION, where, whereArgs) != 0;
}
}

View File

@@ -0,0 +1,85 @@
/*
* 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.batterytip;
import android.app.AppOpsManager;
import android.content.Context;
import android.os.UserManager;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.core.BasePreferenceController;
import com.android.settings.fuelgauge.PowerUsageFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.StringUtil;
/** Preference controller to control the battery manager */
public class BatteryManagerPreferenceController extends BasePreferenceController {
private static final String KEY_BATTERY_MANAGER = "smart_battery_manager";
private PowerUsageFeatureProvider mPowerUsageFeatureProvider;
private AppOpsManager mAppOpsManager;
private UserManager mUserManager;
private boolean mEnableAppBatteryUsagePage;
public BatteryManagerPreferenceController(Context context) {
super(context, KEY_BATTERY_MANAGER);
mPowerUsageFeatureProvider = FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider();
mAppOpsManager = context.getSystemService(AppOpsManager.class);
mUserManager = context.getSystemService(UserManager.class);
mEnableAppBatteryUsagePage =
mContext.getResources().getBoolean(R.bool.config_app_battery_usage_list_enabled);
}
@Override
public int getAvailabilityStatus() {
if (!mPowerUsageFeatureProvider.isBatteryManagerSupported()) {
return UNSUPPORTED_ON_DEVICE;
}
if (!mContext.getResources().getBoolean(R.bool.config_battery_manager_consider_ac)) {
return AVAILABLE_UNSEARCHABLE;
}
return mPowerUsageFeatureProvider.isAdaptiveChargingSupported()
? AVAILABLE_UNSEARCHABLE : UNSUPPORTED_ON_DEVICE;
}
@Override
public void updateState(Preference preference) {
super.updateState(preference);
if (!mEnableAppBatteryUsagePage) {
final int num = BatteryTipUtils.getRestrictedAppsList(mAppOpsManager,
mUserManager).size();
updateSummary(preference, num);
}
}
@VisibleForTesting
void updateSummary(Preference preference, int num) {
if (num > 0) {
preference.setSummary(StringUtil.getIcuPluralsString(mContext, num,
R.string.battery_manager_app_restricted));
} else {
preference.setSummary(
mPowerUsageFeatureProvider.isAdaptiveChargingSupported()
? R.string.battery_manager_summary
: R.string.battery_manager_summary_unsupported);
}
}
}

View File

@@ -0,0 +1,186 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.app.Dialog;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import androidx.annotation.VisibleForTesting;
import androidx.appcompat.app.AlertDialog;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.SettingsActivity;
import com.android.settings.Utils;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import com.android.settings.fuelgauge.batterytip.BatteryTipPreferenceController.BatteryTipListener;
import com.android.settings.fuelgauge.batterytip.actions.BatteryTipAction;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip;
import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip;
import com.android.settingslib.utils.StringUtil;
import java.util.List;
/** Dialog Fragment to show action dialog for each anomaly */
public class BatteryTipDialogFragment extends InstrumentedDialogFragment
implements DialogInterface.OnClickListener {
private static final String ARG_BATTERY_TIP = "battery_tip";
private static final String ARG_METRICS_KEY = "metrics_key";
@VisibleForTesting BatteryTip mBatteryTip;
@VisibleForTesting int mMetricsKey;
public static BatteryTipDialogFragment newInstance(BatteryTip batteryTip, int metricsKey) {
BatteryTipDialogFragment dialogFragment = new BatteryTipDialogFragment();
Bundle args = new Bundle(1);
args.putParcelable(ARG_BATTERY_TIP, batteryTip);
args.putInt(ARG_METRICS_KEY, metricsKey);
dialogFragment.setArguments(args);
return dialogFragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Bundle bundle = getArguments();
final Context context = getContext();
mBatteryTip = bundle.getParcelable(ARG_BATTERY_TIP);
mMetricsKey = bundle.getInt(ARG_METRICS_KEY);
switch (mBatteryTip.getType()) {
case BatteryTip.TipType.SUMMARY:
return new AlertDialog.Builder(context)
.setMessage(R.string.battery_tip_dialog_summary_message)
.setPositiveButton(android.R.string.ok, null)
.create();
case BatteryTip.TipType.HIGH_DEVICE_USAGE:
final HighUsageTip highUsageTip = (HighUsageTip) mBatteryTip;
final RecyclerView view =
(RecyclerView)
LayoutInflater.from(context).inflate(R.layout.recycler_view, null);
view.setLayoutManager(new LinearLayoutManager(context));
view.setAdapter(new HighUsageAdapter(context, highUsageTip.getHighUsageAppList()));
return new AlertDialog.Builder(context)
.setMessage(
getString(
R.string.battery_tip_dialog_message,
highUsageTip.getHighUsageAppList().size()))
.setView(view)
.setPositiveButton(android.R.string.ok, null)
.create();
case BatteryTip.TipType.APP_RESTRICTION:
final RestrictAppTip restrictAppTip = (RestrictAppTip) mBatteryTip;
final List<AppInfo> restrictedAppList = restrictAppTip.getRestrictAppList();
final int num = restrictedAppList.size();
final CharSequence appLabel =
Utils.getApplicationLabel(context, restrictedAppList.get(0).packageName);
final AlertDialog.Builder builder =
new AlertDialog.Builder(context)
.setTitle(
StringUtil.getIcuPluralsString(
context,
num,
R.string.battery_tip_restrict_app_dialog_title))
.setPositiveButton(
R.string.battery_tip_restrict_app_dialog_ok, this)
.setNegativeButton(android.R.string.cancel, null);
if (num == 1) {
builder.setMessage(
getString(R.string.battery_tip_restrict_app_dialog_message, appLabel));
} else if (num <= 5) {
builder.setMessage(
getString(
R.string.battery_tip_restrict_apps_less_than_5_dialog_message));
final RecyclerView restrictionView =
(RecyclerView)
LayoutInflater.from(context)
.inflate(R.layout.recycler_view, null);
restrictionView.setLayoutManager(new LinearLayoutManager(context));
restrictionView.setAdapter(new HighUsageAdapter(context, restrictedAppList));
builder.setView(restrictionView);
} else {
builder.setMessage(
context.getString(
R.string.battery_tip_restrict_apps_more_than_5_dialog_message,
restrictAppTip.getRestrictAppsString(context)));
}
return builder.create();
case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
final UnrestrictAppTip unrestrictAppTip = (UnrestrictAppTip) mBatteryTip;
final CharSequence name =
Utils.getApplicationLabel(context, unrestrictAppTip.getPackageName());
return new AlertDialog.Builder(context)
.setTitle(getString(R.string.battery_tip_unrestrict_app_dialog_title))
.setMessage(R.string.battery_tip_unrestrict_app_dialog_message)
.setPositiveButton(R.string.battery_tip_unrestrict_app_dialog_ok, this)
.setNegativeButton(R.string.battery_tip_unrestrict_app_dialog_cancel, null)
.create();
default:
throw new IllegalArgumentException("unknown type " + mBatteryTip.getType());
}
}
@Override
public int getMetricsCategory() {
return SettingsEnums.FUELGAUGE_BATTERY_TIP_DIALOG;
}
@Override
public void onClick(DialogInterface dialog, int which) {
final BatteryTipListener lsn = (BatteryTipListener) getTargetFragment();
if (lsn == null) {
return;
}
final BatteryTipAction action =
BatteryTipUtils.getActionForBatteryTip(
mBatteryTip,
(SettingsActivity) getActivity(),
(InstrumentedPreferenceFragment) getTargetFragment());
if (action != null) {
action.handlePositiveAction(mMetricsKey);
}
lsn.onBatteryTipHandled(mBatteryTip);
}
private boolean isPluggedIn() {
final Intent batteryIntent =
getContext()
.registerReceiver(
null /* receiver */,
new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
return batteryIntent != null
&& batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
}
}

View File

@@ -0,0 +1,75 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
import android.os.BatteryUsageStats;
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.detectors.BatteryDefenderDetector;
import com.android.settings.fuelgauge.batterytip.detectors.HighUsageDetector;
import com.android.settings.fuelgauge.batterytip.detectors.IncompatibleChargerDetector;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.AsyncLoaderCompat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Loader to compute and return a battery tip list. It will always return a full length list even
* though some tips may have state {@code BaseBatteryTip.StateType.INVISIBLE}.
*/
public class BatteryTipLoader extends AsyncLoaderCompat<List<BatteryTip>> {
private static final String TAG = "BatteryTipLoader";
private BatteryUsageStats mBatteryUsageStats;
@VisibleForTesting BatteryUtils mBatteryUtils;
public BatteryTipLoader(Context context, BatteryUsageStats batteryUsageStats) {
super(context);
mBatteryUsageStats = batteryUsageStats;
mBatteryUtils = BatteryUtils.getInstance(context);
}
@Override
public List<BatteryTip> loadInBackground() {
final List<BatteryTip> tips = new ArrayList<>();
final BatteryTipPolicy batteryTipPolicy = new BatteryTipPolicy(getContext());
final BatteryInfo batteryInfo = mBatteryUtils.getBatteryInfo(TAG);
final Context context = getContext().getApplicationContext();
tips.add(
new HighUsageDetector(context, batteryTipPolicy, mBatteryUsageStats, batteryInfo)
.detect());
tips.add(new BatteryDefenderDetector(batteryInfo, context).detect());
tips.add(new IncompatibleChargerDetector(context).detect());
FeatureFactory.getFeatureFactory()
.getBatterySettingsFeatureProvider()
.addBatteryTipDetector(context, tips, batteryInfo, batteryTipPolicy);
Collections.sort(tips);
return tips;
}
@Override
protected void onDiscardResult(List<BatteryTip> result) {}
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
import android.provider.Settings;
import android.util.KeyValueListParser;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import java.time.Duration;
/** Class to store the policy for battery tips, which comes from {@link Settings.Global} */
public class BatteryTipPolicy {
public static final String TAG = "BatteryTipPolicy";
private static final String KEY_BATTERY_TIP_ENABLED = "battery_tip_enabled";
private static final String KEY_SUMMARY_ENABLED = "summary_enabled";
private static final String KEY_BATTERY_SAVER_TIP_ENABLED = "battery_saver_tip_enabled";
private static final String KEY_HIGH_USAGE_ENABLED = "high_usage_enabled";
private static final String KEY_HIGH_USAGE_APP_COUNT = "high_usage_app_count";
private static final String KEY_HIGH_USAGE_PERIOD_MS = "high_usage_period_ms";
private static final String KEY_HIGH_USAGE_BATTERY_DRAINING = "high_usage_battery_draining";
private static final String KEY_APP_RESTRICTION_ENABLED = "app_restriction_enabled";
private static final String KEY_APP_RESTRICTION_ACTIVE_HOUR = "app_restriction_active_hour";
private static final String KEY_REDUCED_BATTERY_ENABLED = "reduced_battery_enabled";
private static final String KEY_REDUCED_BATTERY_PERCENT = "reduced_battery_percent";
private static final String KEY_LOW_BATTERY_ENABLED = "low_battery_enabled";
private static final String KEY_LOW_BATTERY_HOUR = "low_battery_hour";
private static final String KEY_DATA_HISTORY_RETAIN_DAY = "data_history_retain_day";
private static final String KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE = "excessive_bg_drain_percentage";
private static final String KEY_TEST_BATTERY_SAVER_TIP = "test_battery_saver_tip";
private static final String KEY_TEST_HIGH_USAGE_TIP = "test_high_usage_tip";
private static final String KEY_TEST_SMART_BATTERY_TIP = "test_smart_battery_tip";
private static final String KEY_TEST_LOW_BATTERY_TIP = "test_low_battery_tip";
/**
* {@code true} if general battery tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_BATTERY_TIP_ENABLED
*/
public final boolean batteryTipEnabled;
/**
* {@code true} if summary tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_SUMMARY_ENABLED
*/
public final boolean summaryEnabled;
/**
* {@code true} if battery saver tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_BATTERY_SAVER_TIP_ENABLED
*/
public final boolean batterySaverTipEnabled;
/**
* {@code true} if high usage tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_HIGH_USAGE_ENABLED
*/
public final boolean highUsageEnabled;
/**
* The maximum number of apps shown in high usage
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_HIGH_USAGE_APP_COUNT
*/
public final int highUsageAppCount;
/**
* The size of the window(milliseconds) for checking if the device is being heavily used
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_HIGH_USAGE_PERIOD_MS
*/
public final long highUsagePeriodMs;
/**
* The battery draining threshold to detect whether device is heavily used. If battery drains
* more than {@link #highUsageBatteryDraining} in last {@link #highUsagePeriodMs}, treat device
* as heavily used.
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_HIGH_USAGE_BATTERY_DRAINING
*/
public final int highUsageBatteryDraining;
/**
* {@code true} if app restriction tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_APP_RESTRICTION_ENABLED
*/
public final boolean appRestrictionEnabled;
/**
* Period(hour) to show anomaly apps. If it is 24 hours, it means only show anomaly apps
* happened in last 24 hours.
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_APP_RESTRICTION_ACTIVE_HOUR
*/
public final int appRestrictionActiveHour;
/**
* {@code true} if reduced battery tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_REDUCED_BATTERY_ENABLED
*/
public final boolean reducedBatteryEnabled;
/**
* The percentage of reduced battery to trigger the tip(e.g. 50%)
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_REDUCED_BATTERY_PERCENT
*/
public final int reducedBatteryPercent;
/**
* {@code true} if low battery tip is enabled
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_LOW_BATTERY_ENABLED
*/
public final boolean lowBatteryEnabled;
/**
* Remaining battery hour to trigger the tip(e.g. 16 hours)
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_LOW_BATTERY_HOUR
*/
public final int lowBatteryHour;
/**
* TTL day for anomaly data stored in database
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_DATA_HISTORY_RETAIN_DAY
*/
public final int dataHistoryRetainDay;
/**
* Battery drain percentage threshold for excessive background anomaly(i.e. 10%)
*
* <p>This is an additional check for excessive background, to check whether battery drain for
* an app is larger than x%
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE
*/
public final int excessiveBgDrainPercentage;
/**
* {@code true} if we want to test battery saver tip.
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_TEST_BATTERY_SAVER_TIP
*/
public final boolean testBatterySaverTip;
/**
* {@code true} if we want to test high usage tip.
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_TEST_HIGH_USAGE_TIP
*/
public final boolean testHighUsageTip;
/**
* {@code true} if we want to test smart battery tip.
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_TEST_SMART_BATTERY_TIP
*/
public final boolean testSmartBatteryTip;
/**
* {@code true} if we want to test low battery tip.
*
* @see Settings.Global#BATTERY_TIP_CONSTANTS
* @see #KEY_TEST_LOW_BATTERY_TIP
*/
public final boolean testLowBatteryTip;
private final KeyValueListParser mParser;
public BatteryTipPolicy(Context context) {
this(context, new KeyValueListParser(','));
}
@VisibleForTesting
BatteryTipPolicy(Context context, KeyValueListParser parser) {
mParser = parser;
final String value =
Settings.Global.getString(
context.getContentResolver(), Settings.Global.BATTERY_TIP_CONSTANTS);
try {
mParser.setString(value);
} catch (IllegalArgumentException e) {
Log.e(TAG, "Bad battery tip constants");
}
batteryTipEnabled = mParser.getBoolean(KEY_BATTERY_TIP_ENABLED, true);
summaryEnabled = mParser.getBoolean(KEY_SUMMARY_ENABLED, false);
batterySaverTipEnabled = mParser.getBoolean(KEY_BATTERY_SAVER_TIP_ENABLED, true);
highUsageEnabled = mParser.getBoolean(KEY_HIGH_USAGE_ENABLED, true);
highUsageAppCount = mParser.getInt(KEY_HIGH_USAGE_APP_COUNT, 3);
highUsagePeriodMs =
mParser.getLong(KEY_HIGH_USAGE_PERIOD_MS, Duration.ofHours(2).toMillis());
highUsageBatteryDraining = mParser.getInt(KEY_HIGH_USAGE_BATTERY_DRAINING, 25);
appRestrictionEnabled = mParser.getBoolean(KEY_APP_RESTRICTION_ENABLED, true);
appRestrictionActiveHour = mParser.getInt(KEY_APP_RESTRICTION_ACTIVE_HOUR, 24);
reducedBatteryEnabled = mParser.getBoolean(KEY_REDUCED_BATTERY_ENABLED, false);
reducedBatteryPercent = mParser.getInt(KEY_REDUCED_BATTERY_PERCENT, 50);
lowBatteryEnabled = mParser.getBoolean(KEY_LOW_BATTERY_ENABLED, true);
lowBatteryHour = mParser.getInt(KEY_LOW_BATTERY_HOUR, 3);
dataHistoryRetainDay = mParser.getInt(KEY_DATA_HISTORY_RETAIN_DAY, 30);
excessiveBgDrainPercentage = mParser.getInt(KEY_EXCESSIVE_BG_DRAIN_PERCENTAGE, 10);
testBatterySaverTip = mParser.getBoolean(KEY_TEST_BATTERY_SAVER_TIP, false);
testHighUsageTip = mParser.getBoolean(KEY_TEST_HIGH_USAGE_TIP, false);
testSmartBatteryTip = mParser.getBoolean(KEY_TEST_SMART_BATTERY_TIP, false);
testLowBatteryTip = mParser.getBoolean(KEY_TEST_LOW_BATTERY_TIP, false);
}
}

View File

@@ -0,0 +1,194 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
import android.os.BadParcelableException;
import android.os.Bundle;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.Nullable;
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;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.CardPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/** Controller in charge of the battery tip group */
public class BatteryTipPreferenceController extends BasePreferenceController {
public static final String PREF_NAME = "battery_tip";
private static final String TAG = "BatteryTipPreferenceController";
private static final int REQUEST_ANOMALY_ACTION = 0;
private static final String KEY_BATTERY_TIPS = "key_battery_tips";
private BatteryTipListener mBatteryTipListener;
private List<BatteryTip> mBatteryTips;
private Map<String, BatteryTip> mBatteryTipMap;
private SettingsActivity mSettingsActivity;
private MetricsFeatureProvider mMetricsFeatureProvider;
private boolean mNeedUpdate;
@VisibleForTesting CardPreference mCardPreference;
@VisibleForTesting Context mPrefContext;
InstrumentedPreferenceFragment mFragment;
public BatteryTipPreferenceController(Context context, String preferenceKey) {
super(context, preferenceKey);
mBatteryTipMap = new ArrayMap<>();
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
mNeedUpdate = true;
}
public void setActivity(SettingsActivity activity) {
mSettingsActivity = activity;
}
public void setFragment(InstrumentedPreferenceFragment fragment) {
mFragment = fragment;
}
public void setBatteryTipListener(BatteryTipListener lsn) {
mBatteryTipListener = lsn;
}
@Override
public int getAvailabilityStatus() {
return AVAILABLE_UNSEARCHABLE;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
mPrefContext = screen.getContext();
mCardPreference = screen.findPreference(getPreferenceKey());
// Set preference as invisible since there is no default tips.
mCardPreference.setVisible(false);
}
public void updateBatteryTips(List<BatteryTip> batteryTips) {
if (batteryTips == null) {
return;
}
mBatteryTips = batteryTips;
mCardPreference.setVisible(false);
for (int i = 0, size = batteryTips.size(); i < size; i++) {
final BatteryTip batteryTip = mBatteryTips.get(i);
batteryTip.validateCheck(mContext);
if (batteryTip.getState() != BatteryTip.StateType.INVISIBLE) {
mCardPreference.setVisible(true);
batteryTip.updatePreference(mCardPreference);
mBatteryTipMap.put(mCardPreference.getKey(), batteryTip);
batteryTip.log(mContext, mMetricsFeatureProvider);
mNeedUpdate = batteryTip.needUpdate();
break;
}
}
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
final BatteryTip batteryTip = mBatteryTipMap.get(preference.getKey());
if (batteryTip != null) {
if (batteryTip.shouldShowDialog()) {
BatteryTipDialogFragment dialogFragment =
BatteryTipDialogFragment.newInstance(
batteryTip, mFragment.getMetricsCategory());
dialogFragment.setTargetFragment(mFragment, REQUEST_ANOMALY_ACTION);
dialogFragment.show(mFragment.getFragmentManager(), TAG);
} else {
final BatteryTipAction action =
BatteryTipUtils.getActionForBatteryTip(
batteryTip, mSettingsActivity, mFragment);
if (action != null) {
action.handlePositiveAction(mFragment.getMetricsCategory());
}
if (mBatteryTipListener != null) {
mBatteryTipListener.onBatteryTipHandled(batteryTip);
}
}
return true;
}
return super.handlePreferenceTreeClick(preference);
}
public void restoreInstanceState(Bundle bundle) {
if (bundle == null) {
return;
}
try {
List<BatteryTip> batteryTips = bundle.getParcelableArrayList(KEY_BATTERY_TIPS);
updateBatteryTips(batteryTips);
} catch (BadParcelableException e) {
Log.e(TAG, "failed to invoke restoreInstanceState()", e);
}
}
public void saveInstanceState(Bundle bundle) {
if (bundle == null) {
return;
}
try {
bundle.putParcelableList(KEY_BATTERY_TIPS, mBatteryTips);
} catch (BadParcelableException e) {
Log.e(TAG, "failed to invoke saveInstanceState()", e);
}
}
public boolean needUpdate() {
return mNeedUpdate;
}
/**
* @return current battery tips, null if unavailable.
*/
@Nullable
public BatteryTip getCurrentBatteryTip() {
if (mBatteryTips == null) {
return null;
}
Optional<BatteryTip> visibleBatteryTip =
mBatteryTips.stream().filter(BatteryTip::isVisible).findFirst();
return visibleBatteryTip.orElse(null);
}
/** Listener to give the control back to target fragment */
public interface BatteryTipListener {
/**
* This method is invoked once battery tip is handled, then target fragment could do extra
* work.
*
* @param batteryTip that has been handled
*/
void onBatteryTipHandled(BatteryTip batteryTip);
}
}

View File

@@ -0,0 +1,116 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
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;
import com.android.settings.fuelgauge.batterytip.actions.OpenRestrictAppFragmentAction;
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;
import java.util.ArrayList;
import java.util.List;
/** Utility class for {@link BatteryTip} */
public class BatteryTipUtils {
private static final int REQUEST_CODE = 0;
/** Get a list of restricted apps with {@link AppOpsManager#OP_RUN_ANY_IN_BACKGROUND} */
@NonNull
public static List<AppInfo> getRestrictedAppsList(
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});
final List<AppInfo> appInfos = new ArrayList<>();
for (int i = 0, size = CollectionUtils.size(packageOpsList); i < size; i++) {
final AppOpsManager.PackageOps packageOps = packageOpsList.get(i);
final List<AppOpsManager.OpEntry> entries = packageOps.getOps();
for (int j = 0, entriesSize = entries.size(); j < entriesSize; j++) {
AppOpsManager.OpEntry entry = entries.get(j);
if (entry.getOp() != AppOpsManager.OP_RUN_ANY_IN_BACKGROUND) {
continue;
}
if (entry.getMode() != AppOpsManager.MODE_ALLOWED
&& userHandles.contains(
new UserHandle(UserHandle.getUserId(packageOps.getUid())))) {
appInfos.add(
new AppInfo.Builder()
.setPackageName(packageOps.getPackageName())
.setUid(packageOps.getUid())
.build());
}
}
}
return appInfos;
}
/**
* 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}
* @return an action for {@code batteryTip}
*/
public static BatteryTipAction getActionForBatteryTip(
BatteryTip batteryTip,
SettingsActivity settingsActivity,
InstrumentedPreferenceFragment fragment) {
switch (batteryTip.getType()) {
case BatteryTip.TipType.SMART_BATTERY_MANAGER:
return new SmartBatteryAction(settingsActivity, fragment);
case BatteryTip.TipType.BATTERY_SAVER:
case BatteryTip.TipType.LOW_BATTERY:
return new OpenBatterySaverAction(settingsActivity);
case BatteryTip.TipType.APP_RESTRICTION:
if (batteryTip.getState() == BatteryTip.StateType.HANDLED) {
return new OpenRestrictAppFragmentAction(fragment, (RestrictAppTip) batteryTip);
} else {
return new RestrictAppAction(settingsActivity, (RestrictAppTip) batteryTip);
}
case BatteryTip.TipType.REMOVE_APP_RESTRICTION:
return new UnrestrictAppAction(settingsActivity, (UnrestrictAppTip) batteryTip);
default:
return null;
}
}
/** Detect and return anomaly apps after {@code timeAfterMs} */
public static List<AppInfo> detectAnomalies(Context context, long timeAfterMs) {
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.util.IconDrawableFactory;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import com.android.settings.R;
import com.android.settings.Utils;
import java.util.List;
/** Adapter for the high usage app list */
public class HighUsageAdapter extends RecyclerView.Adapter<HighUsageAdapter.ViewHolder> {
private final Context mContext;
private final IconDrawableFactory mIconDrawableFactory;
private final PackageManager mPackageManager;
private final List<AppInfo> mHighUsageAppList;
public static class ViewHolder extends RecyclerView.ViewHolder {
public View view;
public ImageView appIcon;
public TextView appName;
public TextView appTime;
public ViewHolder(View v) {
super(v);
view = v;
appIcon = v.findViewById(R.id.app_icon);
appName = v.findViewById(R.id.app_name);
appTime = v.findViewById(R.id.app_screen_time);
}
}
public HighUsageAdapter(Context context, List<AppInfo> highUsageAppList) {
mContext = context;
mHighUsageAppList = highUsageAppList;
mIconDrawableFactory = IconDrawableFactory.newInstance(context);
mPackageManager = context.getPackageManager();
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view =
LayoutInflater.from(mContext).inflate(R.layout.app_high_usage_item, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
final AppInfo app = mHighUsageAppList.get(position);
holder.appIcon.setImageDrawable(
Utils.getBadgedIcon(
mIconDrawableFactory,
mPackageManager,
app.packageName,
UserHandle.getUserId(app.uid)));
CharSequence label = Utils.getApplicationLabel(mContext, app.packageName);
if (label == null) {
label = app.packageName;
}
holder.appName.setText(label);
}
@Override
public int getItemCount() {
return mHighUsageAppList.size();
}
}

View File

@@ -0,0 +1,73 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import android.os.BatteryStats;
import com.android.settings.fuelgauge.BatteryInfo;
/** DataParser used to go through battery data and detect whether battery is heavily used. */
public class HighUsageDataParser implements BatteryInfo.BatteryDataParser {
/** Time period to check the battery usage */
private final long mTimePeriodMs;
/**
* Treat device as heavily used if battery usage is more than {@code threshold}. 1 means 1%
* battery usage.
*/
private int mThreshold;
private long mEndTimeMs;
private byte mEndBatteryLevel;
private byte mLastPeriodBatteryLevel;
private int mBatteryDrain;
public HighUsageDataParser(long timePeriodMs, int threshold) {
mTimePeriodMs = timePeriodMs;
mThreshold = threshold;
}
@Override
public void onParsingStarted(long startTime, long endTime) {
mEndTimeMs = endTime;
}
@Override
public void onDataPoint(long time, BatteryStats.HistoryItem record) {
if (time == 0 || record.currentTime <= mEndTimeMs - mTimePeriodMs) {
// Since onDataPoint is invoked sorted by time, so we could use this way to get the
// closet battery level 'mTimePeriodMs' time ago.
mLastPeriodBatteryLevel = record.batteryLevel;
}
mEndBatteryLevel = record.batteryLevel;
}
@Override
public void onDataGap() {
// do nothing
}
@Override
public void onParsingDone() {
mBatteryDrain = mLastPeriodBatteryLevel - mEndBatteryLevel;
}
/** Return {@code true} if the battery drain in {@link #mTimePeriodMs} is too much */
public boolean isDeviceHeavilyUsed() {
return mBatteryDrain > mThreshold;
}
}

View File

@@ -0,0 +1,217 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** This class provides all the configs needed if we want to use {@link android.app.StatsManager} */
public class StatsManagerConfig {
/**
* The key that represents the anomaly config. This value is used in {@link
* android.app.StatsManager#addConfig(long, byte[])}
*/
public static final long ANOMALY_CONFIG_KEY = 1;
/** The key that represents subscriber, which is settings app. */
public static final long SUBSCRIBER_ID = 1;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
AnomalyType.NULL,
AnomalyType.UNKNOWN_REASON,
AnomalyType.EXCESSIVE_WAKELOCK_ALL_SCREEN_OFF,
AnomalyType.EXCESSIVE_WAKEUPS_IN_BACKGROUND,
AnomalyType.EXCESSIVE_UNOPTIMIZED_BLE_SCAN,
AnomalyType.EXCESSIVE_BACKGROUND_SERVICE,
AnomalyType.EXCESSIVE_WIFI_SCAN,
AnomalyType.EXCESSIVE_FLASH_WRITES,
AnomalyType.EXCESSIVE_MEMORY_IN_BACKGROUND,
AnomalyType.EXCESSIVE_DAVEY_RATE,
AnomalyType.EXCESSIVE_JANKY_FRAMES,
AnomalyType.SLOW_COLD_START_TIME,
AnomalyType.SLOW_HOT_START_TIME,
AnomalyType.SLOW_WARM_START_TIME,
AnomalyType.EXCESSIVE_BACKGROUND_SYNCS,
AnomalyType.EXCESSIVE_GPS_SCANS_IN_BACKGROUND,
AnomalyType.EXCESSIVE_JOB_SCHEDULING,
AnomalyType.EXCESSIVE_MOBILE_NETWORK_IN_BACKGROUND,
AnomalyType.EXCESSIVE_WIFI_LOCK_TIME,
AnomalyType.JOB_TIMED_OUT,
AnomalyType.LONG_UNOPTIMIZED_BLE_SCAN,
AnomalyType.BACKGROUND_ANR,
AnomalyType.BACKGROUND_CRASH_RATE,
AnomalyType.EXCESSIVE_ANR_LOOPING,
AnomalyType.EXCESSIVE_ANRS,
AnomalyType.EXCESSIVE_CRASH_RATE,
AnomalyType.EXCESSIVE_CRASH_LOOPING,
AnomalyType.NUMBER_OF_OPEN_FILES,
AnomalyType.EXCESSIVE_CAMERA_USAGE_IN_BACKGROUND,
AnomalyType.EXCESSIVE_CONTACT_ACCESS,
AnomalyType.EXCESSIVE_AUDIO_IN_BACKGROUND,
AnomalyType.EXCESSIVE_CRASH_ANR_IN_BACKGROUND,
AnomalyType.BATTERY_DRAIN_FROM_UNUSED_APP,
})
public @interface AnomalyType {
/** This represents an error condition in the anomaly detection. */
int NULL = -1;
/** The anomaly type does not match any other defined type. */
int UNKNOWN_REASON = 0;
/**
* The application held a partial (screen off) wake lock for a period of time that exceeded
* the threshold with the screen off when not charging.
*/
int EXCESSIVE_WAKELOCK_ALL_SCREEN_OFF = 1;
/**
* The application exceeded the maximum number of wakeups while in the background when not
* charging.
*/
int EXCESSIVE_WAKEUPS_IN_BACKGROUND = 2;
/** The application did unoptimized Bluetooth scans too frequently when not charging. */
int EXCESSIVE_UNOPTIMIZED_BLE_SCAN = 3;
/**
* The application ran in the background for a period of time that exceeded the threshold.
*/
int EXCESSIVE_BACKGROUND_SERVICE = 4;
/** The application exceeded the maximum number of wifi scans when not charging. */
int EXCESSIVE_WIFI_SCAN = 5;
/** The application exceed the maximum number of flash writes */
int EXCESSIVE_FLASH_WRITES = 6;
/**
* The application used more than the maximum memory, while not spending any time in the
* foreground.
*/
int EXCESSIVE_MEMORY_IN_BACKGROUND = 7;
/**
* The application exceeded the maximum percentage of frames with a render rate of greater
* than 700ms.
*/
int EXCESSIVE_DAVEY_RATE = 8;
/**
* The application exceeded the maximum percentage of frames with a render rate greater than
* 16ms.
*/
int EXCESSIVE_JANKY_FRAMES = 9;
/**
* The application exceeded the maximum cold start time - the app has not been launched
* since last system start, died or was killed.
*/
int SLOW_COLD_START_TIME = 10;
/**
* The application exceeded the maximum hot start time - the app and activity are already in
* memory.
*/
int SLOW_HOT_START_TIME = 11;
/**
* The application exceeded the maximum warm start time - the app was already in memory but
* the activity wasnt created yet or was removed from memory.
*/
int SLOW_WARM_START_TIME = 12;
/** The application exceeded the maximum number of syncs while in the background. */
int EXCESSIVE_BACKGROUND_SYNCS = 13;
/** The application exceeded the maximum number of gps scans while in the background. */
int EXCESSIVE_GPS_SCANS_IN_BACKGROUND = 14;
/** The application scheduled more than the maximum number of jobs while not charging. */
int EXCESSIVE_JOB_SCHEDULING = 15;
/**
* The application exceeded the maximum amount of mobile network traffic while in the
* background.
*/
int EXCESSIVE_MOBILE_NETWORK_IN_BACKGROUND = 16;
/**
* The application held the WiFi lock for more than the maximum amount of time while not
* charging.
*/
int EXCESSIVE_WIFI_LOCK_TIME = 17;
/** The application scheduled a job that ran longer than the maximum amount of time. */
int JOB_TIMED_OUT = 18;
/**
* The application did an unoptimized Bluetooth scan that exceeded the maximum time while in
* the background.
*/
int LONG_UNOPTIMIZED_BLE_SCAN = 19;
/** The application exceeded the maximum ANR rate while in the background. */
int BACKGROUND_ANR = 20;
/** The application exceeded the maximum crash rate while in the background. */
int BACKGROUND_CRASH_RATE = 21;
/** The application exceeded the maximum ANR-looping rate. */
int EXCESSIVE_ANR_LOOPING = 22;
/** The application exceeded the maximum ANR rate. */
int EXCESSIVE_ANRS = 23;
/** The application exceeded the maximum crash rate. */
int EXCESSIVE_CRASH_RATE = 24;
/** The application exceeded the maximum crash-looping rate. */
int EXCESSIVE_CRASH_LOOPING = 25;
/** The application crashed because no more file descriptors were available. */
int NUMBER_OF_OPEN_FILES = 26;
/** The application used an excessive amount of CPU while in a background process state. */
int EXCESSIVE_CPU_USAGE_IN_BACKGROUND = 27;
/**
* The application kept the camera open for an excessive amount of time while in a bckground
* process state.
*/
int EXCESSIVE_CAMERA_USAGE_IN_BACKGROUND = 28;
/** The application has accessed the contacts content provider an excessive amount. */
int EXCESSIVE_CONTACT_ACCESS = 29;
/** The application has played too much audio while in a background process state. */
int EXCESSIVE_AUDIO_IN_BACKGROUND = 30;
/**
* The application has crashed or ANRed too many times while in a background process state.
*/
int EXCESSIVE_CRASH_ANR_IN_BACKGROUND = 31;
/**
* An application which has not been used by the user recently was detected to cause an
* excessive amount of battery drain.
*/
int BATTERY_DRAIN_FROM_UNUSED_APP = 32;
}
}

View File

@@ -0,0 +1,39 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import static com.android.settingslib.fuelgauge.BatterySaverLogging.SAVER_ENABLED_UNKNOWN;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settingslib.fuelgauge.BatterySaverUtils;
public class BatterySaverAction extends BatteryTipAction {
public BatterySaverAction(Context context) {
super(context);
}
/** Handle the action when user clicks positive button */
@Override
public void handlePositiveAction(int metricsKey) {
BatterySaverUtils.setPowerSaveMode(
mContext, true, /*needFirstTimeWarning*/ true, SAVER_ENABLED_UNKNOWN);
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_TIP_TURN_ON_BATTERY_SAVER, metricsKey);
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import android.content.Context;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/**
* Abstract class for battery tip action, which is triggered if we need to handle the battery tip
*/
public abstract class BatteryTipAction {
protected Context mContext;
protected MetricsFeatureProvider mMetricsFeatureProvider;
public BatteryTipAction(Context context) {
mContext = context;
mMetricsFeatureProvider = FeatureFactory.getFeatureFactory().getMetricsFeatureProvider();
}
/** Handle the action when user clicks positive button */
public abstract void handlePositiveAction(int metricsKey);
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import android.app.settings.SettingsEnums;
import android.content.Context;
import com.android.settings.core.SubSettingLauncher;
import com.android.settings.fuelgauge.batterysaver.BatterySaverSettings;
/** Action to open the {@link com.android.settings.fuelgauge.batterysaver.BatterySaverSettings} */
public class OpenBatterySaverAction extends BatteryTipAction {
public OpenBatterySaverAction(Context context) {
super(context);
}
/** Handle the action when user clicks positive button */
@Override
public void handlePositiveAction(int metricsKey) {
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_TIP_OPEN_BATTERY_SAVER_PAGE, metricsKey);
new SubSettingLauncher(mContext)
.setDestination(BatterySaverSettings.class.getName())
.setSourceMetricsCategory(metricsKey)
.launch();
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import android.app.settings.SettingsEnums;
import androidx.annotation.VisibleForTesting;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.RestrictedAppDetails;
import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
import com.android.settingslib.utils.ThreadUtils;
import java.util.List;
/** Action to open the {@link com.android.settings.fuelgauge.RestrictedAppDetails} */
public class OpenRestrictAppFragmentAction extends BatteryTipAction {
private final RestrictAppTip mRestrictAppTip;
private final InstrumentedPreferenceFragment mFragment;
@VisibleForTesting BatteryDatabaseManager mBatteryDatabaseManager;
public OpenRestrictAppFragmentAction(
InstrumentedPreferenceFragment fragment, RestrictAppTip tip) {
super(fragment.getContext());
mFragment = fragment;
mRestrictAppTip = tip;
mBatteryDatabaseManager = BatteryDatabaseManager.getInstance(mContext);
}
/** Handle the action when user clicks positive button */
@Override
public void handlePositiveAction(int metricsKey) {
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_TIP_OPEN_APP_RESTRICTION_PAGE, metricsKey);
final List<AppInfo> mAppInfos = mRestrictAppTip.getRestrictAppList();
RestrictedAppDetails.startRestrictedAppDetails(mFragment, mAppInfos);
// Mark all the anomalies as handled, so it won't show up again.
ThreadUtils.postOnBackgroundThread(
() ->
mBatteryDatabaseManager.updateAnomalies(
mAppInfos, AnomalyDatabaseHelper.State.HANDLED));
}
}

View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.annotation.VisibleForTesting;
import com.android.internal.util.CollectionUtils;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
import com.android.settings.fuelgauge.batterytip.tips.RestrictAppTip;
import java.util.List;
/** Action to restrict the apps, then app is not allowed to run in the background. */
public class RestrictAppAction extends BatteryTipAction {
private RestrictAppTip mRestrictAppTip;
@VisibleForTesting BatteryDatabaseManager mBatteryDatabaseManager;
@VisibleForTesting BatteryUtils mBatteryUtils;
public RestrictAppAction(Context context, RestrictAppTip tip) {
super(context);
mRestrictAppTip = tip;
mBatteryUtils = BatteryUtils.getInstance(context);
mBatteryDatabaseManager = BatteryDatabaseManager.getInstance(context);
}
/** Handle the action when user clicks positive button */
@Override
public void handlePositiveAction(int metricsKey) {
final List<AppInfo> appInfos = mRestrictAppTip.getRestrictAppList();
for (int i = 0, size = appInfos.size(); i < size; i++) {
final AppInfo appInfo = appInfos.get(i);
final String packageName = appInfo.packageName;
// Force app standby, then app can't run in the background
mBatteryUtils.setForceAppStandby(appInfo.uid, packageName, AppOpsManager.MODE_IGNORED);
if (CollectionUtils.isEmpty(appInfo.anomalyTypes)) {
// Only log context if there is no anomaly type
mMetricsFeatureProvider.action(
SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_TIP_RESTRICT_APP,
metricsKey,
packageName,
0);
} else {
for (int type : appInfo.anomalyTypes) {
mMetricsFeatureProvider.action(
SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_TIP_RESTRICT_APP,
metricsKey,
packageName,
type);
}
}
}
mBatteryDatabaseManager.updateAnomalies(appInfos, AnomalyDatabaseHelper.State.HANDLED);
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import android.app.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 Fragment mFragment;
public SmartBatteryAction(SettingsActivity settingsActivity, Fragment fragment) {
super(settingsActivity.getApplicationContext());
mSettingsActivity = settingsActivity;
mFragment = fragment;
}
/** Handle the action when user clicks positive button */
@Override
public void handlePositiveAction(int metricsKey) {
mMetricsFeatureProvider.action(
mContext, SettingsEnums.ACTION_TIP_OPEN_SMART_BATTERY, metricsKey);
new SubSettingLauncher(mSettingsActivity)
.setSourceMetricsCategory(
mFragment instanceof Instrumentable
? ((Instrumentable) mFragment).getMetricsCategory()
: Instrumentable.METRICS_CATEGORY_UNKNOWN)
.setDestination(SmartBatterySettings.class.getName())
.setTitleRes(R.string.smart_battery_manager_title)
.launch();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.actions;
import android.app.AppOpsManager;
import android.app.settings.SettingsEnums;
import android.content.Context;
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settings.fuelgauge.batterytip.tips.UnrestrictAppTip;
/** Action to clear the restriction to the app */
public class UnrestrictAppAction extends BatteryTipAction {
private UnrestrictAppTip mUnRestrictAppTip;
@VisibleForTesting BatteryUtils mBatteryUtils;
public UnrestrictAppAction(Context context, UnrestrictAppTip tip) {
super(context);
mUnRestrictAppTip = tip;
mBatteryUtils = BatteryUtils.getInstance(context);
}
/** Handle the action when user clicks positive button */
@Override
public void handlePositiveAction(int metricsKey) {
final AppInfo appInfo = mUnRestrictAppTip.getUnrestrictAppInfo();
// Clear force app standby, then app can run in the background
mBatteryUtils.setForceAppStandby(
appInfo.uid, appInfo.packageName, AppOpsManager.MODE_ALLOWED);
mMetricsFeatureProvider.action(
SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_TIP_UNRESTRICT_APP,
metricsKey,
appInfo.packageName,
0);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.detectors;
import android.content.Context;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.batterytip.tips.BatteryDefenderTip;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.overlay.FeatureFactory;
/** Detect whether the battery is overheated */
public class BatteryDefenderDetector implements BatteryTipDetector {
private final BatteryInfo mBatteryInfo;
private final Context mContext;
public BatteryDefenderDetector(BatteryInfo batteryInfo, Context context) {
mBatteryInfo = batteryInfo;
mContext = context;
}
@Override
public BatteryTip detect() {
final boolean isBasicBatteryDefend =
mBatteryInfo.isBatteryDefender
&& !FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()
.isExtraDefend();
final int state =
isBasicBatteryDefend ? BatteryTip.StateType.NEW : BatteryTip.StateType.INVISIBLE;
final boolean isPluggedIn = mBatteryInfo.pluggedStatus != 0;
return new BatteryDefenderTip(state, isPluggedIn);
}
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.detectors;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
public interface BatteryTipDetector {
/**
* Detect and update the status of {@link BatteryTip}
*
* @return a not null {@link BatteryTip}
*/
BatteryTip detect();
}

View File

@@ -0,0 +1,131 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.detectors;
import static com.android.settings.Utils.SETTINGS_PACKAGE_NAME;
import android.content.Context;
import android.os.BatteryUsageStats;
import android.os.UidBatteryConsumer;
import android.util.Log;
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.BatteryUtils;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.HighUsageDataParser;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Detector whether to show summary tip. This detector should be executed as the last {@link
* BatteryTipDetector} since it need the most up-to-date {@code visibleTips}
*/
public class HighUsageDetector implements BatteryTipDetector {
private static final String TAG = "HighUsageDetector";
private BatteryTipPolicy mPolicy;
private BatteryUsageStats mBatteryUsageStats;
private final BatteryInfo mBatteryInfo;
private List<AppInfo> mHighUsageAppList;
@VisibleForTesting HighUsageDataParser mDataParser;
@VisibleForTesting BatteryUtils mBatteryUtils;
@VisibleForTesting boolean mDischarging;
public HighUsageDetector(
Context context,
BatteryTipPolicy policy,
BatteryUsageStats batteryUsageStats,
BatteryInfo batteryInfo) {
mPolicy = policy;
mBatteryUsageStats = batteryUsageStats;
mBatteryInfo = batteryInfo;
mHighUsageAppList = new ArrayList<>();
mBatteryUtils = BatteryUtils.getInstance(context);
mDataParser =
new HighUsageDataParser(
mPolicy.highUsagePeriodMs, mPolicy.highUsageBatteryDraining);
mDischarging = batteryInfo.discharging;
}
@Override
public BatteryTip detect() {
final long lastFullChargeTimeMs =
mBatteryUtils.calculateLastFullChargeTime(
mBatteryUsageStats, System.currentTimeMillis());
if (mPolicy.highUsageEnabled && mDischarging) {
parseBatteryData();
if (mDataParser.isDeviceHeavilyUsed() || mPolicy.testHighUsageTip) {
final double totalPower = mBatteryUsageStats.getConsumedPower();
final int dischargeAmount = mBatteryUsageStats.getDischargePercentage();
final List<UidBatteryConsumer> uidBatteryConsumers =
mBatteryUsageStats.getUidBatteryConsumers();
// Sort by descending power
uidBatteryConsumers.sort(
(consumer1, consumer2) ->
Double.compare(
consumer2.getConsumedPower(),
consumer1.getConsumedPower()));
for (UidBatteryConsumer consumer : uidBatteryConsumers) {
final double percent =
mBatteryUtils.calculateBatteryPercent(
consumer.getConsumedPower(), totalPower, dischargeAmount);
if ((percent + 0.5f < 1f)
|| mBatteryUtils.shouldHideUidBatteryConsumer(consumer)) {
// Don't show it if we should hide or usage percentage is lower than 1%
continue;
}
mHighUsageAppList.add(
new AppInfo.Builder()
.setUid(consumer.getUid())
.setPackageName(mBatteryUtils.getPackageName(consumer.getUid()))
.build());
if (mHighUsageAppList.size() >= mPolicy.highUsageAppCount) {
break;
}
}
// When in test mode, add an app if necessary
if (mPolicy.testHighUsageTip && mHighUsageAppList.isEmpty()) {
mHighUsageAppList.add(
new AppInfo.Builder()
.setPackageName(SETTINGS_PACKAGE_NAME)
.setScreenOnTimeMs(TimeUnit.HOURS.toMillis(3))
.build());
}
}
}
return new HighUsageTip(lastFullChargeTimeMs, mHighUsageAppList);
}
@VisibleForTesting
void parseBatteryData() {
try {
mBatteryInfo.parseBatteryHistory(mDataParser);
} catch (IllegalStateException e) {
Log.e(TAG, "parseBatteryData() failed", e);
}
}
}

View File

@@ -0,0 +1,45 @@
/*
* 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.batterytip.detectors;
import android.content.Context;
import android.util.Log;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.IncompatibleChargerTip;
import com.android.settingslib.Utils;
/** Detect whether it is in the incompatible charging state */
public final class IncompatibleChargerDetector implements BatteryTipDetector {
private static final String TAG = "IncompatibleChargerDetector";
private final Context mContext;
public IncompatibleChargerDetector(Context context) {
mContext = context;
}
@Override
public BatteryTip detect() {
final boolean isIncompatibleCharging = Utils.containsIncompatibleChargers(mContext, TAG);
final int state =
isIncompatibleCharging ? BatteryTip.StateType.NEW : BatteryTip.StateType.INVISIBLE;
Log.d(
TAG,
"detect() state= " + state + " isIncompatibleCharging: " + isIncompatibleCharging);
return new IncompatibleChargerTip(state);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.detectors;
import android.content.Context;
import android.os.PowerManager;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.LowBatteryTip;
/** Detect whether the battery is too low */
public class LowBatteryDetector implements BatteryTipDetector {
private final BatteryInfo mBatteryInfo;
private final BatteryTipPolicy mBatteryTipPolicy;
private final boolean mIsPowerSaveMode;
private final int mWarningLevel;
public LowBatteryDetector(
Context context, BatteryTipPolicy batteryTipPolicy, BatteryInfo batteryInfo) {
mBatteryTipPolicy = batteryTipPolicy;
mBatteryInfo = batteryInfo;
mWarningLevel =
context.getResources()
.getInteger(com.android.internal.R.integer.config_lowBatteryWarningLevel);
mIsPowerSaveMode = context.getSystemService(PowerManager.class).isPowerSaveMode();
}
@Override
public BatteryTip detect() {
final boolean lowBattery = mBatteryInfo.batteryLevel <= mWarningLevel;
final boolean lowBatteryEnabled = mBatteryTipPolicy.lowBatteryEnabled && !mIsPowerSaveMode;
final boolean dischargingLowBatteryState =
mBatteryTipPolicy.testLowBatteryTip || (mBatteryInfo.discharging && lowBattery);
// Show it as new if in test or in discharging low battery state,
// dismiss it if battery saver is on or disabled by config.
final int state =
lowBatteryEnabled && dischargingLowBatteryState
? BatteryTip.StateType.NEW
: BatteryTip.StateType.INVISIBLE;
return new LowBatteryTip(state, mIsPowerSaveMode);
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.detectors;
import android.content.ContentResolver;
import android.content.Context;
import android.provider.Settings;
import com.android.settings.fuelgauge.BatteryInfo;
import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
import com.android.settings.fuelgauge.batterytip.tips.SmartBatteryTip;
/** Detect whether to show smart battery tip. */
public class SmartBatteryDetector implements BatteryTipDetector {
private static final int EXPECTED_BATTERY_LEVEL = 30;
private final BatteryInfo mBatteryInfo;
private final BatteryTipPolicy mPolicy;
private final ContentResolver mContentResolver;
private final boolean mIsPowerSaveMode;
public SmartBatteryDetector(
Context context,
BatteryTipPolicy policy,
BatteryInfo batteryInfo,
ContentResolver contentResolver,
boolean isPowerSaveMode) {
mPolicy = policy;
mBatteryInfo = batteryInfo;
mContentResolver = contentResolver;
mIsPowerSaveMode = isPowerSaveMode;
}
@Override
public BatteryTip detect() {
final boolean smartBatteryOff =
Settings.Global.getInt(
mContentResolver,
Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
1)
== 0;
final boolean isUnderExpectedBatteryLevel =
mBatteryInfo.batteryLevel <= EXPECTED_BATTERY_LEVEL;
// Show it if in test or smart battery is off.
final boolean enableSmartBatteryTip =
smartBatteryOff && !mIsPowerSaveMode && isUnderExpectedBatteryLevel
|| mPolicy.testSmartBatteryTip;
final int state =
enableSmartBatteryTip ? BatteryTip.StateType.NEW : BatteryTip.StateType.INVISIBLE;
return new SmartBatteryTip(state);
}
}

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.content.Context;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import java.util.function.Predicate;
/** {@link Predicate} for {@link AppInfo} to check whether it has label */
public class AppLabelPredicate implements Predicate<AppInfo> {
private static AppLabelPredicate sInstance;
private Context mContext;
public static AppLabelPredicate getInstance(Context context) {
if (sInstance == null) {
sInstance = new AppLabelPredicate(context.getApplicationContext());
}
return sInstance;
}
private AppLabelPredicate(Context context) {
mContext = context;
}
@Override
public boolean test(AppInfo appInfo) {
// Return true if app doesn't have label
return Utils.getApplicationLabel(mContext, appInfo.packageName) == null;
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.AppOpsManager;
import android.content.Context;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import java.util.function.Predicate;
/** {@link Predicate} for {@link AppInfo} to check whether it is restricted. */
public class AppRestrictionPredicate implements Predicate<AppInfo> {
private static AppRestrictionPredicate sInstance;
private AppOpsManager mAppOpsManager;
public static AppRestrictionPredicate getInstance(Context context) {
if (sInstance == null) {
sInstance = new AppRestrictionPredicate(context.getApplicationContext());
}
return sInstance;
}
private AppRestrictionPredicate(Context context) {
mAppOpsManager = context.getSystemService(AppOpsManager.class);
}
@Override
public boolean test(AppInfo appInfo) {
// Return true if app already been restricted
return mAppOpsManager.checkOpNoThrow(
AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, appInfo.uid, appInfo.packageName)
== AppOpsManager.MODE_IGNORED;
}
}

View File

@@ -0,0 +1,147 @@
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.overlay.FeatureFactory;
import com.android.settings.widget.CardPreference;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import kotlin.Unit;
/** Tip to show current battery is overheated */
public class BatteryDefenderTip extends BatteryTip {
private static final String TAG = "BatteryDefenderTip";
private boolean mIsPluggedIn;
public BatteryDefenderTip(@StateType int state, boolean isPluggedIn) {
super(TipType.BATTERY_DEFENDER, state, false /* showDialog */);
mIsPluggedIn = isPluggedIn;
}
private BatteryDefenderTip(Parcel in) {
super(in);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_limited_temporarily_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_limited_temporarily_summary);
}
@Override
public int getIconId() {
return R.drawable.ic_battery_defender_tip_shield;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
metricsFeatureProvider.action(context, SettingsEnums.ACTION_BATTERY_DEFENDER_TIP, mState);
}
@Override
public void updatePreference(Preference preference) {
super.updatePreference(preference);
final Context context = preference.getContext();
CardPreference cardPreference = castToCardPreferenceSafely(preference);
if (cardPreference == null) {
Log.e(TAG, "cast Preference to CardPreference failed");
return;
}
cardPreference.setSelectable(false);
cardPreference.setIconResId(getIconId());
cardPreference.setPrimaryButtonText(context.getString(R.string.learn_more));
cardPreference.setPrimaryButtonAction(
() -> {
var helpIntent =
HelpUtils.getHelpIntent(
context,
context.getString(R.string.help_url_battery_defender),
/* backupContext= */ "");
ActivityCompat.startActivityForResult(
(Activity) preference.getContext(),
helpIntent,
/* requestCode= */ 0,
/* options= */ null);
return Unit.INSTANCE;
});
cardPreference.setPrimaryButtonVisibility(true);
cardPreference.setPrimaryButtonContentDescription(
context.getString(
R.string.battery_tip_limited_temporarily_sec_button_content_description));
cardPreference.setSecondaryButtonText(
context.getString(R.string.battery_tip_charge_to_full_button));
cardPreference.setSecondaryButtonAction(
() -> {
resumeCharging(context);
preference.setVisible(false);
return Unit.INSTANCE;
});
cardPreference.setSecondaryButtonVisibility(mIsPluggedIn);
cardPreference.buildContent();
}
private void resumeCharging(Context context) {
final Intent intent =
FeatureFactory.getFeatureFactory()
.getPowerUsageFeatureProvider()
.getResumeChargeIntent(false);
if (intent != null) {
context.sendBroadcast(intent);
}
Log.i(TAG, "send resume charging broadcast intent=" + intent);
}
public static final Creator CREATOR =
new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new BatteryDefenderTip(in);
}
public BatteryTip[] newArray(int size) {
return new BatteryDefenderTip[size];
}
};
}

View File

@@ -0,0 +1,215 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseIntArray;
import androidx.annotation.DrawableRes;
import androidx.annotation.IntDef;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import com.android.settings.widget.CardPreference;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Base model for a battery tip(e.g. suggest user to turn on battery saver)
*
* <p>Each {@link BatteryTip} contains basic data(e.g. title, summary, icon) as well as the
* pre-defined action(e.g. turn on battery saver)
*/
public abstract class BatteryTip implements Comparable<BatteryTip>, Parcelable {
@Retention(RetentionPolicy.SOURCE)
@IntDef({StateType.NEW, StateType.HANDLED, StateType.INVISIBLE})
public @interface StateType {
int NEW = 0;
int HANDLED = 1;
int INVISIBLE = 2;
}
@Retention(RetentionPolicy.SOURCE)
@IntDef({
TipType.SUMMARY,
TipType.BATTERY_SAVER,
TipType.HIGH_DEVICE_USAGE,
TipType.SMART_BATTERY_MANAGER,
TipType.APP_RESTRICTION,
TipType.REDUCED_BATTERY,
TipType.LOW_BATTERY,
TipType.REMOVE_APP_RESTRICTION,
TipType.BATTERY_DEFENDER,
TipType.DOCK_DEFENDER,
TipType.INCOMPATIBLE_CHARGER,
TipType.BATTERY_WARNING,
TipType.WIRELESS_CHARGING_WARNING
})
public @interface TipType {
int SMART_BATTERY_MANAGER = 0;
int APP_RESTRICTION = 1;
int HIGH_DEVICE_USAGE = 2;
int BATTERY_SAVER = 3;
int REDUCED_BATTERY = 4;
int LOW_BATTERY = 5;
int SUMMARY = 6;
int REMOVE_APP_RESTRICTION = 7;
int BATTERY_DEFENDER = 8;
int DOCK_DEFENDER = 9;
int INCOMPATIBLE_CHARGER = 10;
int BATTERY_WARNING = 11;
int WIRELESS_CHARGING_WARNING = 12;
}
@VisibleForTesting static final SparseIntArray TIP_ORDER;
static {
TIP_ORDER = new SparseIntArray();
TIP_ORDER.append(TipType.BATTERY_SAVER, 0);
TIP_ORDER.append(TipType.LOW_BATTERY, 1);
TIP_ORDER.append(TipType.BATTERY_DEFENDER, 2);
TIP_ORDER.append(TipType.DOCK_DEFENDER, 3);
TIP_ORDER.append(TipType.INCOMPATIBLE_CHARGER, 4);
TIP_ORDER.append(TipType.APP_RESTRICTION, 5);
TIP_ORDER.append(TipType.HIGH_DEVICE_USAGE, 6);
TIP_ORDER.append(TipType.SUMMARY, 7);
TIP_ORDER.append(TipType.SMART_BATTERY_MANAGER, 8);
TIP_ORDER.append(TipType.REDUCED_BATTERY, 9);
TIP_ORDER.append(TipType.REMOVE_APP_RESTRICTION, 10);
TIP_ORDER.append(TipType.BATTERY_WARNING, 11);
TIP_ORDER.append(TipType.WIRELESS_CHARGING_WARNING, 12);
}
private static final String KEY_PREFIX = "key_battery_tip";
protected int mState;
protected int mType;
protected boolean mShowDialog;
/** Whether we need to update battery tip when configuration change */
protected boolean mNeedUpdate;
public BatteryTip(Parcel in) {
mType = in.readInt();
mState = in.readInt();
mShowDialog = in.readBoolean();
mNeedUpdate = in.readBoolean();
}
public BatteryTip(int type, int state, boolean showDialog) {
mType = type;
mState = state;
mShowDialog = showDialog;
mNeedUpdate = true;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeInt(mState);
dest.writeBoolean(mShowDialog);
dest.writeBoolean(mNeedUpdate);
}
public abstract CharSequence getTitle(Context context);
public abstract CharSequence getSummary(Context context);
/** Gets the drawable resource id for the icon. */
@DrawableRes
public abstract int getIconId();
/**
* Update the current {@link #mState} using the new {@code tip}.
*
* @param tip used to update
*/
public abstract void updateState(BatteryTip tip);
/**
* Check whether data is still make sense. If not, try recover.
*
* @param context used to do validate check
*/
public void validateCheck(Context context) {
// do nothing
}
/** Log the battery tip */
public abstract void log(Context context, MetricsFeatureProvider metricsFeatureProvider);
public void updatePreference(Preference preference) {
final Context context = preference.getContext();
preference.setTitle(getTitle(context));
preference.setSummary(getSummary(context));
preference.setIcon(getIconId());
final CardPreference cardPreference = castToCardPreferenceSafely(preference);
if (cardPreference != null) {
cardPreference.resetLayoutState();
}
}
public boolean shouldShowDialog() {
return mShowDialog;
}
public boolean needUpdate() {
return mNeedUpdate;
}
public String getKey() {
return KEY_PREFIX + mType;
}
public int getType() {
return mType;
}
@StateType
public int getState() {
return mState;
}
public boolean isVisible() {
return mState != StateType.INVISIBLE;
}
@Override
public int compareTo(BatteryTip o) {
return TIP_ORDER.get(mType) - TIP_ORDER.get(o.mType);
}
@Override
public String toString() {
return "type=" + mType + " state=" + mState;
}
public CardPreference castToCardPreferenceSafely(Preference preference) {
return preference instanceof CardPreference ? (CardPreference) preference : null;
}
}

View File

@@ -0,0 +1,122 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import java.util.List;
/** Tip to show general summary about battery life */
public class HighUsageTip extends BatteryTip {
private final long mLastFullChargeTimeMs;
@VisibleForTesting final List<AppInfo> mHighUsageAppList;
public HighUsageTip(long lastFullChargeTimeMs, List<AppInfo> appList) {
super(
TipType.HIGH_DEVICE_USAGE,
appList.isEmpty() ? StateType.INVISIBLE : StateType.NEW,
true /* showDialog */);
mLastFullChargeTimeMs = lastFullChargeTimeMs;
mHighUsageAppList = appList;
}
@VisibleForTesting
HighUsageTip(Parcel in) {
super(in);
mLastFullChargeTimeMs = in.readLong();
mHighUsageAppList = in.createTypedArrayList(AppInfo.CREATOR);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeLong(mLastFullChargeTimeMs);
dest.writeTypedList(mHighUsageAppList);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_high_usage_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_high_usage_summary);
}
@Override
public int getIconId() {
return R.drawable.ic_perm_device_information_theme;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
metricsFeatureProvider.action(context, SettingsEnums.ACTION_HIGH_USAGE_TIP, mState);
for (int i = 0, size = mHighUsageAppList.size(); i < size; i++) {
final AppInfo appInfo = mHighUsageAppList.get(i);
metricsFeatureProvider.action(
context, SettingsEnums.ACTION_HIGH_USAGE_TIP_LIST, appInfo.packageName);
}
}
public long getLastFullChargeTimeMs() {
return mLastFullChargeTimeMs;
}
public List<AppInfo> getHighUsageAppList() {
return mHighUsageAppList;
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder(super.toString());
stringBuilder.append(" {");
for (int i = 0, size = mHighUsageAppList.size(); i < size; i++) {
final AppInfo appInfo = mHighUsageAppList.get(i);
stringBuilder.append(" " + appInfo.toString() + " ");
}
stringBuilder.append('}');
return stringBuilder.toString();
}
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new HighUsageTip(in);
}
public BatteryTip[] newArray(int size) {
return new HighUsageTip[size];
}
};
}

View File

@@ -0,0 +1,118 @@
/*
* 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.batterytip.tips;
import android.app.Activity;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Parcel;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.preference.Preference;
import com.android.settings.R;
import com.android.settings.widget.CardPreference;
import com.android.settingslib.HelpUtils;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import kotlin.Unit;
/** Tip to show incompatible charger state */
public final class IncompatibleChargerTip extends BatteryTip {
private static final String TAG = "IncompatibleChargerTip";
public IncompatibleChargerTip(@StateType int state) {
super(TipType.INCOMPATIBLE_CHARGER, state, /* showDialog */ false);
}
private IncompatibleChargerTip(Parcel in) {
super(in);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_incompatible_charging_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_incompatible_charging_message);
}
@Override
public int getIconId() {
return R.drawable.ic_battery_incompatible_charger;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
metricsFeatureProvider.action(
context, SettingsEnums.ACTION_INCOMPATIBLE_CHARGING_TIP, mState);
}
@Override
public void updatePreference(Preference preference) {
super.updatePreference(preference);
final Context context = preference.getContext();
final CardPreference cardPreference = castToCardPreferenceSafely(preference);
if (cardPreference == null) {
Log.e(TAG, "cast Preference to CardPreference failed");
return;
}
cardPreference.setSelectable(false);
cardPreference.enableDismiss(false);
cardPreference.setIconResId(getIconId());
cardPreference.setPrimaryButtonText(context.getString(R.string.learn_more));
cardPreference.setPrimaryButtonAction(
() -> {
var helpIntent =
HelpUtils.getHelpIntent(
context,
context.getString(R.string.help_url_incompatible_charging),
/* backupContext */ "");
ActivityCompat.startActivityForResult(
(Activity) context,
helpIntent,
/* requestCode= */ 0,
/* options= */ null);
return Unit.INSTANCE;
});
cardPreference.setPrimaryButtonVisibility(true);
cardPreference.setPrimaryButtonContentDescription(
context.getString(R.string.battery_tip_incompatible_charging_content_description));
cardPreference.buildContent();
}
public static final Creator CREATOR =
new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new IncompatibleChargerTip(in);
}
public BatteryTip[] newArray(int size) {
return new IncompatibleChargerTip[size];
}
};
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.settings.R;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/** Tip to show current battery level is low */
public class LowBatteryTip extends BatteryTip {
private boolean mPowerSaveModeOn;
public LowBatteryTip(@StateType int state, boolean powerSaveModeOn) {
super(TipType.LOW_BATTERY, state, false /* showDialog */);
mPowerSaveModeOn = powerSaveModeOn;
}
public LowBatteryTip(Parcel in) {
super(in);
mPowerSaveModeOn = in.readBoolean();
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_low_battery_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_low_battery_summary);
}
@Override
public int getIconId() {
return mState = R.drawable.ic_battery_saver_accent_24dp;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeBoolean(mPowerSaveModeOn);
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
metricsFeatureProvider.action(context, SettingsEnums.ACTION_LOW_BATTERY_TIP, mState);
}
@Override
public void updateState(BatteryTip tip) {
final LowBatteryTip lowBatteryTip = (LowBatteryTip) tip;
mState = lowBatteryTip.mPowerSaveModeOn ? StateType.INVISIBLE : lowBatteryTip.getState();
}
boolean isPowerSaveModeOn() {
return mPowerSaveModeOn;
}
public static final Parcelable.Creator CREATOR =
new Parcelable.Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new LowBatteryTip(in);
}
public BatteryTip[] newArray(int size) {
return new LowBatteryTip[size];
}
};
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.icu.text.ListFormatter;
import android.os.Parcel;
import android.util.ArrayMap;
import androidx.annotation.VisibleForTesting;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
import com.android.settingslib.utils.StringUtil;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/** Tip to suggest user to restrict some bad apps */
public class RestrictAppTip extends BatteryTip {
private List<AppInfo> mRestrictAppList;
public RestrictAppTip(@StateType int state, List<AppInfo> restrictApps) {
super(TipType.APP_RESTRICTION, state, state == StateType.NEW /* showDialog */);
mRestrictAppList = restrictApps;
mNeedUpdate = false;
}
public RestrictAppTip(@StateType int state, AppInfo appInfo) {
super(TipType.APP_RESTRICTION, state, state == StateType.NEW /* showDialog */);
mRestrictAppList = new ArrayList<>();
mRestrictAppList.add(appInfo);
mNeedUpdate = false;
}
@VisibleForTesting
RestrictAppTip(Parcel in) {
super(in);
mRestrictAppList = in.createTypedArrayList(AppInfo.CREATOR);
}
@Override
public CharSequence getTitle(Context context) {
final int num = mRestrictAppList.size();
final CharSequence appLabel =
num > 0
? Utils.getApplicationLabel(context, mRestrictAppList.get(0).packageName)
: "";
Map<String, Object> arguments = new ArrayMap<>();
arguments.put("count", num);
arguments.put("label", appLabel);
return mState == StateType.HANDLED
? StringUtil.getIcuPluralsString(
context, arguments, R.string.battery_tip_restrict_handled_title)
: StringUtil.getIcuPluralsString(
context, arguments, R.string.battery_tip_restrict_title);
}
@Override
public CharSequence getSummary(Context context) {
final int num = mRestrictAppList.size();
final CharSequence appLabel =
num > 0
? Utils.getApplicationLabel(context, mRestrictAppList.get(0).packageName)
: "";
final int resId =
mState == StateType.HANDLED
? R.string.battery_tip_restrict_handled_summary
: R.string.battery_tip_restrict_summary;
Map<String, Object> arguments = new ArrayMap<>();
arguments.put("count", num);
arguments.put("label", appLabel);
return StringUtil.getIcuPluralsString(context, arguments, resId);
}
@Override
public int getIconId() {
return mState == StateType.HANDLED
? R.drawable.ic_perm_device_information_theme
: R.drawable.ic_battery_alert_theme;
}
@Override
public void updateState(BatteryTip tip) {
if (tip.mState == StateType.NEW) {
// Display it if new anomaly comes
mState = StateType.NEW;
mRestrictAppList = ((RestrictAppTip) tip).mRestrictAppList;
mShowDialog = true;
} else if (mState == StateType.NEW && tip.mState == StateType.INVISIBLE) {
// If anomaly becomes invisible, show it as handled
mState = StateType.HANDLED;
mShowDialog = false;
} else {
mState = tip.getState();
mShowDialog = tip.shouldShowDialog();
mRestrictAppList = ((RestrictAppTip) tip).mRestrictAppList;
}
}
@Override
public void validateCheck(Context context) {
super.validateCheck(context);
// Set it invisible if there is no valid app
mRestrictAppList.removeIf(AppLabelPredicate.getInstance(context));
if (mRestrictAppList.isEmpty()) {
mState = StateType.INVISIBLE;
}
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
metricsFeatureProvider.action(context, SettingsEnums.ACTION_APP_RESTRICTION_TIP, mState);
if (mState == StateType.NEW) {
for (int i = 0, size = mRestrictAppList.size(); i < size; i++) {
final AppInfo appInfo = mRestrictAppList.get(i);
for (Integer anomalyType : appInfo.anomalyTypes) {
metricsFeatureProvider.action(
SettingsEnums.PAGE_UNKNOWN,
SettingsEnums.ACTION_APP_RESTRICTION_TIP_LIST,
SettingsEnums.PAGE_UNKNOWN,
appInfo.packageName,
anomalyType);
}
}
}
}
public List<AppInfo> getRestrictAppList() {
return mRestrictAppList;
}
/** Construct the app list string(e.g. app1, app2, and app3) */
public CharSequence getRestrictAppsString(Context context) {
final List<CharSequence> appLabels = new ArrayList<>();
for (int i = 0, size = mRestrictAppList.size(); i < size; i++) {
appLabels.add(Utils.getApplicationLabel(context, mRestrictAppList.get(i).packageName));
}
return ListFormatter.getInstance().format(appLabels);
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder(super.toString());
stringBuilder.append(" {");
for (int i = 0, size = mRestrictAppList.size(); i < size; i++) {
final AppInfo appInfo = mRestrictAppList.get(i);
stringBuilder.append(" " + appInfo.toString() + " ");
}
stringBuilder.append('}');
return stringBuilder.toString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeTypedList(mRestrictAppList);
}
public static final Creator CREATOR =
new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new RestrictAppTip(in);
}
public BatteryTip[] newArray(int size) {
return new RestrictAppTip[size];
}
};
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.app.settings.SettingsEnums;
import android.content.Context;
import android.os.Parcel;
import com.android.settings.R;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/** Tip to suggest turn on smart battery if it is not on */
public class SmartBatteryTip extends BatteryTip {
public SmartBatteryTip(@StateType int state) {
super(TipType.SMART_BATTERY_MANAGER, state, false /* showDialog */);
}
private SmartBatteryTip(Parcel in) {
super(in);
}
@Override
public CharSequence getTitle(Context context) {
return context.getString(R.string.battery_tip_smart_battery_title);
}
@Override
public CharSequence getSummary(Context context) {
return context.getString(R.string.battery_tip_smart_battery_summary);
}
@Override
public int getIconId() {
return R.drawable.ic_perm_device_information_theme;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
metricsFeatureProvider.action(context, SettingsEnums.ACTION_SMART_BATTERY_TIP, mState);
}
public static final Creator CREATOR =
new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new SmartBatteryTip(in);
}
public BatteryTip[] newArray(int size) {
return new SmartBatteryTip[size];
}
};
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.fuelgauge.batterytip.tips;
import android.content.Context;
import android.os.Parcel;
import androidx.annotation.VisibleForTesting;
import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
import com.android.settings.fuelgauge.batterytip.AppInfo;
import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
/**
* Tip to suggest user to remove app restriction. This is the empty tip and it is only used in
* {@link AdvancedPowerUsageDetail} to create dialog.
*/
public class UnrestrictAppTip extends BatteryTip {
private AppInfo mAppInfo;
public UnrestrictAppTip(@StateType int state, AppInfo appInfo) {
super(TipType.REMOVE_APP_RESTRICTION, state, true /* showDialog */);
mAppInfo = appInfo;
}
@VisibleForTesting
UnrestrictAppTip(Parcel in) {
super(in);
mAppInfo = in.readParcelable(getClass().getClassLoader());
}
@Override
public CharSequence getTitle(Context context) {
// Don't need title since this is an empty tip
return null;
}
@Override
public CharSequence getSummary(Context context) {
// Don't need summary since this is an empty tip
return null;
}
@Override
public int getIconId() {
return 0;
}
public String getPackageName() {
return mAppInfo.packageName;
}
@Override
public void updateState(BatteryTip tip) {
mState = tip.mState;
}
@Override
public void log(Context context, MetricsFeatureProvider metricsFeatureProvider) {
// Do nothing
}
public AppInfo getUnrestrictAppInfo() {
return mAppInfo;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeParcelable(mAppInfo, flags);
}
public static final Creator CREATOR =
new Creator() {
public BatteryTip createFromParcel(Parcel in) {
return new UnrestrictAppTip(in);
}
public BatteryTip[] newArray(int size) {
return new UnrestrictAppTip[size];
}
};
}