1 /*
2  * Copyright (C) 2017 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 package com.android.server.power.batterysaver;
17 
18 import android.Manifest;
19 import android.app.ActivityManagerInternal;
20 import android.content.BroadcastReceiver;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.content.IntentFilter;
24 import android.hardware.power.V1_0.PowerHint;
25 import android.os.BatteryManager;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.os.Message;
29 import android.os.PowerManager;
30 import android.os.PowerManagerInternal;
31 import android.os.PowerManagerInternal.LowPowerModeListener;
32 import android.os.PowerSaveState;
33 import android.os.UserHandle;
34 import android.util.ArrayMap;
35 import android.util.Slog;
36 
37 import com.android.internal.annotations.GuardedBy;
38 import com.android.internal.util.ArrayUtils;
39 import com.android.internal.util.Preconditions;
40 import com.android.server.EventLogTags;
41 import com.android.server.LocalServices;
42 import com.android.server.power.BatterySaverPolicy;
43 import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
44 import com.android.server.power.PowerManagerService;
45 import com.android.server.power.batterysaver.BatterySavingStats.BatterySaverState;
46 import com.android.server.power.batterysaver.BatterySavingStats.DozeState;
47 import com.android.server.power.batterysaver.BatterySavingStats.InteractiveState;
48 
49 import java.util.ArrayList;
50 
51 /**
52  * Responsible for battery saver mode transition logic.
53  *
54  * IMPORTANT: This class shares the power manager lock, which is very low in the lock hierarchy.
55  * Do not call out with the lock held. (Settings provider is okay.)
56  */
57 public class BatterySaverController implements BatterySaverPolicyListener {
58     static final String TAG = "BatterySaverController";
59 
60     static final boolean DEBUG = BatterySaverPolicy.DEBUG;
61 
62     private final Object mLock;
63     private final Context mContext;
64     private final MyHandler mHandler;
65     private final FileUpdater mFileUpdater;
66 
67     private PowerManager mPowerManager;
68 
69     private final BatterySaverPolicy mBatterySaverPolicy;
70 
71     private final BatterySavingStats mBatterySavingStats;
72 
73     @GuardedBy("mLock")
74     private final ArrayList<LowPowerModeListener> mListeners = new ArrayList<>();
75 
76     @GuardedBy("mLock")
77     private boolean mEnabled;
78 
79     @GuardedBy("mLock")
80     private boolean mIsPluggedIn;
81 
82     /**
83      * Previously enabled or not; only for the event logging. Only use it from
84      * {@link #handleBatterySaverStateChanged}.
85      */
86     private boolean mPreviouslyEnabled;
87 
88     @GuardedBy("mLock")
89     private boolean mIsInteractive;
90 
91     /**
92      * Read-only list of plugins. No need for synchronization.
93      */
94     private final Plugin[] mPlugins;
95 
96     public static final int REASON_AUTOMATIC_ON = 0;
97     public static final int REASON_AUTOMATIC_OFF = 1;
98     public static final int REASON_MANUAL_ON = 2;
99     public static final int REASON_MANUAL_OFF = 3;
100     public static final int REASON_STICKY_RESTORE = 4;
101     public static final int REASON_INTERACTIVE_CHANGED = 5;
102     public static final int REASON_POLICY_CHANGED = 6;
103     public static final int REASON_PLUGGED_IN = 7;
104     public static final int REASON_SETTING_CHANGED = 8;
105 
106     /**
107      * Plugin interface. All methods are guaranteed to be called on the same (handler) thread.
108      */
109     public interface Plugin {
onSystemReady(BatterySaverController caller)110         void onSystemReady(BatterySaverController caller);
111 
onBatterySaverChanged(BatterySaverController caller)112         void onBatterySaverChanged(BatterySaverController caller);
113     }
114 
115     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
116         @Override
117         public void onReceive(Context context, Intent intent) {
118             if (DEBUG) {
119                 Slog.d(TAG, "onReceive: " + intent);
120             }
121             switch (intent.getAction()) {
122                 case Intent.ACTION_SCREEN_ON:
123                 case Intent.ACTION_SCREEN_OFF:
124                     if (!isEnabled()) {
125                         updateBatterySavingStats();
126                         return; // No need to send it if not enabled.
127                     }
128                     // Don't send the broadcast, because we never did so in this case.
129                     mHandler.postStateChanged(/*sendBroadcast=*/ false,
130                             REASON_INTERACTIVE_CHANGED);
131                     break;
132                 case Intent.ACTION_BATTERY_CHANGED:
133                     synchronized (mLock) {
134                         mIsPluggedIn = (intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0);
135                     }
136                     // Fall-through.
137                 case PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED:
138                 case PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED:
139                     updateBatterySavingStats();
140                     break;
141             }
142         }
143     };
144 
145     /**
146      * Constructor.
147      */
BatterySaverController(Object lock, Context context, Looper looper, BatterySaverPolicy policy, BatterySavingStats batterySavingStats)148     public BatterySaverController(Object lock, Context context, Looper looper,
149             BatterySaverPolicy policy, BatterySavingStats batterySavingStats) {
150         mLock = lock;
151         mContext = context;
152         mHandler = new MyHandler(looper);
153         mBatterySaverPolicy = policy;
154         mBatterySaverPolicy.addListener(this);
155         mFileUpdater = new FileUpdater(context);
156         mBatterySavingStats = batterySavingStats;
157 
158         // Initialize plugins.
159         final ArrayList<Plugin> plugins = new ArrayList<>();
160         plugins.add(new BatterySaverLocationPlugin(mContext));
161 
162         mPlugins = plugins.toArray(new Plugin[plugins.size()]);
163     }
164 
165     /**
166      * Add a listener.
167      */
addListener(LowPowerModeListener listener)168     public void addListener(LowPowerModeListener listener) {
169         synchronized (mLock) {
170             mListeners.add(listener);
171         }
172     }
173 
174     /**
175      * Called by {@link PowerManagerService} on system ready, *with no lock held*.
176      */
systemReady()177     public void systemReady() {
178         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
179         filter.addAction(Intent.ACTION_SCREEN_OFF);
180         filter.addAction(Intent.ACTION_BATTERY_CHANGED);
181         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
182         filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
183         mContext.registerReceiver(mReceiver, filter);
184 
185         mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class)
186                 .isRuntimeRestarted());
187         mHandler.postSystemReady();
188     }
189 
getPowerManager()190     private PowerManager getPowerManager() {
191         if (mPowerManager == null) {
192             mPowerManager =
193                     Preconditions.checkNotNull(mContext.getSystemService(PowerManager.class));
194         }
195         return mPowerManager;
196     }
197 
198     @Override
onBatterySaverPolicyChanged(BatterySaverPolicy policy)199     public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
200         if (!isEnabled()) {
201             return; // No need to send it if not enabled.
202         }
203         mHandler.postStateChanged(/*sendBroadcast=*/ true, REASON_POLICY_CHANGED);
204     }
205 
206     private class MyHandler extends Handler {
207         private static final int MSG_STATE_CHANGED = 1;
208 
209         private static final int ARG_DONT_SEND_BROADCAST = 0;
210         private static final int ARG_SEND_BROADCAST = 1;
211 
212         private static final int MSG_SYSTEM_READY = 2;
213 
MyHandler(Looper looper)214         public MyHandler(Looper looper) {
215             super(looper);
216         }
217 
postStateChanged(boolean sendBroadcast, int reason)218         public void postStateChanged(boolean sendBroadcast, int reason) {
219             obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
220                     ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, reason).sendToTarget();
221         }
222 
postSystemReady()223         public void postSystemReady() {
224             obtainMessage(MSG_SYSTEM_READY, 0, 0).sendToTarget();
225         }
226 
227         @Override
dispatchMessage(Message msg)228         public void dispatchMessage(Message msg) {
229             switch (msg.what) {
230                 case MSG_STATE_CHANGED:
231                     handleBatterySaverStateChanged(
232                             msg.arg1 == ARG_SEND_BROADCAST,
233                             msg.arg2);
234                     break;
235 
236                 case MSG_SYSTEM_READY:
237                     for (Plugin p : mPlugins) {
238                         p.onSystemReady(BatterySaverController.this);
239                     }
240                     break;
241             }
242         }
243     }
244 
245     /**
246      * Called by {@link PowerManagerService} to update the battery saver stete.
247      */
enableBatterySaver(boolean enable, int reason)248     public void enableBatterySaver(boolean enable, int reason) {
249         synchronized (mLock) {
250             if (mEnabled == enable) {
251                 return;
252             }
253             mEnabled = enable;
254 
255             mHandler.postStateChanged(/*sendBroadcast=*/ true, reason);
256         }
257     }
258 
259     /** @return whether battery saver is enabled or not. */
isEnabled()260     public boolean isEnabled() {
261         synchronized (mLock) {
262             return mEnabled;
263         }
264     }
265 
266     /** @return whether device is in interactive state. */
isInteractive()267     public boolean isInteractive() {
268         synchronized (mLock) {
269             return mIsInteractive;
270         }
271     }
272 
273     /** @return Battery saver policy. */
getBatterySaverPolicy()274     public BatterySaverPolicy getBatterySaverPolicy() {
275         return mBatterySaverPolicy;
276     }
277 
278     /**
279      * @return true if launch boost should currently be disabled.
280      */
isLaunchBoostDisabled()281     public boolean isLaunchBoostDisabled() {
282         return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
283     }
284 
285     /**
286      * Dispatch power save events to the listeners.
287      *
288      * This method is always called on the handler thread.
289      *
290      * This method is called only in the following cases:
291      * - When battery saver becomes activated.
292      * - When battery saver becomes deactivated.
293      * - When battery saver is on the interactive state changes.
294      * - When battery saver is on the battery saver policy changes.
295      */
handleBatterySaverStateChanged(boolean sendBroadcast, int reason)296     void handleBatterySaverStateChanged(boolean sendBroadcast, int reason) {
297         final LowPowerModeListener[] listeners;
298 
299         final boolean enabled;
300         final boolean isInteractive = getPowerManager().isInteractive();
301         final ArrayMap<String, String> fileValues;
302 
303         synchronized (mLock) {
304             EventLogTags.writeBatterySaverMode(
305                     mPreviouslyEnabled ? 1 : 0, // Previously off or on.
306                     mEnabled ? 1 : 0, // Now off or on.
307                     isInteractive ?  1 : 0, // Device interactive state.
308                     mEnabled ? mBatterySaverPolicy.toEventLogString() : "",
309                     reason);
310             mPreviouslyEnabled = mEnabled;
311 
312             listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
313 
314             enabled = mEnabled;
315             mIsInteractive = isInteractive;
316 
317             if (enabled) {
318                 fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
319             } else {
320                 fileValues = null;
321             }
322         }
323 
324         final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
325         if (pmi != null) {
326             pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
327         }
328 
329         updateBatterySavingStats();
330 
331         if (ArrayUtils.isEmpty(fileValues)) {
332             mFileUpdater.restoreDefault();
333         } else {
334             mFileUpdater.writeFiles(fileValues);
335         }
336 
337         for (Plugin p : mPlugins) {
338             p.onBatterySaverChanged(this);
339         }
340 
341         if (sendBroadcast) {
342 
343             if (DEBUG) {
344                 Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
345             }
346 
347             // Send the broadcasts and notify the listeners. We only do this when the battery saver
348             // mode changes, but not when only the screen state changes.
349             Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
350                     .putExtra(PowerManager.EXTRA_POWER_SAVE_MODE, enabled)
351                     .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
352             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
353 
354             intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED);
355             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
356             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
357 
358             // Send internal version that requires signature permission.
359             intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGED_INTERNAL);
360             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
361             mContext.sendBroadcastAsUser(intent, UserHandle.ALL,
362                     Manifest.permission.DEVICE_POWER);
363 
364             for (LowPowerModeListener listener : listeners) {
365                 final PowerSaveState result =
366                         mBatterySaverPolicy.getBatterySaverPolicy(
367                                 listener.getServiceType(), enabled);
368                 listener.onLowPowerModeChanged(result);
369             }
370         }
371     }
372 
updateBatterySavingStats()373     private void updateBatterySavingStats() {
374         final PowerManager pm = getPowerManager();
375         if (pm == null) {
376             Slog.wtf(TAG, "PowerManager not initialized");
377             return;
378         }
379         final boolean isInteractive = pm.isInteractive();
380         final int dozeMode =
381                 pm.isDeviceIdleMode() ? DozeState.DEEP
382                         : pm.isLightDeviceIdleMode() ? DozeState.LIGHT
383                         : DozeState.NOT_DOZING;
384 
385         synchronized (mLock) {
386             if (mIsPluggedIn) {
387                 mBatterySavingStats.startCharging();
388                 return;
389             }
390             mBatterySavingStats.transitionState(
391                     mEnabled ? BatterySaverState.ON : BatterySaverState.OFF,
392                     isInteractive ? InteractiveState.INTERACTIVE : InteractiveState.NON_INTERACTIVE,
393                     dozeMode);
394         }
395     }
396 }
397