Merge "Copy DatabaseUtils from SettingsGoogle to Settings and use the new Settings database."
This commit is contained in:
@@ -15,22 +15,319 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.settings.fuelgauge.batteryusage;
|
package com.android.settings.fuelgauge.batteryusage;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.BatteryManager;
|
||||||
|
import android.os.BatteryUsageStats;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.os.SystemClock;
|
||||||
|
import android.os.UserHandle;
|
||||||
import android.os.UserManager;
|
import android.os.UserManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
|
import com.android.settingslib.fuelgauge.BatteryStatus;
|
||||||
|
|
||||||
|
import java.time.Clock;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/** A utility class to operate battery usage database. */
|
/** A utility class to operate battery usage database. */
|
||||||
public final class DatabaseUtils {
|
public final class DatabaseUtils {
|
||||||
private static final String TAG = "DatabaseUtils";
|
private static final String TAG = "DatabaseUtils";
|
||||||
|
private static final String PREF_FILE_NAME = "battery_module_preference";
|
||||||
|
private static final String PREF_FULL_CHARGE_TIMESTAMP_KEY = "last_full_charge_timestamp_key";
|
||||||
|
/** 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();
|
||||||
|
|
||||||
/** An authority name of the battery content provider. */
|
/** An authority name of the battery content provider. */
|
||||||
public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
|
public static final String AUTHORITY = "com.android.settings.battery.usage.provider";
|
||||||
/** A table name for battery usage history. */
|
/** A table name for battery usage history. */
|
||||||
public static final String BATTERY_STATE_TABLE = "BatteryState";
|
public static final String BATTERY_STATE_TABLE = "BatteryState";
|
||||||
|
/** A class name for battery usage data provider. */
|
||||||
|
public static final String SETTINGS_PACKAGE_PATH = "com.android.settings";
|
||||||
|
public static final String BATTERY_PROVIDER_CLASS_PATH =
|
||||||
|
"com.android.settings.fuelgauge.batteryusage.BatteryUsageContentProvider";
|
||||||
|
|
||||||
|
/** A content URI to access battery usage states data. */
|
||||||
|
public static final Uri BATTERY_CONTENT_URI =
|
||||||
|
new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(AUTHORITY)
|
||||||
|
.appendPath(BATTERY_STATE_TABLE)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private DatabaseUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns true if current user is a work profile user. */
|
/** Returns true if current user is a work profile user. */
|
||||||
public static boolean isWorkProfile(Context context) {
|
public static boolean isWorkProfile(Context context) {
|
||||||
final UserManager userManager = context.getSystemService(UserManager.class);
|
final UserManager userManager = context.getSystemService(UserManager.class);
|
||||||
return userManager.isManagedProfile() && !userManager.isSystemUser();
|
return userManager.isManagedProfile() && !userManager.isSystemUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns true if the chart graph design is enabled. */
|
||||||
|
public static boolean isChartGraphEnabled(Context context) {
|
||||||
|
return isContentProviderEnabled(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Long: for timestamp and String: for BatteryHistEntry.getKey() */
|
||||||
|
public static Map<Long, Map<String, BatteryHistEntry>> getHistoryMapSinceLastFullCharge(
|
||||||
|
Context context, Calendar calendar) {
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
|
final long lastFullChargeTimestamp =
|
||||||
|
getStartTimestampForLastFullCharge(context, calendar);
|
||||||
|
// Builds the content uri everytime to avoid cache.
|
||||||
|
final Uri batteryStateUri =
|
||||||
|
new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(AUTHORITY)
|
||||||
|
.appendPath(BATTERY_STATE_TABLE)
|
||||||
|
.appendQueryParameter(
|
||||||
|
QUERY_KEY_TIMESTAMP, Long.toString(lastFullChargeTimestamp))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
final Map<Long, Map<String, BatteryHistEntry>> resultMap =
|
||||||
|
loadHistoryMapFromContentProvider(context, batteryStateUri);
|
||||||
|
if (resultMap == null || resultMap.isEmpty()) {
|
||||||
|
Log.d(TAG, "getHistoryMapSinceLastFullCharge() returns empty or null");
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, String.format("getHistoryMapSinceLastFullCharge() size=%d in %d/ms",
|
||||||
|
resultMap.size(), (System.currentTimeMillis() - startTime)));
|
||||||
|
}
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean isContentProviderEnabled(Context context) {
|
||||||
|
return context.getPackageManager()
|
||||||
|
.getComponentEnabledSetting(
|
||||||
|
new ComponentName(SETTINGS_PACKAGE_PATH, BATTERY_PROVIDER_CLASS_PATH))
|
||||||
|
== PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<ContentValues> sendBatteryEntryData(
|
||||||
|
Context context,
|
||||||
|
List<BatteryEntry> batteryEntryList,
|
||||||
|
BatteryUsageStats batteryUsageStats) {
|
||||||
|
final long startTime = System.currentTimeMillis();
|
||||||
|
final Intent intent = getBatteryIntent(context);
|
||||||
|
if (intent == null) {
|
||||||
|
Log.e(TAG, "sendBatteryEntryData(): cannot fetch battery intent");
|
||||||
|
clearMemory();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final int batteryLevel = getBatteryLevel(intent);
|
||||||
|
final int batteryStatus = intent.getIntExtra(
|
||||||
|
BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_UNKNOWN);
|
||||||
|
final int batteryHealth = intent.getIntExtra(
|
||||||
|
BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_UNKNOWN);
|
||||||
|
// We should use the same timestamp for each data snapshot.
|
||||||
|
final long snapshotTimestamp = Clock.systemUTC().millis();
|
||||||
|
final long snapshotBootTimestamp = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
|
// Creates the ContentValues list to insert them into provider.
|
||||||
|
final List<ContentValues> valuesList = new ArrayList<>();
|
||||||
|
if (batteryEntryList != null) {
|
||||||
|
batteryEntryList.stream()
|
||||||
|
.filter(entry -> {
|
||||||
|
final long foregroundMs = entry.getTimeInForegroundMs();
|
||||||
|
final long backgroundMs = entry.getTimeInBackgroundMs();
|
||||||
|
if (entry.getConsumedPower() == 0
|
||||||
|
&& (foregroundMs != 0 || backgroundMs != 0)) {
|
||||||
|
Log.w(TAG, String.format(
|
||||||
|
"no consumed power but has running time for %s time=%d|%d",
|
||||||
|
entry.getLabel(), foregroundMs, backgroundMs));
|
||||||
|
}
|
||||||
|
return entry.getConsumedPower() != 0
|
||||||
|
|| foregroundMs != 0
|
||||||
|
|| backgroundMs != 0;
|
||||||
|
})
|
||||||
|
.forEach(entry -> valuesList.add(
|
||||||
|
ConvertUtils.convertToContentValues(
|
||||||
|
entry,
|
||||||
|
batteryUsageStats,
|
||||||
|
batteryLevel,
|
||||||
|
batteryStatus,
|
||||||
|
batteryHealth,
|
||||||
|
snapshotBootTimestamp,
|
||||||
|
snapshotTimestamp)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int size = 1;
|
||||||
|
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(BATTERY_CONTENT_URI, valuesArray);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "bulkInsert() data into database error:\n" + e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Inserts one fake data into battery provider.
|
||||||
|
final ContentValues contentValues =
|
||||||
|
ConvertUtils.convertToContentValues(
|
||||||
|
/*entry=*/ null,
|
||||||
|
/*batteryUsageStats=*/ null,
|
||||||
|
batteryLevel,
|
||||||
|
batteryStatus,
|
||||||
|
batteryHealth,
|
||||||
|
snapshotBootTimestamp,
|
||||||
|
snapshotTimestamp);
|
||||||
|
try {
|
||||||
|
resolver.insert(BATTERY_CONTENT_URI, contentValues);
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "insert() data into database error:\n" + e);
|
||||||
|
}
|
||||||
|
valuesList.add(contentValues);
|
||||||
|
}
|
||||||
|
saveLastFullChargeTimestampPref(context, batteryStatus, batteryLevel, snapshotTimestamp);
|
||||||
|
resolver.notifyChange(BATTERY_CONTENT_URI, /*observer=*/ null);
|
||||||
|
Log.d(TAG, String.format("sendBatteryEntryData() size=%d in %d/ms",
|
||||||
|
size, (System.currentTimeMillis() - startTime)));
|
||||||
|
clearMemory();
|
||||||
|
return valuesList;
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static void saveLastFullChargeTimestampPref(
|
||||||
|
Context context, int batteryStatus, int batteryLevel, long timestamp) {
|
||||||
|
// Updates the SharedPreference only when timestamp is valid and phone is full charge.
|
||||||
|
if (!BatteryStatus.isCharged(batteryStatus, batteryLevel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final boolean success =
|
||||||
|
getSharedPreferences(context)
|
||||||
|
.edit()
|
||||||
|
.putLong(PREF_FULL_CHARGE_TIMESTAMP_KEY, timestamp)
|
||||||
|
.commit();
|
||||||
|
if (!success) {
|
||||||
|
Log.w(TAG, "saveLastFullChargeTimestampPref() fail: value=" + timestamp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static long getLastFullChargeTimestampPref(Context context) {
|
||||||
|
return getSharedPreferences(context).getLong(PREF_FULL_CHARGE_TIMESTAMP_KEY, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the start timestamp for "since last full charge" battery usage chart.
|
||||||
|
* If the last full charge happens within the last 7 days, returns the timestamp of last full
|
||||||
|
* charge. Otherwise, returns the timestamp for 00:00 6 days before the calendar date.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
static long getStartTimestampForLastFullCharge(
|
||||||
|
Context context, Calendar calendar) {
|
||||||
|
final long lastFullChargeTimestamp = getLastFullChargeTimestampPref(context);
|
||||||
|
final long sixDayAgoTimestamp = getTimestampSixDaysAgo(calendar);
|
||||||
|
return Math.max(lastFullChargeTimestamp, sixDayAgoTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Map<Long, Map<String, BatteryHistEntry>> loadHistoryMapFromContentProvider(
|
||||||
|
Context context, Uri batteryStateUri) {
|
||||||
|
final boolean isWorkProfileUser = isWorkProfile(context);
|
||||||
|
Log.d(TAG, "loadHistoryMapFromContentProvider() 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isContentProviderEnabled(context)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Map<Long, Map<String, BatteryHistEntry>> resultMap = new HashMap();
|
||||||
|
try (Cursor cursor =
|
||||||
|
context.getContentResolver().query(batteryStateUri, null, null, null)) {
|
||||||
|
if (cursor == null || cursor.getCount() == 0) {
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
// Loads and recovers all BatteryHistEntry data from cursor.
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
final BatteryHistEntry entry = new BatteryHistEntry(cursor);
|
||||||
|
final long timestamp = entry.mTimestamp;
|
||||||
|
final String key = entry.getKey();
|
||||||
|
Map batteryHistEntryMap = resultMap.get(timestamp);
|
||||||
|
// Creates new one if there is no corresponding map.
|
||||||
|
if (batteryHistEntryMap == null) {
|
||||||
|
batteryHistEntryMap = new HashMap<>();
|
||||||
|
resultMap.put(timestamp, batteryHistEntryMap);
|
||||||
|
}
|
||||||
|
batteryHistEntryMap.put(key, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return resultMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the latest sticky battery intent from framework. */
|
||||||
|
private static Intent getBatteryIntent(Context context) {
|
||||||
|
return context.registerReceiver(
|
||||||
|
/*receiver=*/ null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getBatteryLevel(Intent intent) {
|
||||||
|
final int level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
|
||||||
|
final int scale = intent.getIntExtra(BatteryManager.EXTRA_SCALE, 0);
|
||||||
|
return scale == 0
|
||||||
|
? -1 /*invalid battery level*/
|
||||||
|
: Math.round((level / (float) scale) * 100f);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void clearMemory() {
|
||||||
|
if (SystemClock.uptimeMillis() > CLEAR_MEMORY_THRESHOLD_MS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Handler mainHandler = new Handler(Looper.getMainLooper());
|
||||||
|
mainHandler.postDelayed(() -> {
|
||||||
|
System.gc();
|
||||||
|
System.runFinalization();
|
||||||
|
System.gc();
|
||||||
|
Log.w(TAG, "invoke clearMemory()");
|
||||||
|
}, CLEAR_MEMORY_DELAYED_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SharedPreferences getSharedPreferences(Context context) {
|
||||||
|
return context
|
||||||
|
.getApplicationContext() // ensures we bind it with application
|
||||||
|
.createDeviceProtectedStorageContext()
|
||||||
|
.getSharedPreferences(PREF_FILE_NAME, Context.MODE_PRIVATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -44,12 +44,7 @@ import java.util.List;
|
|||||||
/** Tests for {@link BatteryUsageContentProvider}. */
|
/** Tests for {@link BatteryUsageContentProvider}. */
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public final class BatteryUsageContentProviderTest {
|
public final class BatteryUsageContentProviderTest {
|
||||||
private static final Uri VALID_BATTERY_STATE_CONTENT_URI =
|
private static final Uri VALID_BATTERY_STATE_CONTENT_URI = DatabaseUtils.BATTERY_CONTENT_URI;
|
||||||
new Uri.Builder()
|
|
||||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
|
||||||
.authority(DatabaseUtils.AUTHORITY)
|
|
||||||
.appendPath(DatabaseUtils.BATTERY_STATE_TABLE)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private BatteryUsageContentProvider mProvider;
|
private BatteryUsageContentProvider mProvider;
|
||||||
|
@@ -0,0 +1,422 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
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.when;
|
||||||
|
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.os.BatteryManager;
|
||||||
|
import android.os.BatteryUsageStats;
|
||||||
|
import android.os.UserHandle;
|
||||||
|
import android.os.UserManager;
|
||||||
|
|
||||||
|
import com.android.settings.testutils.BatteryTestUtils;
|
||||||
|
|
||||||
|
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 org.robolectric.Shadows;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
@RunWith(RobolectricTestRunner.class)
|
||||||
|
public final class DatabaseUtilsTest {
|
||||||
|
|
||||||
|
private Context mContext;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PackageManager mPackageManager;
|
||||||
|
@Mock private ContentResolver mMockContentResolver;
|
||||||
|
@Mock private ContentResolver mMockContentResolver2;
|
||||||
|
@Mock private BatteryUsageStats mBatteryUsageStats;
|
||||||
|
@Mock private BatteryEntry mMockBatteryEntry1;
|
||||||
|
@Mock private BatteryEntry mMockBatteryEntry2;
|
||||||
|
@Mock private BatteryEntry mMockBatteryEntry3;
|
||||||
|
@Mock private Context mMockContext;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mContext = spy(RuntimeEnvironment.application);
|
||||||
|
setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
|
||||||
|
doReturn(mMockContentResolver2).when(mMockContext).getContentResolver();
|
||||||
|
doReturn(mMockContentResolver).when(mContext).getContentResolver();
|
||||||
|
doReturn(mPackageManager).when(mMockContext).getPackageManager();
|
||||||
|
doReturn(mPackageManager).when(mContext).getPackageManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isWorkProfile_defaultValue_returnFalse() {
|
||||||
|
assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isWorkProfile_withManagedUser_returnTrue() {
|
||||||
|
BatteryTestUtils.setWorkProfile(mContext);
|
||||||
|
assertThat(DatabaseUtils.isWorkProfile(mContext)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isWorkProfile_withSystemUser_returnFalse() {
|
||||||
|
BatteryTestUtils.setWorkProfile(mContext);
|
||||||
|
Shadows.shadowOf(mContext.getSystemService(UserManager.class)).setIsSystemUser(true);
|
||||||
|
|
||||||
|
assertThat(DatabaseUtils.isWorkProfile(mContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isChartGraphEnabled_providerIsEnabled_returnTrue() {
|
||||||
|
setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
|
||||||
|
assertThat(DatabaseUtils.isChartGraphEnabled(mContext)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isChartGraphEnabled_providerIsDisabled_returnFalse() {
|
||||||
|
setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
|
||||||
|
assertThat(DatabaseUtils.isChartGraphEnabled(mContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isContentProviderEnabled_providerEnabled_returnsTrue() {
|
||||||
|
setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
|
||||||
|
assertThat(DatabaseUtils.isContentProviderEnabled(mContext)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isContentProviderEnabled_providerDisabled_returnsFalse() {
|
||||||
|
setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
|
||||||
|
assertThat(DatabaseUtils.isContentProviderEnabled(mContext)).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendBatteryEntryData_nullBatteryIntent_returnsNullValue() {
|
||||||
|
doReturn(null).when(mContext).registerReceiver(any(), any());
|
||||||
|
assertThat(
|
||||||
|
DatabaseUtils.sendBatteryEntryData(
|
||||||
|
mContext, /*batteryEntryList=*/ null, mBatteryUsageStats))
|
||||||
|
.isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendBatteryEntryData_returnsExpectedList() {
|
||||||
|
doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
|
||||||
|
// Configures the testing BatteryEntry data.
|
||||||
|
final List<BatteryEntry> batteryEntryList = new ArrayList<>();
|
||||||
|
batteryEntryList.add(mMockBatteryEntry1);
|
||||||
|
batteryEntryList.add(mMockBatteryEntry2);
|
||||||
|
batteryEntryList.add(mMockBatteryEntry3);
|
||||||
|
doReturn(0.0).when(mMockBatteryEntry1).getConsumedPower();
|
||||||
|
doReturn(0.5).when(mMockBatteryEntry2).getConsumedPower();
|
||||||
|
doReturn(0.0).when(mMockBatteryEntry3).getConsumedPower();
|
||||||
|
doReturn(1L).when(mMockBatteryEntry3).getTimeInForegroundMs();
|
||||||
|
|
||||||
|
final List<ContentValues> valuesList =
|
||||||
|
DatabaseUtils.sendBatteryEntryData(
|
||||||
|
mContext, batteryEntryList, mBatteryUsageStats);
|
||||||
|
|
||||||
|
assertThat(valuesList).hasSize(2);
|
||||||
|
// Verifies the ContentValues content.
|
||||||
|
verifyContentValues(0.5, valuesList.get(0));
|
||||||
|
verifyContentValues(0.0, valuesList.get(1));
|
||||||
|
// Verifies the inserted ContentValues into content provider.
|
||||||
|
final ContentValues[] valuesArray =
|
||||||
|
new ContentValues[] {valuesList.get(0), valuesList.get(1)};
|
||||||
|
verify(mMockContentResolver).bulkInsert(
|
||||||
|
DatabaseUtils.BATTERY_CONTENT_URI, valuesArray);
|
||||||
|
verify(mMockContentResolver).notifyChange(
|
||||||
|
DatabaseUtils.BATTERY_CONTENT_URI, /*observer=*/ null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sendBatteryEntryData_emptyBatteryEntryList_sendFakeDataIntoProvider() {
|
||||||
|
doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
|
||||||
|
|
||||||
|
final List<ContentValues> valuesList =
|
||||||
|
DatabaseUtils.sendBatteryEntryData(
|
||||||
|
mContext,
|
||||||
|
new ArrayList<>(),
|
||||||
|
mBatteryUsageStats);
|
||||||
|
|
||||||
|
assertThat(valuesList).hasSize(1);
|
||||||
|
verifyFakeContentValues(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 sendBatteryEntryData_nullBatteryEntryList_sendFakeDataIntoProvider() {
|
||||||
|
doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
|
||||||
|
|
||||||
|
final List<ContentValues> valuesList =
|
||||||
|
DatabaseUtils.sendBatteryEntryData(
|
||||||
|
mContext,
|
||||||
|
/*batteryEntryList=*/ null,
|
||||||
|
mBatteryUsageStats);
|
||||||
|
|
||||||
|
assertThat(valuesList).hasSize(1);
|
||||||
|
verifyFakeContentValues(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 sendBatteryEntryData_nullBatteryUsageStats_sendFakeDataIntoProvider() {
|
||||||
|
doReturn(getBatteryIntent()).when(mContext).registerReceiver(any(), any());
|
||||||
|
|
||||||
|
final List<ContentValues> valuesList =
|
||||||
|
DatabaseUtils.sendBatteryEntryData(
|
||||||
|
mContext,
|
||||||
|
/*batteryEntryList=*/ null,
|
||||||
|
/*batteryUsageStats=*/ null);
|
||||||
|
|
||||||
|
assertThat(valuesList).hasSize(1);
|
||||||
|
verifyFakeContentValues(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 getHistoryMapSinceLastFullCharge_providerIsDisabled_returnNull() {
|
||||||
|
setProviderSetting(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
|
||||||
|
assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
|
||||||
|
mContext, /*calendar=*/ null)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getHistoryMapSinceLastFullCharge_emptyCursorContent_returnEmptyMap() {
|
||||||
|
final MatrixCursor cursor = new MatrixCursor(
|
||||||
|
new String[] {
|
||||||
|
BatteryHistEntry.KEY_UID,
|
||||||
|
BatteryHistEntry.KEY_USER_ID,
|
||||||
|
BatteryHistEntry.KEY_TIMESTAMP});
|
||||||
|
doReturn(cursor).when(mMockContentResolver).query(any(), any(), any(), any());
|
||||||
|
|
||||||
|
assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
|
||||||
|
mContext, /*calendar=*/ null)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getHistoryMapSinceLastFullCharge_nullCursor_returnEmptyMap() {
|
||||||
|
doReturn(null).when(mMockContentResolver).query(any(), any(), any(), any());
|
||||||
|
assertThat(DatabaseUtils.getHistoryMapSinceLastFullCharge(
|
||||||
|
mContext, /*calendar=*/ null)).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getHistoryMapSinceLastFullCharge_returnExpectedMap() {
|
||||||
|
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});
|
||||||
|
cursor.addRow(new Object[] {
|
||||||
|
"app name2", timestamp2, 2, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
|
||||||
|
cursor.addRow(new Object[] {
|
||||||
|
"app name3", timestamp2, 3, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
|
||||||
|
cursor.addRow(new Object[] {
|
||||||
|
"app name4", timestamp2, 4, ConvertUtils.CONSUMER_TYPE_UID_BATTERY});
|
||||||
|
|
||||||
|
final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
|
||||||
|
DatabaseUtils.getHistoryMapSinceLastFullCharge(
|
||||||
|
mContext, /*calendar=*/ null);
|
||||||
|
|
||||||
|
assertThat(batteryHistMap).hasSize(2);
|
||||||
|
// Verifies the BatteryHistEntry data for timestamp1.
|
||||||
|
Map<String, BatteryHistEntry> batteryMap = batteryHistMap.get(timestamp1);
|
||||||
|
assertThat(batteryMap).hasSize(1);
|
||||||
|
assertThat(batteryMap.get("1").mAppLabel).isEqualTo("app name1");
|
||||||
|
// Verifies the BatteryHistEntry data for timestamp2.
|
||||||
|
batteryMap = batteryHistMap.get(timestamp2);
|
||||||
|
assertThat(batteryMap).hasSize(3);
|
||||||
|
assertThat(batteryMap.get("2").mAppLabel).isEqualTo("app name2");
|
||||||
|
assertThat(batteryMap.get("3").mAppLabel).isEqualTo("app name3");
|
||||||
|
assertThat(batteryMap.get("4").mAppLabel).isEqualTo("app name4");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getHistoryMapSinceLastFullCharge_withWorkProfile_returnExpectedMap()
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
doReturn("com.fake.package").when(mContext).getPackageName();
|
||||||
|
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());
|
||||||
|
|
||||||
|
final Map<Long, Map<String, BatteryHistEntry>> batteryHistMap =
|
||||||
|
DatabaseUtils.getHistoryMapSinceLastFullCharge(
|
||||||
|
mContext, /*calendar=*/ null);
|
||||||
|
|
||||||
|
assertThat(batteryHistMap).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void saveLastFullChargeTimestampPref_notFullCharge_returnsFalse() {
|
||||||
|
DatabaseUtils.saveLastFullChargeTimestampPref(
|
||||||
|
mContext,
|
||||||
|
BatteryManager.BATTERY_STATUS_UNKNOWN,
|
||||||
|
/* level */ 10,
|
||||||
|
/* timestamp */ 1);
|
||||||
|
assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext)).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void saveLastFullChargeTimestampPref_fullStatus_returnsTrue() {
|
||||||
|
long expectedTimestamp = 1;
|
||||||
|
DatabaseUtils.saveLastFullChargeTimestampPref(
|
||||||
|
mContext,
|
||||||
|
BatteryManager.BATTERY_STATUS_FULL,
|
||||||
|
/* level */ 10,
|
||||||
|
/* timestamp */ expectedTimestamp);
|
||||||
|
assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext))
|
||||||
|
.isEqualTo(expectedTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void saveLastFullChargeTimestampPref_level100_returnsTrue() {
|
||||||
|
long expectedTimestamp = 1;
|
||||||
|
DatabaseUtils.saveLastFullChargeTimestampPref(
|
||||||
|
mContext,
|
||||||
|
BatteryManager.BATTERY_STATUS_UNKNOWN,
|
||||||
|
/* level */ 100,
|
||||||
|
/* timestamp */ expectedTimestamp);
|
||||||
|
assertThat(DatabaseUtils.getLastFullChargeTimestampPref(mContext))
|
||||||
|
.isEqualTo(expectedTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStartTimestampForLastFullCharge_noTimestampPreference_returnsSixDaysAgo() {
|
||||||
|
Calendar currentCalendar = Calendar.getInstance();
|
||||||
|
currentCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
|
||||||
|
Calendar expectedCalendar = Calendar.getInstance();
|
||||||
|
expectedCalendar.set(2022, 5, 29, 0, 0, 0); // 2022-06-29 00:00:00
|
||||||
|
expectedCalendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
|
||||||
|
.isEqualTo(expectedCalendar.getTimeInMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStartTimestampForLastFullCharge_lastFullChargeEarlier_returnsSixDaysAgo() {
|
||||||
|
Calendar lastFullCalendar = Calendar.getInstance();
|
||||||
|
lastFullCalendar.set(2021, 11, 25, 6, 30, 50); // 2021-12-25 06:30:50
|
||||||
|
DatabaseUtils.saveLastFullChargeTimestampPref(
|
||||||
|
mContext,
|
||||||
|
BatteryManager.BATTERY_STATUS_UNKNOWN,
|
||||||
|
/* level */ 100,
|
||||||
|
/* timestamp */ lastFullCalendar.getTimeInMillis());
|
||||||
|
Calendar currentCalendar = Calendar.getInstance();
|
||||||
|
currentCalendar.set(2022, 0, 2, 6, 30, 50); // 2022-01-02 06:30:50
|
||||||
|
Calendar expectedCalendar = Calendar.getInstance();
|
||||||
|
expectedCalendar.set(2021, 11, 27, 0, 0, 0); // 2021-12-27 00:00:00
|
||||||
|
expectedCalendar.set(Calendar.MILLISECOND, 0);
|
||||||
|
|
||||||
|
assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
|
||||||
|
.isEqualTo(expectedCalendar.getTimeInMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getStartTimestampForLastFullCharge_lastFullChargeLater_returnsLastFullCharge() {
|
||||||
|
Calendar lastFullCalendar = Calendar.getInstance();
|
||||||
|
lastFullCalendar.set(2022, 6, 1, 6, 30, 50); // 2022-07-01 06:30:50
|
||||||
|
long expectedTimestamp = lastFullCalendar.getTimeInMillis();
|
||||||
|
DatabaseUtils.saveLastFullChargeTimestampPref(
|
||||||
|
mContext,
|
||||||
|
BatteryManager.BATTERY_STATUS_UNKNOWN,
|
||||||
|
/* level */ 100,
|
||||||
|
/* timestamp */ expectedTimestamp);
|
||||||
|
Calendar currentCalendar = Calendar.getInstance();
|
||||||
|
currentCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50
|
||||||
|
|
||||||
|
assertThat(DatabaseUtils.getStartTimestampForLastFullCharge(mContext, currentCalendar))
|
||||||
|
.isEqualTo(expectedTimestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProviderSetting(int value) {
|
||||||
|
when(mPackageManager.getComponentEnabledSetting(
|
||||||
|
new ComponentName(
|
||||||
|
DatabaseUtils.SETTINGS_PACKAGE_PATH,
|
||||||
|
DatabaseUtils.BATTERY_PROVIDER_CLASS_PATH)))
|
||||||
|
.thenReturn(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyContentValues(double consumedPower, ContentValues values) {
|
||||||
|
assertThat(values.getAsDouble(BatteryHistEntry.KEY_CONSUME_POWER))
|
||||||
|
.isEqualTo(consumedPower);
|
||||||
|
assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_LEVEL)).isEqualTo(20);
|
||||||
|
assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_STATUS))
|
||||||
|
.isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
|
||||||
|
assertThat(values.getAsInteger(BatteryHistEntry.KEY_BATTERY_HEALTH))
|
||||||
|
.isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyFakeContentValues(ContentValues values) {
|
||||||
|
assertThat(values.getAsInteger("batteryLevel")).isEqualTo(20);
|
||||||
|
assertThat(values.getAsInteger("batteryStatus"))
|
||||||
|
.isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
|
||||||
|
assertThat(values.getAsInteger("batteryHealth"))
|
||||||
|
.isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
|
||||||
|
assertThat(values.getAsString("packageName"))
|
||||||
|
.isEqualTo(ConvertUtils.FAKE_PACKAGE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Intent getBatteryIntent() {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
|
||||||
|
intent.putExtra(BatteryManager.EXTRA_LEVEL, 20);
|
||||||
|
intent.putExtra(BatteryManager.EXTRA_SCALE, 100);
|
||||||
|
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_FULL);
|
||||||
|
intent.putExtra(BatteryManager.EXTRA_HEALTH, BatteryManager.BATTERY_HEALTH_COLD);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MatrixCursor getMatrixCursor() {
|
||||||
|
return new MatrixCursor(
|
||||||
|
new String[] {
|
||||||
|
BatteryHistEntry.KEY_APP_LABEL,
|
||||||
|
BatteryHistEntry.KEY_TIMESTAMP,
|
||||||
|
BatteryHistEntry.KEY_UID,
|
||||||
|
BatteryHistEntry.KEY_CONSUMER_TYPE});
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user