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