fix: 引入Settings的Module
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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) {}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 wasn’t 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user