1 /*
2  * Copyright (C) 2022 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 com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEBUGGING_CODE;
20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
21 
22 import android.annotation.IntDef;
23 import android.car.CarOccupantZoneManager;
24 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
25 import android.car.ICarOccupantZoneCallback;
26 import android.car.builtin.os.HandlerHelper;
27 import android.car.builtin.util.Slogf;
28 import android.car.builtin.view.DisplayHelper;
29 import android.car.settings.CarSettings;
30 import android.content.Context;
31 import android.database.ContentObserver;
32 import android.hardware.display.DisplayManager;
33 import android.net.Uri;
34 import android.os.Handler;
35 import android.os.Looper;
36 import android.os.Message;
37 import android.os.SystemClock;
38 import android.provider.Settings;
39 import android.text.TextUtils;
40 import android.util.SparseArray;
41 import android.util.SparseIntArray;
42 import android.util.proto.ProtoOutputStream;
43 import android.view.Display;
44 
45 import com.android.car.CarLocalServices;
46 import com.android.car.CarLog;
47 import com.android.car.CarOccupantZoneService;
48 import com.android.car.R;
49 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
50 import com.android.car.internal.util.IndentingPrintWriter;
51 import com.android.car.power.CarPowerDumpProto.ScreenOffHandlerProto;
52 import com.android.car.power.CarPowerDumpProto.ScreenOffHandlerProto.DisplayPowerInfoProto;
53 import com.android.car.systeminterface.SystemInterface;
54 import com.android.internal.annotations.GuardedBy;
55 import com.android.internal.annotations.VisibleForTesting;
56 
57 import java.lang.annotation.ElementType;
58 import java.lang.annotation.Retention;
59 import java.lang.annotation.RetentionPolicy;
60 import java.lang.annotation.Target;
61 import java.lang.ref.WeakReference;
62 import java.time.Duration;
63 import java.util.List;
64 
65 class ScreenOffHandler {
66     private static final String TAG = CarLog.tagFor(ScreenOffHandler.class);
67 
68     // Minimum and maximum timeout in milliseconds when there is no user.
69     private static final int MIN_NO_USER_SCREEN_OFF_TIMEOUT_MS = 15 * 1000; // 15 seconds
70     private static final int MAX_NO_USER_SCREEN_OFF_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
71 
72     private static final String DISPLAY_POWER_MODE_SETTING =
73             CarSettings.Global.DISPLAY_POWER_MODE;
74     private static final Uri DISPLAY_POWER_MODE_URI =
75             Settings.Global.getUriFor(DISPLAY_POWER_MODE_SETTING);
76 
77     // Constants for display power mode
78     /**
79      * Display power mode is unknown. After initialization, needs to be
80      * replaced with other mode as below.
81      */
82     @VisibleForTesting
83     static final int DISPLAY_POWER_MODE_NONE = -1;
84     /**
85      * With this mode, screen keeps off.
86      * And user cannot manually turn on the display.
87      */
88     @VisibleForTesting
89     static final int DISPLAY_POWER_MODE_OFF = 0;
90     /**
91      * With this mode, two kinds of behavior is applied.
92      * When user logged out, screen off timeout involves.
93      * When user logged in, screen keeps on.
94      * And user can manually turn off the display.
95      */
96     @VisibleForTesting
97     static final int DISPLAY_POWER_MODE_ON = 1;
98     /**
99      * With this mode, screen keeps on.
100      * And user can manually turn off the display.
101      */
102     @VisibleForTesting
103     static final int DISPLAY_POWER_MODE_ALWAYS_ON = 2;
104     @Retention(RetentionPolicy.SOURCE)
105     @IntDef(prefix = "DISPLAY_POWER_MODE_", value = {
106             DISPLAY_POWER_MODE_NONE,
107             DISPLAY_POWER_MODE_OFF,
108             DISPLAY_POWER_MODE_ON,
109             DISPLAY_POWER_MODE_ALWAYS_ON,
110     })
111     @Target({ElementType.TYPE_USE})
112     private @interface DisplayPowerMode {}
113 
114     private final Context mContext;
115     private final SystemInterface mSystemInterface;
116     private final CarOccupantZoneService mOccupantZoneService;
117     private final SettingsObserver mSettingsObserver;
118     private final EventHandler mEventHandler;
119     private final ClockInterface mClock;
120 
121     private final boolean mIsAutoPowerSaving;
122     private final int mNoUserScreenOffTimeoutMs;
123     private final Object mLock = new Object();
124     @GuardedBy("mLock")
125     private final SparseArray<DisplayPowerInfo> mDisplayPowerInfos = new SparseArray<>();
126 
127     @GuardedBy("mLock")
128     private boolean mBootCompleted;
129 
ScreenOffHandler(Context context, SystemInterface systemInterface, Looper looper)130     ScreenOffHandler(Context context, SystemInterface systemInterface, Looper looper) {
131         this(context, systemInterface, looper, SystemClock::uptimeMillis);
132     }
133 
134     @VisibleForTesting
ScreenOffHandler(Context context, SystemInterface systemInterface, Looper looper, ClockInterface clock)135     ScreenOffHandler(Context context, SystemInterface systemInterface, Looper looper,
136             ClockInterface clock) {
137         mContext = context;
138         mEventHandler = new EventHandler(looper, this);
139         mSystemInterface = systemInterface;
140         mClock = clock;
141         mSettingsObserver = new SettingsObserver(mEventHandler);
142         mOccupantZoneService = CarLocalServices.getService(CarOccupantZoneService.class);
143         mIsAutoPowerSaving = mContext.getResources().getBoolean(
144                 R.bool.config_enablePassengerDisplayPowerSaving);
145         mNoUserScreenOffTimeoutMs = getNoUserScreenOffTimeout();
146     }
147 
init()148     void init() {
149         if (!mIsAutoPowerSaving) {
150             return;
151         }
152         initializeDisplayPowerInfos();
153         initializeDefaultSettings();
154         mOccupantZoneService.registerCallback(mOccupantZoneCallback);
155         mContext.getContentResolver().registerContentObserver(
156                 DISPLAY_POWER_MODE_URI, /* notifyForDescendants= */ false, mSettingsObserver);
157         mSystemInterface.scheduleActionForBootCompleted(() -> {
158             synchronized (mLock) {
159                 mBootCompleted = true;
160                 updateSettingsLocked();
161                 long eventTime = mClock.uptimeMillis();
162                 for (int i = 0; i < mDisplayPowerInfos.size(); i++) {
163                     int displayId = mDisplayPowerInfos.keyAt(i);
164                     updateUserActivityLocked(displayId, eventTime);
165                 }
166             }
167         }, Duration.ZERO);
168     }
169 
handleDisplayStateChange(int displayId, boolean on)170     void handleDisplayStateChange(int displayId, boolean on) {
171         if (!mIsAutoPowerSaving) {
172             return;
173         }
174         if (on) {
175             synchronized (mLock) {
176                 updateUserActivityLocked(displayId, mClock.uptimeMillis());
177             }
178         }
179     }
180 
updateUserActivity(int displayId, long eventTime)181     void updateUserActivity(int displayId, long eventTime) {
182         synchronized (mLock) {
183             updateUserActivityLocked(displayId, eventTime);
184         }
185     }
186 
187     @GuardedBy("mLock")
updateUserActivityLocked(int displayId, long eventTime)188     private void updateUserActivityLocked(int displayId, long eventTime) {
189         if (!mIsAutoPowerSaving) {
190             return;
191         }
192         if (eventTime > mClock.uptimeMillis()) {
193             throw new IllegalArgumentException("event time must not be in the future");
194         }
195         DisplayPowerInfo info = mDisplayPowerInfos.get(displayId);
196         if (info == null) {
197             Slogf.w(TAG, "Display(id: %d) is not available", displayId);
198             return;
199         }
200         info.setLastUserActivityTime(eventTime);
201         updateDisplayPowerStateLocked(info);
202     }
203 
204     @GuardedBy("mLock")
handleSettingsChangedLocked()205     private void handleSettingsChangedLocked() {
206         updateSettingsLocked();
207         updateAllDisplayPowerStateLocked();
208     }
209 
canTurnOnDisplay(int displayId)210     boolean canTurnOnDisplay(int displayId) {
211         if (!mIsAutoPowerSaving) {
212             return true;
213         }
214         synchronized (mLock) {
215             return canTurnOnDisplayLocked(displayId);
216         }
217     }
218 
219     @GuardedBy("mLock")
canTurnOnDisplayLocked(int displayId)220     private boolean canTurnOnDisplayLocked(int displayId) {
221         DisplayPowerInfo info = mDisplayPowerInfos.get(displayId);
222         if (info == null) {
223             Slogf.w(TAG, "display(%d) power info is not ready yet.", displayId);
224             return false;
225         }
226         if (info.getMode() == DISPLAY_POWER_MODE_OFF) {
227             return false;
228         }
229         return true;
230     }
231 
initializeDefaultSettings()232     private void initializeDefaultSettings() {
233         String setting = Settings.Global.getString(mContext.getContentResolver(),
234                 DISPLAY_POWER_MODE_SETTING);
235         if (!TextUtils.isEmpty(setting)) {
236             Slogf.d(TAG, "stored value of %s: %s", DISPLAY_POWER_MODE_SETTING, setting);
237             return;
238         }
239         // At first boot, initialize default setting value
240         StringBuilder sb = new StringBuilder();
241         synchronized (mLock) {
242             for (int i = 0; i < mDisplayPowerInfos.size(); i++) {
243                 int displayId = mDisplayPowerInfos.keyAt(i);
244                 DisplayPowerInfo info = mDisplayPowerInfos.valueAt(i);
245                 if (info == null) {
246                     continue;
247                 }
248                 int displayPort = getDisplayPort(displayId);
249                 if (displayPort == DisplayHelper.INVALID_PORT) {
250                     continue;
251                 }
252                 if (i > 0) {
253                     sb.append(',');
254                 }
255                 sb.append(displayPort);
256                 sb.append(':');
257                 if (info.isDriverDisplay()) {
258                     // for driver display
259                     info.setMode(DISPLAY_POWER_MODE_ALWAYS_ON);
260                     sb.append(DISPLAY_POWER_MODE_ALWAYS_ON);
261                 } else {
262                     // TODO(b/274050716): Restore passenger displays to ON.
263                     // for passenger display
264                     info.setMode(DISPLAY_POWER_MODE_ALWAYS_ON);
265                     sb.append(DISPLAY_POWER_MODE_ALWAYS_ON);
266                 }
267             }
268         }
269         Settings.Global.putString(
270                 mContext.getContentResolver(), DISPLAY_POWER_MODE_SETTING, sb.toString());
271     }
272 
273     @GuardedBy("mLock")
updateSettingsLocked()274     private void updateSettingsLocked() {
275         String setting = Settings.Global.getString(mContext.getContentResolver(),
276                 DISPLAY_POWER_MODE_SETTING);
277         SparseIntArray mapping = parseModeAssignmentSettingValue(setting);
278         if (mapping == null) {
279             Slogf.d(TAG, "Failed to parse [%s]", setting);
280             initializeDefaultSettings();
281             return;
282         }
283         for (int i = 0; i < mapping.size(); i++) {
284             int displayId = mapping.keyAt(i);
285             @DisplayPowerMode int mode = mapping.valueAt(i);
286             DisplayPowerInfo info = mDisplayPowerInfos.get(displayId);
287             if (info != null) {
288                 // Check if the mode in the corresponding display power info is the same as current
289                 // setting value.
290                 if (info.getMode() != mode) {
291                     info.setMode(mode);
292                     boolean on = mode != DISPLAY_POWER_MODE_OFF;
293                     // Update last user activity time due to mode change by driver
294                     info.setLastUserActivityTime(mClock.uptimeMillis());
295                     mEventHandler.post(() -> {
296                         handleSetDisplayState(displayId, on);
297                     });
298                 }
299             } else {
300                 Slogf.d(TAG, "No matching DisplayPowerInfo(display=%d)", displayId);
301             }
302         }
303     }
304 
305     @GuardedBy("mLock")
updateAllDisplayPowerStateLocked()306     private void updateAllDisplayPowerStateLocked() {
307         for (int i = 0; i < mDisplayPowerInfos.size(); i++) {
308             updateDisplayPowerStateLocked(mDisplayPowerInfos.valueAt(i));
309         }
310     }
311 
312     @GuardedBy("mLock")
updateDisplayPowerStateLocked(DisplayPowerInfo info)313     private void updateDisplayPowerStateLocked(DisplayPowerInfo info) {
314         int displayId = info.getDisplayId();
315         mEventHandler.cancelUserActivityTimeout(displayId);
316 
317         if (!mBootCompleted
318                 || info == null
319                 || info.isDriverDisplay()
320                 || info.getUserId() != CarOccupantZoneManager.INVALID_USER_ID
321                 || info.getMode() == DISPLAY_POWER_MODE_ALWAYS_ON
322                 || !mSystemInterface.isDisplayEnabled(displayId)) {
323             return;
324         }
325 
326         checkUserActivityTimeout(info);
327     }
328 
checkUserActivityTimeout(DisplayPowerInfo info)329     private void checkUserActivityTimeout(DisplayPowerInfo info) {
330         long now = mClock.uptimeMillis();
331         long nextTimeout = info.getLastUserActivityTime() + mNoUserScreenOffTimeoutMs;
332         if (now < nextTimeout) {
333             mEventHandler.handleUserActivityTimeout(info.getDisplayId(), nextTimeout);
334         }
335     }
336 
handleSetDisplayState(int displayId, boolean on)337     private void handleSetDisplayState(int displayId, boolean on) {
338         if (on != mSystemInterface.isDisplayEnabled(displayId)) {
339             mSystemInterface.setDisplayState(displayId, on);
340         }
341     }
342 
343     private final ICarOccupantZoneCallback mOccupantZoneCallback =
344             new ICarOccupantZoneCallback.Stub() {
345                 @Override
346                 public void onOccupantZoneConfigChanged(int flags) {
347                     if ((flags & (CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY
348                             | CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER)) != 0) {
349                         synchronized (mLock) {
350                             handleOccupantZoneConfigChangeLocked(flags);
351                             updateAllDisplayPowerStateLocked();
352                         }
353                     }
354                 }
355             };
356 
357     private final class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler)358         SettingsObserver(Handler handler) {
359             super(handler);
360         }
361 
362         @Override
onChange(boolean selfChange, Uri uri)363         public void onChange(boolean selfChange, Uri uri) {
364             synchronized (mLock) {
365                 handleSettingsChangedLocked();
366             }
367         }
368     }
369 
370     /**
371      * Updates display power info if user occupancy is changed or if display is added or removed.
372      */
373     @GuardedBy("mLock")
handleOccupantZoneConfigChangeLocked(int flags)374     private void handleOccupantZoneConfigChangeLocked(int flags) {
375         List<OccupantZoneInfo> occupantZoneInfos = mOccupantZoneService.getAllOccupantZones();
376         for (int i = 0; i < occupantZoneInfos.size(); i++) {
377             OccupantZoneInfo zoneInfo = occupantZoneInfos.get(i);
378             int zoneId = zoneInfo.zoneId;
379             int displayId = getMainTypeDisplayId(zoneId);
380             if (displayId == Display.INVALID_DISPLAY) {
381                 Slogf.w(TAG, "No main display associated with occupant zone(id: %d)", zoneId);
382                 continue;
383             }
384             DisplayPowerInfo info = mDisplayPowerInfos.get(displayId);
385             if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_USER) != 0
386                     && info != null) {
387                 int userId = mOccupantZoneService.getUserForOccupant(zoneId);
388                 if (info.getUserId() != userId) {
389                     if (userId == CarOccupantZoneManager.INVALID_USER_ID) {
390                         // User logged out
391                         info.setUserId(CarOccupantZoneManager.INVALID_USER_ID);
392                         info.setLastUserActivityTime(mClock.uptimeMillis());
393                     } else {
394                         // User logged in
395                         info.setUserId(userId);
396                     }
397                 }
398             }
399             if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0
400                     && info == null) {
401                 info = createDisplayPowerInfoLocked(displayId);
402                 if (info != null) {
403                     // Display added
404                     int userId = mOccupantZoneService.getUserForOccupant(zoneId);
405                     info.setUserId(userId);
406                 }
407             }
408         }
409         if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) != 0) {
410             for (int i = 0; i < mDisplayPowerInfos.size(); i++) {
411                 DisplayPowerInfo info = mDisplayPowerInfos.valueAt(i);
412                 if (info != null
413                         && mOccupantZoneService.getDisplayType(info.getDisplayId())
414                                 == CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN) {
415                     // Display removed
416                     mDisplayPowerInfos.removeAt(i);
417                 }
418             }
419         }
420     }
421 
initializeDisplayPowerInfos()422     private void initializeDisplayPowerInfos() {
423         List<OccupantZoneInfo> occupantZoneInfos = mOccupantZoneService.getAllOccupantZones();
424         synchronized (mLock) {
425             for (int i = 0; i < occupantZoneInfos.size(); i++) {
426                 OccupantZoneInfo zoneInfo = occupantZoneInfos.get(i);
427                 int zoneId = zoneInfo.zoneId;
428                 int displayId = getMainTypeDisplayId(zoneId);
429                 if (displayId == Display.INVALID_DISPLAY) {
430                     continue;
431                 }
432                 DisplayPowerInfo info = createDisplayPowerInfoLocked(displayId);
433                 int userId = mOccupantZoneService.getUserForOccupant(zoneId);
434                 info.setUserId(userId);
435                 if (zoneInfo.occupantType == CarOccupantZoneManager.OCCUPANT_TYPE_DRIVER) {
436                     info.setDriverDisplay(true);
437                 }
438             }
439         }
440     }
441 
442     @GuardedBy("mLock")
createDisplayPowerInfoLocked(int displayId)443     private DisplayPowerInfo createDisplayPowerInfoLocked(int displayId) {
444         DisplayPowerInfo info = new DisplayPowerInfo(displayId);
445         mDisplayPowerInfos.put(displayId, info);
446         return info;
447     }
448 
getMainTypeDisplayId(int zoneId)449     private int getMainTypeDisplayId(int zoneId) {
450         return mOccupantZoneService.getDisplayForOccupant(zoneId,
451                 CarOccupantZoneManager.DISPLAY_TYPE_MAIN);
452     }
453 
454     // value format: comma-separated displayPort:mode
455     @VisibleForTesting
parseModeAssignmentSettingValue(String value)456     SparseIntArray parseModeAssignmentSettingValue(String value) {
457         SparseIntArray mapping = new SparseIntArray();
458         try {
459             String[] entries = value.split(",");
460             for (int i = 0; i < entries.length; i++) {
461                 String entry = entries[i];
462                 String[] pair = entry.split(":");
463                 if (pair.length != 2) {
464                     return null;
465                 }
466                 int displayPort = Integer.parseInt(pair[0], /* radix= */ 10);
467                 int displayId = getDisplayId(displayPort);
468                 if (displayId == Display.INVALID_DISPLAY) {
469                     Slogf.w(TAG, "Invalid display port: %d", displayPort);
470                     return null;
471                 }
472                 @DisplayPowerMode int mode = Integer.parseInt(pair[1], /* radix= */ 10);
473                 if (mapping.indexOfKey(displayId) >= 0) {
474                     Slogf.w(TAG, "Multiple use of display id: %d", displayId);
475                     return null;
476                 }
477                 if (mode < DISPLAY_POWER_MODE_OFF || mode > DISPLAY_POWER_MODE_ALWAYS_ON) {
478                     Slogf.w(TAG, "Mode is out of range: %d(%s)",
479                             mode, DisplayPowerInfo.displayPowerModeToString(mode));
480                     return null;
481                 }
482                 mapping.append(displayId, mode);
483             }
484         } catch (Exception e) {
485             Slogf.w(TAG, e, "Setting %s has invalid value: ", value);
486             // Parsing error, ignore all.
487             return null;
488         }
489         return mapping;
490     }
491 
getDisplayId(int displayPort)492     private int getDisplayId(int displayPort) {
493         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
494         for (Display display : displayManager.getDisplays()) {
495             if (DisplayHelper.getPhysicalPort(display) == displayPort) {
496                 return display.getDisplayId();
497             }
498         }
499         return Display.INVALID_DISPLAY;
500     }
501 
getDisplayPort(int displayId)502     private int getDisplayPort(int displayId) {
503         DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
504         Display display = displayManager.getDisplay(displayId);
505         if (display != null) {
506             return DisplayHelper.getPhysicalPort(display);
507         }
508         return DisplayHelper.INVALID_PORT;
509     }
510 
511     private static final class EventHandler extends Handler {
512         private static final int MSG_USER_ACTIVITY_TIMEOUT = 0;
513 
514         private final WeakReference<ScreenOffHandler> mScreenOffHandler;
515 
EventHandler(Looper looper, ScreenOffHandler screenOffHandler)516         private EventHandler(Looper looper, ScreenOffHandler screenOffHandler) {
517             super(looper);
518             mScreenOffHandler = new WeakReference<ScreenOffHandler>(screenOffHandler);
519         }
520 
handleUserActivityTimeout(int displayId, long timeMs)521         private void handleUserActivityTimeout(int displayId, long timeMs) {
522             Message msg = obtainMessage(MSG_USER_ACTIVITY_TIMEOUT, displayId);
523             msg.setAsynchronous(true);
524             sendMessageAtTime(msg, timeMs);
525         }
526 
cancelUserActivityTimeout(int displayId)527         private void cancelUserActivityTimeout(int displayId) {
528             HandlerHelper.removeEqualMessages(this, MSG_USER_ACTIVITY_TIMEOUT, displayId);
529         }
530 
531         @Override
handleMessage(Message msg)532         public void handleMessage(Message msg) {
533             ScreenOffHandler screenOffHandler = mScreenOffHandler.get();
534             if (screenOffHandler == null) {
535                 return;
536             }
537             switch (msg.what) {
538                 case MSG_USER_ACTIVITY_TIMEOUT:
539                     screenOffHandler.handleSetDisplayState(/* displayId= */ (Integer) msg.obj,
540                             /* on= */ false);
541                     break;
542                 default:
543                     Slogf.w(TAG, "Invalid message type: %d", msg.what);
544                     break;
545             }
546         }
547     }
548 
getNoUserScreenOffTimeout()549     private int getNoUserScreenOffTimeout() {
550         int timeout = mContext.getResources().getInteger(R.integer.config_noUserScreenOffTimeout);
551         if (timeout < MIN_NO_USER_SCREEN_OFF_TIMEOUT_MS) {
552             Slogf.w(TAG, "config_noUserScreenOffTimeout(%dms) is shorter than %dms and is reset to "
553                     + "%dms", timeout, MIN_NO_USER_SCREEN_OFF_TIMEOUT_MS,
554                     MIN_NO_USER_SCREEN_OFF_TIMEOUT_MS);
555             timeout = MIN_NO_USER_SCREEN_OFF_TIMEOUT_MS;
556         } else if (timeout > MAX_NO_USER_SCREEN_OFF_TIMEOUT_MS) {
557             Slogf.w(TAG, "config_noUserScreenOffTimeout(%dms) is longer than %dms and is reset to "
558                     + "%dms", timeout, MAX_NO_USER_SCREEN_OFF_TIMEOUT_MS,
559                     MAX_NO_USER_SCREEN_OFF_TIMEOUT_MS);
560             timeout = MAX_NO_USER_SCREEN_OFF_TIMEOUT_MS;
561         }
562         return timeout;
563     }
564 
565     private static final class DisplayPowerInfo {
566         private final int mDisplayId;
567 
568         private int mUserId;
569         private @DisplayPowerMode int mMode;
570         private boolean mIsDriverDisplay;
571         private long mLastUserActivityTime;
572 
DisplayPowerInfo(int displayId)573         private DisplayPowerInfo(int displayId) {
574             mDisplayId = displayId;
575             mUserId = CarOccupantZoneManager.INVALID_USER_ID;
576             mMode = DISPLAY_POWER_MODE_NONE;
577             mIsDriverDisplay = false;
578             mLastUserActivityTime = -1;
579         }
580 
getDisplayId()581         private int getDisplayId() {
582             return mDisplayId;
583         }
584 
setUserId(int userId)585         private void setUserId(int userId) {
586             mUserId = userId;
587         }
588 
getUserId()589         private int getUserId() {
590             return mUserId;
591         }
592 
setMode(@isplayPowerMode int mode)593         private void setMode(@DisplayPowerMode int mode) {
594             mMode = mode;
595         }
596 
getMode()597         private @DisplayPowerMode int getMode() {
598             return mMode;
599         }
600 
setDriverDisplay(boolean isDriver)601         private void setDriverDisplay(boolean isDriver) {
602             mIsDriverDisplay = isDriver;
603         }
604 
isDriverDisplay()605         private boolean isDriverDisplay() {
606             return mIsDriverDisplay;
607         }
608 
getLastUserActivityTime()609         private long getLastUserActivityTime() {
610             return mLastUserActivityTime;
611         }
612 
setLastUserActivityTime(long lastUserActivityTime)613         private void setLastUserActivityTime(long lastUserActivityTime) {
614             mLastUserActivityTime = lastUserActivityTime;
615         }
616 
617         @Override
618         @ExcludeFromCodeCoverageGeneratedReport(reason = DEBUGGING_CODE)
toString()619         public String toString() {
620             StringBuilder b = new StringBuilder(64);
621             b.append("  DisplayPowerInfo{mDisplayId=");
622             b.append(mDisplayId);
623             b.append(" mUserId=");
624             b.append(mUserId);
625             b.append(" mMode=");
626             b.append(displayPowerModeToString(mMode));
627             b.append(" mIsDriverDisplay=");
628             b.append(mIsDriverDisplay);
629             b.append(" mLastUserActivityTime=");
630             b.append(mLastUserActivityTime);
631             b.append("}");
632             return b.toString();
633         }
634 
635         @ExcludeFromCodeCoverageGeneratedReport(reason = DEBUGGING_CODE)
displayPowerModeToString(@isplayPowerMode int mode)636         private static String displayPowerModeToString(@DisplayPowerMode int mode) {
637             switch (mode) {
638                 case DISPLAY_POWER_MODE_NONE:
639                     return "NONE";
640                 case DISPLAY_POWER_MODE_ON:
641                     return "ON";
642                 case DISPLAY_POWER_MODE_OFF:
643                     return "OFF";
644                 case DISPLAY_POWER_MODE_ALWAYS_ON:
645                     return "ALWAYS_ON";
646                 default:
647                     return "UNKNOWN";
648             }
649         }
650     }
651 
652     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)653     void dump(IndentingPrintWriter writer) {
654         synchronized (mLock) {
655             writer.println("ScreenOffHandler");
656             writer.increaseIndent();
657             writer.println("mIsAutoPowerSaving=" + mIsAutoPowerSaving);
658             writer.println("mBootCompleted=" + mBootCompleted);
659             writer.println("mNoUserScreenOffTimeoutMs=" + mNoUserScreenOffTimeoutMs);
660             writer.decreaseIndent();
661             for (int i = 0; i < mDisplayPowerInfos.size(); i++) {
662                 writer.println(mDisplayPowerInfos.valueAt(i));
663             }
664         }
665     }
666 
667     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)668     void dumpProto(ProtoOutputStream proto) {
669         synchronized (mLock) {
670             long screenOffHandlerToken = proto.start(CarPowerDumpProto.SCREEN_OFF_HANDLER);
671             proto.write(ScreenOffHandlerProto.IS_AUTO_POWER_SAVING, mIsAutoPowerSaving);
672             proto.write(ScreenOffHandlerProto.BOOT_COMPLETED, mBootCompleted);
673             proto.write(
674                     ScreenOffHandlerProto.NO_USER_SCREEN_OFF_TIMEOUT_MS, mNoUserScreenOffTimeoutMs);
675             for (int i = 0; i < mDisplayPowerInfos.size(); i++) {
676                 long displayPowerInfosToken = proto.start(
677                         ScreenOffHandlerProto.DISPLAY_POWER_INFOS);
678                 DisplayPowerInfo displayInfo = mDisplayPowerInfos.valueAt(i);
679                 proto.write(DisplayPowerInfoProto.DISPLAY_ID, displayInfo.getDisplayId());
680                 proto.write(DisplayPowerInfoProto.USER_ID, displayInfo.getUserId());
681                 proto.write(DisplayPowerInfoProto.MODE, displayInfo.getMode());
682                 proto.write(DisplayPowerInfoProto.IS_DRIVER_DISPLAY, displayInfo.isDriverDisplay());
683                 proto.write(DisplayPowerInfoProto.LAST_USER_ACTIVITY_TIME,
684                         displayInfo.getLastUserActivityTime());
685                 proto.end(displayPowerInfosToken);
686             }
687             proto.end(screenOffHandlerToken);
688         }
689     }
690 
691     /** Functional interface for providing time. */
692     @VisibleForTesting
693     interface ClockInterface {
694         /**
695          * Returns current time in milliseconds since boot, not counting time spent in deep sleep.
696          */
uptimeMillis()697         long uptimeMillis();
698     }
699 }
700