Load app usage events data in the hourly job.

Test: make RunSettingsRoboTests + manual
Bug: 260964679
Change-Id: Iaccaa77bd52fb7356cdcb786c64523f21040b128
diff --git a/Android.bp b/Android.bp
index b5dfe79..dc7270e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -80,6 +80,7 @@
         "guava",
         "jsr305",
         "net-utils-framework-common",
+        "app-usage-event-protos-lite",
         "settings-contextual-card-protos-lite",
         "settings-log-bridge-protos-lite",
         "settings-telephony-protos-lite",
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index 71d56ae..8029194 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -141,12 +141,17 @@
     Intent getResumeChargeIntent(boolean isDockDefender);
 
     /**
-     * Returns {@link Set} for hidding applications background usage time.
+     * Returns {@link Set} for hiding applications background usage time.
      */
     Set<CharSequence> getHideBackgroundUsageTimeSet(Context context);
 
     /**
-     * Returns package names for hidding application in the usage screen.
+     * Returns package names for hiding application in the usage screen.
      */
     CharSequence[] getHideApplicationEntries(Context context);
+
+    /**
+     * Returns {@link Set} for ignoring task root class names for screen on time.
+     */
+    Set<CharSequence> getIgnoreScreenOnTimeTaskRootSet(Context context);
 }
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 932b35d..53d8701 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -165,4 +165,9 @@
     public CharSequence[] getHideApplicationEntries(Context context) {
         return new CharSequence[0];
     }
+
+    @Override
+    public Set<CharSequence> getIgnoreScreenOnTimeTaskRootSet(Context context) {
+        return new ArraySet<>();
+    }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java
new file mode 100644
index 0000000..c336fcd
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoader.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import android.app.usage.UsageEvents;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.util.Log;
+
+import androidx.annotation.VisibleForTesting;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/** Load app usage events data in the background. */
+public final class AppUsageDataLoader {
+    private static final String TAG = "AppUsageDataLoader";
+
+    // For testing only.
+    @VisibleForTesting
+    static Supplier<Map<Long, UsageEvents>> sFakeAppUsageEventsSupplier;
+    @VisibleForTesting
+    static Supplier<List<AppUsageEvent>> sFakeUsageEventsListSupplier;
+
+    private AppUsageDataLoader() {}
+
+    static void enqueueWork(final Context context) {
+        AsyncTask.execute(() -> {
+            Log.d(TAG, "loadAppUsageDataSafely() in the AsyncTask");
+            loadAppUsageDataSafely(context.getApplicationContext());
+        });
+    }
+
+    @VisibleForTesting
+    static void loadAppUsageData(final Context context) {
+        final long start = System.currentTimeMillis();
+        final Map<Long, UsageEvents> appUsageEvents =
+                sFakeAppUsageEventsSupplier != null
+                        ? sFakeAppUsageEventsSupplier.get()
+                        : DataProcessor.getAppUsageEvents(context);
+        if (appUsageEvents == null) {
+            Log.w(TAG, "loadAppUsageData() returns null");
+            return;
+        }
+        final List<AppUsageEvent> appUsageEventList =
+                sFakeUsageEventsListSupplier != null
+                        ? sFakeUsageEventsListSupplier.get()
+                        : DataProcessor.generateAppUsageEventListFromUsageEvents(
+                                context, appUsageEvents);
+        if (appUsageEventList == null || appUsageEventList.isEmpty()) {
+            Log.w(TAG, "loadAppUsageData() returns null or empty content");
+            return;
+        }
+        final long elapsedTime = System.currentTimeMillis() - start;
+        Log.d(TAG, String.format("loadAppUsageData() size=%d in %d/ms", appUsageEventList.size(),
+                elapsedTime));
+        // Uploads the AppUsageEvent data into database.
+        DatabaseUtils.sendAppUsageEventData(context, appUsageEventList);
+    }
+
+    private static void loadAppUsageDataSafely(final Context context) {
+        try {
+            loadAppUsageData(context);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "loadAppUsageData:" + e);
+        }
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
index 4827f8f..4abcdc3 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
@@ -29,6 +29,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventDao;
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
@@ -43,11 +45,11 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     public static final Duration QUERY_DURATION_HOURS = Duration.ofDays(6);
 
-    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-    public static final String QUERY_KEY_TIMESTAMP = "timestamp";
-
     /** Codes */
     private static final int BATTERY_STATE_CODE = 1;
+    private static final int APP_USAGE_LATEST_TIMESTAMP_CODE = 2;
+    private static final int APP_USAGE_EVENT_CODE = 3;
+
     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
     static {
@@ -55,10 +57,19 @@
                 DatabaseUtils.AUTHORITY,
                 /*path=*/ DatabaseUtils.BATTERY_STATE_TABLE,
                 /*code=*/ BATTERY_STATE_CODE);
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.APP_USAGE_LATEST_TIMESTAMP_PATH,
+                /*code=*/ APP_USAGE_LATEST_TIMESTAMP_CODE);
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.APP_USAGE_EVENT_TABLE,
+                /*code=*/ APP_USAGE_EVENT_CODE);
     }
 
     private Clock mClock;
     private BatteryStateDao mBatteryStateDao;
+    private AppUsageEventDao mAppUsageEventDao;
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     public void setClock(Clock clock) {
@@ -73,6 +84,7 @@
         }
         mClock = Clock.systemUTC();
         mBatteryStateDao = BatteryStateDatabase.getInstance(getContext()).batteryStateDao();
+        mAppUsageEventDao = BatteryStateDatabase.getInstance(getContext()).appUsageEventDao();
         Log.w(TAG, "create content provider from " + getCallingPackage());
         return true;
     }
@@ -88,6 +100,8 @@
         switch (sUriMatcher.match(uri)) {
             case BATTERY_STATE_CODE:
                 return getBatteryStates(uri);
+            case APP_USAGE_LATEST_TIMESTAMP_CODE:
+                return getAppUsageLatestTimestamp(uri);
             default:
                 throw new IllegalArgumentException("unknown URI: " + uri);
         }
@@ -111,6 +125,14 @@
                     Log.e(TAG, "insert() from:" + uri + " error:" + e);
                     return null;
                 }
+            case APP_USAGE_EVENT_CODE:
+                try {
+                    mAppUsageEventDao.insert(AppUsageEventEntity.create(contentValues));
+                    return uri;
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "insert() from:" + uri + " error:" + e);
+                    return null;
+                }
             default:
                 throw new IllegalArgumentException("unknown URI: " + uri);
         }
@@ -145,24 +167,54 @@
             Log.e(TAG, "query() from:" + uri + " error:" + e);
         }
         AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext()));
-        Log.w(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms");
+        Log.d(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms");
         return cursor;
     }
 
+    private Cursor getAppUsageLatestTimestamp(Uri uri) {
+        final long queryUserId = getQueryUserId(uri);
+        if (queryUserId == DatabaseUtils.INVALID_USER_ID) {
+            return null;
+        }
+        final long timestamp = mClock.millis();
+        Cursor cursor = null;
+        try {
+            cursor = mAppUsageEventDao.getLatestTimestampOfUser(queryUserId);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "query() from:" + uri + " error:" + e);
+        }
+        Log.d(TAG, String.format("query app usage latest timestamp %d for user %d in %d/ms",
+                timestamp, queryUserId, (mClock.millis() - timestamp)));
+        return cursor;
+    }
+
+    // If URI contains query parameter QUERY_KEY_USERID, use the value directly.
+    // Otherwise, return INVALID_USER_ID.
+    private long getQueryUserId(Uri uri) {
+        Log.d(TAG, "getQueryUserId from uri: " + uri);
+        return getQueryValueFromUri(
+                uri, DatabaseUtils.QUERY_KEY_USERID, DatabaseUtils.INVALID_USER_ID);
+    }
+
     // If URI contains query parameter QUERY_KEY_TIMESTAMP, use the value directly.
     // Otherwise, load the data for QUERY_DURATION_HOURS by default.
     private long getQueryTimestamp(Uri uri, long defaultTimestamp) {
-        final String firstTimestampString = uri.getQueryParameter(QUERY_KEY_TIMESTAMP);
-        if (TextUtils.isEmpty(firstTimestampString)) {
-            Log.w(TAG, "empty query timestamp");
-            return defaultTimestamp;
+        Log.d(TAG, "getQueryTimestamp from uri: " + uri);
+        return getQueryValueFromUri(uri, DatabaseUtils.QUERY_KEY_TIMESTAMP, defaultTimestamp);
+    }
+
+    private long getQueryValueFromUri(Uri uri, String key, long defaultValue) {
+        final String value = uri.getQueryParameter(key);
+        if (TextUtils.isEmpty(value)) {
+            Log.w(TAG, "empty query value");
+            return defaultValue;
         }
 
         try {
-            return Long.parseLong(firstTimestampString);
+            return Long.parseLong(value);
         } catch (NumberFormatException e) {
-            Log.e(TAG, "invalid query timestamp: " + firstTimestampString, e);
-            return defaultTimestamp;
+            Log.e(TAG, "invalid query value: " + value, e);
+            return defaultValue;
         }
     }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
index 3cb5465..d446bb2 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageDataLoader.java
@@ -58,7 +58,7 @@
         final long elapsedTime = System.currentTimeMillis() - start;
         Log.d(TAG, String.format("getBatteryUsageStats() in %d/ms", elapsedTime));
 
-        // Uploads the BatteryEntry data into SettingsIntelligence.
+        // Uploads the BatteryEntry data into database.
         DatabaseUtils.sendBatteryEntryData(
                 context, batteryEntryList, batteryUsageStats, isFullChargeStart);
         DataProcessor.closeBatteryUsageStats(batteryUsageStats);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index c5c0522..38879d9 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -16,8 +16,11 @@
 package com.android.settings.fuelgauge.batteryusage;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.app.usage.UsageEvents.Event;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.os.BatteryUsageStats;
 import android.os.Build;
@@ -25,10 +28,12 @@
 import android.os.UserHandle;
 import android.text.format.DateFormat;
 import android.util.Base64;
+import android.util.Log;
 
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settings.fuelgauge.BatteryUtils;
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -49,7 +54,7 @@
             CONSUMER_TYPE_SYSTEM_BATTERY,
     })
     @Retention(RetentionPolicy.SOURCE)
-    public static @interface ConsumerType {
+    public @interface ConsumerType {
     }
 
     public static final int CONSUMER_TYPE_UNKNOWN = 0;
@@ -60,8 +65,8 @@
     private ConvertUtils() {
     }
 
-    /** Converts to content values */
-    public static ContentValues convertToContentValues(
+    /** Converts {@link BatteryEntry} to content values */
+    public static ContentValues convertBatteryEntryToContentValues(
             final BatteryEntry entry,
             final BatteryUsageStats batteryUsageStats,
             final int batteryLevel,
@@ -103,6 +108,19 @@
         return values;
     }
 
+    /** Converts {@link AppUsageEvent} to content values */
+    public static ContentValues convertAppUsageEventToContentValues(final AppUsageEvent event) {
+        final ContentValues values = new ContentValues();
+        values.put(AppUsageEventEntity.KEY_UID, event.getUid());
+        values.put(AppUsageEventEntity.KEY_USER_ID, event.getUserId());
+        values.put(AppUsageEventEntity.KEY_TIMESTAMP, event.getTimestamp());
+        values.put(AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE, event.getType().getNumber());
+        values.put(AppUsageEventEntity.KEY_PACKAGE_NAME, event.getPackageName());
+        values.put(AppUsageEventEntity.KEY_INSTANCE_ID, event.getInstanceId());
+        values.put(AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME, event.getTaskRootPackageName());
+        return values;
+    }
+
     /** Gets the encoded string from {@link BatteryInformation} instance. */
     public static String convertBatteryInformationToString(
             final BatteryInformation batteryInformation) {
@@ -135,7 +153,7 @@
             BatteryEntry entry,
             BatteryUsageStats batteryUsageStats) {
         return new BatteryHistEntry(
-                convertToContentValues(
+                convertBatteryEntryToContentValues(
                         entry,
                         batteryUsageStats,
                         /*batteryLevel=*/ 0,
@@ -146,6 +164,51 @@
                         /*isFullChargeStart=*/ false));
     }
 
+    /** Converts to {@link AppUsageEvent} from {@link Event} */
+    @Nullable
+    public static AppUsageEvent convertToAppUsageEvent(
+            Context context, final Event event, final long userId) {
+        if (event.getPackageName() == null) {
+            // See b/190609174: Event package names should never be null, but sometimes they are.
+            // Note that system events like device shutting down should still come with the android
+            // package name.
+            Log.w(TAG, String.format(
+                    "Ignoring a usage event with null package name (timestamp=%d, type=%d)",
+                    event.getTimeStamp(), event.getEventType()));
+            return null;
+        }
+
+        final AppUsageEvent.Builder appUsageEventBuilder = AppUsageEvent.newBuilder();
+        appUsageEventBuilder
+                .setTimestamp(event.getTimeStamp())
+                .setType(getAppUsageEventType(event.getEventType()))
+                .setPackageName(event.getPackageName())
+                .setUserId(userId);
+
+        try {
+            final long uid = context
+                    .getPackageManager()
+                    .getPackageUidAsUser(event.getPackageName(), (int) userId);
+            appUsageEventBuilder.setUid(uid);
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, String.format(
+                    "Fail to get uid for package %s of user %d)", event.getPackageName(), userId));
+            return null;
+        }
+
+        try {
+            appUsageEventBuilder.setInstanceId(event.getInstanceId());
+        } catch (NoClassDefFoundError | NoSuchMethodError e) {
+            Log.w(TAG, "UsageEvent instance ID API error");
+        }
+        String taskRootPackageName = getTaskRootPackageName(event);
+        if (taskRootPackageName != null) {
+            appUsageEventBuilder.setTaskRootPackageName(taskRootPackageName);
+        }
+
+        return appUsageEventBuilder.build();
+    }
+
     /** Converts UTC timestamp to human readable local time string. */
     public static String utcToLocalTime(Context context, long timestamp) {
         final Locale locale = getLocale(context);
@@ -185,6 +248,50 @@
                 : Locale.getDefault();
     }
 
+    /**
+     * Returns the package name of the task root when this event was reported when {@code event} is
+     * one of:
+     *
+     * <ul>
+     *   <li>{@link Event#ACTIVITY_RESUMED}
+     *   <li>{@link Event#ACTIVITY_STOPPED}
+     * </ul>
+     */
+    @Nullable
+    private static String getTaskRootPackageName(Event event) {
+        int eventType = event.getEventType();
+        if (eventType != Event.ACTIVITY_RESUMED && eventType != Event.ACTIVITY_STOPPED) {
+            // Task root is only relevant for ACTIVITY_* events.
+            return null;
+        }
+
+        try {
+            String taskRootPackageName = event.getTaskRootPackageName();
+            if (taskRootPackageName == null) {
+                Log.w(TAG, String.format(
+                        "Null task root in event with timestamp %d, type=%d, package %s",
+                        event.getTimeStamp(), event.getEventType(), event.getPackageName()));
+            }
+            return taskRootPackageName;
+        } catch (NoSuchMethodError e) {
+            Log.w(TAG, "Failed to call Event#getTaskRootPackageName()");
+            return null;
+        }
+    }
+
+    private static AppUsageEventType getAppUsageEventType(final int eventType) {
+        switch (eventType) {
+            case Event.ACTIVITY_RESUMED:
+                return AppUsageEventType.ACTIVITY_RESUMED;
+            case Event.ACTIVITY_STOPPED:
+                return AppUsageEventType.ACTIVITY_STOPPED;
+            case Event.DEVICE_SHUTDOWN:
+                return AppUsageEventType.DEVICE_SHUTDOWN;
+            default:
+                return AppUsageEventType.UNKNOWN;
+        }
+    }
+
     private static BatteryInformation constructBatteryInformation(
             final BatteryEntry entry,
             final BatteryUsageStats batteryUsageStats,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 9d2774e..e3e1912 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -18,10 +18,14 @@
 
 import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTime;
 
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.os.AsyncTask;
 import android.os.BatteryConsumer;
 import android.os.BatteryStatsManager;
@@ -30,6 +34,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UidBatteryConsumer;
 import android.os.UserBatteryConsumer;
 import android.os.UserHandle;
@@ -90,6 +96,11 @@
     @VisibleForTesting
     static long sFakeCurrentTimeMillis = 0;
 
+    @VisibleForTesting
+    static IUsageStatsManager sUsageStatsManager =
+            IUsageStatsManager.Stub.asInterface(
+                    ServiceManager.getService(Context.USAGE_STATS_SERVICE));
+
     /** A callback listener when battery usage loading async task is executed. */
     public interface UsageMapAsyncResponse {
         /** The callback function when batteryUsageMap is loaded. */
@@ -184,6 +195,55 @@
     }
 
     /**
+     * Gets the {@link UsageEvents} from system service for all unlocked users.
+     */
+    @Nullable
+    public static Map<Long, UsageEvents> getAppUsageEvents(Context context) {
+        final long start = System.currentTimeMillis();
+        final boolean isWorkProfileUser = DatabaseUtils.isWorkProfile(context);
+        Log.d(TAG, "getAppUsageEvents() isWorkProfileUser:" + isWorkProfileUser);
+        if (isWorkProfileUser) {
+            try {
+                context = context.createPackageContextAsUser(
+                        /*packageName=*/ context.getPackageName(),
+                        /*flags=*/ 0,
+                        /*user=*/ UserHandle.OWNER);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.e(TAG, "context.createPackageContextAsUser() fail:" + e);
+                return null;
+            }
+        }
+        final Map<Long, UsageEvents> resultMap = new HashMap();
+        final UserManager userManager = context.getSystemService(UserManager.class);
+        if (userManager == null) {
+            return null;
+        }
+        final long sixDaysAgoTimestamp =
+                DatabaseUtils.getTimestampSixDaysAgo(Calendar.getInstance());
+        final String callingPackage = context.getPackageName();
+        final long now = System.currentTimeMillis();
+        for (final UserInfo user : userManager.getAliveUsers()) {
+            // When the user is not unlocked, UsageStatsManager will return null, so bypass the
+            // following data loading logics directly.
+            if (!userManager.isUserUnlocked(user.id)) {
+                Log.w(TAG, "fail to load app usage event for user :" + user.id + " because locked");
+                continue;
+            }
+            final long startTime = DatabaseUtils.getAppUsageStartTimestampOfUser(
+                    context, user.id, sixDaysAgoTimestamp);
+            final UsageEvents events = getAppUsageEventsForUser(
+                    sUsageStatsManager, startTime, now, user.id, callingPackage);
+            if (events != null) {
+                resultMap.put(Long.valueOf(user.id), events);
+            }
+        }
+        final long elapsedTime = System.currentTimeMillis() - start;
+        Log.d(TAG, String.format("getAppUsageEvents() for all unlocked users in %d/ms",
+                elapsedTime));
+        return resultMap.isEmpty() ? null : resultMap;
+    }
+
+    /**
      * Closes the {@link BatteryUsageStats} after using it.
      */
     public static void closeBatteryUsageStats(BatteryUsageStats batteryUsageStats) {
@@ -197,6 +257,59 @@
     }
 
     /**
+     * Generates the list of {@link AppUsageEvent} from the supplied {@link UsageEvents}.
+     */
+    public static List<AppUsageEvent> generateAppUsageEventListFromUsageEvents(
+            Context context, Map<Long, UsageEvents> usageEventsMap) {
+        final List<AppUsageEvent> appUsageEventList = new ArrayList<>();
+        long numEventsFetched = 0;
+        long numAllEventsFetched = 0;
+        final Set<CharSequence> ignoreScreenOnTimeTaskRootSet =
+                FeatureFactory.getFactory(context)
+                        .getPowerUsageFeatureProvider(context)
+                        .getIgnoreScreenOnTimeTaskRootSet(context);
+        for (final long userId : usageEventsMap.keySet()) {
+            final UsageEvents usageEvents = usageEventsMap.get(userId);
+            while (usageEvents.hasNextEvent()) {
+                final Event event = new Event();
+                usageEvents.getNextEvent(event);
+                numAllEventsFetched++;
+                switch (event.getEventType()) {
+                    case Event.ACTIVITY_RESUMED:
+                    case Event.ACTIVITY_STOPPED:
+                    case Event.DEVICE_SHUTDOWN:
+                        final String taskRootClassName = event.getTaskRootClassName();
+                        if (!TextUtils.isEmpty(taskRootClassName)
+                                && !ignoreScreenOnTimeTaskRootSet.isEmpty()
+                                && contains(
+                                        taskRootClassName, ignoreScreenOnTimeTaskRootSet)) {
+                            Log.w(TAG, String.format(
+                                    "Ignoring a usage event with task root class name %s, "
+                                            + "(timestamp=%d, type=%d)",
+                                    taskRootClassName,
+                                    event.getTimeStamp(),
+                                    event.getEventType()));
+                            break;
+                        }
+                        final AppUsageEvent appUsageEvent =
+                                ConvertUtils.convertToAppUsageEvent(context, event, userId);
+                        if (appUsageEvent != null) {
+                            numEventsFetched++;
+                            appUsageEventList.add(appUsageEvent);
+                        }
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+        Log.w(TAG, String.format(
+                "Read %d relevant events (%d total) from UsageStatsManager", numEventsFetched,
+                numAllEventsFetched));
+        return appUsageEventList;
+    }
+
+    /**
      * Generates the list of {@link BatteryEntry} from the supplied {@link BatteryUsageStats}.
      */
     @Nullable
@@ -508,13 +621,30 @@
                 asyncResponseDelegate).execute();
     }
 
+    @Nullable
+    private static UsageEvents getAppUsageEventsForUser(
+            final IUsageStatsManager usageStatsManager, final long startTime, final long endTime,
+            final int userId, final String callingPackage) {
+        final long start = System.currentTimeMillis();
+        UsageEvents events = null;
+        try {
+            events = usageStatsManager.queryEventsForUser(
+                    startTime, endTime, userId, callingPackage);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error fetching usage events: ", e);
+        }
+        final long elapsedTime = System.currentTimeMillis() - start;
+        Log.d(TAG, String.format("getAppUsageEventsForUser(): %d from %d to %d in %d/ms", userId,
+                startTime, endTime, elapsedTime));
+        return events;
+    }
+
     /**
      * @return Returns the overall battery usage data from battery stats service directly.
      *
      * The returned value should be always a 2d map and composed by only 1 part:
      * - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL]
      */
-    @Nullable
     private static Map<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService(
             final Context context) {
         final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>();
@@ -1335,6 +1465,7 @@
         return calendar.getTimeInMillis();
     }
 
+    /** Whether the Set contains the target. */
     private static boolean contains(String target, Set<CharSequence> packageNames) {
         if (target != null && packageNames != null) {
             for (CharSequence packageName : packageNames) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index 8ff802d..d7c98a7 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -45,12 +45,11 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 
 /** A utility class to operate battery usage database. */
 public final class DatabaseUtils {
     private static final String TAG = "DatabaseUtils";
-    /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
-    private static final String QUERY_KEY_TIMESTAMP = "timestamp";
     /** Clear memory threshold for device booting phase. **/
     private static final long CLEAR_MEMORY_THRESHOLD_MS = Duration.ofMinutes(5).toMillis();
     private static final long CLEAR_MEMORY_DELAYED_MS = Duration.ofSeconds(2).toMillis();
@@ -62,8 +61,18 @@
     public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
     /** A table name for battery usage history. */
     public static final String BATTERY_STATE_TABLE = "BatteryState";
+    /** A table name for app usage events. */
+    public static final String APP_USAGE_EVENT_TABLE = "AppUsageEvent";
+    /** A path name for app usage latest timestamp query. */
+    public static final String APP_USAGE_LATEST_TIMESTAMP_PATH = "appUsageLatestTimestamp";
     /** A class name for battery usage data provider. */
     public static final String SETTINGS_PACKAGE_PATH = "com.android.settings";
+    /** Key for query parameter timestamp used in BATTERY_CONTENT_URI **/
+    public static final String QUERY_KEY_TIMESTAMP = "timestamp";
+    /** Key for query parameter userid used in APP_USAGE_EVENT_URI **/
+    public static final String QUERY_KEY_USERID = "userid";
+
+    public static final long INVALID_USER_ID = Integer.MIN_VALUE;
 
     /** A content URI to access battery usage states data. */
     public static final Uri BATTERY_CONTENT_URI =
@@ -72,6 +81,19 @@
                     .authority(AUTHORITY)
                     .appendPath(BATTERY_STATE_TABLE)
                     .build();
+    /** A content URI to access app usage events data. */
+    public static final Uri APP_USAGE_EVENT_URI =
+            new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(APP_USAGE_EVENT_TABLE)
+                    .build();
+
+    // For testing only.
+    @VisibleForTesting
+    static Supplier<Cursor> sFakeBatteryStateSupplier;
+    @VisibleForTesting
+    static Supplier<Cursor> sFakeAppUsageLatestTimestampSupplier;
 
     private DatabaseUtils() {
     }
@@ -82,6 +104,27 @@
         return userManager.isManagedProfile() && !userManager.isSystemUser();
     }
 
+    /** Returns the latest timestamp current user data in app usage event table. */
+    public static long getAppUsageStartTimestampOfUser(
+            Context context, final long userId, final long earliestTimestamp) {
+        final long startTime = System.currentTimeMillis();
+        // Builds the content uri everytime to avoid cache.
+        final Uri appUsageLatestTimestampUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(AUTHORITY)
+                        .appendPath(APP_USAGE_LATEST_TIMESTAMP_PATH)
+                        .appendQueryParameter(
+                                QUERY_KEY_USERID, Long.toString(userId))
+                        .build();
+        final long latestTimestamp =
+                loadAppUsageLatestTimestampFromContentProvider(context, appUsageLatestTimestampUri);
+        Log.d(TAG, String.format(
+                "getAppUsageStartTimestampOfUser() userId=%d latestTimestamp=%d in %d/ms",
+                userId, latestTimestamp, (System.currentTimeMillis() - startTime)));
+        return Math.max(latestTimestamp, earliestTimestamp);
+    }
+
     /** Long: for timestamp and String: for BatteryHistEntry.getKey() */
     public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
             Context context, Calendar calendar) {
@@ -113,10 +156,10 @@
     public static void clearAll(Context context) {
         AsyncTask.execute(() -> {
             try {
-                BatteryStateDatabase
-                        .getInstance(context.getApplicationContext())
-                        .batteryStateDao()
-                        .clearAll();
+                final BatteryStateDatabase database = BatteryStateDatabase
+                        .getInstance(context.getApplicationContext());
+                database.batteryStateDao().clearAll();
+                database.appUsageEventDao().clearAll();
             } catch (RuntimeException e) {
                 Log.e(TAG, "clearAll() failed", e);
             }
@@ -127,17 +170,59 @@
     public static void clearExpiredDataIfNeeded(Context context) {
         AsyncTask.execute(() -> {
             try {
-                BatteryStateDatabase
-                        .getInstance(context.getApplicationContext())
-                        .batteryStateDao()
-                        .clearAllBefore(Clock.systemUTC().millis()
-                                - Duration.ofDays(DATA_RETENTION_INTERVAL_DAY).toMillis());
+                final BatteryStateDatabase database = BatteryStateDatabase
+                        .getInstance(context.getApplicationContext());
+                final long earliestTimestamp = Clock.systemUTC().millis()
+                        - Duration.ofDays(DATA_RETENTION_INTERVAL_DAY).toMillis();
+                database.batteryStateDao().clearAllBefore(earliestTimestamp);
+                database.appUsageEventDao().clearAllBefore(earliestTimestamp);
             } catch (RuntimeException e) {
                 Log.e(TAG, "clearAllBefore() failed", e);
             }
         });
     }
 
+    /** Returns the timestamp for 00:00 6 days before the calendar date. */
+    public static long getTimestampSixDaysAgo(Calendar calendar) {
+        Calendar startCalendar =
+                calendar == null ? Calendar.getInstance() : (Calendar) calendar.clone();
+        startCalendar.add(Calendar.DAY_OF_YEAR, -6);
+        startCalendar.set(Calendar.HOUR_OF_DAY, 0);
+        startCalendar.set(Calendar.MINUTE, 0);
+        startCalendar.set(Calendar.SECOND, 0);
+        startCalendar.set(Calendar.MILLISECOND, 0);
+        return startCalendar.getTimeInMillis();
+    }
+
+    static List<ContentValues> sendAppUsageEventData(
+            final Context context, final List<AppUsageEvent> appUsageEventList) {
+        final long startTime = System.currentTimeMillis();
+        // Creates the ContentValues list to insert them into provider.
+        final List<ContentValues> valuesList = new ArrayList<>();
+        appUsageEventList.stream()
+                .filter(appUsageEvent -> appUsageEvent.hasUid())
+                .forEach(appUsageEvent -> valuesList.add(
+                        ConvertUtils.convertAppUsageEventToContentValues(appUsageEvent)));
+        int size = 0;
+        final ContentResolver resolver = context.getContentResolver();
+        // Inserts all ContentValues into battery provider.
+        if (!valuesList.isEmpty()) {
+            final ContentValues[] valuesArray = new ContentValues[valuesList.size()];
+            valuesList.toArray(valuesArray);
+            try {
+                size = resolver.bulkInsert(APP_USAGE_EVENT_URI, valuesArray);
+                resolver.notifyChange(APP_USAGE_EVENT_URI, /*observer=*/ null);
+                Log.d(TAG, "insert() app usage events data into database");
+            } catch (Exception e) {
+                Log.e(TAG, "bulkInsert() app usage data into database error:\n" + e);
+            }
+        }
+        Log.d(TAG, String.format("sendAppUsageEventData() size=%d in %d/ms",
+                size, (System.currentTimeMillis() - startTime)));
+        clearMemory();
+        return valuesList;
+    }
+
     static List<ContentValues> sendBatteryEntryData(
             final Context context,
             final List<BatteryEntry> batteryEntryList,
@@ -178,7 +263,7 @@
                                 || backgroundMs != 0;
                     })
                     .forEach(entry -> valuesList.add(
-                            ConvertUtils.convertToContentValues(
+                            ConvertUtils.convertBatteryEntryToContentValues(
                                     entry,
                                     batteryUsageStats,
                                     batteryLevel,
@@ -197,15 +282,15 @@
             valuesList.toArray(valuesArray);
             try {
                 size = resolver.bulkInsert(BATTERY_CONTENT_URI, valuesArray);
-                Log.d(TAG, "insert() data into database with isFullChargeStart:"
+                Log.d(TAG, "insert() battery states data into database with isFullChargeStart:"
                         + isFullChargeStart);
             } catch (Exception e) {
-                Log.e(TAG, "bulkInsert() data into database error:\n" + e);
+                Log.e(TAG, "bulkInsert() battery states data into database error:\n" + e);
             }
         } else {
             // Inserts one fake data into battery provider.
             final ContentValues contentValues =
-                    ConvertUtils.convertToContentValues(
+                    ConvertUtils.convertBatteryEntryToContentValues(
                             /*entry=*/ null,
                             /*batteryUsageStats=*/ null,
                             batteryLevel,
@@ -231,6 +316,30 @@
         return valuesList;
     }
 
+    private static long loadAppUsageLatestTimestampFromContentProvider(
+            Context context, final Uri appUsageLatestTimestampUri) {
+        // We have already make sure the context here is with OWNER user identity. Don't need to
+        // check whether current user is work profile.
+        try (Cursor cursor = sFakeAppUsageLatestTimestampSupplier != null
+                ? sFakeAppUsageLatestTimestampSupplier.get()
+                : context.getContentResolver().query(
+                        appUsageLatestTimestampUri, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return INVALID_USER_ID;
+            }
+            cursor.moveToFirst();
+            // There is only one column returned so use the index 0 directly.
+            final long latestTimestamp = cursor.getLong(/*columnIndex=*/ 0);
+            try {
+                cursor.close();
+            } catch (Exception e) {
+                Log.e(TAG, "cursor.close() failed", e);
+            }
+            // If there is no data for this user, 0 will be returned from the database.
+            return latestTimestamp == 0 ? INVALID_USER_ID : latestTimestamp;
+        }
+    }
+
     private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
             Context context, Uri batteryStateUri) {
         final boolean isWorkProfileUser = isWorkProfile(context);
@@ -247,7 +356,7 @@
             }
         }
         final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
-        try (Cursor cursor =
+        try (Cursor cursor = sFakeBatteryStateSupplier != null ? sFakeBatteryStateSupplier.get() :
                      context.getContentResolver().query(batteryStateUri, null, null, null)) {
             if (cursor == null || cursor.getCount() == 0) {
                 return resultMap;
@@ -286,17 +395,4 @@
             Log.w(TAG, "invoke clearMemory()");
         }, CLEAR_MEMORY_DELAYED_MS);
     }
-
-    /** Returns the timestamp for 00:00 6 days before the calendar date. */
-    private static long getTimestampSixDaysAgo(Calendar calendar) {
-        Calendar startCalendar =
-                calendar == null ? Calendar.getInstance() : (Calendar) calendar.clone();
-        startCalendar.add(Calendar.DAY_OF_YEAR, -6);
-        startCalendar.set(Calendar.HOUR_OF_DAY, 0);
-        startCalendar.set(Calendar.MINUTE, 0);
-        startCalendar.set(Calendar.SECOND, 0);
-        startCalendar.set(Calendar.MILLISECOND, 0);
-        return startCalendar.getTimeInMillis();
-    }
-
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
index fddf01b..d6a2f62 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
@@ -40,6 +40,7 @@
             return;
         }
         BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ false);
+        AppUsageDataLoader.enqueueWork(context);
         Log.d(TAG, "refresh periodic job from action=" + action);
         PeriodicJobManager.getInstance(context).refreshJob();
         DatabaseUtils.clearExpiredDataIfNeeded(context);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java
new file mode 100644
index 0000000..578a1ff
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import android.database.Cursor;
+
+import androidx.room.Dao;
+import androidx.room.Insert;
+import androidx.room.OnConflictStrategy;
+import androidx.room.Query;
+
+import java.util.List;
+
+/** Data access object for accessing {@link AppUsageEventEntity} in the database. */
+@Dao
+public interface AppUsageEventDao {
+
+    /** Inserts a {@link AppUsageEventEntity} data into the database. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(AppUsageEventEntity event);
+
+    /** Inserts {@link AppUsageEventEntity} data into the database. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insertAll(List<AppUsageEventEntity> events);
+
+    /** Lists all recorded data after a specific timestamp. */
+    @Query("SELECT * FROM AppUsageEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC")
+    List<AppUsageEventEntity> getAllAfter(long timestamp);
+
+    /** Gets the {@link Cursor} of the latest timestamp of the specific user. */
+    @Query("SELECT MAX(timestamp) as timestamp FROM AppUsageEventEntity WHERE userId = :userId")
+    Cursor getLatestTimestampOfUser(long userId);
+
+    /** Deletes all recorded data before a specific timestamp. */
+    @Query("DELETE FROM AppUsageEventEntity WHERE timestamp <= :timestamp")
+    void clearAllBefore(long timestamp);
+
+    /** Clears all recorded data in the database. */
+    @Query("DELETE FROM AppUsageEventEntity")
+    void clearAll();
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventEntity.java b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventEntity.java
new file mode 100644
index 0000000..9d62d07
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventEntity.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import android.content.ContentValues;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/** A {@link Entity} class to save app usage events into database. */
+@Entity
+public class AppUsageEventEntity {
+    private static String sCacheZoneId;
+    private static SimpleDateFormat sCacheSimpleDateFormat;
+
+    /** Keys for accessing {@link ContentValues}. */
+    public static final String KEY_UID = "uid";
+    public static final String KEY_USER_ID = "userId";
+    public static final String KEY_TIMESTAMP = "timestamp";
+    public static final String KEY_APP_USAGE_EVENT_TYPE = "appUsageEventType";
+    public static final String KEY_PACKAGE_NAME = "packageName";
+    public static final String KEY_INSTANCE_ID = "instanceId";
+    public static final String KEY_TASK_ROOT_PACKAGE_NAME = "taskRootPackageName";
+
+    @PrimaryKey(autoGenerate = true)
+    private long mId;
+
+    // Records the app relative information.
+    public final long uid;
+    public final long userId;
+    public final long timestamp;
+    public final int appUsageEventType;
+    public final String packageName;
+    public final int instanceId;
+    public final String taskRootPackageName;
+
+    public AppUsageEventEntity(
+            final long uid,
+            final long userId,
+            final long timestamp,
+            final int appUsageEventType,
+            final String packageName,
+            final int instanceId,
+            final String taskRootPackageName) {
+        this.uid = uid;
+        this.userId = userId;
+        this.timestamp = timestamp;
+        this.appUsageEventType = appUsageEventType;
+        this.packageName = packageName;
+        this.instanceId = instanceId;
+        this.taskRootPackageName = taskRootPackageName;
+    }
+
+    /** Sets the auto-generated content ID. */
+    public void setId(long id) {
+        this.mId = id;
+    }
+
+    /** Gets the auto-generated content ID. */
+    public long getId() {
+        return mId;
+    }
+
+    @Override
+    @SuppressWarnings("JavaUtilDate")
+    public String toString() {
+        final String currentZoneId = TimeZone.getDefault().getID();
+        if (!currentZoneId.equals(sCacheZoneId) || sCacheSimpleDateFormat == null) {
+            sCacheZoneId = currentZoneId;
+            sCacheSimpleDateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.US);
+        }
+        final String recordAtDateTime = sCacheSimpleDateFormat.format(new Date(timestamp));
+        final StringBuilder builder = new StringBuilder()
+                .append("\nAppUsageEvent{")
+                .append(String.format(Locale.US,
+                        "\n\tpackage=%s|uid=%d|userId=%d", packageName, uid, userId))
+                .append(String.format(Locale.US, "\n\ttimestamp=%s|eventType=%d|instanceId=%d",
+                        recordAtDateTime, appUsageEventType, instanceId))
+                .append(String.format(Locale.US, "\n\ttaskRootPackageName=%s",
+                        taskRootPackageName));
+        return builder.toString();
+    }
+
+    /** Creates new {@link AppUsageEventEntity} from {@link ContentValues}. */
+    public static AppUsageEventEntity create(ContentValues contentValues) {
+        Builder builder = AppUsageEventEntity.newBuilder();
+        if (contentValues.containsKey(KEY_UID)) {
+            builder.setUid(contentValues.getAsLong(KEY_UID));
+        }
+        if (contentValues.containsKey(KEY_USER_ID)) {
+            builder.setUserId(contentValues.getAsLong(KEY_USER_ID));
+        }
+        if (contentValues.containsKey(KEY_TIMESTAMP)) {
+            builder.setTimestamp(contentValues.getAsLong(KEY_TIMESTAMP));
+        }
+        if (contentValues.containsKey(KEY_APP_USAGE_EVENT_TYPE)) {
+            builder.setAppUsageEventType(contentValues.getAsInteger(KEY_APP_USAGE_EVENT_TYPE));
+        }
+        if (contentValues.containsKey(KEY_PACKAGE_NAME)) {
+            builder.setPackageName(contentValues.getAsString(KEY_PACKAGE_NAME));
+        }
+        if (contentValues.containsKey(KEY_INSTANCE_ID)) {
+            builder.setInstanceId(
+                    contentValues.getAsInteger(KEY_INSTANCE_ID));
+        }
+        if (contentValues.containsKey(KEY_TASK_ROOT_PACKAGE_NAME)) {
+            builder.setTaskRootPackageName(contentValues.getAsString(KEY_TASK_ROOT_PACKAGE_NAME));
+        }
+        return builder.build();
+    }
+
+    /** Creates a new {@link Builder} instance. */
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** A convenience builder class to improve readability. */
+    public static class Builder {
+        private long mUid;
+        private long mUserId;
+        private long mTimestamp;
+        private int mAppUsageEventType;
+        private String mPackageName;
+        private int mInstanceId;
+        private String mTaskRootPackageName;
+
+        /** Sets the uid. */
+        @CanIgnoreReturnValue
+        public Builder setUid(final long uid) {
+            this.mUid = uid;
+            return this;
+        }
+
+        /** Sets the user ID. */
+        @CanIgnoreReturnValue
+        public Builder setUserId(final long userId) {
+            this.mUserId = userId;
+            return this;
+        }
+
+        /** Sets the timestamp. */
+        @CanIgnoreReturnValue
+        public Builder setTimestamp(final long timestamp) {
+            this.mTimestamp = timestamp;
+            return this;
+        }
+
+        /** Sets the app usage event type. */
+        @CanIgnoreReturnValue
+        public Builder setAppUsageEventType(final int appUsageEventType) {
+            this.mAppUsageEventType = appUsageEventType;
+            return this;
+        }
+
+        /** Sets the package name. */
+        @CanIgnoreReturnValue
+        public Builder setPackageName(final String packageName) {
+            this.mPackageName = packageName;
+            return this;
+        }
+
+        /** Sets the instance ID. */
+        @CanIgnoreReturnValue
+        public Builder setInstanceId(final int instanceId) {
+            this.mInstanceId = instanceId;
+            return this;
+        }
+
+        /** Sets the task root package name. */
+        @CanIgnoreReturnValue
+        public Builder setTaskRootPackageName(final String taskRootPackageName) {
+            this.mTaskRootPackageName = taskRootPackageName;
+            return this;
+        }
+
+        /** Builds the AppUsageEvent. */
+        public AppUsageEventEntity build() {
+            return new AppUsageEventEntity(
+                    mUid,
+                    mUserId,
+                    mTimestamp,
+                    mAppUsageEventType,
+                    mPackageName,
+                    mInstanceId,
+                    mTaskRootPackageName);
+        }
+
+        private Builder() {}
+    }
+
+
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java
index a50578b..9139c10 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryState.java
@@ -204,14 +204,14 @@
             return this;
         }
 
-        /** Sets the consumer type. */
+        /** Sets the battery information. */
         @CanIgnoreReturnValue
         public Builder setBatteryInformation(String batteryInformation) {
             this.mBatteryInformation = batteryInformation;
             return this;
         }
 
-        /** Sets the consumer type. */
+        /** Sets the battery information debug string. */
         @CanIgnoreReturnValue
         public Builder setBatteryInformationDebug(String batteryInformationDebug) {
             this.mBatteryInformationDebug = batteryInformationDebug;
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
index 64c629c..4c8df7e 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
@@ -25,7 +25,7 @@
 
 /** A {@link RoomDatabase} for battery usage states history. */
 @Database(
-        entities = {BatteryState.class},
+        entities = {BatteryState.class, AppUsageEventEntity.class},
         version = 1)
 public abstract class BatteryStateDatabase extends RoomDatabase {
     private static final String TAG = "BatteryStateDatabase";
@@ -34,13 +34,15 @@
 
     /** Provides DAO for battery state table. */
     public abstract BatteryStateDao batteryStateDao();
+    /** Provides DAO for app usage event table. */
+    public abstract AppUsageEventDao appUsageEventDao();
 
     /** Gets or creates an instance of {@link RoomDatabase}. */
     public static BatteryStateDatabase getInstance(Context context) {
         if (sBatteryStateDatabase == null) {
             sBatteryStateDatabase =
                     Room.databaseBuilder(
-                                    context, BatteryStateDatabase.class, "battery-usage-db-v6")
+                                    context, BatteryStateDatabase.class, "battery-usage-db-v7")
                             // Allows accessing data in the main thread for dumping bugreport.
                             .allowMainThreadQueries()
                             .fallbackToDestructiveMigration()
diff --git a/src/com/android/settings/fuelgauge/protos/Android.bp b/src/com/android/settings/fuelgauge/protos/Android.bp
index ab5e36b..2c63af4 100644
--- a/src/com/android/settings/fuelgauge/protos/Android.bp
+++ b/src/com/android/settings/fuelgauge/protos/Android.bp
@@ -13,4 +13,12 @@
         type: "lite",
     },
     srcs: ["fuelgauge_usage_state.proto"],
+}
+
+java_library {
+    name: "app-usage-event-protos-lite",
+    proto: {
+        type: "lite",
+    },
+    srcs: ["app_usage_event.proto"],
 }
\ No newline at end of file
diff --git a/src/com/android/settings/fuelgauge/protos/app_usage_event.proto b/src/com/android/settings/fuelgauge/protos/app_usage_event.proto
new file mode 100644
index 0000000..921fb0a
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/protos/app_usage_event.proto
@@ -0,0 +1,34 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.android.settings.fuelgauge.batteryusage";
+option java_outer_classname = "AppUsageEventProto";
+
+enum AppUsageEventType {
+  UNKNOWN = 0;
+  ACTIVITY_RESUMED = 1;
+  ACTIVITY_STOPPED = 2;
+  DEVICE_SHUTDOWN = 3;
+}
+
+message AppUsageEvent {
+  // Timestamp of the usage event.
+  optional int64 timestamp = 1;
+  // Type of the usage event.
+  optional AppUsageEventType type = 2;
+  // Package name of the app.
+  optional string package_name = 3;
+  // Instance ID for the activity. This is important for matching events of
+  // different event types for the same instance because an activity can be
+  // instantiated multiple times. Only available on Q builds after Dec 13 2018.
+  optional int32 instance_id = 4;
+  // Package name of the task root. For example, if a Twitter activity starts a
+  // Chrome activity within the same task, then while package_name is Chrome,
+  // task_root_package_name will be Twitter.
+  // Note: Activities that are task roots themselves (most activities) will have
+  // this field is populated as package_name.
+  // Note: The task root might be missing due to b/123404490.
+  optional string task_root_package_name = 5;
+  optional int64 user_id = 6;
+  optional int64 uid = 7;
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java
new file mode 100644
index 0000000..4b250a3
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/AppUsageDataLoaderTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+public final class AppUsageDataLoaderTest {
+    private Context mContext;
+    @Mock
+    private ContentResolver mMockContentResolver;
+    @Mock
+    private UserManager mUserManager;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = spy(RuntimeEnvironment.application);
+        doReturn(mContext).when(mContext).getApplicationContext();
+        doReturn(mMockContentResolver).when(mContext).getContentResolver();
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+        doReturn(new Intent()).when(mContext).registerReceiver(any(), any());
+    }
+
+    @Test
+    public void loadAppUsageData_withData_insertFakeDataIntoProvider() {
+        final List<AppUsageEvent> AppUsageEventList = new ArrayList<>();
+        final AppUsageEvent appUsageEvent = AppUsageEvent.newBuilder().setUid(0).build();
+        AppUsageEventList.add(appUsageEvent);
+        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
+        AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> AppUsageEventList;
+
+        AppUsageDataLoader.loadAppUsageData(mContext);
+
+        verify(mMockContentResolver).bulkInsert(any(), any());
+        verify(mMockContentResolver).notifyChange(any(), any());
+    }
+
+    @Test
+    public void loadAppUsageData_nullAppUsageEvents_notInsertDataIntoProvider() {
+        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> null;
+
+        AppUsageDataLoader.loadAppUsageData(mContext);
+
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
+
+    @Test
+    public void loadAppUsageData_nullUsageEventsList_notInsertDataIntoProvider() {
+        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
+        AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> null;
+
+        AppUsageDataLoader.loadAppUsageData(mContext);
+
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
+
+    @Test
+    public void loadAppUsageData_emptyUsageEventsList_notInsertDataIntoProvider() {
+        AppUsageDataLoader.sFakeAppUsageEventsSupplier = () -> new HashMap<>();
+        AppUsageDataLoader.sFakeUsageEventsListSupplier = () -> new ArrayList<>();
+
+        AppUsageDataLoader.loadAppUsageData(mContext);
+
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
index 5c143b1..9667760 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryHistEntryTest.java
@@ -68,7 +68,7 @@
         when(mMockBatteryEntry.getConsumerType())
                 .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
         final ContentValues values =
-                ConvertUtils.convertToContentValues(
+                ConvertUtils.convertBatteryEntryToContentValues(
                         mMockBatteryEntry,
                         mBatteryUsageStats,
                         /*batteryLevel=*/ 12,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
index d578b89..b43727d 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -30,6 +30,7 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
 import com.android.settings.testutils.BatteryTestUtils;
@@ -52,6 +53,8 @@
     private static final String PACKAGE_NAME1 = "com.android.settings1";
     private static final String PACKAGE_NAME2 = "com.android.settings2";
     private static final String PACKAGE_NAME3 = "com.android.settings3";
+    private static final long USER_ID1 = 1;
+    private static final long USER_ID2 = 2;
 
     private Context mContext;
     private BatteryUsageContentProvider mProvider;
@@ -178,6 +181,37 @@
     }
 
     @Test
+    public void query_appUsageTimestamp_returnsExpectedResult() throws Exception {
+        mProvider.onCreate();
+        final long timestamp1 = System.currentTimeMillis();
+        final long timestamp2 = timestamp1 + 2;
+        final long timestamp3 = timestamp1 + 4;
+        // Inserts some valid testing data.
+        BatteryTestUtils.insertDataToAppUsageEventTable(
+                mContext, USER_ID1, timestamp1, PACKAGE_NAME1);
+        BatteryTestUtils.insertDataToAppUsageEventTable(
+                mContext, USER_ID2, timestamp2, PACKAGE_NAME2);
+        BatteryTestUtils.insertDataToAppUsageEventTable(
+                mContext, USER_ID1, timestamp3, PACKAGE_NAME3);
+
+        final Cursor cursor1 = getCursorOfLatestTimestamp(USER_ID1);
+        assertThat(cursor1.getCount()).isEqualTo(1);
+        cursor1.moveToFirst();
+        assertThat(cursor1.getLong(0)).isEqualTo(timestamp3);
+
+        final Cursor cursor2 = getCursorOfLatestTimestamp(USER_ID2);
+        assertThat(cursor2.getCount()).isEqualTo(1);
+        cursor2.moveToFirst();
+        assertThat(cursor2.getLong(0)).isEqualTo(timestamp2);
+
+        final long notExistingUserId = 3;
+        final Cursor cursor3 = getCursorOfLatestTimestamp(notExistingUserId);
+        assertThat(cursor3.getCount()).isEqualTo(1);
+        cursor3.moveToFirst();
+        assertThat(cursor3.getLong(0)).isEqualTo(0);
+    }
+
+    @Test
     public void insert_batteryState_returnsExpectedResult() {
         mProvider.onCreate();
         final DeviceBatteryState deviceBatteryState =
@@ -267,6 +301,34 @@
     }
 
     @Test
+    public void insert_appUsageEvent_returnsExpectedResult() {
+        mProvider.onCreate();
+        ContentValues values = new ContentValues();
+        values.put(AppUsageEventEntity.KEY_UID, 101L);
+        values.put(AppUsageEventEntity.KEY_USER_ID, 1001L);
+        values.put(AppUsageEventEntity.KEY_TIMESTAMP, 10001L);
+        values.put(AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE, 1);
+        values.put(AppUsageEventEntity.KEY_PACKAGE_NAME, "com.android.settings1");
+        values.put(AppUsageEventEntity.KEY_INSTANCE_ID, 100001L);
+        values.put(AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME, "com.android.settings2");
+
+        final Uri uri = mProvider.insert(DatabaseUtils.APP_USAGE_EVENT_URI, values);
+
+        assertThat(uri).isEqualTo(DatabaseUtils.APP_USAGE_EVENT_URI);
+        // Verifies the AppUsageEventEntity content.
+        final List<AppUsageEventEntity> entities =
+                BatteryStateDatabase.getInstance(mContext).appUsageEventDao().getAllAfter(0);
+        assertThat(entities).hasSize(1);
+        assertThat(entities.get(0).uid).isEqualTo(101L);
+        assertThat(entities.get(0).userId).isEqualTo(1001L);
+        assertThat(entities.get(0).timestamp).isEqualTo(10001L);
+        assertThat(entities.get(0).appUsageEventType).isEqualTo(1);
+        assertThat(entities.get(0).packageName).isEqualTo("com.android.settings1");
+        assertThat(entities.get(0).instanceId).isEqualTo(100001L);
+        assertThat(entities.get(0).taskRootPackageName).isEqualTo("com.android.settings2");
+    }
+
+    @Test
     public void delete_throwsUnsupportedOperationException() {
         assertThrows(
                 UnsupportedOperationException.class,
@@ -293,12 +355,12 @@
         mProvider.setClock(fakeClock);
         final long currentTimestamp = currentTime.toMillis();
         // Inserts some valid testing data.
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, currentTimestamp - 2, PACKAGE_NAME1,
                 /*isFullChargeStart=*/ true);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, currentTimestamp - 1, PACKAGE_NAME2);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, currentTimestamp, PACKAGE_NAME3);
 
         final Uri batteryStateQueryContentUri =
@@ -307,7 +369,7 @@
                         .authority(DatabaseUtils.AUTHORITY)
                         .appendPath(DatabaseUtils.BATTERY_STATE_TABLE)
                         .appendQueryParameter(
-                                BatteryUsageContentProvider.QUERY_KEY_TIMESTAMP, queryTimestamp)
+                                DatabaseUtils.QUERY_KEY_TIMESTAMP, queryTimestamp)
                         .build();
 
         final Cursor cursor =
@@ -320,4 +382,22 @@
 
         return cursor;
     }
+
+    private Cursor getCursorOfLatestTimestamp(final long userId) {
+        final Uri appUsageLatestTimestampQueryContentUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(DatabaseUtils.AUTHORITY)
+                        .appendPath(DatabaseUtils.APP_USAGE_LATEST_TIMESTAMP_PATH)
+                        .appendQueryParameter(
+                                DatabaseUtils.QUERY_KEY_USERID, Long.toString(userId))
+                        .build();
+
+        return mProvider.query(
+                        appUsageLatestTimestampQueryContentUri,
+                        /*strings=*/ null,
+                        /*s=*/ null,
+                        /*strings1=*/ null,
+                        /*s1=*/ null);
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
index b67066d..514ac63 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -62,7 +62,7 @@
 
         // Inserts fake data into database for testing.
         final BatteryStateDatabase database = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, Clock.systemUTC().millis(), "com.android.systemui");
         mDao = database.batteryStateDao();
     }
@@ -170,9 +170,9 @@
     private void insertExpiredData(int shiftDay) {
         final long expiredTimeInMs =
                 Clock.systemUTC().millis() - Duration.ofDays(shiftDay).toMillis();
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, expiredTimeInMs - 1, "com.android.systemui");
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, expiredTimeInMs, "com.android.systemui");
         // Ensures the testing environment is correct.
         assertThat(mDao.getAllAfter(0)).hasSize(3);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
index 978930a..42cd7ef 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -17,16 +17,23 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.BatteryManager;
 import android.os.BatteryUsageStats;
 import android.os.LocaleList;
 import android.os.UserHandle;
 
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -44,6 +51,8 @@
     private Context mContext;
 
     @Mock
+    private PackageManager mMockPackageManager;
+    @Mock
     private BatteryUsageStats mBatteryUsageStats;
     @Mock
     private BatteryEntry mMockBatteryEntry;
@@ -52,10 +61,11 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mContext = spy(RuntimeEnvironment.application);
+        when(mContext.getPackageManager()).thenReturn(mMockPackageManager);
     }
 
     @Test
-    public void convertToContentValues_returnsExpectedContentValues() {
+    public void convertBatteryEntryToContentValues_returnsExpectedContentValues() {
         final int expectedType = 3;
         when(mMockBatteryEntry.getUid()).thenReturn(1001);
         when(mMockBatteryEntry.getLabel()).thenReturn("Settings");
@@ -76,7 +86,7 @@
                 .thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
 
         final ContentValues values =
-                ConvertUtils.convertToContentValues(
+                ConvertUtils.convertBatteryEntryToContentValues(
                         mMockBatteryEntry,
                         mBatteryUsageStats,
                         /*batteryLevel=*/ 12,
@@ -121,9 +131,9 @@
     }
 
     @Test
-    public void convertToContentValues_nullBatteryEntry_returnsExpectedContentValues() {
+    public void convertBatteryEntryToContentValues_nullBatteryEntry_returnsExpectedContentValues() {
         final ContentValues values =
-                ConvertUtils.convertToContentValues(
+                ConvertUtils.convertBatteryEntryToContentValues(
                         /*entry=*/ null,
                         /*batteryUsageStats=*/ null,
                         /*batteryLevel=*/ 12,
@@ -152,6 +162,31 @@
     }
 
     @Test
+    public void convertAppUsageEventToContentValues_returnsExpectedContentValues() {
+        final AppUsageEvent appUsageEvent =
+                AppUsageEvent.newBuilder()
+                        .setUid(101L)
+                        .setUserId(1001L)
+                        .setTimestamp(10001L)
+                        .setType(AppUsageEventType.ACTIVITY_RESUMED)
+                        .setPackageName("com.android.settings1")
+                        .setInstanceId(100001)
+                        .setTaskRootPackageName("com.android.settings2")
+                        .build();
+        final ContentValues values =
+                ConvertUtils.convertAppUsageEventToContentValues(appUsageEvent);
+        assertThat(values.getAsLong(AppUsageEventEntity.KEY_UID)).isEqualTo(101L);
+        assertThat(values.getAsLong(AppUsageEventEntity.KEY_USER_ID)).isEqualTo(1001L);
+        assertThat(values.getAsLong(AppUsageEventEntity.KEY_TIMESTAMP)).isEqualTo(10001L);
+        assertThat(values.getAsInteger(AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE)).isEqualTo(1);
+        assertThat(values.getAsString(AppUsageEventEntity.KEY_PACKAGE_NAME))
+                .isEqualTo("com.android.settings1");
+        assertThat(values.getAsInteger(AppUsageEventEntity.KEY_INSTANCE_ID)).isEqualTo(100001);
+        assertThat(values.getAsString(AppUsageEventEntity.KEY_TASK_ROOT_PACKAGE_NAME))
+                .isEqualTo("com.android.settings2");
+    }
+
+    @Test
     public void convertToBatteryHistEntry_returnsExpectedResult() {
         final int expectedType = 3;
         when(mMockBatteryEntry.getUid()).thenReturn(1001);
@@ -230,6 +265,77 @@
     }
 
     @Test
+    public void convertToAppUsageEvent_returnsExpectedResult()
+            throws PackageManager.NameNotFoundException {
+        final Event event = new Event();
+        event.mEventType = UsageEvents.Event.ACTIVITY_RESUMED;
+        event.mPackage = "com.android.settings1";
+        event.mTimeStamp = 101L;
+        event.mInstanceId = 100001;
+        event.mTaskRootPackage = "com.android.settings2";
+        when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1001);
+
+        final long userId = 2;
+        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
+                mContext, event, userId);
+        assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L);
+        assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.ACTIVITY_RESUMED);
+        assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
+        assertThat(appUsageEvent.getInstanceId()).isEqualTo(100001);
+        assertThat(appUsageEvent.getTaskRootPackageName()).isEqualTo("com.android.settings2");
+        assertThat(appUsageEvent.getUid()).isEqualTo(1001L);
+        assertThat(appUsageEvent.getUserId()).isEqualTo(userId);
+    }
+
+    @Test
+    public void convertToAppUsageEvent_emptyInstanceIdAndRootName_returnsExpectedResult()
+            throws PackageManager.NameNotFoundException {
+        final Event event = new Event();
+        event.mEventType = UsageEvents.Event.DEVICE_SHUTDOWN;
+        event.mPackage = "com.android.settings1";
+        event.mTimeStamp = 101L;
+        when(mMockPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(1001);
+
+        final long userId = 1;
+        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
+                mContext, event, userId);
+        assertThat(appUsageEvent.getTimestamp()).isEqualTo(101L);
+        assertThat(appUsageEvent.getType()).isEqualTo(AppUsageEventType.DEVICE_SHUTDOWN);
+        assertThat(appUsageEvent.getPackageName()).isEqualTo("com.android.settings1");
+        assertThat(appUsageEvent.getInstanceId()).isEqualTo(0);
+        assertThat(appUsageEvent.getTaskRootPackageName()).isEqualTo("");
+        assertThat(appUsageEvent.getUid()).isEqualTo(1001L);
+        assertThat(appUsageEvent.getUserId()).isEqualTo(userId);
+    }
+
+    @Test
+    public void convertToAppUsageEvent_emptyPackageName_returnsNull() {
+        final Event event = new Event();
+        event.mPackage = null;
+
+        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
+                mContext, event, /*userId=*/ 0);
+
+        assertThat(appUsageEvent).isNull();
+    }
+
+    @Test
+    public void convertToAppUsageEvent_failToGetUid_returnsNull()
+            throws PackageManager.NameNotFoundException  {
+        final Event event = new Event();
+        event.mEventType = UsageEvents.Event.DEVICE_SHUTDOWN;
+        event.mPackage = "com.android.settings1";
+        when(mMockPackageManager.getPackageUidAsUser(any(), anyInt()))
+                .thenThrow(new PackageManager.NameNotFoundException());
+
+        final long userId = 1;
+        final AppUsageEvent appUsageEvent = ConvertUtils.convertToAppUsageEvent(
+                mContext, event, userId);
+
+        assertThat(appUsageEvent).isNull();
+    }
+
+    @Test
     public void getLocale_nullContext_returnDefaultLocale() {
         assertThat(ConvertUtils.getLocale(/*context=*/ null))
                 .isEqualTo(Locale.getDefault());
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
index 210a21b..ce958c9 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
@@ -25,12 +26,19 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
+import android.app.usage.IUsageStatsManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageEvents.Event;
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.UserInfo;
 import android.os.BatteryConsumer;
 import android.os.BatteryManager;
 import android.os.BatteryUsageStats;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.UserManager;
 import android.text.format.DateUtils;
 
 import com.android.settings.fuelgauge.BatteryUtils;
@@ -65,10 +73,13 @@
 
     @Mock private Intent mIntent;
     @Mock private BatteryUsageStats mBatteryUsageStats;
+    @Mock private UserManager mUserManager;
+    @Mock private IUsageStatsManager mUsageStatsManager;
     @Mock private BatteryEntry mMockBatteryEntry1;
     @Mock private BatteryEntry mMockBatteryEntry2;
     @Mock private BatteryEntry mMockBatteryEntry3;
     @Mock private BatteryEntry mMockBatteryEntry4;
+    @Mock private UsageEvents mUsageEvents1;
 
 
     @Before
@@ -80,9 +91,11 @@
         mFeatureFactory = FakeFeatureFactory.setupForTest();
         mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider;
 
+        DataProcessor.sUsageStatsManager = mUsageStatsManager;
         doReturn(mIntent).when(mContext).registerReceiver(any(), any());
         doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt());
         doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt());
+        doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
     }
 
     @Test
@@ -146,6 +159,86 @@
     }
 
     @Test
+    public void getAppUsageEvents_returnExpectedResult() throws RemoteException {
+        UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);
+        final List<UserInfo> userInfoList = new ArrayList<>();
+        userInfoList.add(userInfo);
+        doReturn(userInfoList).when(mUserManager).getAliveUsers();
+        doReturn(true).when(mUserManager).isUserUnlocked(userInfo.id);
+        doReturn(mUsageEvents1)
+                .when(mUsageStatsManager)
+                .queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
+
+        final Map<Long, UsageEvents> resultMap = DataProcessor.getAppUsageEvents(mContext);
+
+        assertThat(resultMap.size()).isEqualTo(1);
+        assertThat(resultMap.get(Long.valueOf(userInfo.id))).isEqualTo(mUsageEvents1);
+    }
+
+    @Test
+    public void getAppUsageEvents_lockedUser_returnNull() throws RemoteException {
+        UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);
+        final List<UserInfo> userInfoList = new ArrayList<>();
+        userInfoList.add(userInfo);
+        doReturn(userInfoList).when(mUserManager).getAliveUsers();
+        // Test locked user.
+        doReturn(false).when(mUserManager).isUserUnlocked(userInfo.id);
+
+        final Map<Long, UsageEvents> resultMap = DataProcessor.getAppUsageEvents(mContext);
+
+        assertThat(resultMap).isNull();
+    }
+
+    @Test
+    public void getAppUsageEvents_nullUsageEvents_returnNull() throws RemoteException {
+        UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);
+        final List<UserInfo> userInfoList = new ArrayList<>();
+        userInfoList.add(userInfo);
+        doReturn(userInfoList).when(mUserManager).getAliveUsers();
+        doReturn(true).when(mUserManager).isUserUnlocked(userInfo.id);
+        doReturn(null)
+                .when(mUsageStatsManager).queryEventsForUser(anyLong(), anyLong(), anyInt(), any());
+
+        final Map<Long, UsageEvents> resultMap = DataProcessor.getAppUsageEvents(mContext);
+
+        assertThat(resultMap).isNull();
+    }
+
+    @Test public void generateAppUsageEventListFromUsageEvents_returnExpectedResult() {
+        Event event1 = getUsageEvent(Event.NOTIFICATION_INTERRUPTION, /*timestamp=*/ 1);
+        Event event2 = getUsageEvent(Event.ACTIVITY_RESUMED, /*timestamp=*/ 2);
+        Event event3 = getUsageEvent(Event.ACTIVITY_STOPPED, /*timestamp=*/ 3);
+        Event event4 = getUsageEvent(Event.DEVICE_SHUTDOWN, /*timestamp=*/ 4);
+        Event event5 = getUsageEvent(Event.ACTIVITY_RESUMED, /*timestamp=*/ 5);
+        event5.mPackage = null;
+        List<Event> events1 = new ArrayList<>();
+        events1.add(event1);
+        events1.add(event2);
+        List<Event> events2 = new ArrayList<>();
+        events2.add(event3);
+        events2.add(event4);
+        events2.add(event5);
+        final long userId1 = 101L;
+        final long userId2 = 102L;
+        final long userId3 = 103L;
+        final Map<Long, UsageEvents> appUsageEvents = new HashMap();
+        appUsageEvents.put(userId1, getUsageEvents(events1));
+        appUsageEvents.put(userId2, getUsageEvents(events2));
+        appUsageEvents.put(userId3, getUsageEvents(new ArrayList<>()));
+
+        final List<AppUsageEvent> appUsageEventList =
+                DataProcessor.generateAppUsageEventListFromUsageEvents(mContext, appUsageEvents);
+
+        assertThat(appUsageEventList.size()).isEqualTo(3);
+        assetAppUsageEvent(
+                appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 2);
+        assetAppUsageEvent(
+                appUsageEventList.get(1), AppUsageEventType.ACTIVITY_STOPPED, /*timestamp=*/ 3);
+        assetAppUsageEvent(
+                appUsageEventList.get(2), AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 4);
+    }
+
+    @Test
     public void getHistoryMapWithExpectedTimestamps_emptyHistoryMap_returnEmptyMap() {
         assertThat(DataProcessor
                 .getHistoryMapWithExpectedTimestamps(mContext, new HashMap<>()))
@@ -1213,6 +1306,30 @@
         return new BatteryHistEntry(values);
     }
 
+    private UsageEvents getUsageEvents(final List<Event> events) {
+        UsageEvents usageEvents = new UsageEvents(events, new String[] {"package"});
+        Parcel parcel = Parcel.obtain();
+        parcel.setDataPosition(0);
+        usageEvents.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        return UsageEvents.CREATOR.createFromParcel(parcel);
+    }
+
+    private Event getUsageEvent(
+            final int eventType, final long timestamp) {
+        final Event event = new Event();
+        event.mEventType = eventType;
+        event.mPackage = "package";
+        event.mTimeStamp = timestamp;
+        return event;
+    }
+
+    private void assetAppUsageEvent(
+            final AppUsageEvent event, final AppUsageEventType eventType, final long timestamp) {
+        assertThat(event.getType()).isEqualTo(eventType);
+        assertThat(event.getTimestamp()).isEqualTo(timestamp);
+    }
+
     private static void verifyExpectedBatteryLevelData(
             final BatteryLevelData resultData,
             final List<Long> expectedDailyTimestamps,
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
index 0c66267..a04baa0 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 
 import android.content.ContentResolver;
 import android.content.ContentValues;
@@ -34,6 +35,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.testutils.BatteryTestUtils;
 
 import org.junit.Before;
@@ -94,6 +96,53 @@
     }
 
     @Test
+    public void sendAppUsageEventData_returnsExpectedList() {
+        // Configures the testing AppUsageEvent data.
+        final List<AppUsageEvent> appUsageEventList = new ArrayList<>();
+        final AppUsageEvent appUsageEvent1 =
+                AppUsageEvent.newBuilder()
+                        .setUid(101L)
+                        .setType(AppUsageEventType.ACTIVITY_RESUMED)
+                        .build();
+        final AppUsageEvent appUsageEvent2 =
+                AppUsageEvent.newBuilder()
+                        .setUid(1001L)
+                        .setType(AppUsageEventType.ACTIVITY_STOPPED)
+                        .build();
+        final AppUsageEvent appUsageEvent3 =
+                AppUsageEvent.newBuilder()
+                        .setType(AppUsageEventType.DEVICE_SHUTDOWN)
+                        .build();
+        appUsageEventList.add(appUsageEvent1);
+        appUsageEventList.add(appUsageEvent2);
+        appUsageEventList.add(appUsageEvent3);
+
+        final List<ContentValues> valuesList =
+                DatabaseUtils.sendAppUsageEventData(mContext, appUsageEventList);
+
+        assertThat(valuesList).hasSize(2);
+        assertThat(valuesList.get(0).getAsInteger(AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE))
+                .isEqualTo(1);
+        assertThat(valuesList.get(1).getAsInteger(AppUsageEventEntity.KEY_APP_USAGE_EVENT_TYPE))
+                .isEqualTo(2);
+        // Verifies the inserted ContentValues into content provider.
+        final ContentValues[] valuesArray =
+                new ContentValues[] {valuesList.get(0), valuesList.get(1)};
+        verify(mMockContentResolver).bulkInsert(
+                DatabaseUtils.APP_USAGE_EVENT_URI, valuesArray);
+        verify(mMockContentResolver).notifyChange(
+                DatabaseUtils.APP_USAGE_EVENT_URI, /*observer=*/ null);
+    }
+
+    @Test
+    public void sendAppUsageEventData_emptyAppUsageEventList_notSend() {
+        final List<ContentValues> valuesList =
+                DatabaseUtils.sendAppUsageEventData(mContext, new ArrayList<>());
+        assertThat(valuesList).hasSize(0);
+        verifyNoMoreInteractions(mMockContentResolver);
+    }
+
+    @Test
     public void sendBatteryEntryData_nullBatteryIntent_returnsNullValue() {
         doReturn(null).when(mContext).registerReceiver(any(), any());
         assertThat(
@@ -123,8 +172,8 @@
 
         assertThat(valuesList).hasSize(2);
         // Verifies the ContentValues content.
-        verifyContentValues(0.5, valuesList.get(0));
-        verifyContentValues(0.0, valuesList.get(1));
+        verifyBatteryEntryContentValues(0.5, valuesList.get(0));
+        verifyBatteryEntryContentValues(0.0, valuesList.get(1));
         // Verifies the inserted ContentValues into content provider.
         final ContentValues[] valuesArray =
                 new ContentValues[] {valuesList.get(0), valuesList.get(1)};
@@ -146,7 +195,7 @@
                         /*isFullChargeStart=*/ false);
 
         assertThat(valuesList).hasSize(1);
-        verifyFakeContentValues(valuesList.get(0));
+        verifyFakeBatteryEntryContentValues(valuesList.get(0));
         // Verifies the inserted ContentValues into content provider.
         verify(mMockContentResolver).insert(any(), any());
         verify(mMockContentResolver).notifyChange(
@@ -165,7 +214,7 @@
                         /*isFullChargeStart=*/ false);
 
         assertThat(valuesList).hasSize(1);
-        verifyFakeContentValues(valuesList.get(0));
+        verifyFakeBatteryEntryContentValues(valuesList.get(0));
         // Verifies the inserted ContentValues into content provider.
         verify(mMockContentResolver).insert(any(), any());
         verify(mMockContentResolver).notifyChange(
@@ -184,7 +233,7 @@
                         /*isFullChargeStart=*/ false);
 
         assertThat(valuesList).hasSize(1);
-        verifyFakeContentValues(valuesList.get(0));
+        verifyFakeBatteryEntryContentValues(valuesList.get(0));
         // Verifies the inserted ContentValues into content provider.
         verify(mMockContentResolver).insert(any(), any());
         verify(mMockContentResolver).notifyChange(
@@ -192,13 +241,49 @@
     }
 
     @Test
+    public void getAppUsageStartTimestampOfUser_emptyCursorContent_returnEarliestTimestamp() {
+        final MatrixCursor cursor =
+                new MatrixCursor(new String[] {AppUsageEventEntity.KEY_TIMESTAMP});
+        DatabaseUtils.sFakeAppUsageLatestTimestampSupplier = () -> cursor;
+
+        final long earliestTimestamp = 10001L;
+        assertThat(DatabaseUtils.getAppUsageStartTimestampOfUser(
+                mContext, /*userId=*/ 0, earliestTimestamp)).isEqualTo(earliestTimestamp);
+    }
+
+    @Test
+    public void getAppUsageStartTimestampOfUser_nullCursor_returnEarliestTimestamp() {
+        DatabaseUtils.sFakeAppUsageLatestTimestampSupplier = () -> null;
+        final long earliestTimestamp = 10001L;
+        assertThat(DatabaseUtils.getAppUsageStartTimestampOfUser(
+                mContext, /*userId=*/ 0, earliestTimestamp)).isEqualTo(earliestTimestamp);
+    }
+
+    @Test
+    public void getAppUsageStartTimestampOfUser_returnExpectedResult() {
+        final long returnedTimestamp = 10001L;
+        final MatrixCursor cursor =
+                new MatrixCursor(new String[] {AppUsageEventEntity.KEY_TIMESTAMP});
+        // Adds fake data into the cursor.
+        cursor.addRow(new Object[] {returnedTimestamp});
+        DatabaseUtils.sFakeAppUsageLatestTimestampSupplier = () -> cursor;
+
+        final long earliestTimestamp1 = 1001L;
+        assertThat(DatabaseUtils.getAppUsageStartTimestampOfUser(
+                mContext, /*userId=*/ 0, earliestTimestamp1)).isEqualTo(returnedTimestamp);
+        final long earliestTimestamp2 = 100001L;
+        assertThat(DatabaseUtils.getAppUsageStartTimestampOfUser(
+                mContext, /*userId=*/ 0, earliestTimestamp2)).isEqualTo(earliestTimestamp2);
+    }
+
+    @Test
     public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() {
         final MatrixCursor cursor = new MatrixCursor(
                 new String[] {
                         BatteryHistEntry.KEY_UID,
                         BatteryHistEntry.KEY_USER_ID,
                         BatteryHistEntry.KEY_TIMESTAMP});
-        doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
+        DatabaseUtils.sFakeBatteryStateSupplier = () -> cursor;
 
         assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
                 mContext, /*calendar=*/ null)).isEmpty();
@@ -206,7 +291,7 @@
 
     @Test
     public void getHistoryMapSinceLastFullCharge_nullCursor_returnEmptyMap() {
-        doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
+        DatabaseUtils.sFakeBatteryStateSupplier = () -> null;
         assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
                 mContext, /*calendar=*/ null)).isEmpty();
     }
@@ -216,7 +301,6 @@
         final Long timestamp1 = Long.valueOf(1001L);
         final Long timestamp2 = Long.valueOf(1002L);
         final MatrixCursor cursor = getMatrixCursor();
-        doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
         // Adds fake data into the cursor.
         cursor.addRow(new Object[] {
                 "app name1", timestamp1, 1, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
@@ -226,6 +310,7 @@
                 "app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
         cursor.addRow(new Object[] {
                 "app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
+        DatabaseUtils.sFakeBatteryStateSupplier = () -> cursor;
 
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
                 DatabaseUtils.getHistoryMapSinceLastFullCharge(
@@ -251,9 +336,7 @@
         doReturn(mMockContext).when(mContext).createPackageContextAsUser(
                 "com.fake.package", /*flags=*/ 0, UserHandle.OWNER);
         BatteryTestUtils.setWorkProfile(mContext);
-        doReturn(getMatrixCursor()).when(mMockContentResolver2)
-                .query(any(), any(), any(), any());
-        doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
+        DatabaseUtils.sFakeBatteryStateSupplier = () -> getMatrixCursor();
 
         final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
                 DatabaseUtils.getHistoryMapSinceLastFullCharge(
@@ -262,7 +345,8 @@
         assertThat(batteryHistMap).isEmpty();
     }
 
-    private static void verifyContentValues(double consumedPower, ContentValues values) {
+    private static void verifyBatteryEntryContentValues(
+            double consumedPower, ContentValues values) {
         final BatteryInformation batteryInformation =
                 ConvertUtils.getBatteryInformation(
                         values, BatteryHistEntry.KEY_BATTERY_INFORMATION);
@@ -275,7 +359,7 @@
                 .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
     }
 
-    private static void verifyFakeContentValues(ContentValues values) {
+    private static void verifyFakeBatteryEntryContentValues(ContentValues values) {
         final BatteryInformation batteryInformation =
                 ConvertUtils.getBatteryInformation(
                         values, BatteryHistEntry.KEY_BATTERY_INFORMATION);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java
index 3693209..c9a3e64 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiverTest.java
@@ -62,7 +62,7 @@
 
         // Inserts fake data into database for testing.
         final BatteryStateDatabase database = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, Clock.systemUTC().millis(), "com.android.systemui");
         mDao = database.batteryStateDao();
     }
@@ -122,9 +122,9 @@
     private void insertExpiredData(int shiftDay) {
         final long expiredTimeInMs =
                 Clock.systemUTC().millis() - Duration.ofDays(shiftDay).toMillis();
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, expiredTimeInMs - 1, "com.android.systemui");
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, expiredTimeInMs, "com.android.systemui");
         // Ensures the testing environment is correct.
         assertThat(mDao.getAllAfter(0)).hasSize(3);
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
index 87e253f..bbbf45f 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/bugreport/BugReportContentProviderTest.java
@@ -53,9 +53,9 @@
         mBugReportContentProvider.attachInfo(mContext, /*info=*/ null);
         // Inserts fake data into database for testing.
         BatteryTestUtils.setUpBatteryStateDatabase(mContext);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, System.currentTimeMillis(), PACKAGE_NAME1);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, System.currentTimeMillis(), PACKAGE_NAME2);
     }
 
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java
new file mode 100644
index 0000000..ade585f
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDaoTest.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.database.Cursor;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settings.testutils.BatteryTestUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.List;
+
+/** Tests for {@link AppUsageEventDao}. */
+@RunWith(RobolectricTestRunner.class)
+public final class AppUsageEventDaoTest {
+    private static final long TIMESTAMP1 = System.currentTimeMillis();
+    private static final long TIMESTAMP2 = System.currentTimeMillis() + 2;
+    private static final long TIMESTAMP3 = System.currentTimeMillis() + 4;
+    private static final long USER_ID1 = 1;
+    private static final long USER_ID2 = 2;
+    private static final String PACKAGE_NAME1 = "com.android.apps.settings";
+    private static final String PACKAGE_NAME2 = "com.android.apps.calendar";
+    private static final String PACKAGE_NAME3 = "com.android.apps.gmail";
+
+    private Context mContext;
+    private BatteryStateDatabase mDatabase;
+    private AppUsageEventDao mAppUsageEventDao;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
+        mAppUsageEventDao = mDatabase.appUsageEventDao();
+        BatteryTestUtils.insertDataToAppUsageEventTable(
+                mContext, USER_ID1, TIMESTAMP3, PACKAGE_NAME3);
+        BatteryTestUtils.insertDataToAppUsageEventTable(
+                mContext, USER_ID2, TIMESTAMP2, PACKAGE_NAME2);
+        BatteryTestUtils.insertDataToAppUsageEventTable(
+                mContext, USER_ID1, TIMESTAMP1, PACKAGE_NAME1, /*multiple=*/ true);
+    }
+
+    @After
+    public void closeDb() {
+        mDatabase.close();
+        BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
+    }
+
+    @Test
+    public void appUsageEventDao_insertAll() throws Exception {
+        final List<AppUsageEventEntity> entities = mAppUsageEventDao.getAllAfter(TIMESTAMP1);
+        assertThat(entities).hasSize(2);
+        // Verifies the queried battery states.
+        assertAppUsageEvent(entities.get(0), TIMESTAMP3, PACKAGE_NAME3);
+        assertAppUsageEvent(entities.get(1), TIMESTAMP2, PACKAGE_NAME2);
+    }
+
+    @Test
+    public void appUsageEventDao_getLatestTimestampOfUser() throws Exception {
+        final Cursor cursor1 = mAppUsageEventDao.getLatestTimestampOfUser(USER_ID1);
+        assertThat(cursor1.getCount()).isEqualTo(1);
+        cursor1.moveToFirst();
+        assertThat(cursor1.getLong(0)).isEqualTo(TIMESTAMP3);
+
+        final Cursor cursor2 = mAppUsageEventDao.getLatestTimestampOfUser(USER_ID2);
+        assertThat(cursor2.getCount()).isEqualTo(1);
+        cursor2.moveToFirst();
+        assertThat(cursor2.getLong(0)).isEqualTo(TIMESTAMP2);
+
+        final long notExistingUserId = 3;
+        final Cursor cursor3 = mAppUsageEventDao.getLatestTimestampOfUser(notExistingUserId);
+        assertThat(cursor3.getCount()).isEqualTo(1);
+        cursor3.moveToFirst();
+        assertThat(cursor3.getLong(0)).isEqualTo(0);
+    }
+
+    @Test
+    public void appUsageEventDao_clearAllBefore() throws Exception {
+        mAppUsageEventDao.clearAllBefore(TIMESTAMP2);
+
+        final List<AppUsageEventEntity> entities = mAppUsageEventDao.getAllAfter(0);
+        assertThat(entities).hasSize(1);
+        // Verifies the queried battery state.
+        assertAppUsageEvent(entities.get(0), TIMESTAMP3, PACKAGE_NAME3);
+    }
+
+    @Test
+    public void appUsageEventDao_clearAll() throws Exception {
+        assertThat(mAppUsageEventDao.getAllAfter(0)).hasSize(3);
+        mAppUsageEventDao.clearAll();
+        assertThat(mAppUsageEventDao.getAllAfter(0)).isEmpty();
+    }
+
+    @Test
+    public void getInstance_createNewInstance() throws Exception {
+        BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
+        assertThat(BatteryStateDatabase.getInstance(mContext)).isNotNull();
+    }
+
+    private static void assertAppUsageEvent(
+            AppUsageEventEntity entity, long timestamp, String packageName) {
+        assertThat(entity.timestamp).isEqualTo(timestamp);
+        assertThat(entity.packageName).isEqualTo(packageName);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventEntityTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventEntityTest.java
new file mode 100644
index 0000000..3cbf845
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventEntityTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+/** Tests for {@link AppUsageEventEntity}. */
+@RunWith(RobolectricTestRunner.class)
+public final class AppUsageEventEntityTest {
+    @Test
+    public void testBuilder_returnsExpectedResult() {
+        final long uid = 101L;
+        final long userId = 1001L;
+        final long timestamp = 10001L;
+        final int appUsageEventType = 1;
+        final String packageName = "com.android.settings1";
+        final int instanceId = 100001;
+        final String taskRootPackageName = "com.android.settings2";
+
+        AppUsageEventEntity entity = AppUsageEventEntity
+                .newBuilder()
+                .setUid(uid)
+                .setUserId(userId)
+                .setTimestamp(timestamp)
+                .setAppUsageEventType(appUsageEventType)
+                .setPackageName(packageName)
+                .setInstanceId(instanceId)
+                .setTaskRootPackageName(taskRootPackageName)
+                .build();
+
+        // Verifies the app relative information.
+        assertThat(entity.uid).isEqualTo(uid);
+        assertThat(entity.userId).isEqualTo(userId);
+        assertThat(entity.timestamp).isEqualTo(timestamp);
+        assertThat(entity.appUsageEventType).isEqualTo(appUsageEventType);
+        assertThat(entity.packageName).isEqualTo(packageName);
+        assertThat(entity.instanceId).isEqualTo(instanceId);
+        assertThat(entity.taskRootPackageName).isEqualTo(taskRootPackageName);
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
index 3b887ad..57cf648 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDaoTest.java
@@ -53,9 +53,9 @@
         mContext = ApplicationProvider.getApplicationContext();
         mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
         mBatteryStateDao = mDatabase.batteryStateDao();
-        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP3, PACKAGE_NAME3);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP2, PACKAGE_NAME2);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(
+        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP3, PACKAGE_NAME3);
+        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP2, PACKAGE_NAME2);
+        BatteryTestUtils.insertDataToBatteryStateTable(
                 mContext, TIMESTAMP1, PACKAGE_NAME1, /*multiple=*/ true,
                 /*isFullChargeStart=*/ true);
     }
@@ -102,9 +102,9 @@
     public void batteryStateDao_getCursorSinceLastFullCharge_noFullChargeData_returnSevenDaysData()
             throws Exception {
         mBatteryStateDao.clearAll();
-        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP3, PACKAGE_NAME3);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP2, PACKAGE_NAME2);
-        BatteryTestUtils.insertDataToBatteryStateDatabase(mContext, TIMESTAMP1, PACKAGE_NAME1);
+        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP3, PACKAGE_NAME3);
+        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP2, PACKAGE_NAME2);
+        BatteryTestUtils.insertDataToBatteryStateTable(mContext, TIMESTAMP1, PACKAGE_NAME1);
         final Cursor cursor = mBatteryStateDao.getCursorSinceLastFullCharge(TIMESTAMP2);
         assertThat(cursor.getCount()).isEqualTo(2);
         assertThat(cursor.getColumnCount()).isEqualTo(CURSOR_COLUMN_SIZE);
diff --git a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
index f119d6e6..c7680b5 100644
--- a/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
+++ b/tests/robotests/src/com/android/settings/testutils/BatteryTestUtils.java
@@ -26,6 +26,8 @@
 import com.android.settings.fuelgauge.batteryusage.BatteryInformation;
 import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
 import com.android.settings.fuelgauge.batteryusage.DeviceBatteryState;
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventDao;
+import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
@@ -70,21 +72,21 @@
     }
 
     /** Inserts a fake data into the database for testing. */
-    public static void insertDataToBatteryStateDatabase(
+    public static void insertDataToBatteryStateTable(
             Context context, long timestamp, String packageName) {
-        insertDataToBatteryStateDatabase(
+        insertDataToBatteryStateTable(
                 context, timestamp, packageName, /*multiple=*/ false, /*isFullChargeStart=*/ false);
     }
 
     /** Inserts a fake data into the database for testing. */
-    public static void insertDataToBatteryStateDatabase(
+    public static void insertDataToBatteryStateTable(
             Context context, long timestamp, String packageName, boolean isFullChargeStart) {
-        insertDataToBatteryStateDatabase(
+        insertDataToBatteryStateTable(
                 context, timestamp, packageName, /*multiple=*/ false, isFullChargeStart);
     }
 
     /** Inserts a fake data into the database for testing. */
-    public static void insertDataToBatteryStateDatabase(
+    public static void insertDataToBatteryStateTable(
             Context context, long timestamp, String packageName, boolean multiple,
             boolean isFullChargeStart) {
         DeviceBatteryState deviceBatteryState =
@@ -133,6 +135,34 @@
         }
     }
 
+    /** Inserts a fake data into the database for testing. */
+    public static void insertDataToAppUsageEventTable(
+            Context context, long userId, long timestamp, String packageName) {
+        insertDataToAppUsageEventTable(
+                context, userId, timestamp, packageName, /*multiple=*/ false);
+    }
+
+    /** Inserts a fake data into the database for testing. */
+    public static void insertDataToAppUsageEventTable(
+            Context context, long userId, long timestamp, String packageName, boolean multiple) {
+        final AppUsageEventEntity entity =
+                new AppUsageEventEntity(
+                        /*uid=*/ 101L,
+                        userId,
+                        timestamp,
+                        /*appUsageEventType=*/ 2,
+                        packageName,
+                        /*instanceId=*/ 10001,
+                        /*taskRootPackageName=*/ "com.android.settings");
+        AppUsageEventDao dao =
+                BatteryStateDatabase.getInstance(context).appUsageEventDao();
+        if (multiple) {
+            dao.insertAll(ImmutableList.of(entity));
+        } else {
+            dao.insert(entity);
+        }
+    }
+
     public static Intent getCustomBatteryIntent(int plugged, int level, int scale, int status) {
         Intent intent = new Intent();
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, plugged);