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