import android.os.Parcelable;
import android.support.annotation.VisibleForTesting;
import android.text.TextUtils;
+import android.util.ArraySet;
import com.android.settings.fuelgauge.anomaly.Anomaly;
+import java.util.Objects;
+
/**
* Model class stores app info(e.g. package name, type..) that used in battery tip
*/
* Anomaly type of the app
* @see Anomaly.AnomalyType
*/
- public final int anomalyType;
+ public final ArraySet<Integer> anomalyTypes;
public final long screenOnTimeMs;
public final int uid;
private AppInfo(AppInfo.Builder builder) {
packageName = builder.mPackageName;
- anomalyType = builder.mAnomalyType;
+ anomalyTypes = builder.mAnomalyTypes;
screenOnTimeMs = builder.mScreenOnTimeMs;
uid = builder.mUid;
}
@VisibleForTesting
AppInfo(Parcel in) {
packageName = in.readString();
- anomalyType = in.readInt();
+ anomalyTypes = (ArraySet<Integer>) in.readArraySet(null /* loader */);
screenOnTimeMs = in.readLong();
uid = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(packageName);
- dest.writeInt(anomalyType);
+ dest.writeArraySet(anomalyTypes);
dest.writeLong(screenOnTimeMs);
dest.writeInt(uid);
}
@Override
public String toString() {
- return "packageName=" + packageName + ",anomalyType=" + anomalyType + ",screenTime="
+ return "packageName=" + packageName + ",anomalyTypes=" + anomalyTypes + ",screenTime="
+ screenOnTimeMs;
}
}
AppInfo other = (AppInfo) obj;
- return anomalyType == other.anomalyType
+ return Objects.equals(anomalyTypes, other.anomalyTypes)
&& uid == other.uid
&& screenOnTimeMs == other.screenOnTimeMs
&& TextUtils.equals(packageName, other.packageName);
};
public static final class Builder {
- private int mAnomalyType;
+ private ArraySet<Integer> mAnomalyTypes = new ArraySet<>();
private String mPackageName;
private long mScreenOnTimeMs;
private int mUid;
- public Builder setAnomalyType(int type) {
- mAnomalyType = type;
+ public Builder addAnomalyType(int type) {
+ mAnomalyTypes.add(type);
return this;
}
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
+import android.util.ArrayMap;
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}.
try (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,
- TIME_STAMP_MS + " > ? AND " + ANOMALY_STATE + " = ? ",
- new String[]{String.valueOf(timestampMsAfter), String.valueOf(state)}, null,
- null, orderBy)) {
+ try (Cursor cursor = db.query(TABLE_ANOMALY, projection, selection, selectionArgs,
+ null /* groupBy */, null /* having */, orderBy)) {
while (cursor.moveToNext()) {
- AppInfo appInfo = new AppInfo.Builder()
- .setPackageName(cursor.getString(cursor.getColumnIndex(PACKAGE_NAME)))
- .setAnomalyType(cursor.getInt(cursor.getColumnIndex(ANOMALY_TYPE)))
- .setUid(cursor.getInt(cursor.getColumnIndex(UID)))
- .build();
- appInfos.add(appInfo);
+ 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;
package com.android.settings.fuelgauge.batterytip;
-import android.app.Fragment;
-
import com.android.settings.SettingsActivity;
import com.android.settings.core.InstrumentedPreferenceFragment;
import com.android.settings.fuelgauge.batterytip.actions.BatterySaverAction;
@RunWith(RobolectricTestRunner.class)
public class BatteryDatabaseManagerTest {
-
private static String PACKAGE_NAME_NEW = "com.android.app1";
private static int UID_NEW = 345;
private static int TYPE_NEW = 1;
private Context mContext;
private BatteryDatabaseManager mBatteryDatabaseManager;
+ private AppInfo mNewAppInfo;
+ private AppInfo mOldAppInfo;
+ private AppInfo mCombinedAppInfo;
@Before
public void setUp() {
mContext = RuntimeEnvironment.application;
mBatteryDatabaseManager = spy(BatteryDatabaseManager.getInstance(mContext));
+
+ mNewAppInfo = new AppInfo.Builder()
+ .setUid(UID_NEW)
+ .setPackageName(PACKAGE_NAME_NEW)
+ .addAnomalyType(TYPE_NEW)
+ .build();
+ mOldAppInfo = new AppInfo.Builder()
+ .setUid(UID_OLD)
+ .setPackageName(PACKAGE_NAME_OLD)
+ .addAnomalyType(TYPE_OLD)
+ .build();
+ mCombinedAppInfo = new AppInfo.Builder()
+ .setUid(UID_NEW)
+ .setPackageName(PACKAGE_NAME_NEW)
+ .addAnomalyType(TYPE_NEW)
+ .addAnomalyType(TYPE_OLD)
+ .build();
}
@After
// In database, it contains two record
List<AppInfo> totalAppInfos = mBatteryDatabaseManager.queryAllAnomalies(0 /* timeMsAfter */,
AnomalyDatabaseHelper.State.NEW);
- assertThat(totalAppInfos).hasSize(2);
- assertAppInfo(totalAppInfos.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW);
- assertAppInfo(totalAppInfos.get(1), UID_OLD, PACKAGE_NAME_OLD, TYPE_OLD);
+ assertThat(totalAppInfos).containsExactly(mNewAppInfo, mOldAppInfo);
// Only one record shows up if we query by timestamp
List<AppInfo> appInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE,
AnomalyDatabaseHelper.State.NEW);
- assertThat(appInfos).hasSize(1);
- assertAppInfo(appInfos.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW);
+ assertThat(appInfos).containsExactly(mNewAppInfo);
mBatteryDatabaseManager.deleteAllAnomaliesBeforeTimeStamp(ONE_DAY_BEFORE);
// The obsolete record is removed from database
List<AppInfo> appInfos1 = mBatteryDatabaseManager.queryAllAnomalies(0 /* timeMsAfter */,
AnomalyDatabaseHelper.State.NEW);
- assertThat(appInfos1).hasSize(1);
- assertAppInfo(appInfos1.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW);
+ assertThat(appInfos1).containsExactly(mNewAppInfo);
}
@Test
// The state of PACKAGE_NAME_NEW is still new
List<AppInfo> newAppInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE,
AnomalyDatabaseHelper.State.NEW);
- assertThat(newAppInfos).hasSize(1);
- assertAppInfo(newAppInfos.get(0), UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW);
+ assertThat(newAppInfos).containsExactly(mNewAppInfo);
// The state of PACKAGE_NAME_OLD is changed to handled
List<AppInfo> handledAppInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE,
AnomalyDatabaseHelper.State.HANDLED);
- assertThat(handledAppInfos).hasSize(1);
- assertAppInfo(handledAppInfos.get(0), UID_OLD, PACKAGE_NAME_OLD, TYPE_OLD);
+ assertThat(handledAppInfos).containsExactly(mOldAppInfo);
}
- private void assertAppInfo(final AppInfo appInfo, int uid, String packageName, int type) {
- assertThat(appInfo.packageName).isEqualTo(packageName);
- assertThat(appInfo.anomalyType).isEqualTo(type);
- assertThat(appInfo.uid).isEqualTo(uid);
+ @Test
+ public void testQueryAnomalies_removeDuplicateByUid() {
+ mBatteryDatabaseManager.insertAnomaly(UID_NEW, PACKAGE_NAME_NEW, TYPE_NEW,
+ AnomalyDatabaseHelper.State.NEW, NOW);
+ mBatteryDatabaseManager.insertAnomaly(UID_NEW, PACKAGE_NAME_NEW, TYPE_OLD,
+ AnomalyDatabaseHelper.State.NEW, NOW);
+
+ // Only contain one AppInfo with multiple types
+ List<AppInfo> newAppInfos = mBatteryDatabaseManager.queryAllAnomalies(ONE_DAY_BEFORE,
+ AnomalyDatabaseHelper.State.NEW);
+ assertThat(newAppInfos).containsExactly(mCombinedAppInfo);
}
}
public class AppInfoTest {
private static final String PACKAGE_NAME = "com.android.app";
- private static final int ANOMALY_TYPE = Anomaly.AnomalyType.WAKE_LOCK;
+ private static final int TYPE_WAKELOCK = Anomaly.AnomalyType.WAKE_LOCK;
+ private static final int TYPE_WAKEUP = Anomaly.AnomalyType.WAKEUP_ALARM;
private static final long SCREEN_TIME_MS = DateUtils.HOUR_IN_MILLIS;
private static final int UID = 3452;
public void setUp() {
mAppInfo = new AppInfo.Builder()
.setPackageName(PACKAGE_NAME)
- .setAnomalyType(ANOMALY_TYPE)
+ .addAnomalyType(TYPE_WAKELOCK)
+ .addAnomalyType(TYPE_WAKEUP)
.setScreenOnTimeMs(SCREEN_TIME_MS)
.setUid(UID)
.build();
final AppInfo appInfo = new AppInfo(parcel);
assertThat(appInfo.packageName).isEqualTo(PACKAGE_NAME);
- assertThat(appInfo.anomalyType).isEqualTo(ANOMALY_TYPE);
+ assertThat(appInfo.anomalyTypes).containsExactly(TYPE_WAKELOCK, TYPE_WAKEUP);
assertThat(appInfo.screenOnTimeMs).isEqualTo(SCREEN_TIME_MS);
assertThat(appInfo.uid).isEqualTo(UID);
}
public void testCompareTo_hasCorrectOrder() {
final AppInfo appInfo = new AppInfo.Builder()
.setPackageName(PACKAGE_NAME)
- .setAnomalyType(ANOMALY_TYPE)
+ .addAnomalyType(TYPE_WAKELOCK)
.setScreenOnTimeMs(SCREEN_TIME_MS + 100)
.build();
@Test
public void testBuilder() {
assertThat(mAppInfo.packageName).isEqualTo(PACKAGE_NAME);
- assertThat(mAppInfo.anomalyType).isEqualTo(ANOMALY_TYPE);
+ assertThat(mAppInfo.anomalyTypes).containsExactly(TYPE_WAKELOCK, TYPE_WAKEUP);
assertThat(mAppInfo.screenOnTimeMs).isEqualTo(SCREEN_TIME_MS);
assertThat(mAppInfo.uid).isEqualTo(UID);
}
@Test
public void toString_containsAppData() {
assertThat(mBatteryTip.toString()).isEqualTo(
- "type=2 state=0 { packageName=com.android.app,anomalyType=0,screenTime=1800000 }");
+ "type=2 state=0 { packageName=com.android.app,anomalyTypes={},screenTime=1800000 "
+ + "}");
}
}
@RunWith(SettingsRobolectricTestRunner.class)
public class RestrictAppTipTest {
-
private static final String PACKAGE_NAME = "com.android.app";
private static final String DISPLAY_NAME = "app";
+ private static final int ANOMALY_WAKEUP = 0;
+ private static final int ANOMALY_WAKELOCK = 1;
private Context mContext;
private RestrictAppTip mNewBatteryTip;
doReturn(DISPLAY_NAME).when(mApplicationInfo).loadLabel(mPackageManager);
mUsageAppList = new ArrayList<>();
- mUsageAppList.add(new AppInfo.Builder().setPackageName(PACKAGE_NAME).build());
+ mUsageAppList.add(new AppInfo.Builder()
+ .setPackageName(PACKAGE_NAME)
+ .addAnomalyType(ANOMALY_WAKEUP)
+ .addAnomalyType(ANOMALY_WAKELOCK)
+ .build());
mNewBatteryTip = new RestrictAppTip(BatteryTip.StateType.NEW, mUsageAppList);
mHandledBatteryTip = new RestrictAppTip(BatteryTip.StateType.HANDLED, mUsageAppList);
mInvisibleBatteryTip = new RestrictAppTip(BatteryTip.StateType.INVISIBLE, mUsageAppList);
@Test
public void toString_containsAppData() {
assertThat(mNewBatteryTip.toString()).isEqualTo(
- "type=1 state=0 { packageName=com.android.app,anomalyType=0,screenTime=0 }");
+ "type=1 state=0 { packageName=com.android.app,anomalyTypes={0, 1},screenTime=0 }");
}
}