Merge "Add database to store anomaly data"
diff --git a/src/com/android/settings/fuelgauge/batterytip/AnomalyDatabaseHelper.java b/src/com/android/settings/fuelgauge/batterytip/AnomalyDatabaseHelper.java
new file mode 100644
index 0000000..a13df25
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/AnomalyDatabaseHelper.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.settings.fuelgauge.anomaly.Anomaly;
+
+/**
+ * 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 = 1;
+
+    public interface Tables {
+        String TABLE_ANOMALY = "anomaly";
+    }
+
+    public interface AnomalyColumns {
+        /**
+         * The package name of the anomaly app
+         */
+        String PACKAGE_NAME = "package_name";
+        /**
+         * The type of the anomaly app
+         * @see Anomaly.AnomalyType
+         */
+        String ANOMALY_TYPE = "anomaly_type";
+        /**
+         * 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.PACKAGE_NAME +
+                    " TEXT, " +
+                    AnomalyColumns.ANOMALY_TYPE +
+                    " INTEGER, " +
+                    AnomalyColumns.TIME_STAMP_MS +
+                    " INTEGER)";
+
+    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);
+    }
+
+    private void bootstrapDB(SQLiteDatabase db) {
+        db.execSQL(CREATE_ANOMALY_TABLE);
+        Log.i(TAG, "Bootstrapped database");
+    }
+
+    @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 dropTables(SQLiteDatabase db) {
+        db.execSQL("DROP TABLE IF EXISTS " + Tables.TABLE_ANOMALY);
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/AppInfo.java b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java
new file mode 100644
index 0000000..1daff36
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/AppInfo.java
@@ -0,0 +1,101 @@
+/*
+ * 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.support.annotation.VisibleForTesting;
+
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+
+/**
+ * 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 Anomaly.AnomalyType
+     */
+    public final int anomalyType;
+    public final long screenOnTimeMs;
+
+    private AppInfo(AppInfo.Builder builder) {
+        packageName = builder.mPackageName;
+        anomalyType = builder.mAnomalyType;
+        screenOnTimeMs = builder.mScreenOnTimeMs;
+    }
+
+    @VisibleForTesting
+    AppInfo(Parcel in) {
+        packageName = in.readString();
+        anomalyType = in.readInt();
+        screenOnTimeMs = in.readLong();
+    }
+
+    @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.writeInt(anomalyType);
+        dest.writeLong(screenOnTimeMs);
+    }
+
+    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 int mAnomalyType;
+        private String mPackageName;
+        private long mScreenOnTimeMs;
+
+        public Builder setAnomalyType(int type) {
+            mAnomalyType = type;
+            return this;
+        }
+
+        public Builder setPackageName(String packageName) {
+            mPackageName = packageName;
+            return this;
+        }
+
+        public Builder setScreenOnTimeMs(long screenOnTimeMs) {
+            mScreenOnTimeMs = screenOnTimeMs;
+            return this;
+        }
+
+        public AppInfo build() {
+            return new AppInfo(this);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java
new file mode 100644
index 0000000..f87501f
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batterytip/BatteryDatabaseManager.java
@@ -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 static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns
+        .PACKAGE_NAME;
+import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns
+        .ANOMALY_TYPE;
+import static com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper.AnomalyColumns
+        .TIME_STAMP_MS;
+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 java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Database manager for battery data. Now it only contains anomaly data stored in {@link AppInfo}.
+ */
+public class BatteryDatabaseManager {
+    private final AnomalyDatabaseHelper mDatabaseHelper;
+
+    public BatteryDatabaseManager(Context context) {
+        mDatabaseHelper = AnomalyDatabaseHelper.getInstance(context);
+    }
+
+    /**
+     * Insert an anomaly log to database.
+     *
+     * @param packageName the package name of the app
+     * @param type        the type of the anomaly
+     * @param timestampMs the time when it is happened
+     */
+    public void insertAnomaly(String packageName, int type, long timestampMs) {
+        try (SQLiteDatabase db = mDatabaseHelper.getWritableDatabase()) {
+            ContentValues values = new ContentValues();
+            values.put(PACKAGE_NAME, packageName);
+            values.put(ANOMALY_TYPE, type);
+            values.put(TIME_STAMP_MS, timestampMs);
+
+            db.insert(TABLE_ANOMALY, null, values);
+        }
+    }
+
+    /**
+     * Query all the anomalies that happened after {@code timestampMs}.
+     */
+    public List<AppInfo> queryAllAnomaliesAfter(long timestampMs) {
+        final List<AppInfo> appInfos = new ArrayList<>();
+        try (SQLiteDatabase db = mDatabaseHelper.getReadableDatabase()) {
+            final String[] projection = {PACKAGE_NAME, ANOMALY_TYPE};
+            final String orderBy = AnomalyDatabaseHelper.AnomalyColumns.TIME_STAMP_MS + " DESC";
+
+            try (Cursor cursor = db.query(TABLE_ANOMALY, projection, TIME_STAMP_MS + " > ?",
+                    new String[]{String.valueOf(timestampMs)}, null, null, orderBy)) {
+                while (cursor.moveToNext()) {
+                    AppInfo appInfo = new AppInfo.Builder()
+                            .setPackageName(cursor.getString(cursor.getColumnIndex(PACKAGE_NAME)))
+                            .setAnomalyType(cursor.getInt(cursor.getColumnIndex(ANOMALY_TYPE)))
+                            .build();
+                    appInfos.add(appInfo);
+                }
+            }
+        }
+
+        return appInfos;
+    }
+
+    public void deleteAllAnomaliesBeforeTimeStamp(long timestampMs) {
+        try (SQLiteDatabase db = mDatabaseHelper.getWritableDatabase()) {
+            db.delete(TABLE_ANOMALY, TIME_STAMP_MS + " < ?",
+                    new String[]{String.valueOf(timestampMs)});
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java
index 8b74394..60aa6c8 100644
--- a/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java
+++ b/src/com/android/settings/fuelgauge/batterytip/HighUsageAdapter.java
@@ -39,7 +39,7 @@
     private final Context mContext;
     private final IconDrawableFactory mIconDrawableFactory;
     private final PackageManager mPackageManager;
-    private final List<HighUsageApp> mHighUsageAppList;
+    private final List<AppInfo> mHighUsageAppList;
 
     public static class ViewHolder extends RecyclerView.ViewHolder {
         public View view;
@@ -56,7 +56,7 @@
         }
     }
 
-    public HighUsageAdapter(Context context, List<HighUsageApp> highUsageAppList) {
+    public HighUsageAdapter(Context context, List<AppInfo> highUsageAppList) {
         mContext = context;
         mHighUsageAppList = highUsageAppList;
         mIconDrawableFactory = IconDrawableFactory.newInstance(context);
@@ -72,7 +72,7 @@
 
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
-        final HighUsageApp app = mHighUsageAppList.get(position);
+        final AppInfo app = mHighUsageAppList.get(position);
         holder.appIcon.setImageDrawable(
                 Utils.getBadgedIcon(mIconDrawableFactory, mPackageManager, app.packageName,
                         UserHandle.myUserId()));
diff --git a/src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java b/src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java
deleted file mode 100644
index f75ecf0..0000000
--- a/src/com/android/settings/fuelgauge/batterytip/HighUsageApp.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * 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;
-
-/**
- * Class representing app with high screen usage
- */
-public class HighUsageApp implements Comparable<HighUsageApp>, Parcelable {
-    public final String packageName;
-    public final long screenOnTimeMs;
-
-    public HighUsageApp(String packageName, long screenOnTimeMs) {
-        this.packageName = packageName;
-        this.screenOnTimeMs = screenOnTimeMs;
-    }
-
-    private HighUsageApp(Parcel in) {
-        packageName = in.readString();
-        screenOnTimeMs = in.readLong();
-    }
-
-    @Override
-    public int compareTo(HighUsageApp 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.writeLong(screenOnTimeMs);
-    }
-
-    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
-        public HighUsageApp createFromParcel(Parcel in) {
-            return new HighUsageApp(in);
-        }
-
-        public HighUsageApp[] newArray(int size) {
-            return new HighUsageApp[size];
-        }
-    };
-}
\ No newline at end of file
diff --git a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java
index 237f430..3c69667 100644
--- a/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java
+++ b/src/com/android/settings/fuelgauge/batterytip/detectors/HighUsageDetector.java
@@ -23,13 +23,11 @@
 
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
-import com.android.settings.Utils;
 import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.fuelgauge.batterytip.BatteryTipPolicy;
-import com.android.settings.fuelgauge.batterytip.HighUsageApp;
+import com.android.settings.fuelgauge.batterytip.AppInfo;
 import com.android.settings.fuelgauge.batterytip.tips.BatteryTip;
 import com.android.settings.fuelgauge.batterytip.tips.HighUsageTip;
-import com.android.settings.fuelgauge.batterytip.tips.SummaryTip;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -42,7 +40,7 @@
 public class HighUsageDetector implements BatteryTipDetector {
     private BatteryTipPolicy mPolicy;
     private BatteryStatsHelper mBatteryStatsHelper;
-    private List<HighUsageApp> mHighUsageAppList;
+    private List<AppInfo> mHighUsageAppList;
     private Context mContext;
     @VisibleForTesting
     BatteryUtils mBatteryUtils;
@@ -68,9 +66,10 @@
                     final long foregroundTimeMs = mBatteryUtils.getProcessTimeMs(
                             BatteryUtils.StatusType.FOREGROUND, batterySipper.uidObj,
                             BatteryStats.STATS_SINCE_CHARGED);
-                    mHighUsageAppList.add(new HighUsageApp(
-                            mBatteryUtils.getPackageName(batterySipper.getUid()),
-                            foregroundTimeMs));
+                    mHighUsageAppList.add(new AppInfo.Builder()
+                            .setPackageName(mBatteryUtils.getPackageName(batterySipper.getUid()))
+                            .setScreenOnTimeMs(foregroundTimeMs)
+                            .build());
                 }
             }
 
diff --git a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java
index 0316832..2aabf98 100644
--- a/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java
+++ b/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTip.java
@@ -23,7 +23,7 @@
 
 import com.android.settings.R;
 import com.android.settings.Utils;
-import com.android.settings.fuelgauge.batterytip.HighUsageApp;
+import com.android.settings.fuelgauge.batterytip.AppInfo;
 
 import java.util.List;
 
@@ -34,9 +34,9 @@
 
     private final long mScreenTimeMs;
     @VisibleForTesting
-    final List<HighUsageApp> mHighUsageAppList;
+    final List<AppInfo> mHighUsageAppList;
 
-    public HighUsageTip(long screenTimeMs, List<HighUsageApp> appList) {
+    public HighUsageTip(long screenTimeMs, List<AppInfo> appList) {
         super(TipType.HIGH_DEVICE_USAGE, appList.isEmpty() ? StateType.INVISIBLE : StateType.NEW,
                 true /* showDialog */);
         mScreenTimeMs = screenTimeMs;
@@ -47,7 +47,7 @@
     HighUsageTip(Parcel in) {
         super(in);
         mScreenTimeMs = in.readLong();
-        mHighUsageAppList = in.createTypedArrayList(HighUsageApp.CREATOR);
+        mHighUsageAppList = in.createTypedArrayList(AppInfo.CREATOR);
     }
 
     @Override
@@ -82,7 +82,7 @@
         return mScreenTimeMs;
     }
 
-    public List<HighUsageApp> getHighUsageAppList() {
+    public List<AppInfo> getHighUsageAppList() {
         return mHighUsageAppList;
     }
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java
new file mode 100644
index 0000000..ac8800e
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryDatabaseManagerTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.spy;
+
+import android.content.Context;
+import android.text.format.DateUtils;
+
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.batterytip.AppInfo;
+import com.android.settings.fuelgauge.batterytip.BatteryDatabaseManager;
+import com.android.settings.testutils.DatabaseTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class BatteryDatabaseManagerTest {
+    private static String PACKAGE_NAME_NEW = "com.android.app1";
+    private static int TYPE_NEW = 1;
+    private static String PACKAGE_NAME_OLD = "com.android.app2";
+    private static int TYPE_OLD = 2;
+    private static long NOW = System.currentTimeMillis();
+    private static long ONE_DAY_BEFORE = NOW - DateUtils.DAY_IN_MILLIS;
+    private static long TWO_DAYS_BEFORE = NOW - 2 * DateUtils.DAY_IN_MILLIS;
+    private Context mContext;
+    private BatteryDatabaseManager mBatteryDatabaseManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = RuntimeEnvironment.application;
+        mBatteryDatabaseManager = spy(new BatteryDatabaseManager(mContext));
+    }
+
+    @After
+    public void cleanUp() {
+        DatabaseTestUtils.clearDb(mContext);
+    }
+
+    @Test
+    public void testAllFunctions() {
+        mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_NEW, TYPE_NEW, NOW);
+        mBatteryDatabaseManager.insertAnomaly(PACKAGE_NAME_OLD, TYPE_OLD, TWO_DAYS_BEFORE);
+
+        // In database, it contains two record
+        List<AppInfo> totalAppInfos = mBatteryDatabaseManager.queryAllAnomaliesAfter(0);
+        assertThat(totalAppInfos).hasSize(2);
+        verifyAppInfo(totalAppInfos.get(0), PACKAGE_NAME_NEW, TYPE_NEW);
+        verifyAppInfo(totalAppInfos.get(1), PACKAGE_NAME_OLD, TYPE_OLD);
+
+        // Only one record shows up if we query by timestamp
+        List<AppInfo> appInfos = mBatteryDatabaseManager.queryAllAnomaliesAfter(ONE_DAY_BEFORE);
+        assertThat(appInfos).hasSize(1);
+        verifyAppInfo(appInfos.get(0), PACKAGE_NAME_NEW, TYPE_NEW);
+
+        mBatteryDatabaseManager.deleteAllAnomaliesBeforeTimeStamp(ONE_DAY_BEFORE);
+
+        // The obsolete record is removed from database
+        List<AppInfo> appInfos1 = mBatteryDatabaseManager.queryAllAnomaliesAfter(0);
+        assertThat(appInfos1).hasSize(1);
+        verifyAppInfo(appInfos1.get(0), PACKAGE_NAME_NEW, TYPE_NEW);
+
+    }
+
+    private void verifyAppInfo(final AppInfo appInfo, String packageName, int type) {
+        assertThat(appInfo.packageName).isEqualTo(packageName);
+        assertThat(appInfo.anomalyType).isEqualTo(type);
+    }
+
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java
new file mode 100644
index 0000000..16b45df
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/AppInfoTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.text.format.DateUtils;
+
+import com.android.settings.TestConfig;
+import com.android.settings.fuelgauge.anomaly.Anomaly;
+import com.android.settings.testutils.SettingsRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.annotation.Config;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(SettingsRobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+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 long SCREEN_TIME_MS = DateUtils.HOUR_IN_MILLIS;
+
+    private AppInfo mAppInfo;
+
+    @Before
+    public void setUp() {
+        mAppInfo = new AppInfo.Builder()
+                .setPackageName(PACKAGE_NAME)
+                .setAnomalyType(ANOMALY_TYPE)
+                .setScreenOnTimeMs(SCREEN_TIME_MS)
+                .build();
+    }
+
+    @Test
+    public void testParcel() {
+        Parcel parcel = Parcel.obtain();
+        mAppInfo.writeToParcel(parcel, mAppInfo.describeContents());
+        parcel.setDataPosition(0);
+
+        final AppInfo appInfo = new AppInfo(parcel);
+
+        assertThat(appInfo.packageName).isEqualTo(PACKAGE_NAME);
+        assertThat(appInfo.anomalyType).isEqualTo(ANOMALY_TYPE);
+        assertThat(appInfo.screenOnTimeMs).isEqualTo(SCREEN_TIME_MS);
+    }
+
+    @Test
+    public void testCompareTo_hasCorrectOrder() {
+        final AppInfo appInfo = new AppInfo.Builder()
+                .setPackageName(PACKAGE_NAME)
+                .setAnomalyType(ANOMALY_TYPE)
+                .setScreenOnTimeMs(SCREEN_TIME_MS + 100)
+                .build();
+
+        List<AppInfo> appInfos = new ArrayList<>();
+        appInfos.add(appInfo);
+        appInfos.add(mAppInfo);
+
+        Collections.sort(appInfos);
+        assertThat(appInfos.get(0).screenOnTimeMs).isLessThan(appInfos.get(1).screenOnTimeMs);
+    }
+
+    @Test
+    public void testBuilder() {
+        assertThat(mAppInfo.packageName).isEqualTo(PACKAGE_NAME);
+        assertThat(mAppInfo.anomalyType).isEqualTo(ANOMALY_TYPE);
+        assertThat(mAppInfo.screenOnTimeMs).isEqualTo(SCREEN_TIME_MS);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java
index e2f8a26..0689778 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batterytip/tips/HighUsageTipTest.java
@@ -22,7 +22,7 @@
 import android.text.format.DateUtils;
 
 import com.android.settings.TestConfig;
-import com.android.settings.fuelgauge.batterytip.HighUsageApp;
+import com.android.settings.fuelgauge.batterytip.AppInfo;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 
 import org.junit.Before;
@@ -42,14 +42,17 @@
 
     private Context mContext;
     private HighUsageTip mBatteryTip;
-    private List<HighUsageApp> mUsageAppList;
+    private List<AppInfo> mUsageAppList;
 
     @Before
     public void setUp() {
         mContext = RuntimeEnvironment.application;
 
         mUsageAppList = new ArrayList<>();
-        mUsageAppList.add(new HighUsageApp(PACKAGE_NAME, SCREEN_TIME));
+        mUsageAppList.add(new AppInfo.Builder()
+                .setPackageName(PACKAGE_NAME)
+                .setScreenOnTimeMs(SCREEN_TIME)
+                .build());
         mBatteryTip = new HighUsageTip(SCREEN_TIME, mUsageAppList);
     }
 
@@ -67,7 +70,7 @@
         assertThat(parcelTip.getState()).isEqualTo(BatteryTip.StateType.NEW);
         assertThat(parcelTip.getScreenTimeMs()).isEqualTo(SCREEN_TIME);
         assertThat(parcelTip.mHighUsageAppList.size()).isEqualTo(1);
-        final HighUsageApp app = parcelTip.mHighUsageAppList.get(0);
+        final AppInfo app = parcelTip.mHighUsageAppList.get(0);
         assertThat(app.packageName).isEqualTo(PACKAGE_NAME);
         assertThat(app.screenOnTimeMs).isEqualTo(SCREEN_TIME);
     }
diff --git a/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
index 11e740a..499a2f7 100644
--- a/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/DatabaseTestUtils.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 
+import com.android.settings.fuelgauge.batterytip.AnomalyDatabaseHelper;
 import com.android.settings.search.IndexDatabaseHelper;
 import com.android.settings.slices.SlicesDatabaseHelper;
 
@@ -28,6 +29,7 @@
     public static void clearDb(Context context) {
         clearSearchDb(context);
         clearSlicesDb(context);
+        clearAnomalyDb(context);
     }
 
     private static void clearSlicesDb(Context context) {
@@ -45,6 +47,21 @@
         }
     }
 
+    private static void clearAnomalyDb(Context context) {
+        AnomalyDatabaseHelper helper = AnomalyDatabaseHelper.getInstance(context);
+        helper.close();
+
+        Field instance;
+        Class clazz = AnomalyDatabaseHelper.class;
+        try {
+            instance = clazz.getDeclaredField("sSingleton");
+            instance.setAccessible(true);
+            instance.set(null, null);
+        } catch (Exception e) {
+            throw new RuntimeException();
+        }
+    }
+
     private static void clearSearchDb(Context context) {
         IndexDatabaseHelper helper = IndexDatabaseHelper.getInstance(context);
         helper.close();