1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.car.hal;
17 
18 import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_BOOTUP_REASON;
19 import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REPORT;
20 import static android.hardware.automotive.vehicle.VehicleProperty.AP_POWER_STATE_REQ;
21 import static android.hardware.automotive.vehicle.VehicleProperty.DISPLAY_BRIGHTNESS;
22 import static android.hardware.automotive.vehicle.VehicleProperty.PER_DISPLAY_BRIGHTNESS;
23 import static android.hardware.automotive.vehicle.VehicleProperty.SHUTDOWN_REQUEST;
24 import static android.hardware.automotive.vehicle.VehicleProperty.VEHICLE_IN_USE;
25 
26 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
27 
28 import android.annotation.IntDef;
29 import android.annotation.Nullable;
30 import android.car.builtin.util.Slogf;
31 import android.car.builtin.view.DisplayHelper;
32 import android.car.feature.FeatureFlags;
33 import android.content.Context;
34 import android.hardware.automotive.vehicle.VehicleApPowerBootupReason;
35 import android.hardware.automotive.vehicle.VehicleApPowerStateConfigFlag;
36 import android.hardware.automotive.vehicle.VehicleApPowerStateReport;
37 import android.hardware.automotive.vehicle.VehicleApPowerStateReq;
38 import android.hardware.automotive.vehicle.VehicleApPowerStateReqIndex;
39 import android.hardware.automotive.vehicle.VehicleApPowerStateShutdownParam;
40 import android.hardware.automotive.vehicle.VehicleProperty;
41 import android.hardware.automotive.vehicle.VehiclePropertyStatus;
42 import android.hardware.display.DisplayManager;
43 import android.os.ServiceSpecificException;
44 import android.util.SparseArray;
45 import android.util.SparseIntArray;
46 import android.view.Display;
47 
48 import com.android.car.CarLog;
49 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
50 import com.android.internal.annotations.GuardedBy;
51 import com.android.internal.annotations.VisibleForTesting;
52 import com.android.internal.util.Preconditions;
53 
54 import java.io.PrintWriter;
55 import java.lang.annotation.Retention;
56 import java.lang.annotation.RetentionPolicy;
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.List;
60 import java.util.Objects;
61 
62 /**
63  * Translates HAL power events to higher-level semantic information.
64  */
65 public class PowerHalService extends HalServiceBase {
66     // Set display brightness from 0-100%
67     public static final int MAX_BRIGHTNESS = 100;
68 
69     // TODO(b/337307388): replace this with VehicleProperty.PER_DISPLAY_MAX_BRIGHTNESS once we use
70     // property V4.
71     private static final int PER_DISPLAY_MAX_BRIGHTNESS = 0x11410F4E;
72 
PropertyInfo(boolean needSubscription)73     private record PropertyInfo(boolean needSubscription) {}
74 
getSupportedProperties()75     private static SparseArray<PropertyInfo> getSupportedProperties() {
76         SparseArray<PropertyInfo> propertyInfo = new SparseArray<>();
77         propertyInfo.put(AP_POWER_STATE_REQ, new PropertyInfo(/*needSubscription=*/ true));
78         // This is issued from PowerHalService so we do not need to subscribe to it.
79         propertyInfo.put(AP_POWER_STATE_REPORT, new PropertyInfo(/*needSubscription=*/ false));
80         propertyInfo.put(DISPLAY_BRIGHTNESS, new PropertyInfo(/*needSubscription=*/ true));
81         propertyInfo.put(PER_DISPLAY_BRIGHTNESS, new PropertyInfo(/*needSubscription=*/ true));
82         propertyInfo.put(VEHICLE_IN_USE, new PropertyInfo(/*needSubscription=*/ false));
83         propertyInfo.put(AP_POWER_BOOTUP_REASON, new PropertyInfo(/*needSubscription=*/ false));
84         propertyInfo.put(PER_DISPLAY_MAX_BRIGHTNESS, new PropertyInfo(/*needSubscription=*/ false));
85         return propertyInfo;
86     }
87 
88     private static final SparseArray<PropertyInfo> SUPPORTED_PROPERTIES = getSupportedProperties();
89 
90     /**
91      * Unknown bootup reason.
92      */
93     public static final int BOOTUP_REASON_UNKNOWN = -1;
94 
95     /**
96      * Power on due to user's pressing of power key or rotating of ignition switch.
97      */
98     public static final int BOOTUP_REASON_USER_POWER_ON = 0;
99 
100     /**
101      * Automatic power on triggered by door unlock or any other kind of automatic user detection.
102      */
103     public static final int BOOTUP_REASON_SYSTEM_USER_DETECTION = 1;
104 
105     /**
106      * Automatic power on to execute a remote task. This is triggered by receiving a wakeup message
107      * from an external system in the vehicle.
108      */
109     public static final int BOOTUP_REASON_SYSTEM_REMOTE_ACCESS = 2;
110 
111     /**
112      * Automatic power on to enter garage mode. This is triggered by receiving a wakeup message from
113      * an external system in the vehicle.
114      */
115     public static final int BOOTUP_REASON_SYSTEM_ENTER_GARAGE_MODE = 3;
116 
117     /** @hide */
118     @IntDef(prefix = {"BOOTUP_REASON_"}, value = {
119             BOOTUP_REASON_UNKNOWN,
120             BOOTUP_REASON_USER_POWER_ON,
121             BOOTUP_REASON_SYSTEM_USER_DETECTION,
122             BOOTUP_REASON_SYSTEM_REMOTE_ACCESS,
123             BOOTUP_REASON_SYSTEM_ENTER_GARAGE_MODE,
124     })
125     @Retention(RetentionPolicy.SOURCE)
126     public @interface BootupReason {}
127 
128     @VisibleForTesting
129     public static final int SET_WAIT_FOR_VHAL = VehicleApPowerStateReport.WAIT_FOR_VHAL;
130     @VisibleForTesting
131     public static final int SET_DEEP_SLEEP_ENTRY = VehicleApPowerStateReport.DEEP_SLEEP_ENTRY;
132     @VisibleForTesting
133     public static final int SET_DEEP_SLEEP_EXIT = VehicleApPowerStateReport.DEEP_SLEEP_EXIT;
134     @VisibleForTesting
135     public static final int SET_SHUTDOWN_POSTPONE = VehicleApPowerStateReport.SHUTDOWN_POSTPONE;
136     @VisibleForTesting
137     public static final int SET_SHUTDOWN_START = VehicleApPowerStateReport.SHUTDOWN_START;
138     @VisibleForTesting
139     public static final int SET_ON = VehicleApPowerStateReport.ON;
140     @VisibleForTesting
141     public static final int SET_SHUTDOWN_PREPARE = VehicleApPowerStateReport.SHUTDOWN_PREPARE;
142     @VisibleForTesting
143     public static final int SET_SHUTDOWN_CANCELLED = VehicleApPowerStateReport.SHUTDOWN_CANCELLED;
144 
145     @VisibleForTesting
146     public static final int SHUTDOWN_CAN_SLEEP = VehicleApPowerStateShutdownParam.CAN_SLEEP;
147     @VisibleForTesting
148     public static final int SHUTDOWN_IMMEDIATELY =
149             VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY;
150     @VisibleForTesting
151     public static final int SHUTDOWN_ONLY = VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY;
152     @VisibleForTesting
153     public static final int SET_HIBERNATION_ENTRY = VehicleApPowerStateReport.HIBERNATION_ENTRY;
154     @VisibleForTesting
155     public static final int SET_HIBERNATION_EXIT = VehicleApPowerStateReport.HIBERNATION_EXIT;
156 
157     private final Object mLock = new Object();
158 
powerStateReportName(int state)159     private static String powerStateReportName(int state) {
160         String baseName;
161         switch(state) {
162             case SET_WAIT_FOR_VHAL:      baseName = "WAIT_FOR_VHAL";      break;
163             case SET_DEEP_SLEEP_ENTRY:   baseName = "DEEP_SLEEP_ENTRY";   break;
164             case SET_DEEP_SLEEP_EXIT:    baseName = "DEEP_SLEEP_EXIT";    break;
165             case SET_SHUTDOWN_POSTPONE:  baseName = "SHUTDOWN_POSTPONE";  break;
166             case SET_SHUTDOWN_START:     baseName = "SHUTDOWN_START";     break;
167             case SET_ON:                 baseName = "ON";                 break;
168             case SET_SHUTDOWN_PREPARE:   baseName = "SHUTDOWN_PREPARE";   break;
169             case SET_SHUTDOWN_CANCELLED: baseName = "SHUTDOWN_CANCELLED"; break;
170             case SET_HIBERNATION_ENTRY:  baseName = "HIBERNATION_ENTRY";  break;
171             case SET_HIBERNATION_EXIT:   baseName = "HIBERNATION_EXIT";   break;
172             default:                     baseName = "<unknown>";          break;
173         }
174         return baseName + "(" + state + ")";
175     }
176 
powerStateReqName(int state)177     private static String powerStateReqName(int state) {
178         String baseName;
179         switch(state) {
180             case VehicleApPowerStateReq.ON:               baseName = "ON";               break;
181             case VehicleApPowerStateReq.SHUTDOWN_PREPARE: baseName = "SHUTDOWN_PREPARE"; break;
182             case VehicleApPowerStateReq.CANCEL_SHUTDOWN:  baseName = "CANCEL_SHUTDOWN";  break;
183             case VehicleApPowerStateReq.FINISHED:         baseName = "FINISHED";         break;
184             default:                                      baseName = "<unknown>";        break;
185         }
186         return baseName + "(" + state + ")";
187     }
188 
189     /**
190      * Interface to be implemented by any object that wants to be notified by any Vehicle's power
191      * change.
192      */
193     public interface PowerEventListener {
194         /**
195          * Received power state change event.
196          * @param state One of STATE_*
197          */
onApPowerStateChange(PowerState state)198         void onApPowerStateChange(PowerState state);
199 
200         /**
201          * Received display brightness change event.
202          * @param brightness in percentile. 100% full.
203          */
onDisplayBrightnessChange(int brightness)204         void onDisplayBrightnessChange(int brightness);
205 
206         /**
207          * Received display brightness change event.
208          * @param displayId the display id.
209          * @param brightness in percentile. 100% full.
210          */
onDisplayBrightnessChange(int displayId, int brightness)211         void onDisplayBrightnessChange(int displayId, int brightness);
212     }
213 
214     /**
215      * Contains information about the Vehicle's power state.
216      */
217     public static final class PowerState {
218 
219         @IntDef({SHUTDOWN_TYPE_UNDEFINED, SHUTDOWN_TYPE_POWER_OFF, SHUTDOWN_TYPE_DEEP_SLEEP,
220                 SHUTDOWN_TYPE_HIBERNATION, SHUTDOWN_TYPE_EMERGENCY})
221         @Retention(RetentionPolicy.SOURCE)
222         public @interface ShutdownType {}
223 
224         public static final int SHUTDOWN_TYPE_UNDEFINED = 0;
225         public static final int SHUTDOWN_TYPE_POWER_OFF = 1;
226         public static final int SHUTDOWN_TYPE_DEEP_SLEEP = 2;
227         public static final int SHUTDOWN_TYPE_HIBERNATION = 3;
228         public static final int SHUTDOWN_TYPE_EMERGENCY = 4;
229         /**
230          * One of STATE_*
231          */
232         public final int mState;
233         public final int mParam;
234 
PowerState(int state, int param)235         public PowerState(int state, int param) {
236             this.mState = state;
237             this.mParam = param;
238         }
239 
240         /**
241          * Whether the current PowerState allows postponing or not. Calling this for
242          * power state other than STATE_SHUTDOWN_PREPARE will trigger exception.
243          * @return
244          * @throws IllegalStateException
245          */
canPostponeShutdown()246         public boolean canPostponeShutdown() {
247             if (mState != VehicleApPowerStateReq.SHUTDOWN_PREPARE) {
248                 throw new IllegalStateException("wrong state");
249             }
250             return (mParam != VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY
251                     && mParam != VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY
252                     && mParam != VehicleApPowerStateShutdownParam.HIBERNATE_IMMEDIATELY
253                     && mParam != VehicleApPowerStateShutdownParam.EMERGENCY_SHUTDOWN);
254         }
255 
256         /**
257          * Gets whether the current PowerState allows suspend or not.
258          *
259          * @throws IllegalStateException if called in state other than {@code
260          * STATE_SHUTDOWN_PREPARE}
261          */
canSuspend()262         public boolean canSuspend() {
263             Preconditions.checkArgument(mState == VehicleApPowerStateReq.SHUTDOWN_PREPARE,
264                     "canSuspend was called in the wrong state! State = %d", mState);
265 
266             return (mParam == VehicleApPowerStateShutdownParam.CAN_HIBERNATE
267                     || mParam == VehicleApPowerStateShutdownParam.HIBERNATE_IMMEDIATELY
268                     || mParam == VehicleApPowerStateShutdownParam.CAN_SLEEP
269                     || mParam == VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY);
270         }
271 
272         /**
273          * Gets shutdown type
274          *
275          * @return {@code ShutdownType} - type of shutdown
276          * @throws IllegalStateException if called in state other than {@code
277          * STATE_SHUTDOWN_PREPARE}
278          */
279         @ShutdownType
getShutdownType()280         public int getShutdownType() {
281             Preconditions.checkArgument(mState == VehicleApPowerStateReq.SHUTDOWN_PREPARE,
282                     "getShutdownType was called in the wrong state! State = %d", mState);
283 
284             int result = SHUTDOWN_TYPE_POWER_OFF;
285             if (mParam == VehicleApPowerStateShutdownParam.CAN_SLEEP
286                     || mParam == VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY) {
287                 result = SHUTDOWN_TYPE_DEEP_SLEEP;
288             } else if (mParam == VehicleApPowerStateShutdownParam.CAN_HIBERNATE
289                     || mParam == VehicleApPowerStateShutdownParam.HIBERNATE_IMMEDIATELY) {
290                 result = SHUTDOWN_TYPE_HIBERNATION;
291             } else if (mParam == VehicleApPowerStateShutdownParam.EMERGENCY_SHUTDOWN) {
292                 result = SHUTDOWN_TYPE_EMERGENCY;
293             }
294 
295             return result;
296         }
297 
298         @Override
equals(Object o)299         public boolean equals(Object o) {
300             if (this == o) {
301                 return true;
302             }
303             if (!(o instanceof PowerState)) {
304                 return false;
305             }
306             PowerState that = (PowerState) o;
307             return this.mState == that.mState && this.mParam == that.mParam;
308         }
309 
310         @Override
hashCode()311         public int hashCode() {
312             return Objects.hash(mState, mParam);
313         }
314 
315         @Override
toString()316         public String toString() {
317             return "PowerState state:" + mState + ", param:" + mParam;
318         }
319     }
320 
321     @GuardedBy("mLock")
322     private final SparseArray<HalPropConfig> mProperties = new SparseArray<>();
323     private final Context mContext;
324     private final VehicleHal mHal;
325     private final FeatureFlags mFeatureFlags;
326     @Nullable
327     @GuardedBy("mLock")
328     private ArrayList<HalPropValue> mQueuedEvents;
329     @GuardedBy("mLock")
330     private PowerEventListener mListener;
331     @GuardedBy("mLock")
332     private int mMaxDisplayBrightness;
333     @GuardedBy("mLock")
334     private SparseIntArray mMaxPerDisplayBrightness = new SparseIntArray();
335     @GuardedBy("mLock")
336     private boolean mPerDisplayBrightnessSupported;
337 
PowerHalService(Context context, FeatureFlags featureFlags, VehicleHal hal)338     public PowerHalService(Context context, FeatureFlags featureFlags, VehicleHal hal) {
339         mContext = context;
340         mFeatureFlags = featureFlags;
341         mHal = hal;
342     }
343 
344     /**
345      * Sets the event listener to receive Vehicle's power events.
346      */
setListener(PowerEventListener listener)347     public void setListener(PowerEventListener listener) {
348         ArrayList<HalPropValue> eventsToDispatch = null;
349         synchronized (mLock) {
350             mListener = listener;
351             if (mQueuedEvents != null && !mQueuedEvents.isEmpty()) {
352                 eventsToDispatch = mQueuedEvents;
353             }
354             mQueuedEvents = null;
355         }
356         // do this outside lock
357         if (eventsToDispatch != null) {
358             dispatchEvents(eventsToDispatch, listener);
359         }
360     }
361 
362     /**
363      * Send WaitForVhal message to VHAL
364      */
sendWaitForVhal()365     public void sendWaitForVhal() {
366         Slogf.i(CarLog.TAG_POWER, "send wait for vhal");
367         setPowerState(VehicleApPowerStateReport.WAIT_FOR_VHAL, 0);
368     }
369 
370     /**
371      * Send SleepEntry message to VHAL
372      * @param wakeupTimeSec Notify VHAL when system wants to be woken from sleep.
373      */
sendSleepEntry(int wakeupTimeSec)374     public void sendSleepEntry(int wakeupTimeSec) {
375         Slogf.i(CarLog.TAG_POWER, "send sleep entry");
376         setPowerState(VehicleApPowerStateReport.DEEP_SLEEP_ENTRY, wakeupTimeSec);
377     }
378 
379     /**
380      * Send SleepExit message to VHAL
381      * Notifies VHAL when SOC has woken.
382      */
sendSleepExit()383     public void sendSleepExit() {
384         Slogf.i(CarLog.TAG_POWER, "send sleep exit");
385         setPowerState(VehicleApPowerStateReport.DEEP_SLEEP_EXIT, 0);
386     }
387 
388     /**
389      * Sends HibernationEntry message to VHAL
390      *
391      * @param wakeupTimeSec Number of seconds from now to be woken from sleep.
392      */
sendHibernationEntry(int wakeupTimeSec)393     public void sendHibernationEntry(int wakeupTimeSec) {
394         Slogf.i(CarLog.TAG_POWER, "send hibernation entry - wakeupTimeSec = %d",
395                 wakeupTimeSec);
396         setPowerState(VehicleApPowerStateReport.HIBERNATION_ENTRY, wakeupTimeSec);
397     }
398 
399     /**
400      * Sends HibernationExit message to VHAL
401      *
402      * Notifies VHAL after SOC woke up from hibernation.
403      */
sendHibernationExit()404     public void sendHibernationExit() {
405         Slogf.i(CarLog.TAG_POWER, "send hibernation exit");
406         setPowerState(VehicleApPowerStateReport.HIBERNATION_EXIT, 0);
407     }
408 
409     /**
410      * Send Shutdown Postpone message to VHAL
411      */
sendShutdownPostpone(int postponeTimeMs)412     public void sendShutdownPostpone(int postponeTimeMs) {
413         Slogf.i(CarLog.TAG_POWER, "send shutdown postpone, time:" + postponeTimeMs);
414         setPowerState(VehicleApPowerStateReport.SHUTDOWN_POSTPONE, postponeTimeMs);
415     }
416 
417     /**
418      * Send Shutdown Start message to VHAL
419      */
sendShutdownStart(int wakeupTimeSec)420     public void sendShutdownStart(int wakeupTimeSec) {
421         Slogf.i(CarLog.TAG_POWER, "send shutdown start");
422         setPowerState(VehicleApPowerStateReport.SHUTDOWN_START, wakeupTimeSec);
423     }
424 
425     /**
426      * Send On message to VHAL
427      */
sendOn()428     public void sendOn() {
429         Slogf.i(CarLog.TAG_POWER, "send on");
430         setPowerState(VehicleApPowerStateReport.ON, 0);
431     }
432 
433     /**
434      * Send Shutdown Prepare message to VHAL
435      */
sendShutdownPrepare()436     public void sendShutdownPrepare() {
437         Slogf.i(CarLog.TAG_POWER, "send shutdown prepare");
438         setPowerState(VehicleApPowerStateReport.SHUTDOWN_PREPARE, 0);
439     }
440 
441     /**
442      * Send Shutdown Cancel message to VHAL
443      */
sendShutdownCancel()444     public void sendShutdownCancel() {
445         Slogf.i(CarLog.TAG_POWER, "send shutdown cancel");
446         setPowerState(VehicleApPowerStateReport.SHUTDOWN_CANCELLED, 0);
447     }
448 
449     /**
450      * Sets the display brightness for the vehicle.
451      * @param brightness value from 0 to 100.
452      */
sendDisplayBrightness(int brightness)453     public void sendDisplayBrightness(int brightness) {
454         int brightnessToSet = adjustBrightness(brightness, /* minBrightness= */ 0,
455                 /* maxBrightness= */ 100);
456 
457         synchronized (mLock) {
458             if (mProperties.get(DISPLAY_BRIGHTNESS) == null) {
459                 return;
460             }
461             if (mPerDisplayBrightnessSupported) {
462                 Slogf.w(CarLog.TAG_POWER, "PER_DISPLAY_BRIGHTNESS is supported and "
463                         + "sendDisplayBrightness(int displayId, int brightness) should be used "
464                         + "instead of DISPLAY_BRIGHTNESS");
465                 return;
466             }
467         }
468         try {
469             mHal.set(VehicleProperty.DISPLAY_BRIGHTNESS, 0).to(brightnessToSet);
470             Slogf.i(CarLog.TAG_POWER, "send display brightness = " + brightnessToSet);
471         } catch (ServiceSpecificException | IllegalArgumentException e) {
472             Slogf.e(CarLog.TAG_POWER, "cannot set DISPLAY_BRIGHTNESS", e);
473         }
474     }
475 
476     /**
477      * Received display brightness change event.
478      * @param displayId the display id.
479      * @param brightness in percentile. 100% full.
480      */
sendDisplayBrightness(int displayId, int brightness)481     public void sendDisplayBrightness(int displayId, int brightness) {
482         int brightnessToSet = adjustBrightness(brightness, /* minBrightness= */ 0,
483                 /* maxBrightness= */ 100);
484 
485         synchronized (mLock) {
486             if (!mPerDisplayBrightnessSupported) {
487                 Slogf.w(CarLog.TAG_POWER, "PER_DISPLAY_BRIGHTNESS is not supported");
488                 return;
489             }
490         }
491         int displayPort = getDisplayPort(displayId);
492         if (displayPort == DisplayHelper.INVALID_PORT) {
493             return;
494         }
495         try {
496             HalPropValue value = mHal.getHalPropValueBuilder()
497                     .build(PER_DISPLAY_BRIGHTNESS, /* areaId= */ 0,
498                             new int[]{displayPort, brightnessToSet});
499             mHal.set(value);
500             Slogf.i(CarLog.TAG_POWER, "send display brightness = %d, port = %d",
501                     brightnessToSet, displayPort);
502         } catch (ServiceSpecificException e) {
503             Slogf.e(CarLog.TAG_POWER, e, "cannot set PER_DISPLAY_BRIGHTNESS port = %d",
504                     displayPort);
505         }
506     }
507 
508     /**
509      * Sends {@code SHUTDOWN_REQUEST} to the VHAL.
510      */
requestShutdownAp(@owerState.ShutdownType int powerState, boolean runGarageMode)511     public void requestShutdownAp(@PowerState.ShutdownType int powerState, boolean runGarageMode) {
512         int shutdownParam = VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY;
513         switch (powerState) {
514             case PowerState.SHUTDOWN_TYPE_POWER_OFF:
515                 shutdownParam = runGarageMode ? VehicleApPowerStateShutdownParam.SHUTDOWN_ONLY
516                         : VehicleApPowerStateShutdownParam.SHUTDOWN_IMMEDIATELY;
517                 break;
518             case PowerState.SHUTDOWN_TYPE_DEEP_SLEEP:
519                 shutdownParam = runGarageMode ? VehicleApPowerStateShutdownParam.CAN_SLEEP
520                         : VehicleApPowerStateShutdownParam.SLEEP_IMMEDIATELY;
521                 break;
522             case PowerState.SHUTDOWN_TYPE_HIBERNATION:
523                 shutdownParam = runGarageMode ? VehicleApPowerStateShutdownParam.CAN_HIBERNATE
524                         : VehicleApPowerStateShutdownParam.HIBERNATE_IMMEDIATELY;
525                 break;
526             case PowerState.SHUTDOWN_TYPE_UNDEFINED:
527             default:
528                 Slogf.w(CarLog.TAG_POWER, "Unknown power state(%d) for requestShutdownAp",
529                         powerState);
530                 return;
531         }
532 
533         try {
534             mHal.set(SHUTDOWN_REQUEST, /* areaId= */ 0).to(shutdownParam);
535         } catch (ServiceSpecificException | IllegalArgumentException e) {
536             Slogf.e(CarLog.TAG_POWER, "cannot send SHUTDOWN_REQUEST to VHAL", e);
537         }
538     }
539 
setPowerState(int state, int additionalParam)540     private void setPowerState(int state, int additionalParam) {
541         if (isPowerStateSupported()) {
542             int[] values = { state, additionalParam };
543             try {
544                 mHal.set(VehicleProperty.AP_POWER_STATE_REPORT, 0).to(values);
545                 Slogf.i(CarLog.TAG_POWER, "setPowerState=" + powerStateReportName(state)
546                         + " param=" + additionalParam);
547             } catch (ServiceSpecificException e) {
548                 Slogf.e(CarLog.TAG_POWER, "cannot set to AP_POWER_STATE_REPORT", e);
549             }
550         }
551     }
552 
553     /**
554      * Returns a {@link PowerState} representing the current power state for the vehicle.
555      */
556     @Nullable
getCurrentPowerState()557     public PowerState getCurrentPowerState() {
558         HalPropValue value;
559         try {
560             value = mHal.get(VehicleProperty.AP_POWER_STATE_REQ);
561         } catch (ServiceSpecificException e) {
562             Slogf.e(CarLog.TAG_POWER, "Cannot get AP_POWER_STATE_REQ", e);
563             return null;
564         }
565         return new PowerState(value.getInt32Value(VehicleApPowerStateReqIndex.STATE),
566                 value.getInt32Value(VehicleApPowerStateReqIndex.ADDITIONAL));
567     }
568 
569     /**
570      * Determines if the current properties describe a valid power state
571      * @return true if both the power state request and power state report are valid
572      */
isPowerStateSupported()573     public boolean isPowerStateSupported() {
574         synchronized (mLock) {
575             return (mProperties.get(VehicleProperty.AP_POWER_STATE_REQ) != null)
576                     && (mProperties.get(VehicleProperty.AP_POWER_STATE_REPORT) != null);
577         }
578     }
579 
580     /**
581      * Returns if the vehicle is currently in use.
582      *
583      * In use means a human user is present in the vehicle and is currently using the vehicle or
584      * will use the vehicle soon.
585      */
isVehicleInUse()586     public boolean isVehicleInUse() {
587         try {
588             HalPropValue value = mHal.get(VEHICLE_IN_USE);
589             return (value.getStatus() == VehiclePropertyStatus.AVAILABLE
590                     && value.getInt32ValuesSize() >= 1 && value.getInt32Value(0) != 0);
591         } catch (ServiceSpecificException | IllegalArgumentException e) {
592             Slogf.w(CarLog.TAG_POWER,
593                     "Failed to get VEHICLE_IN_USE value, assume vehicle is in use", e);
594             return true;
595         }
596     }
597 
598     /**
599      * Returns whether {@code VEHICLE_IN_USE} is supported and getting it returns a valid value.
600      */
isVehicleInUseSupported()601     public boolean isVehicleInUseSupported() {
602         try {
603             HalPropValue value = mHal.get(VEHICLE_IN_USE);
604             if (value.getStatus() != VehiclePropertyStatus.AVAILABLE) {
605                 Slogf.w(CarLog.TAG_POWER,
606                         "VEHICLE_IN_USE is supported in config but getting it returns a property "
607                         + "value: " + value + " which does not contain AVAILABLE status");
608                 return false;
609             }
610             return true;
611         } catch (ServiceSpecificException | IllegalArgumentException e) {
612             Slogf.w(CarLog.TAG_POWER, "VEHICLE_IN_USE is not supported", e);
613             return false;
614         }
615     }
616 
617     /**
618      * Returns whether {@code SHUTDOWN_REQUEST} is supported
619      */
isShutdownRequestSupported()620     public boolean isShutdownRequestSupported() {
621         return mHal.getPropConfig(SHUTDOWN_REQUEST) != null;
622     }
623 
624     /**
625      * Gets the head unit's bootup reason.
626      *
627      * This reason is only set once during bootup and will not change if, say user enters the
628      * vehicle after the vehicle was booted up for remote access.
629      */
getVehicleApBootupReason()630     public @BootupReason int getVehicleApBootupReason() {
631         try {
632             HalPropValue value = mHal.get(AP_POWER_BOOTUP_REASON);
633             if (value.getStatus() != VehiclePropertyStatus.AVAILABLE) {
634                 Slogf.w(CarLog.TAG_POWER, "AP_POWER_BOOTUP_REASON is not available");
635                 return BOOTUP_REASON_UNKNOWN;
636             }
637             if (value.getInt32ValuesSize() < 1) {
638                 Slogf.w(CarLog.TAG_POWER, "Invalid AP_POWER_BOOTUP_REASON, no value");
639                 return BOOTUP_REASON_UNKNOWN;
640             }
641             switch (value.getInt32Value(0)) {
642                 case VehicleApPowerBootupReason.USER_POWER_ON:
643                     return BOOTUP_REASON_USER_POWER_ON;
644                 case VehicleApPowerBootupReason.SYSTEM_USER_DETECTION:
645                     return BOOTUP_REASON_SYSTEM_USER_DETECTION;
646                 case VehicleApPowerBootupReason.SYSTEM_REMOTE_ACCESS:
647                     return BOOTUP_REASON_SYSTEM_REMOTE_ACCESS;
648                 case VehicleApPowerBootupReason.SYSTEM_ENTER_GARAGE_MODE:
649                     return BOOTUP_REASON_SYSTEM_ENTER_GARAGE_MODE;
650                 default:
651                     return BOOTUP_REASON_UNKNOWN;
652             }
653         } catch (ServiceSpecificException | IllegalArgumentException e) {
654             Slogf.w(CarLog.TAG_POWER, "Failed to get AP_POWER_BOOTUP_REASON value", e);
655         }
656         return BOOTUP_REASON_UNKNOWN;
657     }
658 
isConfigFlagSet(int flag)659     private boolean isConfigFlagSet(int flag) {
660         HalPropConfig config;
661         synchronized (mLock) {
662             config = mProperties.get(VehicleProperty.AP_POWER_STATE_REQ);
663         }
664         if (config == null) {
665             return false;
666         }
667         int[] configArray = config.getConfigArray();
668         if (configArray.length < 1) {
669             return false;
670         }
671         return (configArray[0] & flag) != 0;
672     }
673 
isDeepSleepAllowed()674     public boolean isDeepSleepAllowed() {
675         return isConfigFlagSet(VehicleApPowerStateConfigFlag.ENABLE_DEEP_SLEEP_FLAG);
676     }
677 
isHibernationAllowed()678     public boolean isHibernationAllowed() {
679         return isConfigFlagSet(VehicleApPowerStateConfigFlag.ENABLE_HIBERNATION_FLAG);
680     }
681 
isTimedWakeupAllowed()682     public boolean isTimedWakeupAllowed() {
683         return isConfigFlagSet(VehicleApPowerStateConfigFlag.CONFIG_SUPPORT_TIMER_POWER_ON_FLAG);
684     }
685 
686     @Override
init()687     public void init() {
688         synchronized (mLock) {
689             for (int i = 0; i < mProperties.size(); i++) {
690                 int propId = mProperties.valueAt(i).getPropId();
691                 if (mProperties.contains(propId)
692                         && SUPPORTED_PROPERTIES.get(propId).needSubscription) {
693                     mHal.subscribeProperty(this, propId);
694                 }
695             }
696             HalPropConfig brightnessProperty = mProperties.get(PER_DISPLAY_BRIGHTNESS);
697             mPerDisplayBrightnessSupported = brightnessProperty != null;
698             if (brightnessProperty == null) {
699                 brightnessProperty = mProperties.get(DISPLAY_BRIGHTNESS);
700             }
701             if (brightnessProperty != null) {
702                 HalAreaConfig[] areaConfigs = brightnessProperty.getAreaConfigs();
703                 mMaxDisplayBrightness = areaConfigs.length > 0
704                         ? areaConfigs[0].getMaxInt32Value() : 0;
705                 if (mMaxDisplayBrightness <= 0) {
706                     Slogf.w(CarLog.TAG_POWER, "Max display brightness from vehicle HAL is invalid:"
707                             + mMaxDisplayBrightness);
708                     mMaxDisplayBrightness = 1;
709                 }
710 
711                 if (mFeatureFlags.perDisplayMaxBrightness()) {
712                     getMaxPerDisplayBrightnessFromVhalLocked();
713                 }
714             }
715         }
716     }
717 
718     @GuardedBy("mLock")
getMaxPerDisplayBrightnessFromVhalLocked()719     private void getMaxPerDisplayBrightnessFromVhalLocked() {
720         if (!mPerDisplayBrightnessSupported
721                 || !mProperties.contains(PER_DISPLAY_MAX_BRIGHTNESS)) {
722             return;
723         }
724 
725         try {
726             HalPropValue value = mHal.get(PER_DISPLAY_MAX_BRIGHTNESS);
727             for (int i = 0; i + 1 < value.getInt32ValuesSize(); i += 2) {
728                 int displayPort = value.getInt32Value(i);
729                 int maxDisplayBrightness = value.getInt32Value(i + 1);
730                 if (maxDisplayBrightness <= 0) {
731                     Slogf.w(CarLog.TAG_POWER,
732                             "Max display brightness from vehicle HAL for display port: %d is "
733                             + "invalid:  %d", displayPort, maxDisplayBrightness);
734                     maxDisplayBrightness = 1;
735                 }
736                 mMaxPerDisplayBrightness.put(displayPort, maxDisplayBrightness);
737             }
738         } catch (ServiceSpecificException e) {
739             Slogf.e(CarLog.TAG_POWER, "Cannot get PER_DISPLAY_MAX_BRIGHTNESS", e);
740         }
741 
742     }
743 
744     @Override
release()745     public void release() {
746         synchronized (mLock) {
747             for (int i = 0; i < mProperties.size(); i++) {
748                 int propId = mProperties.valueAt(i).getPropId();
749                 if (SUPPORTED_PROPERTIES.get(propId).needSubscription) {
750                     mHal.unsubscribePropertySafe(this, propId);
751                 }
752             }
753             mProperties.clear();
754         }
755     }
756 
757     @Override
getAllSupportedProperties()758     public int[] getAllSupportedProperties() {
759         int[] propertyIds = new int[SUPPORTED_PROPERTIES.size()];
760         for (int i = 0; i < SUPPORTED_PROPERTIES.size(); i++) {
761             propertyIds[i] = SUPPORTED_PROPERTIES.keyAt(i);
762         }
763         return propertyIds;
764     }
765 
766     @Override
takeProperties(Collection<HalPropConfig> properties)767     public void takeProperties(Collection<HalPropConfig> properties) {
768         if (properties.isEmpty()) {
769             return;
770         }
771         synchronized (mLock) {
772             for (HalPropConfig config : properties) {
773                 mProperties.put(config.getPropId(), config);
774             }
775         }
776     }
777 
778     @Override
onHalEvents(List<HalPropValue> values)779     public void onHalEvents(List<HalPropValue> values) {
780         PowerEventListener listener;
781         synchronized (mLock) {
782             if (mListener == null) {
783                 if (mQueuedEvents == null) {
784                     mQueuedEvents = new ArrayList<>(values.size());
785                 }
786                 mQueuedEvents.addAll(values);
787                 return;
788             }
789             listener = mListener;
790         }
791         dispatchEvents(values, listener);
792     }
793 
dispatchEvents(List<HalPropValue> values, PowerEventListener listener)794     private void dispatchEvents(List<HalPropValue> values, PowerEventListener listener) {
795         for (int i = 0; i < values.size(); i++) {
796             HalPropValue v = values.get(i);
797             switch (v.getPropId()) {
798                 case AP_POWER_STATE_REPORT:
799                     // Ignore this property event. It was generated inside of CarService.
800                     break;
801                 case AP_POWER_STATE_REQ:
802                     int state;
803                     int param;
804                     try {
805                         state = v.getInt32Value(VehicleApPowerStateReqIndex.STATE);
806                         param = v.getInt32Value(VehicleApPowerStateReqIndex.ADDITIONAL);
807                     } catch (IndexOutOfBoundsException e) {
808                         Slogf.e(CarLog.TAG_POWER, "Received invalid event, ignore, int32Values: "
809                                 + v.dumpInt32Values(), e);
810                         break;
811                     }
812                     Slogf.i(CarLog.TAG_POWER, "Received AP_POWER_STATE_REQ="
813                             + powerStateReqName(state) + " param=" + param);
814                     listener.onApPowerStateChange(new PowerState(state, param));
815                     break;
816                 case DISPLAY_BRIGHTNESS:
817                 {
818                     int maxBrightness;
819                     synchronized (mLock) {
820                         if (mPerDisplayBrightnessSupported) {
821                             Slogf.w(CarLog.TAG_POWER, "Received DISPLAY_BRIGHTNESS "
822                                     + "while PER_DISPLAY_BRIGHTNESS is supported, ignore");
823                             return;
824                         }
825                         maxBrightness = mMaxDisplayBrightness;
826                     }
827                     int brightness;
828                     try {
829                         brightness = v.getInt32Value(0) * MAX_BRIGHTNESS / maxBrightness;
830                     } catch (IndexOutOfBoundsException e) {
831                         Slogf.e(CarLog.TAG_POWER, "Received invalid event, ignore, int32Values: "
832                                 + v.dumpInt32Values(), e);
833                         break;
834                     }
835                     if (brightness < 0) {
836                         Slogf.e(CarLog.TAG_POWER, "invalid brightness: " + brightness
837                                 + ", set to 0");
838                         brightness = 0;
839                     } else if (brightness > MAX_BRIGHTNESS) {
840                         Slogf.e(CarLog.TAG_POWER, "invalid brightness: " + brightness + ", set to "
841                                 + MAX_BRIGHTNESS);
842                         brightness = MAX_BRIGHTNESS;
843                     }
844                     Slogf.i(CarLog.TAG_POWER, "Received DISPLAY_BRIGHTNESS=" + brightness);
845                     listener.onDisplayBrightnessChange(brightness);
846                     break;
847                 }
848                 case PER_DISPLAY_BRIGHTNESS:
849                 {
850                     int displayPort;
851                     int brightness;
852                     try {
853                         displayPort = v.getInt32Value(0);
854                         brightness = v.getInt32Value(1);
855                     } catch (IndexOutOfBoundsException e) {
856                         Slogf.e(CarLog.TAG_POWER, "Received invalid event, ignore, int32Values: "
857                                 + v.dumpInt32Values(), e);
858                         break;
859                     }
860                     int maxBrightness;
861                     synchronized (mLock) {
862                         if (!mFeatureFlags.perDisplayMaxBrightness()
863                                 || mMaxPerDisplayBrightness.size() == 0) {
864                             maxBrightness = mMaxDisplayBrightness;
865                         } else {
866                             maxBrightness = mMaxPerDisplayBrightness.get(displayPort,
867                                     /* valueIfKeyNotFound= */ 1);
868                         }
869                     }
870                     brightness = brightness * MAX_BRIGHTNESS / maxBrightness;
871                     brightness = adjustBrightness(brightness, /* minBrightness= */ 0,
872                             MAX_BRIGHTNESS);
873                     Slogf.i(CarLog.TAG_POWER, "Received PER_DISPLAY_BRIGHTNESS=" + brightness
874                             + ", displayPort=" + displayPort);
875                     int displayId = getDisplayId(displayPort);
876                     listener.onDisplayBrightnessChange(displayId, brightness);
877                     break;
878                 }
879                 default:
880                     Slogf.w(CarLog.TAG_POWER, "Received event with invalid property id: %d",
881                             v.getPropId());
882                     break;
883             }
884         }
885     }
886 
getDisplayId(int displayPort)887     private int getDisplayId(int displayPort) {
888         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
889         int displayId = Display.DEFAULT_DISPLAY;
890         for (Display display : displayManager.getDisplays()) {
891             if (displayPort == DisplayHelper.getPhysicalPort(display)) {
892                 displayId = display.getDisplayId();
893                 break;
894             }
895         }
896         return displayId;
897     }
898 
getDisplayPort(int displayId)899     private int getDisplayPort(int displayId) {
900         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
901         Display display = displayManager.getDisplay(displayId);
902         if (display != null) {
903             int displayPort = DisplayHelper.getPhysicalPort(display);
904             if (displayPort != DisplayHelper.INVALID_PORT) {
905                 return displayPort;
906             }
907         }
908         Slogf.w(CarLog.TAG_POWER, "cannot get display port from displayId = %d",
909                 displayId);
910         return DisplayHelper.INVALID_PORT;
911     }
912 
adjustBrightness(int brightness, int minBrightness, int maxBrightness)913     private int adjustBrightness(int brightness, int minBrightness, int maxBrightness) {
914         if (brightness < minBrightness) {
915             Slogf.w(CarLog.TAG_POWER, "invalid brightness: %d, brightness is set to %d", brightness,
916                     minBrightness);
917             brightness = minBrightness;
918         } else if (brightness > maxBrightness) {
919             Slogf.w(CarLog.TAG_POWER, "invalid brightness: %d, brightness is set to %d", brightness,
920                     maxBrightness);
921             brightness = maxBrightness;
922         }
923         return brightness;
924     }
925 
926     @Override
927     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(PrintWriter writer)928     public void dump(PrintWriter writer) {
929         writer.println("*Power HAL*");
930         writer.printf("isPowerStateSupported:%b, isDeepSleepAllowed:%b, isHibernationAllowed:%b\n",
931                 isPowerStateSupported(), isDeepSleepAllowed(), isHibernationAllowed());
932 
933     }
934 }
935