1 /*
2  * Copyright (C) 2018 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;
18 
19 import static android.car.CarOccupantZoneManager.DISPLAY_TYPE_MAIN;
20 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
21 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
22 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
23 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
24 import static android.car.drivingstate.CarUxRestrictionsConfiguration.Builder.SpeedRange.MAX_SPEED;
25 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
26 
27 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
28 
29 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
30 
31 import android.annotation.FloatRange;
32 import android.annotation.IntDef;
33 import android.annotation.NonNull;
34 import android.annotation.Nullable;
35 import android.car.Car;
36 import android.car.CarOccupantZoneManager;
37 import android.car.CarOccupantZoneManager.OccupantZoneInfo;
38 import android.car.ICarOccupantZoneCallback;
39 import android.car.VehicleAreaType;
40 import android.car.builtin.os.BinderHelper;
41 import android.car.builtin.os.BuildHelper;
42 import android.car.builtin.util.Slogf;
43 import android.car.drivingstate.CarDrivingStateEvent;
44 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
45 import android.car.drivingstate.CarUxRestrictions;
46 import android.car.drivingstate.CarUxRestrictionsConfiguration;
47 import android.car.drivingstate.CarUxRestrictionsManager;
48 import android.car.drivingstate.ICarDrivingStateChangeListener;
49 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
50 import android.car.drivingstate.ICarUxRestrictionsManager;
51 import android.car.hardware.CarPropertyValue;
52 import android.car.hardware.property.CarPropertyEvent;
53 import android.car.hardware.property.ICarPropertyEventListener;
54 import android.content.Context;
55 import android.content.pm.PackageManager;
56 import android.hardware.automotive.vehicle.VehicleProperty;
57 import android.hardware.display.DisplayManager;
58 import android.os.Binder;
59 import android.os.Handler;
60 import android.os.HandlerThread;
61 import android.os.Process;
62 import android.os.RemoteCallbackList;
63 import android.os.RemoteException;
64 import android.os.SystemClock;
65 import android.util.ArrayMap;
66 import android.util.ArraySet;
67 import android.util.AtomicFile;
68 import android.util.JsonReader;
69 import android.util.JsonToken;
70 import android.util.JsonWriter;
71 import android.util.Log;
72 import android.util.SparseArray;
73 import android.util.proto.ProtoOutputStream;
74 import android.view.Display;
75 
76 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
77 import com.android.car.internal.util.DebugUtils;
78 import com.android.car.internal.util.IndentingPrintWriter;
79 import com.android.car.internal.util.IntArray;
80 import com.android.car.systeminterface.SystemInterface;
81 import com.android.car.util.TransitionLog;
82 import com.android.internal.annotations.GuardedBy;
83 import com.android.internal.annotations.VisibleForTesting;
84 
85 import org.xmlpull.v1.XmlPullParserException;
86 
87 import java.io.File;
88 import java.io.FileOutputStream;
89 import java.io.IOException;
90 import java.io.InputStreamReader;
91 import java.io.OutputStreamWriter;
92 import java.lang.annotation.Retention;
93 import java.lang.annotation.RetentionPolicy;
94 import java.nio.charset.StandardCharsets;
95 import java.nio.file.Files;
96 import java.nio.file.Path;
97 import java.util.ArrayList;
98 import java.util.Arrays;
99 import java.util.Collection;
100 import java.util.HashSet;
101 import java.util.LinkedList;
102 import java.util.List;
103 import java.util.Map;
104 import java.util.Objects;
105 import java.util.Optional;
106 import java.util.Set;
107 
108 /**
109  * A service that listens to current driving state of the vehicle and maps it to the
110  * appropriate UX restrictions for that driving state.
111  * <p>
112  * <h1>UX Restrictions Configuration</h1>
113  * When this service starts, it will first try reading the configuration set through
114  * {@link #saveUxRestrictionsConfigurationForNextBoot(List)}.
115  * If one is not available, it will try reading the configuration saved in
116  * {@code R.xml.car_ux_restrictions_map}. If XML is somehow unavailable, it will
117  * fall back to a hard-coded configuration.
118  * <p>
119  * <h1>Multi-Display</h1>
120  * Only physical displays that are available at service initialization are recognized.
121  * This service does not support pluggable displays.
122  */
123 public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements
124         CarServiceBase {
125     private static final String TAG = CarLog.tagFor(CarUxRestrictionsManagerService.class);
126     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
127     private static final int MAX_TRANSITION_LOG_SIZE = 20;
128     private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
129 
130     // Using 8 as the initial capacity for several data structures as the number of displays
131     // will most likely be below 8.
132     private static final int INITIAL_DISPLAYS_SIZE = 8;
133 
134     private static final int UNKNOWN_JSON_SCHEMA_VERSION = -1;
135     private static final int JSON_SCHEMA_VERSION_V1 = 1;
136     private static final int JSON_SCHEMA_VERSION_V2 = 2;
137 
138     @IntDef({UNKNOWN_JSON_SCHEMA_VERSION, JSON_SCHEMA_VERSION_V1, JSON_SCHEMA_VERSION_V2})
139     @Retention(RetentionPolicy.SOURCE)
140     private @interface JsonSchemaVersion {}
141 
142     private static final String JSON_NAME_SCHEMA_VERSION = "schema_version";
143     private static final String JSON_NAME_RESTRICTIONS = "restrictions";
144 
145     @VisibleForTesting
146     static final String CONFIG_FILENAME_PRODUCTION = "ux_restrictions_prod_config.json";
147     @VisibleForTesting
148     static final String CONFIG_FILENAME_STAGED = "ux_restrictions_staged_config.json";
149 
150     private final Context mContext;
151     private final DisplayManager mDisplayManager;
152     private final CarDrivingStateService mDrivingStateService;
153     private final CarPropertyService mCarPropertyService;
154     private final CarOccupantZoneService mCarOccupantZoneService;
155     private final HandlerThread mClientDispatchThread  = CarServiceUtils.getHandlerThread(
156             getClass().getSimpleName());
157     private final Handler mClientDispatchHandler  = new Handler(mClientDispatchThread.getLooper());
158     private final RemoteCallbackList<ICarUxRestrictionsChangeListener> mUxRClients =
159             new RemoteCallbackList<>();
160 
161     /**
162      * Metadata associated with a binder callback.
163      */
164     private static class RemoteCallbackListCookie {
165         final int mDisplayId;
166 
RemoteCallbackListCookie(int displayId)167         RemoteCallbackListCookie(int displayId) {
168             mDisplayId = displayId;
169         }
170     }
171 
172     private final Object mLock = new Object();
173 
174     // DisplayIdentifier identifies a display by the combination of occupant zone id and display
175     // type.
176     private static final class DisplayIdentifier {
177         public final int occupantZoneId;
178         public final int displayType;
179         private int mHashCode;
180 
DisplayIdentifier(int occupantZoneId, int displayType)181         DisplayIdentifier(int occupantZoneId, int displayType) {
182             this.displayType = displayType;
183             this.occupantZoneId = occupantZoneId;
184         }
185 
186         @Override
equals(Object o)187         public boolean equals(Object o) {
188             if (this == o) {
189                 return true;
190             }
191             if (o == null || !(o instanceof DisplayIdentifier)) {
192                 return false;
193             }
194             DisplayIdentifier that = (DisplayIdentifier) o;
195             return occupantZoneId == that.occupantZoneId
196                 && displayType == that.displayType;
197         }
198 
199         @Override
hashCode()200         public int hashCode() {
201             if (mHashCode == 0) {
202                 mHashCode = Objects.hash(occupantZoneId, displayType);
203             }
204             return mHashCode;
205         }
206 
207         @Override
toString()208         public String toString() {
209             return "{occupantZoneId=" + occupantZoneId + " displayType="
210                     + Integer.toHexString(displayType) + "}";
211         }
212     }
213 
214     // In memory representation of Ux Restrictions config, key'ed by DisplayIdentifier
215     // (occupant zone id, display type).
216     @GuardedBy("mLock")
217     private Map<DisplayIdentifier, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations;
218 
219     // Current Ux Restrictions, key'ed by logical display id.
220     @GuardedBy("mLock")
221     private SparseArray<CarUxRestrictions> mCurrentUxRestrictions;
222 
223     @GuardedBy("mLock")
224     private String mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
225 
226     @GuardedBy("mLock")
227     private float mCurrentMovingSpeed;
228 
229     // DisplayIdentifier for the default display.
230     @GuardedBy("mLock")
231     private DisplayIdentifier mDefaultDisplayIdentifier;
232 
233     // Logical display ids for all displays.
234     @GuardedBy("mLock")
235     private IntArray mDisplayIds = new IntArray(INITIAL_DISPLAYS_SIZE);
236 
237     // Flag to disable broadcasting UXR changes - for development purposes
238     @GuardedBy("mLock")
239     private boolean mUxRChangeBroadcastEnabled = true;
240 
241     // For dumpsys logging
242     @GuardedBy("mLock")
243     private final LinkedList<TransitionLog> mTransitionLogs = new LinkedList<>();
244 
245     /**
246      * Callback registered with {@link CarOccupantZoneService} for getting display change
247      * notifications and updating UxRs on changed displays.
248      */
249     private final ICarOccupantZoneCallback mOccupantZoneCallback =
250             new ICarOccupantZoneCallback.Stub() {
251                 @Override
252                 public void onOccupantZoneConfigChanged(int flags) throws RemoteException {
253                     // Respond to display changes.
254                     if ((flags & CarOccupantZoneManager.ZONE_CONFIG_CHANGE_FLAG_DISPLAY) == 0) {
255                         return;
256                     }
257 
258                     if (DBG) {
259                         String flagString = DebugUtils.flagsToString(
260                                 CarOccupantZoneManager.class, "ZONE_CONFIG_CHANGE_FLAG_", flags);
261                         Slogf.d(TAG, "onOccupantZoneConfigChanged: display zone change flag=%s",
262                                 flagString);
263                     }
264                     List<OccupantZoneInfo> occupantZoneInfos =
265                             mCarOccupantZoneService.getAllOccupantZones();
266                     IntArray updatedDisplayIds = new IntArray(8);
267                     IntArray newlyAddedDisplayIds = new IntArray(8);
268                     CarUxRestrictions unrestrictedRestrictions = createUnrestrictedRestrictions();
269                     synchronized (mLock) {
270                         for (int i = 0; i < occupantZoneInfos.size(); i++) {
271                             OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(i);
272                             int zoneId = occupantZoneInfo.zoneId;
273                             int[] displayIds =
274                                     mCarOccupantZoneService.getAllDisplaysForOccupantZone(
275                                             zoneId);
276                             for (int j = 0; j < displayIds.length; j++) {
277                                 int displayId = displayIds[j];
278                                 updatedDisplayIds.add(displayId);
279                                 // Populate UxR only for newly added displays.
280                                 if (mDisplayIds.indexOf(displayId) != -1) {
281                                     continue;
282                                 }
283                                 newlyAddedDisplayIds.add(displayId);
284                                 if (DBG) {
285                                     Slogf.d(TAG, "Populating UxR for display %d", displayId);
286                                 }
287                                 // UxR will be updated in initializeUxRestrictions below.
288                                 mCurrentUxRestrictions.put(displayId, unrestrictedRestrictions);
289                             }
290                         }
291                         // Remove UxR for removed displays from
292                         // mCurrentUxRestrictions.
293                         for (int i = 0; i < mDisplayIds.size(); i++) {
294                             int displayId = mDisplayIds.get(i);
295                             if (updatedDisplayIds.indexOf(displayId) == -1) {
296                                 if (DBG) {
297                                     Slogf.d(TAG, "Removing UxR for display %d", displayId);
298                                 }
299                                 mCurrentUxRestrictions.remove(displayId);
300                             }
301                         }
302 
303                         mDisplayIds = updatedDisplayIds;
304                         if (DBG) {
305                             Slogf.d(TAG, "Display ids are updated to: %s", mDisplayIds);
306                         }
307                     }
308 
309                     if (newlyAddedDisplayIds.size() > 0) {
310                         // Initialize UxR on newly added displays.
311                         initializeUxRestrictions(newlyAddedDisplayIds);
312                     }
313                 }
314             };
315 
CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, CarPropertyService propertyService, CarOccupantZoneService carOccupantZoneService)316     public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService,
317             CarPropertyService propertyService, CarOccupantZoneService carOccupantZoneService) {
318         mContext = context;
319         mDisplayManager = mContext.getSystemService(DisplayManager.class);
320         mDrivingStateService = drvService;
321         mCarPropertyService = propertyService;
322         mCarOccupantZoneService = carOccupantZoneService;
323     }
324 
325     @Override
init()326     public void init() {
327         synchronized (mLock) {
328             initDisplayIdsLocked();
329 
330             // If getCurrentUxRestrictions() is called before init(), null will be returned.
331             mCurrentUxRestrictions = new SparseArray<>(8);
332 
333             // Unrestricted until driving state information is received. During boot up, we don't
334             // want everything to be blocked until data is available from CarPropertyManager.
335             // If we start driving and still don't get speed or gear information,
336             // we have bigger problems.
337             CarUxRestrictions unrestrictedRestrictions = createUnrestrictedRestrictions();
338             for (int i = 0; i < mDisplayIds.size(); i++) {
339                 int displayId = mDisplayIds.get(i);
340                 mCurrentUxRestrictions.put(displayId, unrestrictedRestrictions);
341             }
342 
343             // Load the prod config, or if there is a staged one, promote that first only if the
344             // current driving state, as provided by the driving state service, is parked.
345             mCarUxRestrictionsConfigurations = convertToMapLocked(loadConfig());
346         }
347 
348         // Subscribe to driving state changes.
349         mDrivingStateService.registerDrivingStateChangeListener(
350                 mICarDrivingStateChangeEventListener);
351         // Subscribe to property service for speed.
352         mCarPropertyService.registerListenerSafe(VehicleProperty.PERF_VEHICLE_SPEED,
353                 PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
354 
355         IntArray displayIds;
356         synchronized (mLock) {
357             displayIds = IntArray.fromArray(mDisplayIds.toArray(), mDisplayIds.size());
358         }
359         initializeUxRestrictions(displayIds);
360 
361         // Register callback to respond to display changes and update UxRs on changed displays.
362         mCarOccupantZoneService.registerCallback(mOccupantZoneCallback);
363     }
364 
365     @Override
getConfigs()366     public List<CarUxRestrictionsConfiguration> getConfigs() {
367         CarServiceUtils.assertPermission(mContext,
368                 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
369         synchronized (mLock) {
370             return new ArrayList<>(mCarUxRestrictionsConfigurations.values());
371         }
372     }
373 
374     /**
375      * Loads UX restrictions configurations and returns them.
376      *
377      * <p>Reads config from the following sources in order:
378      * <ol>
379      * <li>saved config set by {@link #saveUxRestrictionsConfigurationForNextBoot(List)};
380      * <li>XML resource config from {@code R.xml.car_ux_restrictions_map};
381      * <li>hardcoded default config.
382      * </ol>
383      *
384      * This method attempts to promote staged config file, which requires getting the current
385      * driving state.
386      */
387     @VisibleForTesting
loadConfig()388     List<CarUxRestrictionsConfiguration> loadConfig() {
389         promoteStagedConfig();
390         List<CarUxRestrictionsConfiguration> configs;
391 
392         // Production config, if available, is the first choice.
393         File prodConfig = getFile(CONFIG_FILENAME_PRODUCTION);
394         if (prodConfig.exists()) {
395             logd("Attempting to read production config");
396             configs = readPersistedConfig(prodConfig);
397             if (configs != null) {
398                 return configs;
399             }
400         }
401 
402         // XML config is the second choice.
403         logd("Attempting to read config from XML resource");
404         configs = readXmlConfig();
405         if (configs != null) {
406             return configs;
407         }
408 
409         // This should rarely happen.
410         Slogf.w(TAG, "Creating default config");
411 
412         configs = new ArrayList<>();
413         synchronized (mLock) {
414             for (int i = 0; i < mDisplayIds.size(); i++) {
415                 int displayId = mDisplayIds.get(i);
416                 DisplayIdentifier displayIdentifier = getDisplayIdentifier(displayId);
417                 if (displayIdentifier == null) {
418                     Slogf.e(TAG, "loadConfig: cannot map display id %d to DisplayIdentifier",
419                             displayId);
420                     continue;
421                 }
422                 configs.add(createDefaultConfig(displayIdentifier));
423             }
424         }
425         return configs;
426     }
427 
getFile(String filename)428     private File getFile(String filename) {
429         SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
430         return new File(systemInterface.getSystemCarDir(), filename);
431     }
432 
433     @Nullable
readXmlConfig()434     private List<CarUxRestrictionsConfiguration> readXmlConfig() {
435         try {
436             return CarUxRestrictionsConfigurationXmlParser.parse(
437                     mContext, R.xml.car_ux_restrictions_map);
438         } catch (IOException | XmlPullParserException e) {
439             Slogf.e(TAG, "Could not read config from XML resource", e);
440         }
441         return null;
442     }
443 
444     /**
445      * Promotes the staged config to prod, by replacing the prod file. Only do this if the car is
446      * parked to avoid changing the restrictions during a drive.
447      */
promoteStagedConfig()448     private void promoteStagedConfig() {
449         Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath();
450 
451         CarDrivingStateEvent currentDrivingStateEvent =
452                 mDrivingStateService.getCurrentDrivingState();
453         // Only promote staged config when car is parked.
454         if (currentDrivingStateEvent != null
455                 && currentDrivingStateEvent.eventValue == DRIVING_STATE_PARKED
456                 && Files.exists(stagedConfig)) {
457 
458             Path prod = getFile(CONFIG_FILENAME_PRODUCTION).toPath();
459             try {
460                 logd("Attempting to promote stage config");
461                 Files.move(stagedConfig, prod, REPLACE_EXISTING);
462             } catch (IOException e) {
463                 Slogf.e(TAG, "Could not promote state config", e);
464             }
465         }
466     }
467 
468     // Update current restrictions on the given displays by getting the current driving state and
469     // speed.
initializeUxRestrictions(IntArray displayIds)470     private void initializeUxRestrictions(IntArray displayIds) {
471         CarDrivingStateEvent currentDrivingStateEvent =
472                 mDrivingStateService.getCurrentDrivingState();
473         // if we don't have enough information from the CarPropertyService to compute the UX
474         // restrictions, then leave the UX restrictions unchanged from what it was initialized to.
475         if (currentDrivingStateEvent == null
476                 || currentDrivingStateEvent.eventValue == DRIVING_STATE_UNKNOWN) {
477             return;
478         }
479 
480         // At this point the underlying CarPropertyService has provided us enough information to
481         // compute the UX restrictions that could be potentially different from the initial UX
482         // restrictions.
483         synchronized (mLock) {
484             handleDrivingStateEventOnDisplaysLocked(currentDrivingStateEvent, displayIds);
485         }
486     }
487 
getCurrentSpeed()488     private @FloatRange(from = 0f) Optional<Float> getCurrentSpeed() {
489         CarPropertyValue value = mCarPropertyService.getPropertySafe(
490                 VehicleProperty.PERF_VEHICLE_SPEED, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL);
491         if (value != null && value.getStatus() == CarPropertyValue.STATUS_AVAILABLE) {
492             return Optional.of(Math.abs((Float) value.getValue()));
493         }
494         return Optional.empty();
495     }
496 
497     @Override
release()498     public void release() {
499         while (mUxRClients.getRegisteredCallbackCount() > 0) {
500             for (int i = mUxRClients.getRegisteredCallbackCount() - 1; i >= 0; i--) {
501                 ICarUxRestrictionsChangeListener client = mUxRClients.getRegisteredCallbackItem(i);
502                 if (client == null) {
503                     continue;
504                 }
505                 mUxRClients.unregister(client);
506             }
507         }
508         mDrivingStateService.unregisterDrivingStateChangeListener(
509                 mICarDrivingStateChangeEventListener);
510     }
511 
512     // Binder methods
513 
514     /**
515      * Registers a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX
516      * restrictions.
517      *
518      * @param listener  Listener to register
519      * @param displayId UX restrictions on this display will be notified.
520      */
521     @Override
registerUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener, int displayId)522     public void registerUxRestrictionsChangeListener(
523             ICarUxRestrictionsChangeListener listener, int displayId) {
524         if (listener == null) {
525             Slogf.e(TAG, "registerUxRestrictionsChangeListener(): listener null");
526             throw new IllegalArgumentException("Listener is null");
527         }
528         mUxRClients.register(listener, new RemoteCallbackListCookie(displayId));
529     }
530 
531     /**
532      * Unregister the given UX Restrictions listener
533      *
534      * @param listener client to unregister
535      */
536     @Override
unregisterUxRestrictionsChangeListener(ICarUxRestrictionsChangeListener listener)537     public void unregisterUxRestrictionsChangeListener(ICarUxRestrictionsChangeListener listener) {
538         if (listener == null) {
539             Slogf.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
540             throw new IllegalArgumentException("Listener is null");
541         }
542 
543         mUxRClients.unregister(listener);
544     }
545 
546     /**
547      * Gets the current UX restrictions for a display.
548      *
549      * @param displayId UX restrictions on this display will be returned.
550      */
551     @Nullable
552     @Override
getCurrentUxRestrictions(int displayId)553     public CarUxRestrictions getCurrentUxRestrictions(int displayId) {
554         CarUxRestrictions restrictions = null;
555         if (DBG) {
556             Slogf.d(TAG, "getCurrentUxRestrictions: display id %d", displayId);
557         }
558         synchronized (mLock) {
559             if (mCurrentUxRestrictions == null) {
560                 Slogf.wtf(TAG, "getCurrentUxRestrictions() called before init()");
561                 return null;
562             }
563 
564             if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
565                 Slogf.d(TAG, "Returning unrestricted UX Restriction due to setting");
566                 return createUnrestrictedRestrictions();
567             }
568             restrictions = mCurrentUxRestrictions.get(displayId);
569         }
570 
571         if (restrictions == null) {
572             Slogf.e(TAG, "Cannot find restrictions for displayId: %d"
573                     + ". Returning full restrictions.", displayId);
574             restrictions = createFullyRestrictedRestrictions();
575         }
576         return restrictions;
577     }
578 
579     /**
580      * Convenience method to retrieve restrictions for default display.
581      */
582     @Nullable
getCurrentUxRestrictions()583     public CarUxRestrictions getCurrentUxRestrictions() {
584         return getCurrentUxRestrictions(Display.DEFAULT_DISPLAY);
585     }
586 
587     @Override
saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)588     public boolean saveUxRestrictionsConfigurationForNextBoot(
589             List<CarUxRestrictionsConfiguration> configs) {
590         CarServiceUtils.assertPermission(mContext,
591                 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
592 
593         validateConfigs(configs);
594 
595         return persistConfig(configs, CONFIG_FILENAME_STAGED);
596     }
597 
598     @Override
599     @Nullable
getStagedConfigs()600     public List<CarUxRestrictionsConfiguration> getStagedConfigs() {
601         CarServiceUtils.assertPermission(mContext,
602                 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
603 
604         File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
605         if (stagedConfig.exists()) {
606             logd("Attempting to read staged config");
607             return readPersistedConfig(stagedConfig);
608         } else {
609             return null;
610         }
611     }
612 
613     /**
614      * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to
615      * be applied in the same driving state. Restrictions for each mode can be configured through
616      * {@link CarUxRestrictionsConfiguration}.
617      *
618      * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
619      *
620      * @param mode the restriction mode
621      * @return {@code true} if mode was successfully changed; {@code false} otherwise.
622      * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
623      * @see CarUxRestrictionsConfiguration.Builder
624      */
625     @Override
setRestrictionMode(@onNull String mode)626     public boolean setRestrictionMode(@NonNull String mode) {
627         CarServiceUtils.assertPermission(mContext,
628                 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
629         Objects.requireNonNull(mode, "mode must not be null");
630 
631         synchronized (mLock) {
632             if (mRestrictionMode.equals(mode)) {
633                 return true;
634             }
635 
636             addTransitionLogLocked(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
637                     "Restriction mode");
638             mRestrictionMode = mode;
639             logd("Set restriction mode to: " + mode);
640 
641             handleDrivingStateEventLocked(mDrivingStateService.getCurrentDrivingState());
642         }
643         return true;
644     }
645 
646     @Override
647     @NonNull
getRestrictionMode()648     public String getRestrictionMode() {
649         CarServiceUtils.assertPermission(mContext,
650                 Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
651 
652         synchronized (mLock) {
653             return mRestrictionMode;
654         }
655     }
656 
657     /**
658      * Returns all supported restriction modes
659      */
660     @NonNull
getSupportedRestrictionModes()661     public List<String> getSupportedRestrictionModes() {
662         Set<String> modes = new HashSet<>();
663         Collection<CarUxRestrictionsConfiguration> configs;
664         synchronized (mLock) {
665             configs = mCarUxRestrictionsConfigurations.values();
666         }
667 
668         for (CarUxRestrictionsConfiguration config : configs) {
669             modes.addAll(config.getSupportedRestrictionModes());
670         }
671 
672         return new ArrayList<>(modes);
673     }
674 
675     /**
676      * Writes configuration into the specified file.
677      *
678      * IO access on file is not thread safe. Caller should ensure threading protection.
679      */
persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename)680     private boolean persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename) {
681         File file = getFile(filename);
682         AtomicFile stagedFile = new AtomicFile(file);
683         FileOutputStream fos;
684         try {
685             fos = stagedFile.startWrite();
686         } catch (IOException e) {
687             Slogf.e(TAG, "Could not open file to persist config", e);
688             return false;
689         }
690         try (JsonWriter jsonWriter = new JsonWriter(
691                 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
692             writeJson(jsonWriter, configs);
693         } catch (IOException e) {
694             Slogf.e(TAG, "Could not persist config", e);
695             stagedFile.failWrite(fos);
696             return false;
697         }
698         stagedFile.finishWrite(fos);
699         return true;
700     }
701 
702     @VisibleForTesting
writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)703     void writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)
704             throws IOException {
705         jsonWriter.beginObject();
706         jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2);
707         jsonWriter.name(JSON_NAME_RESTRICTIONS);
708         jsonWriter.beginArray();
709         for (int i = 0; i < configs.size(); i++) {
710             CarUxRestrictionsConfiguration config = configs.get(i);
711             config.writeJson(jsonWriter);
712         }
713         jsonWriter.endArray();
714         jsonWriter.endObject();
715     }
716 
717     @Nullable
readPersistedConfig(File file)718     private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) {
719         if (!file.exists()) {
720             Slogf.e(TAG, "Could not find config file: " + file.getName());
721             return null;
722         }
723 
724         // Take one pass at the file to check the version and then a second pass to read the
725         // contents. We could assess the version and read in one pass, but we're preferring
726         // clarity over complexity here.
727         int schemaVersion = readFileSchemaVersion(file);
728 
729         AtomicFile configFile = new AtomicFile(file);
730         try (JsonReader reader = new JsonReader(
731                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
732             List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
733             switch (schemaVersion) {
734                 case JSON_SCHEMA_VERSION_V1:
735                     readV1Json(reader, configs);
736                     break;
737                 case JSON_SCHEMA_VERSION_V2:
738                     readV2Json(reader, configs);
739                     break;
740                 default:
741                     Slogf.e(TAG, "Unable to parse schema for version " + schemaVersion);
742             }
743 
744             return configs;
745         } catch (IOException e) {
746             Slogf.e(TAG, "Could not read persisted config file " + file.getName(), e);
747         }
748         return null;
749     }
750 
readV1Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)751     private void readV1Json(JsonReader reader,
752             List<CarUxRestrictionsConfiguration> configs) throws IOException {
753         readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1);
754     }
755 
readV2Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)756     private void readV2Json(JsonReader reader,
757             List<CarUxRestrictionsConfiguration> configs) throws IOException {
758         reader.beginObject();
759         while (reader.hasNext()) {
760             String name = reader.nextName();
761             switch (name) {
762                 case JSON_NAME_RESTRICTIONS:
763                     readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2);
764                     break;
765                 default:
766                     reader.skipValue();
767             }
768         }
769         reader.endObject();
770     }
771 
readFileSchemaVersion(File file)772     private int readFileSchemaVersion(File file) {
773         AtomicFile configFile = new AtomicFile(file);
774         try (JsonReader reader = new JsonReader(
775                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
776             if (reader.peek() == JsonToken.BEGIN_ARRAY) {
777                 // only schema V1 beings with an array - no need to keep reading
778                 reader.close();
779                 return JSON_SCHEMA_VERSION_V1;
780             } else {
781                 reader.beginObject();
782                 while (reader.hasNext()) {
783                     String name = reader.nextName();
784                     switch (name) {
785                         case JSON_NAME_SCHEMA_VERSION:
786                             int schemaVersion = reader.nextInt();
787                             // got the version, no need to continue reading
788                             reader.close();
789                             return schemaVersion;
790                         default:
791                             reader.skipValue();
792                     }
793                 }
794                 reader.endObject();
795             }
796         } catch (IOException e) {
797             Slogf.e(TAG, "Could not read persisted config file " + file.getName(), e);
798         }
799         return UNKNOWN_JSON_SCHEMA_VERSION;
800     }
801 
readRestrictionsArray(JsonReader reader, List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)802     private void readRestrictionsArray(JsonReader reader,
803             List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)
804             throws IOException {
805         reader.beginArray();
806         while (reader.hasNext()) {
807             configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion));
808         }
809         reader.endArray();
810     }
811 
812     /**
813      * Enable/disable UX restrictions change broadcast blocking.
814      * Setting this to true will stop broadcasts of UX restriction change to listeners.
815      * This method works only on debug builds and the caller of this method needs to have the same
816      * signature of the car service.
817      */
setUxRChangeBroadcastEnabled(boolean enable)818     public void setUxRChangeBroadcastEnabled(boolean enable) {
819         if (!isDebugBuild()) {
820             Slogf.e(TAG, "Cannot set UX restriction change broadcast.");
821             return;
822         }
823         // Check if the caller has the same signature as that of the car service.
824         if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid())
825                 != PackageManager.SIGNATURE_MATCH) {
826             throw new SecurityException(
827                     "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())
828                             + " does not have the right signature");
829         }
830 
831         synchronized (mLock) {
832             if (enable) {
833                 // if enabling it back, send the current restrictions
834                 mUxRChangeBroadcastEnabled = true;
835                 handleDrivingStateEventLocked(
836                         mDrivingStateService.getCurrentDrivingState());
837             } else {
838                 // fake parked state, so if the system is currently restricted, the restrictions are
839                 // relaxed.
840                 handleDispatchUxRestrictionsLocked(DRIVING_STATE_PARKED, /* speed= */ 0f,
841                         mDisplayIds);
842                 mUxRChangeBroadcastEnabled = false;
843             }
844         }
845     }
846 
isDebugBuild()847     private boolean isDebugBuild() {
848         return BuildHelper.isUserDebugBuild() || BuildHelper.isEngBuild();
849     }
850 
851     @Override
852     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)853     public void dump(IndentingPrintWriter writer) {
854         synchronized (mLock) {
855             writer.println("*CarUxRestrictionsManagerService*");
856 
857             writer.println("UX Restrictions Clients:");
858             writer.increaseIndent();
859             BinderHelper.dumpRemoteCallbackList(mUxRClients, writer);
860             writer.decreaseIndent();
861             for (int i = 0; i < mCurrentUxRestrictions.size(); i++) {
862                 writer.printf("Display id: %d UXR: %s\n", mCurrentUxRestrictions.keyAt(i),
863                         mCurrentUxRestrictions.valueAt(i));
864             }
865             if (isDebugBuild()) {
866                 writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled);
867             }
868             writer.println("UX Restriction configurations:");
869             for (CarUxRestrictionsConfiguration config :
870                     mCarUxRestrictionsConfigurations.values()) {
871                 config.dump(writer);
872             }
873             writer.println("UX Restriction change log:");
874             for (TransitionLog tlog : mTransitionLogs) {
875                 writer.println(tlog);
876             }
877         }
878     }
879 
880     @Override
881     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dumpProto(ProtoOutputStream proto)882     public void dumpProto(ProtoOutputStream proto) {}
883 
884     /**
885      * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
886      * for getting driving state change notifications.
887      */
888     private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
889             new ICarDrivingStateChangeListener.Stub() {
890                 @Override
891                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
892                     logd("Driving State Changed:" + event.eventValue);
893                     synchronized (mLock) {
894                         handleDrivingStateEventLocked(event);
895                     }
896                 }
897             };
898 
899     /**
900      * Handles the driving state change events coming from the {@link CarDrivingStateService} on
901      * the given displays.
902      * <p>Maps the driving state to the corresponding UX Restrictions and dispatch the
903      * UX Restriction change to the registered clients.
904      */
905     @GuardedBy("mLock")
handleDrivingStateEventOnDisplaysLocked(CarDrivingStateEvent event, IntArray displayIds)906     void handleDrivingStateEventOnDisplaysLocked(CarDrivingStateEvent event,
907             IntArray displayIds) {
908         if (event == null) {
909             return;
910         }
911         int drivingState = event.eventValue;
912         Optional<Float> currentSpeed = getCurrentSpeed();
913 
914         if (currentSpeed.isPresent()) {
915             mCurrentMovingSpeed = currentSpeed.get();
916             handleDispatchUxRestrictionsLocked(drivingState, mCurrentMovingSpeed, displayIds);
917         } else if (drivingState != DRIVING_STATE_MOVING) {
918             // If speed is unavailable, but the driving state is parked or unknown, it can still be
919             // handled.
920             logd("Speed null when driving state is: " + drivingState);
921             handleDispatchUxRestrictionsLocked(drivingState, /* speed= */ 0f, displayIds);
922         } else {
923             // If we get here, it means the car is moving while the speed is unavailable.
924             // This only happens in the case of a fault. We should take the safest route by assuming
925             // the car is moving at a speed in the highest speed range.
926             Slogf.e(TAG, "Unexpected:  Speed null when driving state is: " + drivingState);
927             logd("Treating speed null when driving state is: " + drivingState
928                     + " as in highest speed range");
929             handleDispatchUxRestrictionsLocked(DRIVING_STATE_MOVING, /* speed= */ MAX_SPEED,
930                     displayIds);
931         }
932     }
933 
934     /**
935      * Handles the driving state change events coming from the {@link CarDrivingStateService} on
936      * all displays.
937      *
938      * <p>Maps the driving state to the corresponding UX Restrictions and dispatch the
939      * UX Restriction change to the registered clients.
940      */
941     @VisibleForTesting
942     @GuardedBy("mLock")
handleDrivingStateEventLocked(CarDrivingStateEvent event)943     void handleDrivingStateEventLocked(CarDrivingStateEvent event) {
944         handleDrivingStateEventOnDisplaysLocked(event, mDisplayIds);
945     }
946 
947     /**
948      * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
949      * speed change notifications.
950      */
951     private final ICarPropertyEventListener mICarPropertyEventListener =
952             new ICarPropertyEventListener.Stub() {
953                 @Override
954                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
955                     synchronized (mLock) {
956                         for (int i = 0; i < events.size(); i++) {
957                             CarPropertyEvent event = events.get(i);
958                             if ((event.getEventType()
959                                     == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
960                                     && (event.getCarPropertyValue().getPropertyId()
961                                     == VehicleProperty.PERF_VEHICLE_SPEED)) {
962                                 handleSpeedChangeLocked(
963                                         Math.abs((Float) event.getCarPropertyValue().getValue()));
964                             }
965                         }
966                     }
967                 }
968             };
969 
970     @GuardedBy("mLock")
handleSpeedChangeLocked(@loatRangefrom = 0f) float newSpeed)971     private void handleSpeedChangeLocked(@FloatRange(from = 0f) float newSpeed) {
972         if (newSpeed == mCurrentMovingSpeed) {
973             // Ignore if speed hasn't changed
974             return;
975         }
976         mCurrentMovingSpeed = newSpeed;
977         int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
978         if (currentDrivingState != DRIVING_STATE_MOVING) {
979             // Ignore speed changes if the vehicle is not moving
980             return;
981         }
982         handleDispatchUxRestrictionsLocked(currentDrivingState, mCurrentMovingSpeed, mDisplayIds);
983     }
984 
985     /**
986      * Handle dispatching UX restrictions change on a set of display ids.
987      *
988      * <p> This method also handles the special case when the car is moving but its speed
989      * is unavailable in which case highest speed will be assumed.
990      *
991      * @param currentDrivingState driving state of the vehicle
992      * @param speed               speed of the vehicle
993      * @param displayIds          Ids of displays on which to dispatch Ux restrictions change
994      */
995     @GuardedBy("mLock")
handleDispatchUxRestrictionsLocked(@arDrivingState int currentDrivingState, @FloatRange(from = 0f) float speed, IntArray displayIds)996     private void handleDispatchUxRestrictionsLocked(@CarDrivingState int currentDrivingState,
997             @FloatRange(from = 0f) float speed, IntArray displayIds) {
998         Objects.requireNonNull(mCarUxRestrictionsConfigurations,
999                 "mCarUxRestrictionsConfigurations must be initialized");
1000         Objects.requireNonNull(mCurrentUxRestrictions,
1001                 "mCurrentUxRestrictions must be initialized");
1002 
1003         if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
1004             Slogf.d(TAG, "Not dispatching UX Restriction due to setting");
1005             return;
1006         }
1007 
1008         // keep track of only those changed UxR for dispatching.
1009         SparseArray<CarUxRestrictions> updatedUxRestrictions = new SparseArray<>(
1010                 INITIAL_DISPLAYS_SIZE);
1011         IntArray displaysToDispatch = new IntArray(INITIAL_DISPLAYS_SIZE);
1012         for (int i = 0; i < displayIds.size(); i++) {
1013             int displayId = displayIds.get(i);
1014             if (DBG) {
1015                 Slogf.d(TAG,
1016                         "handleDispatchUxRestrictionsLocked: Recalculating UxR for display %d...",
1017                         displayId);
1018             }
1019 
1020             CarUxRestrictions uxRestrictions;
1021             // Map logical display to DisplayIdentifier to get UxR from config based on
1022             // DisplayIdentifier.
1023             DisplayIdentifier displayIdentifier = getDisplayIdentifier(displayId);
1024             if (displayIdentifier == null) {
1025                 Slogf.w(TAG,
1026                         "handleDispatchUxRestrictionsLocked: cannot map display id %d to"
1027                         + " DisplayIdentifier based on which to get UxR from config,"
1028                         + " defaulting to fully restricted restrictions",
1029                         displayId);
1030                 uxRestrictions = createFullyRestrictedRestrictions();
1031             } else {
1032                 if (DBG) {
1033                     Slogf.d(TAG,
1034                             "handleDispatchUxRestrictionsLocked: mapped display id %d to"
1035                             + " DisplayIdentifier %s", displayId, displayIdentifier);
1036                 }
1037                 CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get(
1038                         displayIdentifier);
1039                 if (config == null) {
1040                     Slogf.w(TAG,
1041                             "handleDispatchUxRestrictionsLocked: cannot find UxR"
1042                             + " DisplayIdentifier %s from config, defaulting to fully restricted"
1043                             + " restrictions", displayIdentifier);
1044                     // If UxR config is not found for a physical port, assume it's fully restricted.
1045                     uxRestrictions = createFullyRestrictedRestrictions();
1046                 } else {
1047                     uxRestrictions = config.getUxRestrictions(
1048                             currentDrivingState, speed, mRestrictionMode);
1049                 }
1050             }
1051 
1052             if (!mCurrentUxRestrictions.contains(displayId)) {
1053                 // This should never happen.
1054                 Slogf.wtf(TAG, "handleDispatchUxRestrictionsLocked: Unrecognized display %d:"
1055                         + " in new UxR", displayId);
1056                 continue;
1057             }
1058             CarUxRestrictions currentUxRestrictions = mCurrentUxRestrictions.get(displayId);
1059             if (DBG) {
1060                 Slogf.d(TAG, "Display id %d\tDO old->new: %b -> %b", displayId,
1061                         currentUxRestrictions.isRequiresDistractionOptimization(),
1062                         uxRestrictions.isRequiresDistractionOptimization());
1063                 Slogf.d(TAG, "Display id %d\tUxR old->new: 0x%x -> 0x%x", displayId,
1064                         currentUxRestrictions.getActiveRestrictions(),
1065                         uxRestrictions.getActiveRestrictions());
1066             }
1067             if (!currentUxRestrictions.isSameRestrictions(uxRestrictions)) {
1068                 if (DBG) {
1069                     Slogf.d(TAG, "Updating UxR on display %d", displayId);
1070                 }
1071                 // Update UxR for displayId in place.
1072                 mCurrentUxRestrictions.put(displayId, uxRestrictions);
1073                 // Ignore dispatching if the restrictions have not changed.
1074                 displaysToDispatch.add(displayId);
1075                 updatedUxRestrictions.put(displayId, uxRestrictions);
1076                 addTransitionLogLocked(currentUxRestrictions, uxRestrictions);
1077             }
1078         }
1079 
1080         if (displaysToDispatch.size() != 0) {
1081             dispatchRestrictionsToClientsLocked(updatedUxRestrictions, displaysToDispatch);
1082         }
1083     }
1084 
dispatchRestrictionsToClientsLocked( SparseArray<CarUxRestrictions> updatedUxRestrictions, IntArray displaysToDispatch)1085     private void dispatchRestrictionsToClientsLocked(
1086             SparseArray<CarUxRestrictions> updatedUxRestrictions,
1087             IntArray displaysToDispatch) {
1088         logd("dispatching to clients");
1089         boolean success = mClientDispatchHandler.post(() -> {
1090             int numClients = mUxRClients.beginBroadcast();
1091             for (int i = 0; i < numClients; i++) {
1092                 ICarUxRestrictionsChangeListener callback = mUxRClients.getBroadcastItem(i);
1093                 RemoteCallbackListCookie cookie =
1094                         (RemoteCallbackListCookie) mUxRClients.getBroadcastCookie(i);
1095                 if (displaysToDispatch.indexOf(cookie.mDisplayId) == -1) {
1096                     continue;
1097                 }
1098                 CarUxRestrictions restrictions = updatedUxRestrictions.get(cookie.mDisplayId);
1099                 if (restrictions == null) {
1100                     // Don't dispatch to displays without configurations
1101                     Slogf.w(TAG, "Can't find UxR on display %d for dispatching",
1102                             cookie.mDisplayId);
1103                     continue;
1104                 }
1105                 if (DBG) {
1106                     Slogf.d(TAG, "Dispatching UxR change %s to display %d", restrictions,
1107                             cookie.mDisplayId);
1108                 }
1109                 try {
1110                     callback.onUxRestrictionsChanged(restrictions);
1111                 } catch (RemoteException e) {
1112                     Slogf.e(TAG, "Dispatch to listener %s failed for restrictions (%s)", callback,
1113                             restrictions);
1114                 }
1115             }
1116             mUxRClients.finishBroadcast();
1117         });
1118 
1119         if (!success) {
1120             Slogf.e(TAG, "Failed to post Ux Restrictions changes event to dispatch handler");
1121         }
1122     }
1123 
1124     @GuardedBy("mLock")
initDisplayIdsLocked()1125     private void initDisplayIdsLocked() {
1126         // Populate logical display ids of all displays in all occupant zones.
1127         List<CarOccupantZoneManager.OccupantZoneInfo> occupantZoneInfos =
1128                 mCarOccupantZoneService.getAllOccupantZones();
1129         for (int i = 0; i < occupantZoneInfos.size(); i++) {
1130             CarOccupantZoneManager.OccupantZoneInfo occupantZoneInfo = occupantZoneInfos.get(i);
1131             int zoneId = occupantZoneInfo.zoneId;
1132             int[] displayIds = mCarOccupantZoneService.getAllDisplaysForOccupantZone(zoneId);
1133             for (int j = 0; j < displayIds.length; j++) {
1134                 int displayId = displayIds[j];
1135                 Slogf.i(TAG, "initDisplayIdsLocked: adding display: %d", displayId);
1136                 mDisplayIds.add(displayId);
1137             }
1138         }
1139 
1140         // Find the default display id by zone from driver main display.
1141         // This will be used when UxR config does not specify display id.
1142         IntArray displayIds = mCarOccupantZoneService.getAllDisplayIdsForDriver(DISPLAY_TYPE_MAIN);
1143         if (DBG) {
1144             Slogf.d(TAG, "Driver displayIds: " + Arrays.toString(displayIds.toArray()));
1145         }
1146         if (displayIds.size() > 0) {
1147             int driverMainDisplayId = displayIds.get(0);
1148             DisplayIdentifier displayIdentifier = getDisplayIdentifier(driverMainDisplayId);
1149             if (displayIdentifier == null) {
1150                 Slogf.w(TAG, "initDisplayIdsLocked: cannot map driver main display id %d"
1151                         + " to DisplayIdentifier", driverMainDisplayId);
1152                 displayIdentifier = new DisplayIdentifier(
1153                         CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID,
1154                         CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN);
1155             }
1156             // The first display id from the driver displays will be the default display id.
1157             Slogf.i(TAG, "initDisplayIdsLocked: setting default display id by zone to %s",
1158                     displayIdentifier);
1159             mDefaultDisplayIdentifier = displayIdentifier;
1160         } else {
1161             Slogf.w(TAG, "initDisplayIdsLocked: driver main display not found");
1162             mDefaultDisplayIdentifier = new DisplayIdentifier(
1163                     CarOccupantZoneManager.OccupantZoneInfo.INVALID_ZONE_ID,
1164                     CarOccupantZoneManager.DISPLAY_TYPE_UNKNOWN);
1165         }
1166     }
1167 
1168     @Nullable
getDisplayIdentifier(int displayId)1169     private DisplayIdentifier getDisplayIdentifier(int displayId) {
1170         CarOccupantZoneService.DisplayConfig displayConfig =
1171                 mCarOccupantZoneService.findDisplayConfigForDisplayId(displayId);
1172         if (displayConfig == null) return null;
1173         DisplayIdentifier displayIdentifier = new DisplayIdentifier(
1174                 displayConfig.occupantZoneId, displayConfig.displayType);
1175         return displayIdentifier;
1176     }
1177 
1178     @Nullable
getDisplayIdentifierFromPort(int port)1179     private DisplayIdentifier getDisplayIdentifierFromPort(int port) {
1180         CarOccupantZoneService.DisplayConfig displayConfig =
1181                 mCarOccupantZoneService.findDisplayConfigForPort(port);
1182         if (displayConfig == null) return null;
1183         DisplayIdentifier displayIdentifier = new DisplayIdentifier(
1184                 displayConfig.occupantZoneId, displayConfig.displayType);
1185         return displayIdentifier;
1186     }
1187 
1188     @GuardedBy("mLock")
convertToMapLocked( List<CarUxRestrictionsConfiguration> configs)1189     private Map<DisplayIdentifier, CarUxRestrictionsConfiguration> convertToMapLocked(
1190             List<CarUxRestrictionsConfiguration> configs) {
1191         validateConfigs(configs);
1192 
1193         Map<DisplayIdentifier, CarUxRestrictionsConfiguration> result = new ArrayMap<>();
1194 
1195         if (configs.size() == 1) {
1196             CarUxRestrictionsConfiguration config = configs.get(0);
1197             if (config.getPhysicalPort() == null
1198                     && config.getOccupantZoneId() == OccupantZoneInfo.INVALID_ZONE_ID) {
1199                 // If no display is specified, the default from driver's main display
1200                 // is assumed.
1201                 result.put(mDefaultDisplayIdentifier, config);
1202                 return result;
1203             }
1204         }
1205 
1206         for (int i = 0; i < configs.size(); i++) {
1207             CarUxRestrictionsConfiguration config = configs.get(i);
1208             DisplayIdentifier displayIdentifier;
1209             // A display is specified either by physical port or the combination of occupant zone id
1210             // and display type.
1211             // Note: physical port and the combination of occupant zone id and display type won't
1212             // coexist. This has been checked when parsing the UxR config.
1213             if (config.getPhysicalPort() != null) {
1214                 displayIdentifier = getDisplayIdentifierFromPort(config.getPhysicalPort());
1215                 if (displayIdentifier != null) {
1216                     if (DBG) {
1217                         Slogf.d(TAG,
1218                                 "convertToMapLocked: port %d is mapped to DisplayIdentifier %s",
1219                                 config.getPhysicalPort(), displayIdentifier);
1220                     }
1221                 } else {
1222                     Slogf.w(TAG,
1223                             "convertToMapLocked: port %d can't be mapped to DisplayIdentifier",
1224                             config.getPhysicalPort());
1225                     continue;
1226                 }
1227             } else {
1228                 displayIdentifier = new DisplayIdentifier(
1229                     config.getOccupantZoneId(), config.getDisplayType());
1230             }
1231             result.put(displayIdentifier, config);
1232         }
1233         return result;
1234     }
1235 
1236     /**
1237      * Validates configs for multi-display:
1238      * <p><ol>
1239      * <li> Each sets either display port or (occupant zone id, display type);
1240      * <li> Display port is unique;
1241      * <li> The combination of (occupant zone id, display type) is unique.
1242      * </ol>
1243      */
1244     @VisibleForTesting
validateConfigs(List<CarUxRestrictionsConfiguration> configs)1245     void validateConfigs(List<CarUxRestrictionsConfiguration> configs) {
1246         if (configs.size() == 0) {
1247             throw new IllegalArgumentException("Empty configuration.");
1248         }
1249 
1250         if (configs.size() == 1) {
1251             return;
1252         }
1253 
1254         Set<Integer> existingPorts = new ArraySet<>(configs.size());
1255         Set<DisplayIdentifier> existingDisplayIdentifiers = new ArraySet<>(configs.size());
1256         for (int i = 0; i < configs.size(); i++) {
1257             CarUxRestrictionsConfiguration config = configs.get(i);
1258             Integer port = config.getPhysicalPort();
1259             int occupantZoneId = config.getOccupantZoneId();
1260             if (port == null && occupantZoneId == OccupantZoneInfo.INVALID_ZONE_ID) {
1261                 // Size was checked above; safe to assume there are multiple configs.
1262                 throw new IllegalArgumentException(
1263                         "Input contains multiple configurations; "
1264                         + "each must set physical port or the combination of occupant zone id "
1265                         + "and display type");
1266             }
1267 
1268             if (port != null) {
1269                 if (!existingPorts.add(port)) {
1270                     throw new IllegalStateException("Multiple configurations for port " + port);
1271                 }
1272             } else {
1273                 // TODO(b/241589812): Validate occupant zone.
1274                 DisplayIdentifier displayIdentifier = new DisplayIdentifier(
1275                         occupantZoneId, config.getDisplayType());
1276                 if (!existingDisplayIdentifiers.add(displayIdentifier)) {
1277                     throw new IllegalStateException(
1278                             "Multiple configurations for " + displayIdentifier.toString());
1279                 }
1280             }
1281         }
1282     }
1283 
createUnrestrictedRestrictions()1284     private CarUxRestrictions createUnrestrictedRestrictions() {
1285         return new CarUxRestrictions.Builder(/* reqOpt= */ false,
1286                 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos())
1287                 .build();
1288     }
1289 
createFullyRestrictedRestrictions()1290     private CarUxRestrictions createFullyRestrictedRestrictions() {
1291         return new CarUxRestrictions.Builder(
1292                 /*reqOpt= */ true,
1293                 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED,
1294                 SystemClock.elapsedRealtimeNanos()).build();
1295     }
1296 
createDefaultConfig(DisplayIdentifier displayIdentifier)1297     CarUxRestrictionsConfiguration createDefaultConfig(DisplayIdentifier displayIdentifier) {
1298         return new CarUxRestrictionsConfiguration.Builder()
1299                 .setOccupantZoneId(displayIdentifier.occupantZoneId)
1300                 .setDisplayType(displayIdentifier.displayType)
1301                 .setUxRestrictions(DRIVING_STATE_PARKED,
1302                         false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
1303                 .setUxRestrictions(DRIVING_STATE_IDLING,
1304                         false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
1305                 .setUxRestrictions(DRIVING_STATE_MOVING,
1306                         true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
1307                 .setUxRestrictions(DRIVING_STATE_UNKNOWN,
1308                         true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
1309                 .build();
1310     }
1311 
1312     @GuardedBy("mLock")
addTransitionLogLocked(String name, String from, String to, long timestamp, String extra)1313     private void addTransitionLogLocked(String name, String from, String to, long timestamp,
1314             String extra) {
1315         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
1316             mTransitionLogs.remove();
1317         }
1318 
1319         TransitionLog tLog = new TransitionLog(name, from, to, timestamp, extra);
1320         mTransitionLogs.add(tLog);
1321     }
1322 
1323     @GuardedBy("mLock")
addTransitionLogLocked( CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions)1324     private void addTransitionLogLocked(
1325             CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions) {
1326         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
1327             mTransitionLogs.remove();
1328         }
1329         StringBuilder extra = new StringBuilder();
1330         extra.append(oldRestrictions.isRequiresDistractionOptimization() ? "DO -> " : "No DO -> ");
1331         extra.append(newRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO");
1332 
1333         TransitionLog tLog = new TransitionLog(TAG,
1334                 oldRestrictions.getActiveRestrictions(), newRestrictions.getActiveRestrictions(),
1335                 System.currentTimeMillis(), extra.toString());
1336         mTransitionLogs.add(tLog);
1337     }
1338 
logd(String msg)1339     private static void logd(String msg) {
1340         if (DBG) {
1341             Slogf.d(TAG, msg);
1342         }
1343     }
1344 }
1345