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.content.BroadcastReceiver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.os.Build;
23 import android.os.SystemClock;
24 import android.util.Log;
25 
26 import androidx.annotation.VisibleForTesting;
27 
28 import com.android.settings.fuelgauge.BatteryUtils;
29 import com.android.settings.overlay.FeatureFactory;
30 import com.android.settingslib.fuelgauge.BatteryStatus;
31 
32 import java.time.Duration;
33 import java.util.concurrent.ExecutorService;
34 import java.util.concurrent.Executors;
35 
36 /** A {@link BatteryUsageBroadcastReceiver} for battery usage data requesting. */
37 public final class BatteryUsageBroadcastReceiver extends BroadcastReceiver {
38     private static final String TAG = "BatteryUsageBroadcastReceiver";
39 
40     /** An intent action to request Settings to clear cache data. */
41     public static final String ACTION_CLEAR_BATTERY_CACHE_DATA =
42             "com.android.settings.battery.action.CLEAR_BATTERY_CACHE_DATA";
43 
44     /** An intent action for power is plugging. */
45     public static final String ACTION_BATTERY_PLUGGING =
46             "com.android.settings.battery.action.ACTION_BATTERY_PLUGGING";
47 
48     /** An intent action for power is unplugging. */
49     public static final String ACTION_BATTERY_UNPLUGGING =
50             "com.android.settings.battery.action.ACTION_BATTERY_UNPLUGGING";
51 
52     @VisibleForTesting static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis();
53     @VisibleForTesting static boolean sIsDebugMode = Build.TYPE.equals("userdebug");
54 
55     @VisibleForTesting boolean mFetchBatteryUsageData = false;
56 
57     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
58 
59     @Override
onReceive(Context context, Intent intent)60     public void onReceive(Context context, Intent intent) {
61         if (intent == null || intent.getAction() == null) {
62             return;
63         }
64         final String action = intent.getAction();
65         Log.d(TAG, "onReceive:" + action);
66         if (com.android.settingslib.fuelgauge.BatteryUtils.isWorkProfile(context)) {
67             Log.w(TAG, "do nothing for work profile action=" + action);
68             return;
69         }
70         DatabaseUtils.recordDateTime(context, action);
71         final String fullChargeIntentAction =
72                 FeatureFactory.getFeatureFactory()
73                         .getPowerUsageFeatureProvider()
74                         .getFullChargeIntentAction();
75         switch (action) {
76             case Intent.ACTION_BATTERY_LEVEL_CHANGED:
77                 // Only when fullChargeIntentAction is ACTION_BATTERY_LEVEL_CHANGED,
78                 // ACTION_BATTERY_LEVEL_CHANGED will be considered as the full charge event and then
79                 // start usage events fetching.
80                 if (Intent.ACTION_BATTERY_LEVEL_CHANGED.equals(fullChargeIntentAction)) {
81                     Log.d(TAG, "fetch data because of event: ACTION_BATTERY_LEVEL_CHANGED");
82                     tryToFetchUsageData(context);
83                 }
84                 break;
85             case ACTION_BATTERY_PLUGGING:
86                 sendBatteryEventData(context, BatteryEventType.POWER_CONNECTED);
87                 break;
88             case ACTION_BATTERY_UNPLUGGING:
89                 sendBatteryEventData(context, BatteryEventType.POWER_DISCONNECTED);
90                 // Only when fullChargeIntentAction is ACTION_POWER_DISCONNECTED,
91                 // ACTION_BATTERY_UNPLUGGING will be considered as the full charge event and then
92                 // start usage events fetching.
93                 if (Intent.ACTION_POWER_DISCONNECTED.equals(fullChargeIntentAction)) {
94                     Log.d(TAG, "fetch data because of event: ACTION_POWER_DISCONNECTED");
95                     tryToFetchUsageData(context);
96                 }
97                 break;
98             case ACTION_CLEAR_BATTERY_CACHE_DATA:
99                 if (sIsDebugMode) {
100                     BatteryDiffEntry.clearCache();
101                     BatteryEntry.clearUidCache();
102                 }
103                 break;
104         }
105     }
106 
tryToFetchUsageData(Context context)107     private void tryToFetchUsageData(Context context) {
108         final Intent batteryIntent = BatteryUtils.getBatteryIntent(context);
109         // Returns when battery is not fully charged.
110         if (!BatteryStatus.isCharged(batteryIntent)) {
111             return;
112         }
113 
114         final boolean delayHourlyJobWhenBooting =
115                 FeatureFactory.getFeatureFactory()
116                         .getPowerUsageFeatureProvider()
117                         .delayHourlyJobWhenBooting();
118         final long broadcastDelay = sBroadcastDelayFromBoot - SystemClock.elapsedRealtime();
119         // If current boot time is smaller than expected delay, cancel sending the broadcast.
120         if (delayHourlyJobWhenBooting && broadcastDelay > 0) {
121             Log.d(
122                     TAG,
123                     "cancel sendBroadcastToFetchUsageData when broadcastDelay is "
124                             + broadcastDelay
125                             + "ms.");
126             return;
127         }
128 
129         mFetchBatteryUsageData = true;
130         BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true);
131         BootBroadcastReceiver.invokeJobRecheck(context);
132     }
133 
sendBatteryEventData(Context context, BatteryEventType batteryEventType)134     private void sendBatteryEventData(Context context, BatteryEventType batteryEventType) {
135         final long timestamp = System.currentTimeMillis();
136         final Intent intent = BatteryUtils.getBatteryIntent(context);
137         final int batteryLevel = BatteryStatus.getBatteryLevel(intent);
138         mExecutor.execute(
139                 () ->
140                         DatabaseUtils.sendBatteryEventData(
141                                 context,
142                                 ConvertUtils.convertToBatteryEvent(
143                                         timestamp, batteryEventType, batteryLevel)));
144     }
145 }
146