Loading AndroidManifest.xml +20 −0 Original line number Diff line number Diff line Loading @@ -2983,6 +2983,26 @@ </intent-filter> </receiver> <receiver android:name=".fuelgauge.batteryusage.BootBroadcastReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED"/> <action android:name="com.google.android.setupwizard.SETUP_WIZARD_FINISHED"/> <action android:name="com.android.settings.battery.action.PERIODIC_JOB_RECHECK"/> </intent-filter> </receiver> <receiver android:name=".fuelgauge.batteryusage.PeriodicJobReceiver" android:exported="false"> <intent-filter> <action android:name="com.android.settings.battery.action.PERIODIC_JOB_UPDATE"/> </intent-filter> </receiver> <activity android:name="Settings$BatterySaverSettingsActivity" android:label="@string/battery_saver" Loading src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java +2 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; Loading Loading @@ -144,7 +145,7 @@ public class BatteryUsageContentProvider extends ContentProvider { } catch (RuntimeException e) { Log.e(TAG, "query() from:" + uri + " error:" + e); } // TODO: Invokes hourly job recheck. AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext())); Log.w(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms"); return cursor; } Loading src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * 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.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.util.Log; import java.time.Duration; /** Receives broadcasts to start or stop the periodic fetching job. */ public final class BootBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "BootBroadcastReceiver"; private static final long RESCHEDULE_FOR_BOOT_ACTION = Duration.ofSeconds(6).toMillis(); private final Handler mHandler = new Handler(Looper.getMainLooper()); public static final String ACTION_PERIODIC_JOB_RECHECK = "com.android.settings.battery.action.PERIODIC_JOB_RECHECK"; public static final String ACTION_SETUP_WIZARD_FINISHED = "com.google.android.setupwizard.SETUP_WIZARD_FINISHED"; /** Invokes periodic job rechecking process. */ public static void invokeJobRecheck(Context context) { context = context.getApplicationContext(); final Intent intent = new Intent(ACTION_PERIODIC_JOB_RECHECK); intent.setClass(context, BootBroadcastReceiver.class); context.sendBroadcast(intent); } @Override public void onReceive(Context context, Intent intent) { final String action = intent == null ? "" : intent.getAction(); if (DatabaseUtils.isWorkProfile(context)) { Log.w(TAG, "do not start job for work profile action=" + action); return; } switch (action) { case Intent.ACTION_BOOT_COMPLETED: case Intent.ACTION_MY_PACKAGE_REPLACED: case Intent.ACTION_MY_PACKAGE_UNSUSPENDED: case ACTION_SETUP_WIZARD_FINISHED: case ACTION_PERIODIC_JOB_RECHECK: Log.d(TAG, "refresh periodic job from action=" + action); refreshJobs(context); break; case Intent.ACTION_TIME_CHANGED: Log.d(TAG, "refresh job and clear all data from action=" + action); DatabaseUtils.clearAll(context); PeriodicJobManager.getInstance(context).refreshJob(); break; default: Log.w(TAG, "receive unsupported action=" + action); } // Waits a while to recheck the scheduler to avoid AlarmManager is not ready. if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { final Intent recheckIntent = new Intent(ACTION_PERIODIC_JOB_RECHECK); recheckIntent.setClass(context, BootBroadcastReceiver.class); mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), RESCHEDULE_FOR_BOOT_ACTION); } } private static void refreshJobs(Context context) { // Clears useless data from battery usage database if needed. DatabaseUtils.clearExpiredDataIfNeeded(context); PeriodicJobManager.getInstance(context).refreshJob(); } } src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +5 −4 Original line number Diff line number Diff line Loading @@ -57,7 +57,9 @@ public final class DatabaseUtils { /** 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(); private static final long DATA_RETENTION_INTERVAL_MS = Duration.ofDays(9).toMillis(); @VisibleForTesting static final int DATA_RETENTION_INTERVAL_DAY = 9; /** An authority name of the battery content provider. */ public static final String AUTHORITY = "com.android.settings.battery.usage.provider"; Loading @@ -65,8 +67,6 @@ public final class DatabaseUtils { 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 = Loading Loading @@ -133,7 +133,8 @@ public final class DatabaseUtils { BatteryStateDatabase .getInstance(context.getApplicationContext()) .batteryStateDao() .clearAllBefore(Clock.systemUTC().millis() - DATA_RETENTION_INTERVAL_MS); .clearAllBefore(Clock.systemUTC().millis() - Duration.ofDays(DATA_RETENTION_INTERVAL_DAY).toMillis()); } catch (RuntimeException e) { Log.e(TAG, "clearAllBefore() failed", e); } Loading src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.text.SimpleDateFormat; import java.time.Clock; import java.time.Duration; import java.util.Date; import java.util.Locale; /** Manages the periodic job to schedule or cancel the next job. */ public final class PeriodicJobManager { private static final String TAG = "PeriodicJobManager"; private static final int ALARM_MANAGER_REQUEST_CODE = TAG.hashCode(); private static PeriodicJobManager sSingleton; private final Context mContext; private final AlarmManager mAlarmManager; private final SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.ENGLISH); @VisibleForTesting static final int DATA_FETCH_INTERVAL_MINUTE = 60; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) void reset() { sSingleton = null; // for testing only } /** Gets or creates the new {@link PeriodicJobManager} instance. */ public static synchronized PeriodicJobManager getInstance(Context context) { if (sSingleton == null || sSingleton.mAlarmManager == null) { sSingleton = new PeriodicJobManager(context); } return sSingleton; } private PeriodicJobManager(Context context) { this.mContext = context.getApplicationContext(); this.mAlarmManager = context.getSystemService(AlarmManager.class); } /** Schedules the next alarm job if it is available. */ @SuppressWarnings("JavaUtilDate") public void refreshJob() { if (mAlarmManager == null) { Log.e(TAG, "cannot schedule next alarm job"); return; } // Cancels the previous alert job and schedules the next one. final PendingIntent pendingIntent = getPendingIntent(); cancelJob(pendingIntent); if (!canScheduleExactAlarms()) { Log.w(TAG, "cannot schedule exact alarm job"); return; } // Uses UTC time to avoid scheduler is impacted by different timezone. final long triggerAtMillis = getTriggerAtMillis(Clock.systemUTC()); mAlarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); Log.d(TAG, "schedule next alarm job at " + mSimpleDateFormat.format(new Date(triggerAtMillis))); } void cancelJob(PendingIntent pendingIntent) { if (mAlarmManager != null) { mAlarmManager.cancel(pendingIntent); } else { Log.e(TAG, "cannot cancel the alarm job"); } } /** Gets the next alarm trigger UTC time in milliseconds. */ static long getTriggerAtMillis(Clock clock) { long currentTimeMillis = clock.millis(); // Rounds to the previous nearest time slot and shifts to the next one. long timeSlotUnit = Duration.ofMinutes(DATA_FETCH_INTERVAL_MINUTE).toMillis(); return (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit; } private PendingIntent getPendingIntent() { final Intent broadcastIntent = new Intent(mContext, PeriodicJobReceiver.class) .setAction(PeriodicJobReceiver.ACTION_PERIODIC_JOB_UPDATE); return PendingIntent.getBroadcast( mContext.getApplicationContext(), ALARM_MANAGER_REQUEST_CODE, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); } private boolean canScheduleExactAlarms() { return canScheduleExactAlarms(mAlarmManager); } /** Whether we can schedule exact alarm or not? */ public static boolean canScheduleExactAlarms(AlarmManager alarmManager) { return alarmManager.canScheduleExactAlarms(); } } Loading
AndroidManifest.xml +20 −0 Original line number Diff line number Diff line Loading @@ -2983,6 +2983,26 @@ </intent-filter> </receiver> <receiver android:name=".fuelgauge.batteryusage.BootBroadcastReceiver" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <action android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED"/> <action android:name="com.google.android.setupwizard.SETUP_WIZARD_FINISHED"/> <action android:name="com.android.settings.battery.action.PERIODIC_JOB_RECHECK"/> </intent-filter> </receiver> <receiver android:name=".fuelgauge.batteryusage.PeriodicJobReceiver" android:exported="false"> <intent-filter> <action android:name="com.android.settings.battery.action.PERIODIC_JOB_UPDATE"/> </intent-filter> </receiver> <activity android:name="Settings$BatterySaverSettingsActivity" android:label="@string/battery_saver" Loading
src/com/android/settings/fuelgauge/batteryusage/BatteryUsageContentProvider.java +2 −1 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.content.ContentValues; import android.content.UriMatcher; import android.database.Cursor; import android.net.Uri; import android.os.AsyncTask; import android.text.TextUtils; import android.util.Log; Loading Loading @@ -144,7 +145,7 @@ public class BatteryUsageContentProvider extends ContentProvider { } catch (RuntimeException e) { Log.e(TAG, "query() from:" + uri + " error:" + e); } // TODO: Invokes hourly job recheck. AsyncTask.execute(() -> BootBroadcastReceiver.invokeJobRecheck(getContext())); Log.w(TAG, "query battery states in " + (mClock.millis() - timestamp) + "/ms"); return cursor; } Loading
src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java 0 → 100644 +88 −0 Original line number Diff line number Diff line /* * 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.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.Looper; import android.util.Log; import java.time.Duration; /** Receives broadcasts to start or stop the periodic fetching job. */ public final class BootBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "BootBroadcastReceiver"; private static final long RESCHEDULE_FOR_BOOT_ACTION = Duration.ofSeconds(6).toMillis(); private final Handler mHandler = new Handler(Looper.getMainLooper()); public static final String ACTION_PERIODIC_JOB_RECHECK = "com.android.settings.battery.action.PERIODIC_JOB_RECHECK"; public static final String ACTION_SETUP_WIZARD_FINISHED = "com.google.android.setupwizard.SETUP_WIZARD_FINISHED"; /** Invokes periodic job rechecking process. */ public static void invokeJobRecheck(Context context) { context = context.getApplicationContext(); final Intent intent = new Intent(ACTION_PERIODIC_JOB_RECHECK); intent.setClass(context, BootBroadcastReceiver.class); context.sendBroadcast(intent); } @Override public void onReceive(Context context, Intent intent) { final String action = intent == null ? "" : intent.getAction(); if (DatabaseUtils.isWorkProfile(context)) { Log.w(TAG, "do not start job for work profile action=" + action); return; } switch (action) { case Intent.ACTION_BOOT_COMPLETED: case Intent.ACTION_MY_PACKAGE_REPLACED: case Intent.ACTION_MY_PACKAGE_UNSUSPENDED: case ACTION_SETUP_WIZARD_FINISHED: case ACTION_PERIODIC_JOB_RECHECK: Log.d(TAG, "refresh periodic job from action=" + action); refreshJobs(context); break; case Intent.ACTION_TIME_CHANGED: Log.d(TAG, "refresh job and clear all data from action=" + action); DatabaseUtils.clearAll(context); PeriodicJobManager.getInstance(context).refreshJob(); break; default: Log.w(TAG, "receive unsupported action=" + action); } // Waits a while to recheck the scheduler to avoid AlarmManager is not ready. if (Intent.ACTION_BOOT_COMPLETED.equals(action)) { final Intent recheckIntent = new Intent(ACTION_PERIODIC_JOB_RECHECK); recheckIntent.setClass(context, BootBroadcastReceiver.class); mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent), RESCHEDULE_FOR_BOOT_ACTION); } } private static void refreshJobs(Context context) { // Clears useless data from battery usage database if needed. DatabaseUtils.clearExpiredDataIfNeeded(context); PeriodicJobManager.getInstance(context).refreshJob(); } }
src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java +5 −4 Original line number Diff line number Diff line Loading @@ -57,7 +57,9 @@ public final class DatabaseUtils { /** 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(); private static final long DATA_RETENTION_INTERVAL_MS = Duration.ofDays(9).toMillis(); @VisibleForTesting static final int DATA_RETENTION_INTERVAL_DAY = 9; /** An authority name of the battery content provider. */ public static final String AUTHORITY = "com.android.settings.battery.usage.provider"; Loading @@ -65,8 +67,6 @@ public final class DatabaseUtils { 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 = Loading Loading @@ -133,7 +133,8 @@ public final class DatabaseUtils { BatteryStateDatabase .getInstance(context.getApplicationContext()) .batteryStateDao() .clearAllBefore(Clock.systemUTC().millis() - DATA_RETENTION_INTERVAL_MS); .clearAllBefore(Clock.systemUTC().millis() - Duration.ofDays(DATA_RETENTION_INTERVAL_DAY).toMillis()); } catch (RuntimeException e) { Log.e(TAG, "clearAllBefore() failed", e); } Loading
src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java 0 → 100644 +123 −0 Original line number Diff line number Diff line /* * 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.AlarmManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.util.Log; import androidx.annotation.VisibleForTesting; import java.text.SimpleDateFormat; import java.time.Clock; import java.time.Duration; import java.util.Date; import java.util.Locale; /** Manages the periodic job to schedule or cancel the next job. */ public final class PeriodicJobManager { private static final String TAG = "PeriodicJobManager"; private static final int ALARM_MANAGER_REQUEST_CODE = TAG.hashCode(); private static PeriodicJobManager sSingleton; private final Context mContext; private final AlarmManager mAlarmManager; private final SimpleDateFormat mSimpleDateFormat = new SimpleDateFormat("MMM dd,yyyy HH:mm:ss", Locale.ENGLISH); @VisibleForTesting static final int DATA_FETCH_INTERVAL_MINUTE = 60; @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) void reset() { sSingleton = null; // for testing only } /** Gets or creates the new {@link PeriodicJobManager} instance. */ public static synchronized PeriodicJobManager getInstance(Context context) { if (sSingleton == null || sSingleton.mAlarmManager == null) { sSingleton = new PeriodicJobManager(context); } return sSingleton; } private PeriodicJobManager(Context context) { this.mContext = context.getApplicationContext(); this.mAlarmManager = context.getSystemService(AlarmManager.class); } /** Schedules the next alarm job if it is available. */ @SuppressWarnings("JavaUtilDate") public void refreshJob() { if (mAlarmManager == null) { Log.e(TAG, "cannot schedule next alarm job"); return; } // Cancels the previous alert job and schedules the next one. final PendingIntent pendingIntent = getPendingIntent(); cancelJob(pendingIntent); if (!canScheduleExactAlarms()) { Log.w(TAG, "cannot schedule exact alarm job"); return; } // Uses UTC time to avoid scheduler is impacted by different timezone. final long triggerAtMillis = getTriggerAtMillis(Clock.systemUTC()); mAlarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); Log.d(TAG, "schedule next alarm job at " + mSimpleDateFormat.format(new Date(triggerAtMillis))); } void cancelJob(PendingIntent pendingIntent) { if (mAlarmManager != null) { mAlarmManager.cancel(pendingIntent); } else { Log.e(TAG, "cannot cancel the alarm job"); } } /** Gets the next alarm trigger UTC time in milliseconds. */ static long getTriggerAtMillis(Clock clock) { long currentTimeMillis = clock.millis(); // Rounds to the previous nearest time slot and shifts to the next one. long timeSlotUnit = Duration.ofMinutes(DATA_FETCH_INTERVAL_MINUTE).toMillis(); return (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit; } private PendingIntent getPendingIntent() { final Intent broadcastIntent = new Intent(mContext, PeriodicJobReceiver.class) .setAction(PeriodicJobReceiver.ACTION_PERIODIC_JOB_UPDATE); return PendingIntent.getBroadcast( mContext.getApplicationContext(), ALARM_MANAGER_REQUEST_CODE, broadcastIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); } private boolean canScheduleExactAlarms() { return canScheduleExactAlarms(mAlarmManager); } /** Whether we can schedule exact alarm or not? */ public static boolean canScheduleExactAlarms(AlarmManager alarmManager) { return alarmManager.canScheduleExactAlarms(); } }