• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Clock;
32 import java.time.Duration;
33 
34 /** Manages the periodic job to schedule or cancel the next job. */
35 public final class PeriodicJobManager {
36     private static final String TAG = "PeriodicJobManager";
37     private static final int ALARM_MANAGER_REQUEST_CODE = TAG.hashCode();
38 
39     private static PeriodicJobManager sSingleton;
40 
41     private final Context mContext;
42     private final AlarmManager mAlarmManager;
43 
44     @VisibleForTesting
45     static final int DATA_FETCH_INTERVAL_MINUTE = 60;
46 
47     @VisibleForTesting
48     static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis();
49 
50     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
reset()51     void reset() {
52         sSingleton = null; // for testing only
53     }
54 
55     /** Gets or creates the new {@link PeriodicJobManager} instance. */
getInstance(Context context)56     public static synchronized PeriodicJobManager getInstance(Context context) {
57         if (sSingleton == null || sSingleton.mAlarmManager == null) {
58             sSingleton = new PeriodicJobManager(context);
59         }
60         return sSingleton;
61     }
62 
PeriodicJobManager(Context context)63     private PeriodicJobManager(Context context) {
64         this.mContext = context.getApplicationContext();
65         this.mAlarmManager = context.getSystemService(AlarmManager.class);
66     }
67 
68     /** Schedules the next alarm job if it is available. */
refreshJob(final boolean fromBoot)69     public void refreshJob(final boolean fromBoot) {
70         if (mAlarmManager == null) {
71             BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB,
72                     "cannot schedule next alarm job due to AlarmManager is null");
73             Log.e(TAG, "cannot schedule next alarm job");
74             return;
75         }
76         // Cancels the previous alert job and schedules the next one.
77         final PendingIntent pendingIntent = getPendingIntent();
78         cancelJob(pendingIntent);
79         // Uses UTC time to avoid scheduler is impacted by different timezone.
80         final long triggerAtMillis = getTriggerAtMillis(mContext, Clock.systemUTC(), fromBoot);
81         mAlarmManager.setExactAndAllowWhileIdle(
82                 AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
83 
84         final String utcToLocalTime = ConvertUtils.utcToLocalTimeForLogging(triggerAtMillis);
85         BatteryUsageLogUtils.writeLog(mContext, Action.SCHEDULE_JOB,
86                 String.format("triggerTime=%s, fromBoot=%b", utcToLocalTime, fromBoot));
87         Log.d(TAG, "schedule next alarm job at " + utcToLocalTime);
88     }
89 
cancelJob(PendingIntent pendingIntent)90     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 UTC time in milliseconds. */
getTriggerAtMillis(Context context, Clock clock, final boolean fromBoot)99     static long getTriggerAtMillis(Context context, Clock clock, final boolean fromBoot) {
100         long currentTimeMillis = clock.millis();
101         final boolean delayHourlyJobWhenBooting =
102                 FeatureFactory.getFactory(context)
103                         .getPowerUsageFeatureProvider(context)
104                         .delayHourlyJobWhenBooting();
105         // Rounds to the previous nearest time slot and shifts to the next one.
106         long timeSlotUnit = Duration.ofMinutes(DATA_FETCH_INTERVAL_MINUTE).toMillis();
107         long targetTime = (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit;
108         if (delayHourlyJobWhenBooting
109                 && fromBoot
110                 && (targetTime - currentTimeMillis) <= sBroadcastDelayFromBoot) {
111             targetTime += timeSlotUnit;
112         }
113         return targetTime;
114     }
115 
getPendingIntent()116     private PendingIntent getPendingIntent() {
117         final Intent broadcastIntent =
118                 new Intent(mContext, PeriodicJobReceiver.class)
119                         .setAction(PeriodicJobReceiver.ACTION_PERIODIC_JOB_UPDATE);
120         return PendingIntent.getBroadcast(
121                 mContext.getApplicationContext(),
122                 ALARM_MANAGER_REQUEST_CODE,
123                 broadcastIntent,
124                 PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
125     }
126 }
127