Load app usage events data in the hourly job.
Test: make RunSettingsRoboTests + manual Bug: 260964679 Change-Id: Iaccaa77bd52fb7356cdcb786c64523f21040b128
This commit is contained in:
@@ -80,6 +80,7 @@ android_library {
|
||||
"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",
|
||||
|
@@ -141,12 +141,17 @@ public interface PowerUsageFeatureProvider {
|
||||
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);
|
||||
}
|
||||
|
@@ -165,4 +165,9 @@ public class PowerUsageFeatureProviderImpl implements PowerUsageFeatureProvider
|
||||
public CharSequence[] getHideApplicationEntries(Context context) {
|
||||
return new CharSequence[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<CharSequence> getIgnoreScreenOnTimeTaskRootSet(Context context) {
|
||||
return new ArraySet<>();
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -29,6 +29,8 @@ import androidx.annotation.NonNull;
|
||||
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 @@ public class BatteryUsageContentProvider extends ContentProvider {
|
||||
@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 @@ public class BatteryUsageContentProvider extends ContentProvider {
|
||||
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 @@ public class BatteryUsageContentProvider extends ContentProvider {
|
||||
}
|
||||
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 @@ public class BatteryUsageContentProvider extends ContentProvider {
|
||||
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 @@ public class BatteryUsageContentProvider extends ContentProvider {
|
||||
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 @@ public class BatteryUsageContentProvider extends ContentProvider {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -58,7 +58,7 @@ public final class BatteryUsageDataLoader {
|
||||
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);
|
||||
|
@@ -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.LocaleList;
|
||||
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 @@ public final class ConvertUtils {
|
||||
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 @@ public final class ConvertUtils {
|
||||
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 @@ public final class ConvertUtils {
|
||||
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 @@ public final class ConvertUtils {
|
||||
BatteryEntry entry,
|
||||
BatteryUsageStats batteryUsageStats) {
|
||||
return new BatteryHistEntry(
|
||||
convertToContentValues(
|
||||
convertBatteryEntryToContentValues(
|
||||
entry,
|
||||
batteryUsageStats,
|
||||
/*batteryLevel=*/ 0,
|
||||
@@ -146,6 +164,51 @@ public final class ConvertUtils {
|
||||
/*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 @@ public final class ConvertUtils {
|
||||
: 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,
|
||||
|
@@ -18,10 +18,14 @@ package com.android.settings.fuelgauge.batteryusage;
|
||||
|
||||
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.BatteryUsageStatsQuery;
|
||||
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 @@ public final class DataProcessor {
|
||||
@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. */
|
||||
@@ -183,6 +194,55 @@ public final class DataProcessor {
|
||||
.getBatteryUsageStats(batteryUsageStatsQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -196,6 +256,59 @@ public final class DataProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@@ -508,13 +621,30 @@ public final class DataProcessor {
|
||||
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 @@ public final class DataProcessor {
|
||||
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) {
|
||||
|
@@ -45,12 +45,11 @@ import java.util.Calendar;
|
||||
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 final class DatabaseUtils {
|
||||
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 @@ public final class DatabaseUtils {
|
||||
.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 @@ public final class DatabaseUtils {
|
||||
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 final class DatabaseUtils {
|
||||
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 final class DatabaseUtils {
|
||||
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 @@ public final class DatabaseUtils {
|
||||
|| backgroundMs != 0;
|
||||
})
|
||||
.forEach(entry -> valuesList.add(
|
||||
ConvertUtils.convertToContentValues(
|
||||
ConvertUtils.convertBatteryEntryToContentValues(
|
||||
entry,
|
||||
batteryUsageStats,
|
||||
batteryLevel,
|
||||
@@ -197,15 +282,15 @@ public final class DatabaseUtils {
|
||||
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 @@ public final class DatabaseUtils {
|
||||
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 @@ public final class DatabaseUtils {
|
||||
}
|
||||
}
|
||||
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 @@ public final class DatabaseUtils {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -40,6 +40,7 @@ public final class PeriodicJobReceiver extends BroadcastReceiver {
|
||||
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);
|
||||
|
@@ -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();
|
||||
}
|
@@ -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() {}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -204,14 +204,14 @@ public class BatteryState {
|
||||
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;
|
||||
|
@@ -25,7 +25,7 @@ import androidx.room.RoomDatabase;
|
||||
|
||||
/** 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 @@ public abstract class BatteryStateDatabase extends RoomDatabase {
|
||||
|
||||
/** 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()
|
||||
|
@@ -13,4 +13,12 @@ java_library {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["fuelgauge_usage_state.proto"],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "app-usage-event-protos-lite",
|
||||
proto: {
|
||||
type: "lite",
|
||||
},
|
||||
srcs: ["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;
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -68,7 +68,7 @@ public final class BatteryHistEntryTest {
|
||||
when(mMockBatteryEntry.getConsumerType())
|
||||
.thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
|
||||
final ContentValues values =
|
||||
ConvertUtils.convertToContentValues(
|
||||
ConvertUtils.convertBatteryEntryToContentValues(
|
||||
mMockBatteryEntry,
|
||||
mBatteryUsageStats,
|
||||
/*batteryLevel=*/ 12,
|
||||
|
@@ -30,6 +30,7 @@ import android.net.Uri;
|
||||
|
||||
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 @@ public final class BatteryUsageContentProviderTest {
|
||||
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;
|
||||
@@ -177,6 +180,37 @@ public final class BatteryUsageContentProviderTest {
|
||||
BootBroadcastReceiver.ACTION_PERIODIC_JOB_RECHECK);
|
||||
}
|
||||
|
||||
@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();
|
||||
@@ -266,6 +300,34 @@ public final class BatteryUsageContentProviderTest {
|
||||
assertThat(states.get(0).batteryInformation).isEqualTo(expectedBatteryInformationString);
|
||||
}
|
||||
|
||||
@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(
|
||||
@@ -293,12 +355,12 @@ public final class BatteryUsageContentProviderTest {
|
||||
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 @@ public final class BatteryUsageContentProviderTest {
|
||||
.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 @@ public final class BatteryUsageContentProviderTest {
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@@ -62,7 +62,7 @@ public final class BootBroadcastReceiverTest {
|
||||
|
||||
// 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 @@ public final class BootBroadcastReceiverTest {
|
||||
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);
|
||||
|
@@ -17,16 +17,23 @@ package com.android.settings.fuelgauge.batteryusage;
|
||||
|
||||
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;
|
||||
@@ -43,6 +50,8 @@ public final class ConvertUtilsTest {
|
||||
|
||||
private Context mContext;
|
||||
|
||||
@Mock
|
||||
private PackageManager mMockPackageManager;
|
||||
@Mock
|
||||
private BatteryUsageStats mBatteryUsageStats;
|
||||
@Mock
|
||||
@@ -52,10 +61,11 @@ public final class ConvertUtilsTest {
|
||||
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 @@ public final class ConvertUtilsTest {
|
||||
.thenReturn(ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
|
||||
|
||||
final ContentValues values =
|
||||
ConvertUtils.convertToContentValues(
|
||||
ConvertUtils.convertBatteryEntryToContentValues(
|
||||
mMockBatteryEntry,
|
||||
mBatteryUsageStats,
|
||||
/*batteryLevel=*/ 12,
|
||||
@@ -121,9 +131,9 @@ public final class ConvertUtilsTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertToContentValues_nullBatteryEntry_returnsExpectedContentValues() {
|
||||
public void convertBatteryEntryToContentValues_nullBatteryEntry_returnsExpectedContentValues() {
|
||||
final ContentValues values =
|
||||
ConvertUtils.convertToContentValues(
|
||||
ConvertUtils.convertBatteryEntryToContentValues(
|
||||
/*entry=*/ null,
|
||||
/*batteryUsageStats=*/ null,
|
||||
/*batteryLevel=*/ 12,
|
||||
@@ -151,6 +161,31 @@ public final class ConvertUtilsTest {
|
||||
.isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@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;
|
||||
@@ -229,6 +264,77 @@ public final class ConvertUtilsTest {
|
||||
.isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
|
||||
}
|
||||
|
||||
@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))
|
||||
|
@@ -18,6 +18,7 @@ package com.android.settings.fuelgauge.batteryusage;
|
||||
|
||||
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.eq;
|
||||
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 @@ public class DataProcessorTest {
|
||||
|
||||
@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 @@ public class DataProcessorTest {
|
||||
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
|
||||
@@ -145,6 +158,86 @@ public class DataProcessorTest {
|
||||
expectedHourlyLevels);
|
||||
}
|
||||
|
||||
@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
|
||||
@@ -1213,6 +1306,30 @@ public class DataProcessorTest {
|
||||
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,
|
||||
|
@@ -22,6 +22,7 @@ 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.ContentValues;
|
||||
@@ -34,6 +35,7 @@ import android.os.BatteryUsageStats;
|
||||
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;
|
||||
@@ -93,6 +95,53 @@ public final class DatabaseUtilsTest {
|
||||
assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
|
||||
}
|
||||
|
||||
@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());
|
||||
@@ -123,8 +172,8 @@ public final class DatabaseUtilsTest {
|
||||
|
||||
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 @@ public final class DatabaseUtilsTest {
|
||||
/*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 @@ public final class DatabaseUtilsTest {
|
||||
/*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,13 +233,49 @@ public final class DatabaseUtilsTest {
|
||||
/*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(
|
||||
DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
|
||||
}
|
||||
|
||||
@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(
|
||||
@@ -198,7 +283,7 @@ public final class DatabaseUtilsTest {
|
||||
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 @@ public final class DatabaseUtilsTest {
|
||||
|
||||
@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 @@ public final class DatabaseUtilsTest {
|
||||
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 @@ public final class DatabaseUtilsTest {
|
||||
"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 @@ public final class DatabaseUtilsTest {
|
||||
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 @@ public final class DatabaseUtilsTest {
|
||||
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 @@ public final class DatabaseUtilsTest {
|
||||
.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);
|
||||
|
@@ -62,7 +62,7 @@ public final class PeriodicJobReceiverTest {
|
||||
|
||||
// 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 @@ public final class PeriodicJobReceiverTest {
|
||||
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);
|
||||
|
@@ -53,9 +53,9 @@ public final class BugReportContentProviderTest {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -53,9 +53,9 @@ public final class BatteryStateDaoTest {
|
||||
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 final class BatteryStateDaoTest {
|
||||
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);
|
||||
|
@@ -26,6 +26,8 @@ import androidx.room.Room;
|
||||
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 @@ public class BatteryTestUtils {
|
||||
}
|
||||
|
||||
/** 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 @@ public class BatteryTestUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/** 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);
|
||||
|
Reference in New Issue
Block a user