1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.fuelgauge.batteryusage; 18 19 import android.app.AlarmManager; 20 import android.app.PendingIntent; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.util.Log; 24 25 import androidx.annotation.VisibleForTesting; 26 27 import com.android.settings.fuelgauge.BatteryUsageHistoricalLogEntry.Action; 28 import com.android.settings.fuelgauge.batteryusage.bugreport.BatteryUsageLogUtils; 29 import com.android.settings.overlay.FeatureFactory; 30 31 import java.time.Duration; 32 33 /** Manages the periodic job to schedule or cancel the next job. */ 34 public final class PeriodicJobManager { 35 private static final String TAG = "PeriodicJobManager"; 36 private static final int ALARM_MANAGER_REQUEST_CODE = TAG.hashCode(); 37 38 private static PeriodicJobManager sSingleton; 39 40 private final Context mContext; 41 private final AlarmManager mAlarmManager; 42 43 @VisibleForTesting static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis(); 44 45 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) reset()46 void reset() { 47 sSingleton = null; // for testing only 48 } 49 50 /** Gets or creates the new {@link PeriodicJobManager} instance. */ getInstance(Context context)51 public static synchronized PeriodicJobManager getInstance(Context context) { 52 if (sSingleton == null || sSingleton.mAlarmManager == null) { 53 sSingleton = new PeriodicJobManager(context); 54 } 55 return sSingleton; 56 } 57 PeriodicJobManager(Context context)58 private PeriodicJobManager(Context context) { 59 this.mContext = context.getApplicationContext(); 60 this.mAlarmManager = context.getSystemService(AlarmManager.class); 61 } 62 63 /** Schedules the next alarm job if it is available. */ refreshJob(final boolean fromBoot)64 public void refreshJob(final boolean fromBoot) { 65 if (mAlarmManager == null) { 66 BatteryUsageLogUtils.writeLog( 67 mContext, 68 Action.SCHEDULE_JOB, 69 "cannot schedule next alarm job due to AlarmManager is null"); 70 Log.e(TAG, "cannot schedule next alarm job"); 71 return; 72 } 73 // Cancels the previous alert job and schedules the next one. 74 final PendingIntent pendingIntent = getPendingIntent(); 75 cancelJob(pendingIntent); 76 // Uses the timestamp of next full hour in local timezone. 77 long currentTimeMillis = System.currentTimeMillis(); 78 final long triggerAtMillis = getTriggerAtMillis(currentTimeMillis, fromBoot); 79 mAlarmManager.setExactAndAllowWhileIdle( 80 AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent); 81 82 final String timeForLogging = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis); 83 BatteryUsageLogUtils.writeLog( 84 mContext, 85 Action.SCHEDULE_JOB, 86 String.format("triggerTime=%s, fromBoot=%b", timeForLogging, fromBoot)); 87 Log.d(TAG, "schedule next alarm job at " + timeForLogging); 88 } 89 cancelJob(PendingIntent pendingIntent)90 private void cancelJob(PendingIntent pendingIntent) { 91 if (mAlarmManager != null) { 92 mAlarmManager.cancel(pendingIntent); 93 } else { 94 Log.e(TAG, "cannot cancel the alarm job"); 95 } 96 } 97 98 /** Gets the next alarm trigger time in milliseconds. */ 99 @VisibleForTesting getTriggerAtMillis(final long currentTimeMillis, final boolean fromBoot)100 static long getTriggerAtMillis(final long currentTimeMillis, final boolean fromBoot) { 101 final boolean delayHourlyJobWhenBooting = 102 FeatureFactory.getFeatureFactory() 103 .getPowerUsageFeatureProvider() 104 .delayHourlyJobWhenBooting(); 105 long targetTimeMillis = TimestampUtils.getNextHourTimestamp(currentTimeMillis); 106 if (delayHourlyJobWhenBooting 107 && fromBoot 108 && (targetTimeMillis - currentTimeMillis) <= sBroadcastDelayFromBoot) { 109 // Skips this time broadcast, schedule in the next alarm trigger. 110 targetTimeMillis = TimestampUtils.getNextHourTimestamp(targetTimeMillis); 111 } 112 return targetTimeMillis; 113 } 114 getPendingIntent()115 private PendingIntent getPendingIntent() { 116 final Intent broadcastIntent = 117 new Intent(mContext, PeriodicJobReceiver.class) 118 .setAction(PeriodicJobReceiver.ACTION_PERIODIC_JOB_UPDATE); 119 return PendingIntent.getBroadcast( 120 mContext.getApplicationContext(), 121 ALARM_MANAGER_REQUEST_CODE, 122 broadcastIntent, 123 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); 124 } 125 } 126