1 /*
2  * Copyright (C) 2020 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.car.power;
18 
19 import static android.car.hardware.power.PowerComponent.BLUETOOTH;
20 import static android.car.hardware.power.PowerComponent.DISPLAY;
21 import static android.car.hardware.power.PowerComponent.VOICE_INTERACTION;
22 import static android.car.hardware.power.PowerComponent.WIFI;
23 import static android.car.hardware.power.PowerComponentUtil.FIRST_POWER_COMPONENT;
24 import static android.car.hardware.power.PowerComponentUtil.INVALID_POWER_COMPONENT;
25 import static android.car.hardware.power.PowerComponentUtil.LAST_POWER_COMPONENT;
26 import static android.car.hardware.power.PowerComponentUtil.powerComponentToString;
27 import static android.car.hardware.power.PowerComponentUtil.toPowerComponent;
28 
29 import android.annotation.Nullable;
30 import android.bluetooth.BluetoothAdapter;
31 import android.car.hardware.power.CarPowerPolicy;
32 import android.car.hardware.power.CarPowerPolicyFilter;
33 import android.car.hardware.power.PowerComponent;
34 import android.content.Context;
35 import android.content.pm.PackageManager;
36 import android.net.wifi.WifiManager;
37 import android.os.RemoteException;
38 import android.os.ServiceManager;
39 import android.util.AtomicFile;
40 import android.util.IndentingPrintWriter;
41 import android.util.SparseArray;
42 import android.util.SparseBooleanArray;
43 
44 import com.android.car.CarLog;
45 import com.android.car.systeminterface.SystemInterface;
46 import com.android.internal.annotations.GuardedBy;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.internal.app.IVoiceInteractionManagerService;
49 import com.android.server.utils.Slogf;
50 
51 import java.io.BufferedReader;
52 import java.io.BufferedWriter;
53 import java.io.File;
54 import java.io.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.io.InputStreamReader;
58 import java.io.OutputStreamWriter;
59 import java.nio.charset.StandardCharsets;
60 
61 /**
62  * Class that manages power components in the system. A power component mediator corresponding to a
63  * power component is created and registered to this class. A power component mediator encapsulates
64  * the function of powering on/off.
65  */
66 @VisibleForTesting
67 public final class PowerComponentHandler {
68     private static final String TAG = CarLog.tagFor(PowerComponentHandler.class);
69     private static final String FORCED_OFF_COMPONENTS_FILENAME =
70             "forced_off_components";
71 
72     private final Object mLock = new Object();
73     private final Context mContext;
74     private final SystemInterface mSystemInterface;
75     private final AtomicFile mOffComponentsByUserFile;
76     private final SparseArray<PowerComponentMediator> mPowerComponentMediators =
77             new SparseArray<>();
78     @GuardedBy("mLock")
79     private final SparseBooleanArray mComponentStates =
80             new SparseBooleanArray(LAST_POWER_COMPONENT - FIRST_POWER_COMPONENT + 1);
81     @GuardedBy("mLock")
82     private final SparseBooleanArray mComponentsOffByPolicy = new SparseBooleanArray();
83     @GuardedBy("mLock")
84     private final SparseBooleanArray mLastModifiedComponents = new SparseBooleanArray();
85     private final IVoiceInteractionManagerService mVoiceInteractionServiceHolder;
86     private final PackageManager mPackageManager;
87 
88     @GuardedBy("mLock")
89     private String mCurrentPolicyId = "";
90 
PowerComponentHandler(Context context, SystemInterface systemInterface)91     PowerComponentHandler(Context context, SystemInterface systemInterface) {
92         this(context, systemInterface, /* voiceInteractionService= */ null,
93                 new AtomicFile(new File(systemInterface.getSystemCarDir(),
94                         FORCED_OFF_COMPONENTS_FILENAME)));
95     }
96 
PowerComponentHandler(Context context, SystemInterface systemInterface, IVoiceInteractionManagerService voiceInteractionService, AtomicFile componentStateFile)97     public PowerComponentHandler(Context context, SystemInterface systemInterface,
98             IVoiceInteractionManagerService voiceInteractionService,
99             AtomicFile componentStateFile) {
100         mContext = context;
101         mPackageManager = mContext.getPackageManager();
102         mSystemInterface = systemInterface;
103         mVoiceInteractionServiceHolder = voiceInteractionService;
104         mOffComponentsByUserFile = componentStateFile;
105     }
106 
init()107     void init() {
108         PowerComponentMediatorFactory factory = new PowerComponentMediatorFactory();
109         synchronized (mLock) {
110             readUserOffComponentsLocked();
111             for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT;
112                     component++) {
113                 mComponentStates.put(component, false);
114                 PowerComponentMediator mediator = factory.createPowerComponent(component);
115                 String componentName = powerComponentToString(component);
116                 if (mediator == null || !mediator.isComponentAvailable()) {
117                     // We don't not associate a mediator with the component.
118                     continue;
119                 }
120                 mPowerComponentMediators.put(component, mediator);
121             }
122         }
123     }
124 
getAccumulatedPolicy()125     CarPowerPolicy getAccumulatedPolicy() {
126         synchronized (mLock) {
127             int enabledComponentsCount = 0;
128             int disabledComponentsCount = 0;
129             for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT;
130                     component++) {
131                 if (mComponentStates.get(component, /* valueIfKeyNotFound= */ false)) {
132                     enabledComponentsCount++;
133                 } else {
134                     disabledComponentsCount++;
135                 }
136             }
137             int[] enabledComponents = new int[enabledComponentsCount];
138             int[] disabledComponents = new int[disabledComponentsCount];
139             int enabledIndex = 0;
140             int disabledIndex = 0;
141             for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT;
142                     component++) {
143                 if (mComponentStates.get(component, /* valueIfKeyNotFound= */ false)) {
144                     enabledComponents[enabledIndex++] = component;
145                 } else {
146                     disabledComponents[disabledIndex++] = component;
147                 }
148             }
149             return new CarPowerPolicy(mCurrentPolicyId, enabledComponents, disabledComponents);
150         }
151     }
152 
153     /**
154      * Applies the given policy considering user setting.
155      *
156      * <p> If a component is the policy is not applied due to user setting, it is not notified to
157      * listeners.
158      */
applyPowerPolicy(CarPowerPolicy policy)159     void applyPowerPolicy(CarPowerPolicy policy) {
160         int[] enabledComponents = policy.getEnabledComponents();
161         int[] disabledComponents = policy.getDisabledComponents();
162         synchronized (mLock) {
163             mLastModifiedComponents.clear();
164             for (int i = 0; i < enabledComponents.length; i++) {
165                 int component = enabledComponents[i];
166                 if (setComponentEnabledLocked(component, /* enabled= */ true)) {
167                     mLastModifiedComponents.put(component, /* value= */ true);
168                 }
169             }
170             for (int i = 0; i < disabledComponents.length; i++) {
171                 int component = disabledComponents[i];
172                 if (setComponentEnabledLocked(component, /* enabled= */ false)) {
173                     mLastModifiedComponents.put(component, /* value= */ true);
174                 }
175             }
176             mCurrentPolicyId = policy.getPolicyId();
177         }
178     }
179 
isComponentChanged(CarPowerPolicyFilter filter)180     boolean isComponentChanged(CarPowerPolicyFilter filter) {
181         synchronized (mLock) {
182             int[] components = filter.getComponents();
183             for (int i = 0; i < components.length; i++) {
184                 if (mLastModifiedComponents.get(components[i], false)) {
185                     return true;
186                 }
187             }
188             return false;
189         }
190     }
191 
dump(IndentingPrintWriter writer)192     void dump(IndentingPrintWriter writer) {
193         synchronized (mLock) {
194             writer.println("Power components state:");
195             writer.increaseIndent();
196             for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT;
197                     component++) {
198                 writer.printf("%s: %s\n", powerComponentToString(component),
199                         mComponentStates.get(component, /* valueIfKeyNotFound= */ false)
200                                 ? "on" : "off");
201             }
202             writer.decreaseIndent();
203             writer.println("Components powered off by power policy:");
204             writer.increaseIndent();
205             for (int i = 0; i < mComponentsOffByPolicy.size(); i++) {
206                 writer.println(powerComponentToString(mComponentsOffByPolicy.keyAt(i)));
207             }
208             writer.decreaseIndent();
209             writer.print("Components changed by the last policy: ");
210             writer.increaseIndent();
211             for (int i = 0; i < mLastModifiedComponents.size(); i++) {
212                 if (i > 0) writer.print(", ");
213                 writer.print(powerComponentToString(mLastModifiedComponents.keyAt(i)));
214             }
215             writer.println();
216             writer.decreaseIndent();
217         }
218     }
219 
220     /**
221      * Modifies power component's state, considering user setting.
222      *
223      * @return {@code true} if power state is changed. Otherwise, {@code false}
224      */
setComponentEnabledLocked(int component, boolean enabled)225     private boolean setComponentEnabledLocked(int component, boolean enabled) {
226         boolean oldState = mComponentStates.get(component, /* valueIfKeyNotFound= */ false);
227         if (oldState == enabled) {
228             return false;
229         }
230 
231         mComponentStates.put(component, enabled);
232 
233         PowerComponentMediator mediator = mPowerComponentMediators.get(component);
234         if (mediator == null) {
235             return true;
236         }
237 
238         boolean needPowerChange = false;
239         if (mediator.isUserControllable()) {
240             if (!enabled && mediator.isEnabled()) {
241                 mComponentsOffByPolicy.put(component, /* value= */ true);
242                 needPowerChange = true;
243             }
244             if (enabled && mComponentsOffByPolicy.get(component, /* valueIfKeyNotFound= */ false)) {
245                 mComponentsOffByPolicy.delete(component);
246                 needPowerChange = true;
247             }
248             if (needPowerChange) {
249                 writeUserOffComponentsLocked();
250             }
251         } else {
252             needPowerChange = true;
253         }
254 
255         if (needPowerChange) {
256             mediator.setEnabled(enabled);
257         }
258         return true;
259     }
260 
readUserOffComponentsLocked()261     private void readUserOffComponentsLocked() {
262         boolean invalid = false;
263         mComponentsOffByPolicy.clear();
264         try (BufferedReader reader = new BufferedReader(
265                 new InputStreamReader(mOffComponentsByUserFile.openRead(),
266                         StandardCharsets.UTF_8))) {
267             String line;
268             while ((line = reader.readLine()) != null) {
269                 int component = toPowerComponent(line.trim(), /* prefix= */ false);
270                 if (component == INVALID_POWER_COMPONENT) {
271                     invalid = true;
272                     break;
273                 }
274                 mComponentsOffByPolicy.put(component, /* value= */ true);
275             }
276         } catch (FileNotFoundException e) {
277             // Behave as if there are no forced-off components.
278             return;
279         } catch (IOException e) {
280             Slogf.w(TAG, "Failed to read %s: %s", FORCED_OFF_COMPONENTS_FILENAME, e);
281             return;
282         }
283         if (invalid) {
284             mOffComponentsByUserFile.delete();
285         }
286     }
287 
writeUserOffComponentsLocked()288     private void writeUserOffComponentsLocked() {
289         FileOutputStream fos;
290         try {
291             fos = mOffComponentsByUserFile.startWrite();
292         } catch (IOException e) {
293             Slogf.e(TAG, e, "Cannot create %s", FORCED_OFF_COMPONENTS_FILENAME);
294             return;
295         }
296 
297         try (BufferedWriter writer = new BufferedWriter(
298                 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
299             for (int i = 0; i < mComponentsOffByPolicy.size(); i++) {
300                 if (!mComponentsOffByPolicy.valueAt(i)) {
301                     continue;
302                 }
303                 writer.write(powerComponentToString(mComponentsOffByPolicy.keyAt(i)));
304                 writer.newLine();
305             }
306             writer.flush();
307             mOffComponentsByUserFile.finishWrite(fos);
308         } catch (IOException e) {
309             mOffComponentsByUserFile.failWrite(fos);
310             Slogf.e(TAG, e, "Writing %s failed", FORCED_OFF_COMPONENTS_FILENAME);
311         }
312     }
313 
314     abstract static class PowerComponentMediator {
315         protected int mComponentId;
316 
PowerComponentMediator(int component)317         PowerComponentMediator(int component) {
318             mComponentId = component;
319         }
320 
isComponentAvailable()321         public boolean isComponentAvailable() {
322             return false;
323         }
324 
isUserControllable()325         public boolean isUserControllable() {
326             return false;
327         }
328 
isEnabled()329         public boolean isEnabled() {
330             return false;
331         }
332 
setEnabled(boolean enabled)333         public void setEnabled(boolean enabled) {}
334     }
335 
336     // TODO(b/178824607): Check if power policy can turn on/off display as quickly as the existing
337     // implementation.
338     private final class DisplayPowerComponentMediator extends PowerComponentMediator {
DisplayPowerComponentMediator()339         DisplayPowerComponentMediator() {
340             super(DISPLAY);
341         }
342 
343         @Override
isComponentAvailable()344         public boolean isComponentAvailable() {
345             // It is assumed that display is supported in all vehicles.
346             return true;
347         }
348 
349         @Override
isEnabled()350         public boolean isEnabled() {
351             return mSystemInterface.isDisplayEnabled();
352         }
353 
354         @Override
setEnabled(boolean enabled)355         public void setEnabled(boolean enabled) {
356             mSystemInterface.setDisplayState(enabled);
357             Slogf.d(TAG, "Display power component is %s", enabled ? "on" : "off");
358         }
359     }
360 
361     private final class WifiPowerComponentMediator extends PowerComponentMediator {
362         private final WifiManager mWifiManager;
363 
WifiPowerComponentMediator()364         WifiPowerComponentMediator() {
365             super(WIFI);
366             mWifiManager = mContext.getSystemService(WifiManager.class);
367         }
368 
369         @Override
isComponentAvailable()370         public boolean isComponentAvailable() {
371             return mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI);
372         }
373 
374         @Override
isUserControllable()375         public boolean isUserControllable() {
376             return true;
377         }
378 
379         @Override
isEnabled()380         public boolean isEnabled() {
381             return mWifiManager.isWifiEnabled();
382         }
383 
384         @Override
setEnabled(boolean enabled)385         public void setEnabled(boolean enabled) {
386             mWifiManager.setWifiEnabled(enabled);
387             Slogf.d(TAG, "Wifi power component is %s", enabled ? "on" : "off");
388         }
389     }
390 
391     private final class VoiceInteractionPowerComponentMediator extends PowerComponentMediator {
392         private final IVoiceInteractionManagerService mVoiceInteractionManagerService;
393 
394         private boolean mIsEnabled = true;
395 
VoiceInteractionPowerComponentMediator()396         VoiceInteractionPowerComponentMediator() {
397             super(VOICE_INTERACTION);
398             if (mVoiceInteractionServiceHolder == null) {
399                 mVoiceInteractionManagerService = IVoiceInteractionManagerService.Stub.asInterface(
400                         ServiceManager.getService(Context.VOICE_INTERACTION_MANAGER_SERVICE));
401             } else {
402                 mVoiceInteractionManagerService = mVoiceInteractionServiceHolder;
403             }
404         }
405 
406         @Override
isComponentAvailable()407         public boolean isComponentAvailable() {
408             return mVoiceInteractionManagerService != null;
409         }
410 
411         @Override
isEnabled()412         public boolean isEnabled() {
413             return mIsEnabled;
414         }
415 
416         @Override
setEnabled(boolean enabled)417         public void setEnabled(boolean enabled) {
418             try {
419                 mVoiceInteractionManagerService.setDisabled(!enabled);
420                 mIsEnabled = enabled;
421                 Slogf.d(TAG, "Voice Interaction power component is %s", enabled ? "on" : "off");
422             } catch (RemoteException e) {
423                 Slogf.w(TAG, e, "IVoiceInteractionManagerService.setDisabled(%b) failed", !enabled);
424             }
425         }
426     }
427 
428     private final class BluetoothPowerComponentMediator extends PowerComponentMediator {
429         private final BluetoothAdapter mBluetoothAdapter;
430 
BluetoothPowerComponentMediator()431         BluetoothPowerComponentMediator() {
432             super(BLUETOOTH);
433             mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
434         }
435 
436         @Override
isComponentAvailable()437         public boolean isComponentAvailable() {
438             return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
439         }
440 
441         @Override
isEnabled()442         public boolean isEnabled() {
443             return mBluetoothAdapter.isEnabled();
444         }
445 
446         @Override
setEnabled(boolean enabled)447         public void setEnabled(boolean enabled) {
448             // No op
449             Slogf.w(TAG, "Bluetooth power is controlled by "
450                     + "com.android.car.BluetoothDeviceConnectionPolicy");
451         }
452     }
453 
454     private final class PowerComponentMediatorFactory {
455         @Nullable
createPowerComponent(int component)456         PowerComponentMediator createPowerComponent(int component) {
457             switch (component) {
458                 case PowerComponent.AUDIO:
459                     // We don't control audio in framework level, because audio is enabled or
460                     // disabled in audio HAL according to the current power policy.
461                     return null;
462                 case PowerComponent.MEDIA:
463                     return null;
464                 case PowerComponent.DISPLAY:
465                     return new DisplayPowerComponentMediator();
466                 case PowerComponent.WIFI:
467                     return new WifiPowerComponentMediator();
468                 case PowerComponent.CELLULAR:
469                     return null;
470                 case PowerComponent.ETHERNET:
471                     return null;
472                 case PowerComponent.PROJECTION:
473                     return null;
474                 case PowerComponent.NFC:
475                     return null;
476                 case PowerComponent.INPUT:
477                     return null;
478                 case PowerComponent.VOICE_INTERACTION:
479                     return new VoiceInteractionPowerComponentMediator();
480                 case PowerComponent.VISUAL_INTERACTION:
481                     return null;
482                 case PowerComponent.TRUSTED_DEVICE_DETECTION:
483                     return null;
484                 case PowerComponent.MICROPHONE:
485                     // We don't control microphone in framework level, because microphone is enabled
486                     // or disabled in audio HAL according to the current power policy.
487                     return null;
488                 case PowerComponent.BLUETOOTH:
489                     // com.android.car.BluetoothDeviceConnectionPolicy handles power state change.
490                     // So, bluetooth mediator doesn't directly turn on/off BT, but it changes policy
491                     // behavior, considering user intervetion.
492                     return new BluetoothPowerComponentMediator();
493                 case PowerComponent.LOCATION:
494                     // GNSS HAL handles power state change.
495                     return null;
496                 case PowerComponent.CPU:
497                     return null;
498                 default:
499                     Slogf.w(TAG, "Unknown component(%d)", component);
500                     return null;
501             }
502         }
503     }
504 
505     static class PowerComponentException extends Exception {
PowerComponentException(String message)506         PowerComponentException(String message) {
507             super(message);
508         }
509     }
510 }
511