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 static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
30 
31 import android.annotation.Nullable;
32 import android.bluetooth.BluetoothAdapter;
33 import android.car.builtin.app.AppOpsManagerHelper;
34 import android.car.builtin.app.VoiceInteractionHelper;
35 import android.car.builtin.util.Slogf;
36 import android.car.hardware.power.CarPowerPolicy;
37 import android.car.hardware.power.CarPowerPolicyFilter;
38 import android.car.hardware.power.PowerComponent;
39 import android.content.Context;
40 import android.content.pm.PackageManager;
41 import android.net.wifi.WifiManager;
42 import android.os.Process;
43 import android.os.RemoteException;
44 import android.util.ArrayMap;
45 import android.util.AtomicFile;
46 import android.util.SparseArray;
47 import android.util.SparseBooleanArray;
48 import android.util.proto.ProtoOutputStream;
49 
50 import com.android.car.CarLog;
51 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
52 import com.android.car.internal.util.IndentingPrintWriter;
53 import com.android.car.internal.util.IntArray;
54 import com.android.car.power.CarPowerDumpProto.PowerComponentHandlerProto;
55 import com.android.car.power.CarPowerDumpProto.PowerComponentHandlerProto.PowerComponentToState;
56 import com.android.car.systeminterface.SystemInterface;
57 import com.android.internal.annotations.GuardedBy;
58 
59 import java.io.BufferedReader;
60 import java.io.BufferedWriter;
61 import java.io.File;
62 import java.io.FileNotFoundException;
63 import java.io.FileOutputStream;
64 import java.io.IOException;
65 import java.io.InputStreamReader;
66 import java.io.OutputStreamWriter;
67 import java.nio.charset.StandardCharsets;
68 
69 /**
70  * Class that manages power components in the system. A power component mediator corresponding to a
71  * power component is created and registered to this class. A power component mediator encapsulates
72  * the function of powering on/off.
73  */
74 public final class PowerComponentHandler {
75     private static final String TAG = CarLog.tagFor(PowerComponentHandler.class);
76     private static final String FORCED_OFF_COMPONENTS_FILENAME =
77             "forced_off_components";
78 
79     private final Object mLock = new Object();
80     private final Context mContext;
81     private final SystemInterface mSystemInterface;
82     private final AtomicFile mOffComponentsByUserFile;
83     private final SparseArray<PowerComponentMediator> mPowerComponentMediators =
84             new SparseArray<>();
85     @GuardedBy("mLock")
86     private final SparseBooleanArray mComponentStates =
87             new SparseBooleanArray(LAST_POWER_COMPONENT - FIRST_POWER_COMPONENT + 1);
88     @GuardedBy("mLock")
89     private final SparseBooleanArray mComponentsOffByPolicy = new SparseBooleanArray();
90     @GuardedBy("mLock")
91     private final SparseBooleanArray mLastModifiedComponents = new SparseBooleanArray();
92     @GuardedBy("mLock")
93     private final IntArray mRegisteredComponents = new IntArray();
94     private final PackageManager mPackageManager;
95 
96     // TODO(b/286303350): remove after power policy refactor is complete; only used for getting
97     //                    accumulated policy, and that will be done by CPPD
98     @GuardedBy("mLock")
99     private String mCurrentPolicyId = "";
100 
PowerComponentHandler(Context context, SystemInterface systemInterface)101     PowerComponentHandler(Context context, SystemInterface systemInterface) {
102         this(context, systemInterface, new AtomicFile(new File(systemInterface.getSystemCarDir(),
103                 FORCED_OFF_COMPONENTS_FILENAME)));
104     }
105 
PowerComponentHandler(Context context, SystemInterface systemInterface, AtomicFile componentStateFile)106     public PowerComponentHandler(Context context, SystemInterface systemInterface,
107             AtomicFile componentStateFile) {
108         mContext = context;
109         mPackageManager = mContext.getPackageManager();
110         mSystemInterface = systemInterface;
111         mOffComponentsByUserFile = componentStateFile;
112     }
113 
init(ArrayMap<String, Integer> customComponents)114     void init(ArrayMap<String, Integer> customComponents) {
115         AppOpsManagerHelper.setTurnScreenOnAllowed(mContext, Process.myUid(),
116                 mContext.getOpPackageName(), /* isAllowed= */ true);
117         PowerComponentMediatorFactory factory = new PowerComponentMediatorFactory();
118         synchronized (mLock) {
119             readUserOffComponentsLocked();
120             for (int component = FIRST_POWER_COMPONENT; component <= LAST_POWER_COMPONENT;
121                     component++) {
122                 // initialize set of known components with pre-defined components
123                 mRegisteredComponents.add(component);
124                 mComponentStates.put(component, false);
125                 PowerComponentMediator mediator = factory.createPowerComponent(component);
126                 if (mediator == null || !mediator.isComponentAvailable()) {
127                     // We don't not associate a mediator with the component.
128                     continue;
129                 }
130                 mComponentStates.put(component, mediator.isEnabled());
131                 mPowerComponentMediators.put(component, mediator);
132             }
133             if (customComponents != null) {
134                 for (int i = 0; i < customComponents.size(); ++i)  {
135                     mRegisteredComponents.add(customComponents.valueAt(i));
136                 }
137             }
138         }
139     }
140 
getAccumulatedPolicy()141     CarPowerPolicy getAccumulatedPolicy() {
142         synchronized (mLock) {
143             int enabledComponentsCount = 0;
144             int disabledComponentsCount = 0;
145             for (int i = 0; i < mRegisteredComponents.size(); ++i) {
146                 if (mComponentStates.get(mRegisteredComponents.get(i), /* valueIfKeyNotFound= */
147                         false)) {
148                     enabledComponentsCount++;
149                 } else {
150                     disabledComponentsCount++;
151                 }
152             }
153             int[] enabledComponents = new int[enabledComponentsCount];
154             int[] disabledComponents = new int[disabledComponentsCount];
155             int enabledIndex = 0;
156             int disabledIndex = 0;
157             for (int i = 0; i < mRegisteredComponents.size(); ++i) {
158                 int component = mRegisteredComponents.get(i);
159                 if (mComponentStates.get(component, /* valueIfKeyNotFound= */ false)) {
160                     enabledComponents[enabledIndex++] = component;
161                 } else {
162                     disabledComponents[disabledIndex++] = component;
163                 }
164             }
165             return new CarPowerPolicy(mCurrentPolicyId, enabledComponents, disabledComponents);
166         }
167     }
168 
169     /**
170      * Applies the given policy considering user setting.
171      *
172      * <p> If a component is the policy is not applied due to user setting, it is not notified to
173      * listeners.
174      */
applyPowerPolicy(CarPowerPolicy policy)175     void applyPowerPolicy(CarPowerPolicy policy) {
176         int[] enabledComponents = policy.getEnabledComponents();
177         int[] disabledComponents = policy.getDisabledComponents();
178         synchronized (mLock) {
179             mLastModifiedComponents.clear();
180             for (int i = 0; i < enabledComponents.length; i++) {
181                 int component = enabledComponents[i];
182                 if (mRegisteredComponents.indexOf(component) == -1) {
183                     throw new IllegalStateException(
184                             "Component with id " + component + " is not registered");
185                 }
186                 if (setComponentEnabledLocked(component, /* enabled= */ true)) {
187                     mLastModifiedComponents.put(component, /* value= */ true);
188                 }
189             }
190             for (int i = 0; i < disabledComponents.length; i++) {
191                 int component = disabledComponents[i];
192                 if (mRegisteredComponents.indexOf(component) == -1) {
193                     throw new IllegalStateException(
194                             "Component with id " + component + " is not registered");
195                 }
196                 if (setComponentEnabledLocked(component, /* enabled= */ false)) {
197                     mLastModifiedComponents.put(component, /* value= */ true);
198                 }
199             }
200             mCurrentPolicyId = policy.getPolicyId();
201         }
202     }
203 
204     /**
205      * Method to get array of components modified during last power policy change
206      */
getLastModifiedComponents()207     public SparseBooleanArray getLastModifiedComponents() {
208         synchronized (mLock) {
209             return mLastModifiedComponents.clone();
210         }
211     }
212 
213     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)214     void dump(IndentingPrintWriter writer) {
215         synchronized (mLock) {
216             writer.println("Power components state:");
217             writer.increaseIndent();
218             for (int i = 0; i < mRegisteredComponents.size(); ++i) {
219                 int component = mRegisteredComponents.get(i);
220                 writer.printf("%s: %s\n", powerComponentToString(component),
221                         mComponentStates.get(component, /* valueIfKeyNotFound= */ false)
222                                 ? "on" : "off");
223             }
224             writer.decreaseIndent();
225             writer.println("Components powered off by power policy:");
226             writer.increaseIndent();
227             for (int i = 0; i < mComponentsOffByPolicy.size(); i++) {
228                 writer.println(powerComponentToString(mComponentsOffByPolicy.keyAt(i)));
229             }
230             writer.decreaseIndent();
231             writer.print("Components changed by the last policy: ");
232             writer.increaseIndent();
233             for (int i = 0; i < mLastModifiedComponents.size(); i++) {
234                 if (i > 0) writer.print(", ");
235                 writer.print(powerComponentToString(mLastModifiedComponents.keyAt(i)));
236             }
237             writer.println();
238             writer.decreaseIndent();
239         }
240     }
241 
242     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)243     void dumpProto(ProtoOutputStream proto) {
244         synchronized (mLock) {
245             long powerComponentHandlerToken = proto.start(
246                     CarPowerDumpProto.POWER_COMPONENT_HANDLER);
247 
248             for (int i = 0; i < mRegisteredComponents.size(); ++i) {
249                 long powerComponentStateMappingToken = proto.start(
250                         PowerComponentHandlerProto.POWER_COMPONENT_STATE_MAPPINGS);
251                 int component = mRegisteredComponents.get(i);
252                 proto.write(
253                         PowerComponentToState.POWER_COMPONENT, powerComponentToString(component));
254                 proto.write(PowerComponentToState.STATE, mComponentStates.get(
255                         component, /* valueIfKeyNotFound= */ false));
256                 proto.end(powerComponentStateMappingToken);
257             }
258 
259             for (int i = 0; i < mComponentsOffByPolicy.size(); i++) {
260                 proto.write(PowerComponentHandlerProto.COMPONENTS_OFF_BY_POLICY,
261                         powerComponentToString(mComponentsOffByPolicy.keyAt(i)));
262             }
263 
264             StringBuilder lastModifiedComponents = new StringBuilder();
265             for (int i = 0; i < mLastModifiedComponents.size(); i++) {
266                 if (i > 0) lastModifiedComponents.append(", ");
267                 lastModifiedComponents.append(
268                         powerComponentToString(mLastModifiedComponents.keyAt(i)));
269             }
270             proto.write(PowerComponentHandlerProto.LAST_MODIFIED_COMPONENTS,
271                     lastModifiedComponents.toString());
272 
273             proto.end(powerComponentHandlerToken);
274         }
275     }
276 
277     /**
278      * Modifies power component's state, considering user setting.
279      *
280      * @return {@code true} if power state is changed. Otherwise, {@code false}
281      */
282     @GuardedBy("mLock")
setComponentEnabledLocked(int component, boolean enabled)283     private boolean setComponentEnabledLocked(int component, boolean enabled) {
284         int componentIndex = mComponentStates.indexOfKey(component); // check if component exists
285         boolean oldState = mComponentStates.get(component, /* valueIfKeyNotFound= */ false);
286         // If components is not in mComponentStates and enabled is false, oldState will be false,
287         // as result function will return false without adding component to mComponentStates
288         if (oldState == enabled && componentIndex >= 0) {
289             return false;
290         }
291 
292         mComponentStates.put(component, enabled);
293 
294         PowerComponentMediator mediator = mPowerComponentMediators.get(component);
295         if (mediator == null) {
296             return true;
297         }
298 
299         boolean needPowerChange = false;
300         if (mediator.isUserControllable()) {
301             if (!enabled && mediator.isEnabled()) {
302                 mComponentsOffByPolicy.put(component, /* value= */ true);
303                 needPowerChange = true;
304             }
305             if (enabled && mComponentsOffByPolicy.get(component, /* valueIfKeyNotFound= */ false)) {
306                 mComponentsOffByPolicy.delete(component);
307                 needPowerChange = true;
308             }
309             if (needPowerChange) {
310                 writeUserOffComponentsLocked();
311             }
312         } else {
313             needPowerChange = true;
314         }
315 
316         if (needPowerChange) {
317             mediator.setEnabled(enabled);
318         }
319         return true;
320     }
321 
322     @GuardedBy("mLock")
readUserOffComponentsLocked()323     private void readUserOffComponentsLocked() {
324         boolean invalid = false;
325         mComponentsOffByPolicy.clear();
326         try (BufferedReader reader = new BufferedReader(
327                 new InputStreamReader(mOffComponentsByUserFile.openRead(),
328                         StandardCharsets.UTF_8))) {
329             String line;
330             while ((line = reader.readLine()) != null) {
331                 int component = toPowerComponent(line.trim(), /* prefix= */ false);
332                 if (component == INVALID_POWER_COMPONENT) {
333                     invalid = true;
334                     break;
335                 }
336                 mComponentsOffByPolicy.put(component, /* value= */ true);
337             }
338         } catch (FileNotFoundException e) {
339             // Behave as if there are no forced-off components.
340             return;
341         } catch (IOException e) {
342             Slogf.w(TAG, "Failed to read %s: %s", FORCED_OFF_COMPONENTS_FILENAME, e);
343             return;
344         }
345         if (invalid) {
346             mOffComponentsByUserFile.delete();
347         }
348     }
349 
writeUserOffComponentsLocked()350     private void writeUserOffComponentsLocked() {
351         FileOutputStream fos;
352         try {
353             fos = mOffComponentsByUserFile.startWrite();
354         } catch (IOException e) {
355             Slogf.e(TAG, e, "Cannot create %s", FORCED_OFF_COMPONENTS_FILENAME);
356             return;
357         }
358 
359         try (BufferedWriter writer = new BufferedWriter(
360                 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
361             synchronized (mLock) {
362                 for (int i = 0; i < mComponentsOffByPolicy.size(); i++) {
363                     if (!mComponentsOffByPolicy.valueAt(i)) {
364                         continue;
365                     }
366                     writer.write(powerComponentToString(mComponentsOffByPolicy.keyAt(i)));
367                     writer.newLine();
368                 }
369             }
370             writer.flush();
371             mOffComponentsByUserFile.finishWrite(fos);
372         } catch (IOException e) {
373             mOffComponentsByUserFile.failWrite(fos);
374             Slogf.e(TAG, e, "Writing %s failed", FORCED_OFF_COMPONENTS_FILENAME);
375         }
376     }
377 
378     /**
379      * Method to be used from tests and when policy is defined through command line
380      */
registerCustomComponents(Integer[] components)381     public void registerCustomComponents(Integer[] components) {
382         synchronized (mLock) {
383             for (int i = 0; i < components.length; i++) {
384                 int componentId = components[i];
385                 // Add only new components
386                 if (mRegisteredComponents.indexOf(componentId) == -1) {
387                     mRegisteredComponents.add(componentId);
388                 }
389             }
390         }
391     }
392 
393     /**
394      * Utility method to check if array contains component from filter
395      */
isComponentChanged(SparseBooleanArray updatedComponents, CarPowerPolicyFilter carPowerPolicyFilter)396     public static boolean isComponentChanged(SparseBooleanArray updatedComponents,
397             CarPowerPolicyFilter carPowerPolicyFilter) {
398         int[] components = carPowerPolicyFilter.getComponents();
399         for (int i = 0; i < components.length; i++) {
400             if (updatedComponents.get(components[i], false)) {
401                 return true;
402             }
403         }
404         return false;
405     }
406 
407     abstract static class PowerComponentMediator {
408         protected int mComponentId;
409 
PowerComponentMediator(int component)410         PowerComponentMediator(int component) {
411             mComponentId = component;
412         }
413 
isComponentAvailable()414         public boolean isComponentAvailable() {
415             return false;
416         }
417 
isUserControllable()418         public boolean isUserControllable() {
419             return false;
420         }
421 
isEnabled()422         public boolean isEnabled() {
423             return false;
424         }
425 
setEnabled(boolean enabled)426         public void setEnabled(boolean enabled) {}
427     }
428 
429     // TODO(b/178824607): Check if power policy can turn on/off display as quickly as the existing
430     // implementation.
431     private final class DisplayPowerComponentMediator extends PowerComponentMediator {
DisplayPowerComponentMediator()432         DisplayPowerComponentMediator() {
433             super(DISPLAY);
434         }
435 
436         @Override
isComponentAvailable()437         public boolean isComponentAvailable() {
438             // It is assumed that display is supported in all vehicles.
439             return true;
440         }
441 
442         @Override
isEnabled()443         public boolean isEnabled() {
444             return mSystemInterface.isAnyDisplayEnabled();
445         }
446 
447         @Override
setEnabled(boolean enabled)448         public void setEnabled(boolean enabled) {
449             mSystemInterface.setAllDisplayState(enabled);
450             Slogf.d(TAG, "Display power component is %s", enabled ? "on" : "off");
451         }
452     }
453 
454     private final class WifiPowerComponentMediator extends PowerComponentMediator {
455         private final WifiManager mWifiManager;
456 
WifiPowerComponentMediator()457         WifiPowerComponentMediator() {
458             super(WIFI);
459             mWifiManager = mContext.getSystemService(WifiManager.class);
460         }
461 
462         @Override
isComponentAvailable()463         public boolean isComponentAvailable() {
464             return mPackageManager.hasSystemFeature(PackageManager.FEATURE_WIFI);
465         }
466 
467         @Override
isUserControllable()468         public boolean isUserControllable() {
469             return true;
470         }
471 
472         @Override
isEnabled()473         public boolean isEnabled() {
474             return mWifiManager.isWifiEnabled();
475         }
476 
477         @Override
setEnabled(boolean enabled)478         public void setEnabled(boolean enabled) {
479             mWifiManager.setWifiEnabled(enabled);
480             Slogf.d(TAG, "Wifi power component is %s", enabled ? "on" : "off");
481         }
482     }
483 
484     private static final class VoiceInteractionPowerComponentMediator
485             extends PowerComponentMediator {
486 
487         private boolean mIsEnabled = true;
488 
VoiceInteractionPowerComponentMediator()489         VoiceInteractionPowerComponentMediator() {
490             super(VOICE_INTERACTION);
491         }
492 
493         @Override
isComponentAvailable()494         public boolean isComponentAvailable() {
495             return VoiceInteractionHelper.isAvailable();
496         }
497 
498         @Override
isEnabled()499         public boolean isEnabled() {
500             return mIsEnabled;
501         }
502 
503         @Override
setEnabled(boolean enabled)504         public void setEnabled(boolean enabled) {
505             try {
506                 VoiceInteractionHelper.setEnabled(enabled);
507                 mIsEnabled = enabled;
508                 Slogf.d(TAG, "Voice Interaction power component is %s", enabled ? "on" : "off");
509             } catch (RemoteException e) {
510                 Slogf.w(TAG, e, "VoiceInteractionHelper.setEnabled(%b) failed", enabled);
511             }
512         }
513     }
514 
515     private final class BluetoothPowerComponentMediator extends PowerComponentMediator {
516         private final BluetoothAdapter mBluetoothAdapter;
517 
BluetoothPowerComponentMediator()518         BluetoothPowerComponentMediator() {
519             super(BLUETOOTH);
520             mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
521         }
522 
523         @Override
isComponentAvailable()524         public boolean isComponentAvailable() {
525             return mPackageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH);
526         }
527 
528         @Override
isUserControllable()529         public boolean isUserControllable() {
530             return true;
531         }
532 
533         @Override
isEnabled()534         public boolean isEnabled() {
535             return mBluetoothAdapter.isEnabled();
536         }
537 
538         @Override
setEnabled(boolean enabled)539         public void setEnabled(boolean enabled) {
540             // No op
541             Slogf.w(TAG, "Bluetooth power is controlled by "
542                     + "com.android.car.BluetoothPowerPolicy");
543         }
544     }
545 
546     private final class PowerComponentMediatorFactory {
547         @Nullable
createPowerComponent(int component)548         PowerComponentMediator createPowerComponent(int component) {
549             switch (component) {
550                 case PowerComponent.AUDIO:
551                     // We don't control audio in framework level, because audio is enabled or
552                     // disabled in audio HAL according to the current power policy.
553                     return null;
554                 case PowerComponent.MEDIA:
555                     return null;
556                 case PowerComponent.DISPLAY:
557                     return new DisplayPowerComponentMediator();
558                 case PowerComponent.WIFI:
559                     return new WifiPowerComponentMediator();
560                 case PowerComponent.CELLULAR:
561                     return null;
562                 case PowerComponent.ETHERNET:
563                     return null;
564                 case PowerComponent.PROJECTION:
565                     return null;
566                 case PowerComponent.NFC:
567                     return null;
568                 case PowerComponent.INPUT:
569                     return null;
570                 case PowerComponent.VOICE_INTERACTION:
571                     return new VoiceInteractionPowerComponentMediator();
572                 case PowerComponent.VISUAL_INTERACTION:
573                     return null;
574                 case PowerComponent.TRUSTED_DEVICE_DETECTION:
575                     return null;
576                 case PowerComponent.MICROPHONE:
577                     // We don't control microphone in framework level, because microphone is enabled
578                     // or disabled in audio HAL according to the current power policy.
579                     return null;
580                 case PowerComponent.BLUETOOTH:
581                     // com.android.car.BluetoothDeviceConnectionPolicy handles power state change.
582                     // So, bluetooth mediator doesn't directly turn on/off BT, but it changes policy
583                     // behavior, considering user intervetion.
584                     return new BluetoothPowerComponentMediator();
585                 case PowerComponent.LOCATION:
586                     // GNSS HAL handles power state change.
587                     return null;
588                 case PowerComponent.CPU:
589                     return null;
590                 default:
591                     Slogf.w(TAG, "Unknown component(%d)", component);
592                     return null;
593             }
594         }
595     }
596 }
597