Exclude screen on time in battery usage page when the device is in charging.

Bug: 265751163
Fix: 265751163
Test: manual
Change-Id: I4ed71e1d6fad56a7cbfc9cd47ed4d791f45261ce
diff --git a/Android.bp b/Android.bp
index ea2c983..4d07913 100644
--- a/Android.bp
+++ b/Android.bp
@@ -81,6 +81,7 @@
         "jsr305",
         "net-utils-framework-common",
         "app-usage-event-protos-lite",
+        "battery-event-protos-lite",
         "settings-contextual-card-protos-lite",
         "settings-log-bridge-protos-lite",
         "settings-telephony-protos-lite",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index ccf002a..21b3fa4 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -3110,6 +3110,7 @@
             <intent-filter>
                 <action android:name="android.intent.action.BATTERY_LEVEL_CHANGED"/>
                 <action android:name="com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA"/>
+                <action android:name="com.android.settings.battery.action.ACTION_BATTERY_PLUGGING"/>
                 <action android:name="com.android.settings.battery.action.ACTION_BATTERY_UNPLUGGING"/>
             </intent-filter>
         </receiver>
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
index a437877..07a97dc 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageBroadcastReceiver.java
@@ -30,6 +30,8 @@
 import com.android.settingslib.fuelgauge.BatteryStatus;
 
 import java.time.Duration;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /** A {@link BatteryUsageBroadcastReceiver} for battery usage data requesting. */
 public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver {
@@ -37,7 +39,10 @@
     /** An intent action to request Settings to clear cache data. */
     public static final String ACTION_CLEAR_BATTERY_CACHE_DATA =
             "com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA";
-    /** An intent action to request Settings to clear cache data. */
+    /** An intent action for power is plugging. */
+    public static final String ACTION_BATTERY_PLUGGING =
+            "com.android.settings.battery.action.ACTION_BATTERY_PLUGGING";
+    /** An intent action for power is unplugging. */
     public static final String ACTION_BATTERY_UNPLUGGING =
             "com.android.settings.battery.action.ACTION_BATTERY_UNPLUGGING";
 
@@ -49,6 +54,8 @@
     @VisibleForTesting
     boolean mFetchBatteryUsageData = false;
 
+    private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+
     @Override
     public void onReceive(Context context, Intent intent) {
         if (intent == null || intent.getAction() == null) {
@@ -70,7 +77,11 @@
                     tryToFetchUsageData(context);
                 }
                 break;
+            case ACTION_BATTERY_PLUGGING:
+                sendBatteryEventData(context, BatteryEventType.POWER_CONNECTED);
+                break;
             case ACTION_BATTERY_UNPLUGGING:
+                sendBatteryEventData(context, BatteryEventType.POWER_DISCONNECTED);
                 // Only when fullChargeIntentAction is ACTION_POWER_DISCONNECTED,
                 // ACTION_BATTERY_UNPLUGGING will be considered as the full charge event and then
                 // start usage events fetching.
@@ -106,4 +117,12 @@
         mFetchBatteryUsageData = true;
         BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ true);
     }
+
+    private void sendBatteryEventData(Context context, BatteryEventType batteryEventType) {
+        final long timestamp = System.currentTimeMillis();
+        final Intent intent = BatteryUtils.getBatteryIntent(context);
+        final int batteryLevel = BatteryStatus.getBatteryLevel(intent);
+        mExecutor.execute(() -> DatabaseUtils.sendBatteryEventData(context,
+                ConvertUtils.convertToBatteryEvent(timestamp, batteryEventType, batteryLevel)));
+    }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
index 42937e5..1b2d4cd 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java
@@ -31,6 +31,8 @@
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventDao;
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryEventDao;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDao;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
@@ -52,6 +54,7 @@
     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 int BATTERY_EVENT_CODE = 4;
 
     private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
 
@@ -68,11 +71,16 @@
                 DatabaseUtils.AUTHORITY,
                 /*path=*/ DatabaseUtils.APP_USAGE_EVENT_TABLE,
                 /*code=*/ APP_USAGE_EVENT_CODE);
+        sUriMatcher.addURI(
+                DatabaseUtils.AUTHORITY,
+                /*path=*/ DatabaseUtils.BATTERY_EVENT_TABLE,
+                /*code=*/ BATTERY_EVENT_CODE);
     }
 
     private Clock mClock;
     private BatteryStateDao mBatteryStateDao;
     private AppUsageEventDao mAppUsageEventDao;
+    private BatteryEventDao mBatteryEventDao;
 
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     public void setClock(Clock clock) {
@@ -88,6 +96,7 @@
         mClock = Clock.systemUTC();
         mBatteryStateDao = BatteryStateDatabase.getInstance(getContext()).batteryStateDao();
         mAppUsageEventDao = BatteryStateDatabase.getInstance(getContext()).appUsageEventDao();
+        mBatteryEventDao = BatteryStateDatabase.getInstance(getContext()).batteryEventDao();
         Log.w(TAG, "create content provider from " + getCallingPackage());
         return true;
     }
@@ -107,6 +116,8 @@
                 return getAppUsageEvents(uri);
             case APP_USAGE_LATEST_TIMESTAMP_CODE:
                 return getAppUsageLatestTimestamp(uri);
+            case BATTERY_EVENT_CODE:
+                return getBatteryEvents(uri);
             default:
                 throw new IllegalArgumentException("unknown URI: " + uri);
         }
@@ -138,6 +149,14 @@
                     Log.e(TAG, "insert() from:" + uri + " error:" + e);
                     return null;
                 }
+            case BATTERY_EVENT_CODE:
+                try {
+                    mBatteryEventDao.insert(BatteryEventEntity.create(contentValues));
+                    return uri;
+                } catch (RuntimeException e) {
+                    Log.e(TAG, "insert() from:" + uri + " error:" + e);
+                    return null;
+                }
             default:
                 throw new IllegalArgumentException("unknown URI: " + uri);
         }
@@ -190,7 +209,6 @@
         }
         Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
         return cursor;
-
     }
 
     private Cursor getAppUsageLatestTimestamp(Uri uri) {
@@ -210,6 +228,19 @@
         return cursor;
     }
 
+    private Cursor getBatteryEvents(Uri uri) {
+        final long queryTimestamp = getQueryTimestamp(uri);
+        final long timestamp = mClock.millis();
+        Cursor cursor = null;
+        try {
+            cursor = mBatteryEventDao.getAllAfter(queryTimestamp);
+        } catch (RuntimeException e) {
+            Log.e(TAG, "query() from:" + uri + " error:" + e);
+        }
+        Log.w(TAG, "query app usage events in " + (mClock.millis() - timestamp) + "/ms");
+        return cursor;
+    }
+
     // If URI contains query parameter QUERY_KEY_USERID, use the value directly.
     // Otherwise, return null.
     private List<Long> getQueryUserIds(Uri uri) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
index fcf934f..c6c548f 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/ConvertUtils.java
@@ -38,6 +38,7 @@
 
 import com.android.settings.fuelgauge.BatteryUtils;
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -125,6 +126,15 @@
         return values;
     }
 
+    /** Converts {@link BatteryEvent} to content values */
+    public static ContentValues convertBatteryEventToContentValues(final BatteryEvent event) {
+        final ContentValues values = new ContentValues();
+        values.put(BatteryEventEntity.KEY_TIMESTAMP, event.getTimestamp());
+        values.put(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE, event.getType().getNumber());
+        values.put(BatteryEventEntity.KEY_BATTERY_LEVEL, event.getBatteryLevel());
+        return values;
+    }
+
     /** Gets the encoded string from {@link BatteryInformation} instance. */
     public static String convertBatteryInformationToString(
             final BatteryInformation batteryInformation) {
@@ -237,6 +247,29 @@
         return eventBuilder.build();
     }
 
+    /** Converts to {@link BatteryEvent} from {@link BatteryEventType} */
+    public static BatteryEvent convertToBatteryEvent(
+            long timestamp, BatteryEventType type, int batteryLevel) {
+        final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
+        eventBuilder.setTimestamp(timestamp);
+        eventBuilder.setType(type);
+        eventBuilder.setBatteryLevel(batteryLevel);
+        return eventBuilder.build();
+    }
+
+    /** Converts to {@link BatteryEvent} from {@link Cursor} */
+    public static BatteryEvent convertToBatteryEventFromCursor(final Cursor cursor) {
+        final BatteryEvent.Builder eventBuilder = BatteryEvent.newBuilder();
+        eventBuilder.setTimestamp(getLongFromCursor(cursor, BatteryEventEntity.KEY_TIMESTAMP));
+        eventBuilder.setType(
+                BatteryEventType.forNumber(
+                        getIntegerFromCursor(
+                                cursor, BatteryEventEntity.KEY_BATTERY_EVENT_TYPE)));
+        eventBuilder.setBatteryLevel(
+                getIntegerFromCursor(cursor, BatteryEventEntity.KEY_BATTERY_LEVEL));
+        return eventBuilder.build();
+    }
+
     /** Converts UTC timestamp to local time string for logging only, so use the US locale for
      *  better readability in debugging. */
     public static String utcToLocalTimeForLogging(long timestamp) {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
index 1f25ae0..6b007de 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java
@@ -72,6 +72,8 @@
 
     private final Handler mHandler;
     private final DataProcessor.UsageMapAsyncResponse mCallbackFunction;
+    private final List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
+    private final List<BatteryEvent> mBatteryEventList = new ArrayList<>();
 
     private Context mContext;
     private UserManager mUserManager;
@@ -84,12 +86,12 @@
     private boolean mIsCurrentBatteryHistoryLoaded = false;
     private boolean mIsCurrentAppUsageLoaded = false;
     private boolean mIsDatabaseAppUsageLoaded = false;
+    private boolean mIsBatteryEventLoaded = false;
     // Used to identify whether screen-on time data should be shown in the UI.
     private boolean mShowScreenOnTime = true;
     // Used to identify whether battery level data should be shown in the UI.
     private boolean mShowBatteryLevel = true;
 
-    private List<AppUsageEvent> mAppUsageEventList = new ArrayList<>();
     /**
      * The indexed {@link AppUsagePeriod} list data for each corresponding time slot.
      * <p>{@code Long} stands for the userId.</p>
@@ -146,6 +148,8 @@
             loadDatabaseAppUsageList();
             // Loads the latest app usage list from the service.
             loadCurrentAppUsageList();
+            // Loads the battery event list from database.
+            loadBatteryEventList();
         } else {
             // If there is no battery level data, only load the battery history data from service
             // and show it as the app list directly.
@@ -175,6 +179,11 @@
     }
 
     @VisibleForTesting
+    boolean getIsBatteryEventLoaded() {
+        return mIsBatteryEventLoaded;
+    }
+
+    @VisibleForTesting
     boolean getIsCurrentBatteryHistoryLoaded() {
         return mIsCurrentBatteryHistoryLoaded;
     }
@@ -290,7 +299,7 @@
                     return null;
                 }
                 final long startTime = System.currentTimeMillis();
-                // Loads the current battery usage data from the battery stats service.
+                // Loads the app usage data from the database.
                 final List<AppUsageEvent> appUsageEventList =
                         DatabaseUtils.getAppUsageEventForUsers(
                                 mContext, Calendar.getInstance(), getCurrentUserIds(),
@@ -314,6 +323,35 @@
         }.execute();
     }
 
+    private void loadBatteryEventList() {
+        new AsyncTask<Void, Void, List<BatteryEvent>>() {
+            @Override
+            protected List<BatteryEvent> doInBackground(Void... voids) {
+                final long startTime = System.currentTimeMillis();
+                // Loads the battery event data from the database.
+                final List<BatteryEvent> batteryEventList =
+                        DatabaseUtils.getBatteryEvents(
+                                mContext, Calendar.getInstance(), mRawStartTimestamp);
+                Log.d(TAG, String.format("execute loadBatteryEventList size=%d in %d/ms",
+                        batteryEventList.size(), (System.currentTimeMillis() - startTime)));
+                return batteryEventList;
+            }
+
+            @Override
+            protected void onPostExecute(
+                    final List<BatteryEvent> batteryEventList) {
+                if (batteryEventList == null || batteryEventList.isEmpty()) {
+                    Log.d(TAG, "batteryEventList is null or empty");
+                } else {
+                    mBatteryEventList.clear();
+                    mBatteryEventList.addAll(batteryEventList);
+                }
+                mIsBatteryEventLoaded = true;
+                tryToProcessAppUsageData();
+            }
+        }.execute();
+    }
+
     private void loadAndApplyBatteryMapFromServiceOnly() {
         new AsyncTask<Void, Void, BatteryCallbackData>() {
             @Override
@@ -344,9 +382,8 @@
     }
 
     private void tryToProcessAppUsageData() {
-        // Only when all app usage events has been loaded, start processing app usage data to an
-        // intermediate result for further use.
-        if (!mIsCurrentAppUsageLoaded || !mIsDatabaseAppUsageLoaded) {
+        // Ignore processing the data if any required data is not loaded.
+        if (!mIsCurrentAppUsageLoaded || !mIsDatabaseAppUsageLoaded || !mIsBatteryEventLoaded) {
             return;
         }
         processAppUsageData();
@@ -360,16 +397,16 @@
         }
         // Generates the indexed AppUsagePeriod list data for each corresponding time slot for
         // further use.
-        mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(
-                mRawStartTimestamp, mHourlyBatteryLevelsPerDay, mAppUsageEventList);
+        mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(mRawStartTimestamp,
+                mHourlyBatteryLevelsPerDay, mAppUsageEventList, mBatteryEventList);
     }
 
     private void tryToGenerateFinalDataAndApplyCallback() {
-        // Only when both battery history data and app usage events data has been loaded, start the
-        // final data processing.
+        // Ignore processing the data if any required data is not loaded.
         if (!mIsCurrentBatteryHistoryLoaded
                 || !mIsCurrentAppUsageLoaded
-                || !mIsDatabaseAppUsageLoaded) {
+                || !mIsDatabaseAppUsageLoaded
+                || !mIsBatteryEventLoaded) {
             return;
         }
         generateFinalDataAndApplyCallback();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
index 38d4e69..6270eb3 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java
@@ -95,6 +95,14 @@
     static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
 
     @VisibleForTesting
+    static final Comparator<AppUsageEvent> APP_USAGE_EVENT_TIMESTAMP_COMPARATOR =
+            Comparator.comparing(AppUsageEvent::getTimestamp);
+
+    @VisibleForTesting
+    static final Comparator<BatteryEvent> BATTERY_EVENT_TIMESTAMP_COMPARATOR =
+            Comparator.comparing(BatteryEvent::getTimestamp);
+
+    @VisibleForTesting
     static boolean sDebug = false;
 
     @VisibleForTesting
@@ -110,8 +118,6 @@
 
     public static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
             "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";
-    public static final Comparator<AppUsageEvent> TIMESTAMP_COMPARATOR =
-            Comparator.comparing(AppUsageEvent::getTimestamp);
 
     /** A callback listener when battery usage loading async task is executed. */
     public interface UsageMapAsyncResponse {
@@ -266,14 +272,16 @@
             generateAppUsagePeriodMap(
                     final long rawStartTimestamp,
                     final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
-                    final List<AppUsageEvent> appUsageEventList) {
+                    final List<AppUsageEvent> appUsageEventList,
+                    final List<BatteryEvent> batteryEventList) {
         if (appUsageEventList.isEmpty()) {
             Log.w(TAG, "appUsageEventList is empty");
             return null;
         }
-        // Sorts the appUsageEventList in ascending order based on the timestamp before
-        // distribution.
-        Collections.sort(appUsageEventList, TIMESTAMP_COMPARATOR);
+        // Sorts the appUsageEventList and batteryEventList in ascending order based on the
+        // timestamp before distribution.
+        Collections.sort(appUsageEventList, APP_USAGE_EVENT_TIMESTAMP_COMPARATOR);
+        Collections.sort(batteryEventList, BATTERY_EVENT_TIMESTAMP_COMPARATOR);
         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> resultMap =
                 new ArrayMap<>();
 
@@ -309,8 +317,8 @@
                 // The value could be null when there is no data in the hourly slot.
                 dailyMap.put(
                         hourlyIndex,
-                        buildAppUsagePeriodList(
-                                hourlyAppUsageEventList, startTimestamp, endTimestamp));
+                        buildAppUsagePeriodList(hourlyAppUsageEventList, batteryEventList,
+                                startTimestamp, endTimestamp));
             }
         }
         return resultMap;
@@ -717,7 +725,8 @@
     @VisibleForTesting
     @Nullable
     static Map<Long, Map<String, List<AppUsagePeriod>>> buildAppUsagePeriodList(
-            final List<AppUsageEvent> allAppUsageEvents, final long startTime, final long endTime) {
+            final List<AppUsageEvent> allAppUsageEvents, final List<BatteryEvent> batteryEventList,
+            final long startTime, final long endTime) {
         if (allAppUsageEvents.isEmpty()) {
             return null;
         }
@@ -763,12 +772,14 @@
             usageEvents.addAll(deviceEvents);
             // Sorts the usageEvents in ascending order based on the timestamp before computing the
             // period.
-            Collections.sort(usageEvents, TIMESTAMP_COMPARATOR);
+            Collections.sort(usageEvents, APP_USAGE_EVENT_TIMESTAMP_COMPARATOR);
 
             // A package might have multiple instances. Computes the usage period per instance id
             // and then merges them into the same user-package map.
             final List<AppUsagePeriod> usagePeriodList =
-                    buildAppUsagePeriodListPerInstance(usageEvents, startTime, endTime);
+                    excludePowerConnectedTimeFromAppUsagePeriodList(
+                            buildAppUsagePeriodListPerInstance(usageEvents, startTime, endTime),
+                            batteryEventList);
             if (!usagePeriodList.isEmpty()) {
                 addToUsagePeriodMap(allUsagePeriods, usagePeriodList, eventUserId, packageName);
             }
@@ -837,6 +848,53 @@
     }
 
     @VisibleForTesting
+    static List<AppUsagePeriod> excludePowerConnectedTimeFromAppUsagePeriodList(
+            final List<AppUsagePeriod> usagePeriodList,
+            final List<BatteryEvent> batteryEventList) {
+        final List<AppUsagePeriod> resultList = new ArrayList<>();
+        for (AppUsagePeriod inputPeriod : usagePeriodList) {
+            long lastStartTime = inputPeriod.getStartTime();
+            for (BatteryEvent batteryEvent : batteryEventList) {
+                if (batteryEvent.getTimestamp() < inputPeriod.getStartTime()) {
+                    // Because the batteryEventList has been sorted, here is to mark the power
+                    // connection state when the usage period starts. If power is connected when
+                    // the usage period starts, the starting period will be ignored; otherwise it
+                    // will be added.
+                    if (batteryEvent.getType() == BatteryEventType.POWER_CONNECTED) {
+                        lastStartTime = 0;
+                    } else if (batteryEvent.getType() == BatteryEventType.POWER_DISCONNECTED) {
+                        lastStartTime = inputPeriod.getStartTime();
+                    }
+                    continue;
+                }
+                if (batteryEvent.getTimestamp() > inputPeriod.getEndTime()) {
+                    // Because the batteryEventList has been sorted, if any event is already after
+                    // the end time, all the following events should be able to drop directly.
+                    break;
+                }
+
+                if (batteryEvent.getType() == BatteryEventType.POWER_CONNECTED
+                        && lastStartTime != 0) {
+                    resultList.add(AppUsagePeriod.newBuilder()
+                            .setStartTime(lastStartTime)
+                            .setEndTime(batteryEvent.getTimestamp())
+                            .build());
+                    lastStartTime = 0;
+                } else if (batteryEvent.getType() == BatteryEventType.POWER_DISCONNECTED) {
+                    lastStartTime = batteryEvent.getTimestamp();
+                }
+            }
+            if (lastStartTime != 0) {
+                resultList.add(AppUsagePeriod.newBuilder()
+                        .setStartTime(lastStartTime)
+                        .setEndTime(inputPeriod.getEndTime())
+                        .build());
+            }
+        }
+        return resultList;
+    }
+
+    @VisibleForTesting
     static long getScreenOnTime(
             final Map<Long, Map<String, List<AppUsagePeriod>>> appUsageMap,
             final long userId,
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index ef1434c..84a39bb 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -64,10 +64,12 @@
 
     /** An authority name of the battery content provider. */
     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 table name for battery events. */
+    public static final String BATTERY_EVENT_TABLE = "BatteryEvent";
+    /** A table name for battery usage history. */
+    public static final String BATTERY_STATE_TABLE = "BatteryState";
     /** 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. */
@@ -84,13 +86,6 @@
      */
     public static final long USAGE_QUERY_BUFFER_HOURS = Duration.ofHours(3).toMillis();
 
-    /** A content URI to access battery usage states data. */
-    public static final Uri BATTERY_CONTENT_URI =
-            new Uri.Builder()
-                    .scheme(ContentResolver.SCHEME_CONTENT)
-                    .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()
@@ -98,6 +93,20 @@
                     .authority(AUTHORITY)
                     .appendPath(APP_USAGE_EVENT_TABLE)
                     .build();
+    /** A content URI to access battery events data. */
+    public static final Uri BATTERY_EVENT_URI =
+            new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(BATTERY_EVENT_TABLE)
+                    .build();
+    /** A content URI to access battery usage states data. */
+    public static final Uri BATTERY_CONTENT_URI =
+            new Uri.Builder()
+                    .scheme(ContentResolver.SCHEME_CONTENT)
+                    .authority(AUTHORITY)
+                    .appendPath(BATTERY_STATE_TABLE)
+                    .build();
 
     // For testing only.
     @VisibleForTesting
@@ -106,6 +115,8 @@
     static Supplier<Cursor> sFakeAppUsageEventSupplier;
     @VisibleForTesting
     static Supplier<Cursor> sFakeAppUsageLatestTimestampSupplier;
+    @VisibleForTesting
+    static Supplier<Cursor> sFakeBatteryEventSupplier;
 
     private DatabaseUtils() {
     }
@@ -176,6 +187,32 @@
         return appUsageEventList;
     }
 
+    /** Returns the battery event data since the query timestamp in battery event table. */
+    public static List<BatteryEvent> getBatteryEvents(
+            Context context,
+            final Calendar calendar,
+            final long rawStartTimestamp) {
+        final long startTime = System.currentTimeMillis();
+        final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
+        final long queryTimestamp = Math.max(rawStartTimestamp, sixDaysAgoTimestamp);
+        Log.d(TAG, "getBatteryEvents for timestamp: " + queryTimestamp);
+        // Builds the content uri everytime to avoid cache.
+        final Uri batteryEventUri =
+                new Uri.Builder()
+                        .scheme(ContentResolver.SCHEME_CONTENT)
+                        .authority(AUTHORITY)
+                        .appendPath(BATTERY_EVENT_TABLE)
+                        .appendQueryParameter(
+                                QUERY_KEY_TIMESTAMP, Long.toString(queryTimestamp))
+                        .build();
+
+        final List<BatteryEvent> batteryEventList =
+                loadBatteryEventsFromContentProvider(context, batteryEventUri);
+        Log.d(TAG, String.format("getBatteryEvents size=%d in %d/ms", batteryEventList.size(),
+                (System.currentTimeMillis() - startTime)));
+        return batteryEventList;
+    }
+
     /** Long: for timestamp and String: for BatteryHistEntry.getKey() */
     public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
             Context context, Calendar calendar) {
@@ -210,8 +247,9 @@
             try {
                 final BatteryStateDatabase database = BatteryStateDatabase
                         .getInstance(context.getApplicationContext());
-                database.batteryStateDao().clearAll();
                 database.appUsageEventDao().clearAll();
+                database.batteryEventDao().clearAll();
+                database.batteryStateDao().clearAll();
             } catch (RuntimeException e) {
                 Log.e(TAG, "clearAll() failed", e);
             }
@@ -226,8 +264,9 @@
                         .getInstance(context.getApplicationContext());
                 final long earliestTimestamp = Clock.systemUTC().millis()
                         - Duration.ofDays(DATA_RETENTION_INTERVAL_DAY).toMillis();
-                database.batteryStateDao().clearAllBefore(earliestTimestamp);
                 database.appUsageEventDao().clearAllBefore(earliestTimestamp);
+                database.batteryEventDao().clearAllBefore(earliestTimestamp);
+                database.batteryStateDao().clearAllBefore(earliestTimestamp);
             } catch (RuntimeException e) {
                 Log.e(TAG, "clearAllBefore() failed", e);
             }
@@ -292,6 +331,23 @@
         return valuesList;
     }
 
+    static ContentValues sendBatteryEventData(
+            final Context context, final BatteryEvent batteryEvent) {
+        final long startTime = System.currentTimeMillis();
+        ContentValues contentValues = ConvertUtils.convertBatteryEventToContentValues(batteryEvent);
+        final ContentResolver resolver = context.getContentResolver();
+        try {
+            resolver.insert(BATTERY_EVENT_URI, contentValues);
+            Log.d(TAG, "insert() battery event data into database: " + batteryEvent.toString());
+        } catch (Exception e) {
+            Log.e(TAG, "insert() battery event data into database error:\n" + e);
+        }
+        Log.d(TAG, String.format("sendBatteryEventData() in %d/ms",
+                (System.currentTimeMillis() - startTime)));
+        clearMemory();
+        return contentValues;
+    }
+
     static List<ContentValues> sendBatteryEntryData(
             final Context context,
             final List<BatteryEntry> batteryEntryList,
@@ -392,6 +448,8 @@
     public static void dump(Context context, PrintWriter writer) {
         writeString(context, writer, "BatteryLevelChanged",
                 Intent.ACTION_BATTERY_LEVEL_CHANGED);
+        writeString(context, writer, "BatteryPlugging",
+                BatteryUsageBroadcastReceiver.ACTION_BATTERY_PLUGGING);
         writeString(context, writer, "BatteryUnplugging",
                 BatteryUsageBroadcastReceiver.ACTION_BATTERY_UNPLUGGING);
         writeString(context, writer, "ClearBatteryCacheData",
@@ -475,6 +533,32 @@
         return appUsageEventList;
     }
 
+    private static List<BatteryEvent> loadBatteryEventsFromContentProvider(
+            Context context, Uri batteryEventUri) {
+        final List<BatteryEvent> batteryEventList = new ArrayList<>();
+        context = getParentContext(context);
+        if (context == null) {
+            return batteryEventList;
+        }
+        try (Cursor cursor = sFakeBatteryEventSupplier != null
+                ? sFakeBatteryEventSupplier.get()
+                : context.getContentResolver().query(batteryEventUri, null, null, null)) {
+            if (cursor == null || cursor.getCount() == 0) {
+                return batteryEventList;
+            }
+            // Loads and recovers all AppUsageEvent data from cursor.
+            while (cursor.moveToNext()) {
+                batteryEventList.add(ConvertUtils.convertToBatteryEventFromCursor(cursor));
+            }
+            try {
+                cursor.close();
+            } catch (Exception e) {
+                Log.e(TAG, "cursor.close() failed", e);
+            }
+        }
+        return batteryEventList;
+    }
+
     private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
             Context context, Uri batteryStateUri) {
         context = DatabaseUtils.getParentContext(context);
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
new file mode 100644
index 0000000..a638d09
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.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 BatteryEventEntity} in the database. */
+@Dao
+public interface BatteryEventDao {
+    /** Inserts a {@link BatteryEventEntity} data into the database. */
+    @Insert(onConflict = OnConflictStrategy.REPLACE)
+    void insert(BatteryEventEntity event);
+
+    /** Gets all recorded data. */
+    @Query("SELECT * FROM BatteryEventEntity ORDER BY timestamp DESC")
+    List<BatteryEventEntity> getAll();
+
+    /** Gets the {@link Cursor} of all recorded data after a specific timestamp. */
+    @Query("SELECT * FROM BatteryEventEntity WHERE timestamp > :timestamp ORDER BY timestamp DESC")
+    Cursor getAllAfter(long timestamp);
+
+    /** Deletes all recorded data before a specific timestamp. */
+    @Query("DELETE FROM BatteryEventEntity WHERE timestamp <= :timestamp")
+    void clearAllBefore(long timestamp);
+
+    /** Clears all recorded data in the database. */
+    @Query("DELETE FROM BatteryEventEntity")
+    void clearAll();
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventEntity.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventEntity.java
new file mode 100644
index 0000000..66cadda
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventEntity.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.batteryusage.db;
+
+import android.content.ContentValues;
+
+import androidx.room.Entity;
+import androidx.room.PrimaryKey;
+
+import com.android.settings.fuelgauge.batteryusage.ConvertUtils;
+
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+import java.util.Locale;
+
+/** A {@link Entity} class to save battery events into database. */
+@Entity
+public class BatteryEventEntity {
+    /** Keys for accessing {@link ContentValues}. */
+    public static final String KEY_TIMESTAMP = "timestamp";
+    public static final String KEY_BATTERY_EVENT_TYPE = "batteryEventType";
+    public static final String KEY_BATTERY_LEVEL = "batteryLevel";
+
+    @PrimaryKey(autoGenerate = true)
+    private long mId;
+
+    public final long timestamp;
+    public final int batteryEventType;
+    public final int batteryLevel;
+
+    public BatteryEventEntity(
+            final long timestamp,
+            final int batteryEventType,
+            final int batteryLevel) {
+        this.timestamp = timestamp;
+        this.batteryEventType = batteryEventType;
+        this.batteryLevel = batteryLevel;
+    }
+
+    /** 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
+    public String toString() {
+        final String recordAtDateTime = ConvertUtils.utcToLocalTimeForLogging(timestamp);
+        final StringBuilder builder = new StringBuilder()
+                .append("\nBatteryEvent{")
+                .append(String.format(Locale.US,
+                        "\n\ttimestamp=%s|batteryEventType=%d|batteryLevel=%d",
+                        recordAtDateTime, batteryEventType, batteryLevel))
+                .append("\n}");
+        return builder.toString();
+    }
+
+    /** Creates new {@link BatteryEventEntity} from {@link ContentValues}. */
+    public static BatteryEventEntity create(ContentValues contentValues) {
+        Builder builder = BatteryEventEntity.newBuilder();
+        if (contentValues.containsKey(KEY_TIMESTAMP)) {
+            builder.setTimestamp(contentValues.getAsLong(KEY_TIMESTAMP));
+        }
+        if (contentValues.containsKey(KEY_BATTERY_EVENT_TYPE)) {
+            builder.setBatteryEventType(contentValues.getAsInteger(KEY_BATTERY_EVENT_TYPE));
+        }
+        if (contentValues.containsKey(KEY_BATTERY_LEVEL)) {
+            builder.setBatteryLevel(contentValues.getAsInteger(KEY_BATTERY_LEVEL));
+        }
+        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 mTimestamp;
+        private int mBatteryEventType;
+        private int mBatteryLevel;
+
+        /** Sets the timestamp. */
+        @CanIgnoreReturnValue
+        public Builder setTimestamp(final long timestamp) {
+            mTimestamp = timestamp;
+            return this;
+        }
+
+        /** Sets the battery event type. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryEventType(final int batteryEventType) {
+            mBatteryEventType = batteryEventType;
+            return this;
+        }
+
+        /** Sets the battery level. */
+        @CanIgnoreReturnValue
+        public Builder setBatteryLevel(final int batteryLevel) {
+            mBatteryLevel = batteryLevel;
+            return this;
+        }
+
+        /** Builds the {@link BatteryEventEntity}. */
+        public BatteryEventEntity build() {
+            return new BatteryEventEntity(
+                    mTimestamp,
+                    mBatteryEventType,
+                    mBatteryLevel);
+        }
+
+        private Builder() {}
+    }
+}
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
index 4c8df7e..466a7ca 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDatabase.java
@@ -25,24 +25,26 @@
 
 /** A {@link RoomDatabase} for battery usage states history. */
 @Database(
-        entities = {BatteryState.class, AppUsageEventEntity.class},
+        entities = {AppUsageEventEntity.class, BatteryEventEntity.class, BatteryState.class},
         version = 1)
 public abstract class BatteryStateDatabase extends RoomDatabase {
     private static final String TAG = "BatteryStateDatabase";
 
     private static BatteryStateDatabase sBatteryStateDatabase;
 
-    /** Provides DAO for battery state table. */
-    public abstract BatteryStateDao batteryStateDao();
     /** Provides DAO for app usage event table. */
     public abstract AppUsageEventDao appUsageEventDao();
+    /** Provides DAO for battery event table. */
+    public abstract BatteryEventDao batteryEventDao();
+    /** Provides DAO for battery state table. */
+    public abstract BatteryStateDao batteryStateDao();
 
     /** 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-v7")
+                                    context, BatteryStateDatabase.class, "battery-usage-db-v8")
                             // 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 2c63af4..3af2aef 100644
--- a/src/com/android/settings/fuelgauge/protos/Android.bp
+++ b/src/com/android/settings/fuelgauge/protos/Android.bp
@@ -8,17 +8,25 @@
 }
 
 java_library {
+    name: "app-usage-event-protos-lite",
+    proto: {
+        type: "lite",
+    },
+    srcs: ["app_usage_event.proto"],
+}
+
+java_library {
+    name: "battery-event-protos-lite",
+    proto: {
+        type: "lite",
+    },
+    srcs: ["battery_event.proto"],
+}
+
+java_library {
     name: "fuelgauge-usage-state-protos-lite",
     proto: {
         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/battery_event.proto b/src/com/android/settings/fuelgauge/protos/battery_event.proto
new file mode 100644
index 0000000..80ccb3b
--- /dev/null
+++ b/src/com/android/settings/fuelgauge/protos/battery_event.proto
@@ -0,0 +1,17 @@
+syntax = "proto2";
+
+option java_multiple_files = true;
+option java_package = "com.android.settings.fuelgauge.batteryusage";
+option java_outer_classname = "BatteryEventProto";
+
+enum BatteryEventType {
+  UNKNOWN_EVENT = 0;
+  POWER_CONNECTED = 1;
+  POWER_DISCONNECTED = 2;
+}
+
+message BatteryEvent {
+  optional int64 timestamp = 1;
+  optional BatteryEventType type = 2;
+  optional int32 battery_level = 3;
+}
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 e5547c5..05a6f2b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProviderTest.java
@@ -31,6 +31,7 @@
 import androidx.test.core.app.ApplicationProvider;
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryState;
 import com.android.settings.fuelgauge.batteryusage.db.BatteryStateDatabase;
 import com.android.settings.testutils.BatteryTestUtils;
@@ -354,6 +355,28 @@
     }
 
     @Test
+    public void insert_batteryEvent_returnsExpectedResult() {
+        mProvider.onCreate();
+        ContentValues values = new ContentValues();
+        values.put(BatteryEventEntity.KEY_TIMESTAMP, 10001L);
+        values.put(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE,
+                BatteryEventType.POWER_CONNECTED.getNumber());
+        values.put(BatteryEventEntity.KEY_BATTERY_LEVEL, 66);
+
+        final Uri uri = mProvider.insert(DatabaseUtils.BATTERY_EVENT_URI, values);
+
+        assertThat(uri).isEqualTo(DatabaseUtils.BATTERY_EVENT_URI);
+        // Verifies the AppUsageEventEntity content.
+        final List<BatteryEventEntity> entities =
+                BatteryStateDatabase.getInstance(mContext).batteryEventDao().getAll();
+        assertThat(entities).hasSize(1);
+        assertThat(entities.get(0).timestamp).isEqualTo(10001L);
+        assertThat(entities.get(0).batteryEventType).isEqualTo(
+                BatteryEventType.POWER_CONNECTED.getNumber());
+        assertThat(entities.get(0).batteryLevel).isEqualTo(66);
+    }
+
+    @Test
     public void delete_throwsUnsupportedOperationException() {
         assertThrows(
                 UnsupportedOperationException.class,
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 fe1bff6..6b8073b 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/ConvertUtilsTest.java
@@ -39,6 +39,7 @@
 import android.os.UserHandle;
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -195,6 +196,22 @@
     }
 
     @Test
+    public void convertBatteryEventToContentValues_normalCase_returnsExpectedContentValues() {
+        final BatteryEvent batteryEvent =
+                BatteryEvent.newBuilder()
+                        .setTimestamp(10001L)
+                        .setType(BatteryEventType.POWER_CONNECTED)
+                        .setBatteryLevel(66)
+                        .build();
+        final ContentValues values =
+                ConvertUtils.convertBatteryEventToContentValues(batteryEvent);
+        assertThat(values.getAsLong(BatteryEventEntity.KEY_TIMESTAMP)).isEqualTo(10001L);
+        assertThat(values.getAsInteger(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE)).isEqualTo(
+                BatteryEventType.POWER_CONNECTED.getNumber());
+        assertThat(values.getAsInteger(BatteryEventEntity.KEY_BATTERY_LEVEL)).isEqualTo(66);
+    }
+
+    @Test
     public void convertToBatteryHistEntry_returnsExpectedResult() {
         final int expectedType = 3;
         when(mMockBatteryEntry.getUid()).thenReturn(1001);
@@ -406,6 +423,15 @@
     }
 
     @Test
+    public void convertToBatteryEvent_normalCase_returnsExpectedResult() {
+        final BatteryEvent batteryEvent = ConvertUtils.convertToBatteryEvent(
+                666L, BatteryEventType.POWER_DISCONNECTED, 88);
+        assertThat(batteryEvent.getTimestamp()).isEqualTo(666L);
+        assertThat(batteryEvent.getType()).isEqualTo(BatteryEventType.POWER_DISCONNECTED);
+        assertThat(batteryEvent.getBatteryLevel()).isEqualTo(88);
+    }
+
+    @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/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
index aab91e0..753a7f7 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java
@@ -183,7 +183,7 @@
         assertThat(dataProcessManager.getIsCurrentBatteryHistoryLoaded()).isTrue();
         assertThat(dataProcessManager.getShowScreenOnTime()).isTrue();
         final List<AppUsageEvent> appUsageEventList = dataProcessManager.getAppUsageEventList();
-        Collections.sort(appUsageEventList, DataProcessor.TIMESTAMP_COMPARATOR);
+        Collections.sort(appUsageEventList, DataProcessor.APP_USAGE_EVENT_TIMESTAMP_COMPARATOR);
         assertThat(appUsageEventList.size()).isEqualTo(6);
         assertAppUsageEvent(
                 appUsageEventList.get(0), AppUsageEventType.ACTIVITY_RESUMED, /*timestamp=*/ 1);
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 aa15472..66e48c0 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java
@@ -250,7 +250,7 @@
 
         final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> periodMap =
                 DataProcessor.generateAppUsagePeriodMap(
-                        14400000L, hourlyBatteryLevelsPerDay, appUsageEventList);
+                        14400000L, hourlyBatteryLevelsPerDay, appUsageEventList, new ArrayList<>());
 
         assertThat(periodMap).hasSize(3);
         // Day 1
@@ -288,7 +288,7 @@
         hourlyBatteryLevelsPerDay.add(
                 new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
         assertThat(DataProcessor.generateAppUsagePeriodMap(
-                0L, hourlyBatteryLevelsPerDay, new ArrayList<>())).isNull();
+                0L, hourlyBatteryLevelsPerDay, new ArrayList<>(), new ArrayList<>())).isNull();
     }
 
     @Test
@@ -1669,7 +1669,8 @@
                 /*instanceId=*/ 4, packageName2));
 
         final Map<Long, Map<String, List<AppUsagePeriod>>> appUsagePeriodMap =
-                DataProcessor.buildAppUsagePeriodList(appUsageEvents, 0, 5);
+                DataProcessor.buildAppUsagePeriodList(
+                        appUsageEvents, new ArrayList<>(), 0, 5);
 
         assertThat(appUsagePeriodMap).hasSize(2);
         final Map<String, List<AppUsagePeriod>> userMap1 = appUsagePeriodMap.get(1L);
@@ -1693,7 +1694,7 @@
     @Test
     public void buildAppUsagePeriodList_emptyEventList_returnNull() {
         assertThat(DataProcessor.buildAppUsagePeriodList(
-                new ArrayList<>(), 0, 1)).isNull();
+                new ArrayList<>(), new ArrayList<>(), 0, 1)).isNull();
     }
 
     @Test
@@ -1705,7 +1706,7 @@
                 AppUsageEventType.DEVICE_SHUTDOWN, /*timestamp=*/ 2));
 
         assertThat(DataProcessor.buildAppUsagePeriodList(
-                appUsageEvents, 0, 3)).isNull();
+                appUsageEvents, new ArrayList<>(), 0, 3)).isNull();
     }
 
     @Test
@@ -1766,6 +1767,89 @@
     }
 
     @Test
+    public void excludePowerConnectedTime_startEndNotCharging_returnExpectedResult() {
+        final List<AppUsagePeriod> appUsagePeriodList = List.of(
+                AppUsagePeriod.newBuilder().setStartTime(100).setEndTime(200).build());
+        final List<BatteryEvent> batteryEventList = List.of(
+                BatteryEvent.newBuilder().setTimestamp(50).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(166).setType(
+                        BatteryEventType.POWER_CONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(188).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(280).setType(
+                        BatteryEventType.POWER_CONNECTED).build());
+
+        final List<AppUsagePeriod> resultList =
+                DataProcessor.excludePowerConnectedTimeFromAppUsagePeriodList(
+                        appUsagePeriodList, batteryEventList);
+
+        assertThat(resultList).hasSize(2);
+        assertAppUsagePeriod(resultList.get(0), 100, 166);
+        assertAppUsagePeriod(resultList.get(1), 188, 200);
+    }
+
+    @Test
+    public void excludePowerConnectedTime_startEndInCharging_returnExpectedResult() {
+        final List<AppUsagePeriod> appUsagePeriodList = List.of(
+                AppUsagePeriod.newBuilder().setStartTime(100).setEndTime(200).build());
+        final List<BatteryEvent> batteryEventList = List.of(
+                BatteryEvent.newBuilder().setTimestamp(50).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(80).setType(
+                        BatteryEventType.POWER_CONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(120).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(150).setType(
+                        BatteryEventType.POWER_CONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(160).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(180).setType(
+                        BatteryEventType.POWER_CONNECTED).build());
+
+        final List<AppUsagePeriod> resultList =
+                DataProcessor.excludePowerConnectedTimeFromAppUsagePeriodList(
+                        appUsagePeriodList, batteryEventList);
+
+        assertThat(resultList).hasSize(2);
+        assertAppUsagePeriod(resultList.get(0), 120, 150);
+        assertAppUsagePeriod(resultList.get(1), 160, 180);
+    }
+
+    @Test
+    public void excludePowerConnectedTime_wholePeriodNotCharging_returnExpectedResult() {
+        final List<AppUsagePeriod> appUsagePeriodList = List.of(
+                AppUsagePeriod.newBuilder().setStartTime(100).setEndTime(200).build());
+        final List<BatteryEvent> batteryEventList = List.of(
+                BatteryEvent.newBuilder().setTimestamp(50).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build(),
+                BatteryEvent.newBuilder().setTimestamp(80).setType(
+                        BatteryEventType.POWER_CONNECTED).build());
+
+        final List<AppUsagePeriod> resultList =
+                DataProcessor.excludePowerConnectedTimeFromAppUsagePeriodList(
+                        appUsagePeriodList, batteryEventList);
+
+        assertThat(resultList).isEmpty();
+    }
+
+    @Test
+    public void excludePowerConnectedTime_wholePeriodInCharging_returnExpectedResult() {
+        final List<AppUsagePeriod> appUsagePeriodList = List.of(
+                AppUsagePeriod.newBuilder().setStartTime(100).setEndTime(200).build());
+        final List<BatteryEvent> batteryEventList = List.of(
+                BatteryEvent.newBuilder().setTimestamp(50).setType(
+                        BatteryEventType.POWER_DISCONNECTED).build());
+
+        final List<AppUsagePeriod> resultList =
+                DataProcessor.excludePowerConnectedTimeFromAppUsagePeriodList(
+                        appUsagePeriodList, batteryEventList);
+
+        assertThat(resultList).hasSize(1);
+        assertAppUsagePeriod(resultList.get(0), 100, 200);
+    }
+
+    @Test
     public void getScreenOnTime_returnExpectedResult() {
         final long userId = 1;
         final String packageName = "com.android.settings";
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 0cb3d1d..20799d4 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtilsTest.java
@@ -36,6 +36,7 @@
 import android.os.UserManager;
 
 import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity;
+import com.android.settings.fuelgauge.batteryusage.db.BatteryEventEntity;
 import com.android.settings.testutils.BatteryTestUtils;
 
 import org.junit.Before;
@@ -137,6 +138,28 @@
     }
 
     @Test
+    public void sendBatteryEventData_returnsExpectedList() {
+        final BatteryEvent batteryEvent =
+                BatteryEvent.newBuilder()
+                        .setTimestamp(10001L)
+                        .setType(BatteryEventType.POWER_CONNECTED)
+                        .setBatteryLevel(66)
+                        .build();
+
+        final ContentValues contentValues =
+                DatabaseUtils.sendBatteryEventData(mContext, batteryEvent);
+
+        assertThat(contentValues.getAsInteger(BatteryEventEntity.KEY_TIMESTAMP))
+                .isEqualTo(10001L);
+        assertThat(contentValues.getAsInteger(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE))
+                .isEqualTo(BatteryEventType.POWER_CONNECTED.getNumber());
+        assertThat(contentValues.getAsInteger(BatteryEventEntity.KEY_BATTERY_LEVEL))
+                .isEqualTo(66);
+        // Verifies the inserted ContentValues into content provider.
+        verify(mMockContentResolver).insert(DatabaseUtils.BATTERY_EVENT_URI, contentValues);
+    }
+
+    @Test
     public void sendBatteryEntryData_nullBatteryIntent_returnsNullValue() {
         doReturn(null).when(mContext).registerReceiver(any(), any());
         assertThat(
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java
new file mode 100644
index 0000000..941f444
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDaoTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+/** Tests for {@link BatteryEventDao}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryEventDaoTest {
+    private Context mContext;
+    private BatteryStateDatabase mDatabase;
+    private BatteryEventDao mBatteryEventDao;
+
+    @Before
+    public void setUp() {
+        mContext = ApplicationProvider.getApplicationContext();
+        mDatabase = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
+        mBatteryEventDao = mDatabase.batteryEventDao();
+    }
+
+    @After
+    public void closeDb() {
+        mDatabase.close();
+        BatteryStateDatabase.setBatteryStateDatabase(/*database=*/ null);
+    }
+
+    @Test
+    public void getAllAfter_returnExpectedResult() {
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(100L)
+                .setBatteryEventType(1)
+                .setBatteryLevel(66)
+                .build());
+        mBatteryEventDao.insert(BatteryEventEntity.newBuilder()
+                .setTimestamp(200L)
+                .setBatteryEventType(2)
+                .setBatteryLevel(88)
+                .build());
+
+        final Cursor cursor = mBatteryEventDao.getAllAfter(160L);
+        assertThat(cursor.getCount()).isEqualTo(1);
+        cursor.moveToFirst();
+        assertThat(cursor.getLong(cursor.getColumnIndex(BatteryEventEntity.KEY_TIMESTAMP)))
+                .isEqualTo(200L);
+        assertThat(cursor.getInt(cursor.getColumnIndex(BatteryEventEntity.KEY_BATTERY_EVENT_TYPE)))
+                .isEqualTo(2);
+        assertThat(cursor.getInt(cursor.getColumnIndex(BatteryEventEntity.KEY_BATTERY_LEVEL)))
+                .isEqualTo(88);
+
+        mBatteryEventDao.clearAll();
+        assertThat(mBatteryEventDao.getAll()).isEmpty();
+    }
+}
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventEntityTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventEntityTest.java
new file mode 100644
index 0000000..a34d651
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventEntityTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.fuelgauge.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 BatteryEventEntity}. */
+@RunWith(RobolectricTestRunner.class)
+public final class BatteryEventEntityTest {
+
+    @Test
+    public void testBuilder_returnsExpectedResult() {
+        final long timestamp = 10001L;
+        final int batteryEventType = 1;
+        final int batteryLevel = 66;
+
+        BatteryEventEntity entity = BatteryEventEntity
+                .newBuilder()
+                .setTimestamp(timestamp)
+                .setBatteryEventType(batteryEventType)
+                .setBatteryLevel(batteryLevel)
+                .build();
+
+        // Verifies the app relative information.
+        assertThat(entity.timestamp).isEqualTo(timestamp);
+        assertThat(entity.batteryEventType).isEqualTo(batteryEventType);
+        assertThat(entity.batteryLevel).isEqualTo(batteryLevel);
+    }
+}