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.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
20 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
21 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
22 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_UNKNOWN;
23 import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
24 
25 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
26 
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.car.Car;
31 import android.car.drivingstate.CarDrivingStateEvent;
32 import android.car.drivingstate.CarDrivingStateEvent.CarDrivingState;
33 import android.car.drivingstate.CarUxRestrictions;
34 import android.car.drivingstate.CarUxRestrictionsConfiguration;
35 import android.car.drivingstate.CarUxRestrictionsManager;
36 import android.car.drivingstate.ICarDrivingStateChangeListener;
37 import android.car.drivingstate.ICarUxRestrictionsChangeListener;
38 import android.car.drivingstate.ICarUxRestrictionsManager;
39 import android.car.hardware.CarPropertyValue;
40 import android.car.hardware.property.CarPropertyEvent;
41 import android.car.hardware.property.ICarPropertyEventListener;
42 import android.content.Context;
43 import android.content.pm.PackageManager;
44 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
45 import android.hardware.display.DisplayManager;
46 import android.os.Binder;
47 import android.os.Build;
48 import android.os.IBinder;
49 import android.os.Process;
50 import android.os.RemoteException;
51 import android.os.SystemClock;
52 import android.util.ArraySet;
53 import android.util.AtomicFile;
54 import android.util.JsonReader;
55 import android.util.JsonToken;
56 import android.util.JsonWriter;
57 import android.util.Log;
58 import android.util.Slog;
59 import android.view.Display;
60 import android.view.DisplayAddress;
61 
62 import com.android.car.systeminterface.SystemInterface;
63 import com.android.internal.annotations.GuardedBy;
64 import com.android.internal.annotations.VisibleForTesting;
65 import com.android.internal.util.Preconditions;
66 
67 import org.xmlpull.v1.XmlPullParserException;
68 
69 import java.io.File;
70 import java.io.FileOutputStream;
71 import java.io.IOException;
72 import java.io.InputStreamReader;
73 import java.io.OutputStreamWriter;
74 import java.io.PrintWriter;
75 import java.lang.annotation.Retention;
76 import java.lang.annotation.RetentionPolicy;
77 import java.nio.charset.StandardCharsets;
78 import java.nio.file.Files;
79 import java.nio.file.Path;
80 import java.util.ArrayList;
81 import java.util.HashMap;
82 import java.util.LinkedList;
83 import java.util.List;
84 import java.util.Map;
85 import java.util.Objects;
86 import java.util.Set;
87 
88 /**
89  * A service that listens to current driving state of the vehicle and maps it to the
90  * appropriate UX restrictions for that driving state.
91  * <p>
92  * <h1>UX Restrictions Configuration</h1>
93  * When this service starts, it will first try reading the configuration set through
94  * {@link #saveUxRestrictionsConfigurationForNextBoot(List)}.
95  * If one is not available, it will try reading the configuration saved in
96  * {@code R.xml.car_ux_restrictions_map}. If XML is somehow unavailable, it will
97  * fall back to a hard-coded configuration.
98  * <p>
99  * <h1>Multi-Display</h1>
100  * Only physical displays that are available at service initialization are recognized.
101  * This service does not support pluggable displays.
102  */
103 public class CarUxRestrictionsManagerService extends ICarUxRestrictionsManager.Stub implements
104         CarServiceBase {
105     private static final String TAG = "CarUxR";
106     private static final boolean DBG = false;
107     private static final int MAX_TRANSITION_LOG_SIZE = 20;
108     private static final int PROPERTY_UPDATE_RATE = 5; // Update rate in Hz
109     private static final float SPEED_NOT_AVAILABLE = -1.0F;
110     private static final byte DEFAULT_PORT = 0;
111 
112     private static final int UNKNOWN_JSON_SCHEMA_VERSION = -1;
113     private static final int JSON_SCHEMA_VERSION_V1 = 1;
114     private static final int JSON_SCHEMA_VERSION_V2 = 2;
115 
116     @IntDef({UNKNOWN_JSON_SCHEMA_VERSION, JSON_SCHEMA_VERSION_V1, JSON_SCHEMA_VERSION_V2})
117     @Retention(RetentionPolicy.SOURCE)
118     private @interface JsonSchemaVersion {}
119 
120     private static final String JSON_NAME_SCHEMA_VERSION = "schema_version";
121     private static final String JSON_NAME_RESTRICTIONS = "restrictions";
122 
123     @VisibleForTesting
124     static final String CONFIG_FILENAME_PRODUCTION = "ux_restrictions_prod_config.json";
125     @VisibleForTesting
126     static final String CONFIG_FILENAME_STAGED = "ux_restrictions_staged_config.json";
127 
128     private final Context mContext;
129     private final DisplayManager mDisplayManager;
130     private final CarDrivingStateService mDrivingStateService;
131     private final CarPropertyService mCarPropertyService;
132     // List of clients listening to UX restriction events.
133     private final List<UxRestrictionsClient> mUxRClients = new ArrayList<>();
134 
135     private float mCurrentMovingSpeed;
136 
137     // Byte represents a physical port for display.
138     private byte mDefaultDisplayPhysicalPort;
139     private final List<Byte> mPhysicalPorts = new ArrayList<>();
140     // This lookup caches the mapping from an int display id
141     // to a byte that represents a physical port.
142     private final Map<Integer, Byte> mPortLookup = new HashMap<>();
143     private Map<Byte, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations;
144     private Map<Byte, CarUxRestrictions> mCurrentUxRestrictions;
145 
146     private String mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
147 
148     // Flag to disable broadcasting UXR changes - for development purposes
149     @GuardedBy("this")
150     private boolean mUxRChangeBroadcastEnabled = true;
151     // For dumpsys logging
152     private final LinkedList<Utils.TransitionLog> mTransitionLogs = new LinkedList<>();
153 
CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService, CarPropertyService propertyService)154     public CarUxRestrictionsManagerService(Context context, CarDrivingStateService drvService,
155             CarPropertyService propertyService) {
156         mContext = context;
157         mDisplayManager = mContext.getSystemService(DisplayManager.class);
158         mDrivingStateService = drvService;
159         mCarPropertyService = propertyService;
160     }
161 
162     @Override
init()163     public synchronized void init() {
164         mDefaultDisplayPhysicalPort = getDefaultDisplayPhysicalPort();
165 
166         initPhysicalPort();
167 
168         // Unrestricted until driving state information is received. During boot up, we don't want
169         // everything to be blocked until data is available from CarPropertyManager.  If we start
170         // driving and we don't get speed or gear information, we have bigger problems.
171         mCurrentUxRestrictions = new HashMap<>();
172         for (byte port : mPhysicalPorts) {
173             mCurrentUxRestrictions.put(port, createUnrestrictedRestrictions());
174         }
175 
176         // Load the prod config, or if there is a staged one, promote that first only if the
177         // current driving state, as provided by the driving state service, is parked.
178         mCarUxRestrictionsConfigurations = convertToMap(loadConfig());
179 
180         // subscribe to driving state changes
181         mDrivingStateService.registerDrivingStateChangeListener(
182                 mICarDrivingStateChangeEventListener);
183         // subscribe to property service for speed
184         mCarPropertyService.registerListener(VehicleProperty.PERF_VEHICLE_SPEED,
185                 PROPERTY_UPDATE_RATE, mICarPropertyEventListener);
186 
187         initializeUxRestrictions();
188     }
189 
190     @Override
getConfigs()191     public List<CarUxRestrictionsConfiguration> getConfigs() {
192         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
193         return new ArrayList<>(mCarUxRestrictionsConfigurations.values());
194     }
195 
196     /**
197      * Loads UX restrictions configurations and returns them.
198      *
199      * <p>Reads config from the following sources in order:
200      * <ol>
201      * <li>saved config set by {@link #saveUxRestrictionsConfigurationForNextBoot(List)};
202      * <li>XML resource config from {@code R.xml.car_ux_restrictions_map};
203      * <li>hardcoded default config.
204      * </ol>
205      *
206      * This method attempts to promote staged config file, which requires getting the current
207      * driving state.
208      */
209     @VisibleForTesting
loadConfig()210     synchronized List<CarUxRestrictionsConfiguration> loadConfig() {
211         promoteStagedConfig();
212         List<CarUxRestrictionsConfiguration> configs;
213 
214         // Production config, if available, is the first choice.
215         File prodConfig = getFile(CONFIG_FILENAME_PRODUCTION);
216         if (prodConfig.exists()) {
217             logd("Attempting to read production config");
218             configs = readPersistedConfig(prodConfig);
219             if (configs != null) {
220                 return configs;
221             }
222         }
223 
224         // XML config is the second choice.
225         logd("Attempting to read config from XML resource");
226         configs = readXmlConfig();
227         if (configs != null) {
228             return configs;
229         }
230 
231         // This should rarely happen.
232         Log.w(TAG, "Creating default config");
233 
234         configs = new ArrayList<>();
235         for (byte port : mPhysicalPorts) {
236             configs.add(createDefaultConfig(port));
237         }
238         return configs;
239     }
240 
getFile(String filename)241     private File getFile(String filename) {
242         SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
243         return new File(systemInterface.getSystemCarDir(), filename);
244     }
245 
246     @Nullable
readXmlConfig()247     private List<CarUxRestrictionsConfiguration> readXmlConfig() {
248         try {
249             return CarUxRestrictionsConfigurationXmlParser.parse(
250                     mContext, R.xml.car_ux_restrictions_map);
251         } catch (IOException | XmlPullParserException e) {
252             Log.e(TAG, "Could not read config from XML resource", e);
253         }
254         return null;
255     }
256 
257     /**
258      * Promotes the staged config to prod, by replacing the prod file. Only do this if the car is
259      * parked to avoid changing the restrictions during a drive.
260      */
promoteStagedConfig()261     private void promoteStagedConfig() {
262         Path stagedConfig = getFile(CONFIG_FILENAME_STAGED).toPath();
263 
264         CarDrivingStateEvent currentDrivingStateEvent =
265                 mDrivingStateService.getCurrentDrivingState();
266         // Only promote staged config when car is parked.
267         if (currentDrivingStateEvent != null
268                 && currentDrivingStateEvent.eventValue == DRIVING_STATE_PARKED
269                 && Files.exists(stagedConfig)) {
270 
271             Path prod = getFile(CONFIG_FILENAME_PRODUCTION).toPath();
272             try {
273                 logd("Attempting to promote stage config");
274                 Files.move(stagedConfig, prod, REPLACE_EXISTING);
275             } catch (IOException e) {
276                 Log.e(TAG, "Could not promote state config", e);
277             }
278         }
279     }
280 
281     // Update current restrictions by getting the current driving state and speed.
initializeUxRestrictions()282     private void initializeUxRestrictions() {
283         CarDrivingStateEvent currentDrivingStateEvent =
284                 mDrivingStateService.getCurrentDrivingState();
285         // if we don't have enough information from the CarPropertyService to compute the UX
286         // restrictions, then leave the UX restrictions unchanged from what it was initialized to
287         // in the constructor.
288         if (currentDrivingStateEvent == null
289                 || currentDrivingStateEvent.eventValue == DRIVING_STATE_UNKNOWN) {
290             return;
291         }
292         int currentDrivingState = currentDrivingStateEvent.eventValue;
293         Float currentSpeed = getCurrentSpeed();
294         if (currentSpeed == SPEED_NOT_AVAILABLE) {
295             return;
296         }
297         // At this point the underlying CarPropertyService has provided us enough information to
298         // compute the UX restrictions that could be potentially different from the initial UX
299         // restrictions.
300         handleDispatchUxRestrictions(currentDrivingState, currentSpeed);
301     }
302 
getCurrentSpeed()303     private Float getCurrentSpeed() {
304         CarPropertyValue value = mCarPropertyService.getProperty(VehicleProperty.PERF_VEHICLE_SPEED,
305                 0);
306         if (value != null) {
307             return (Float) value.getValue();
308         }
309         return SPEED_NOT_AVAILABLE;
310     }
311 
312     @Override
release()313     public synchronized void release() {
314         for (UxRestrictionsClient client : mUxRClients) {
315             client.listenerBinder.unlinkToDeath(client, 0);
316         }
317         mUxRClients.clear();
318         mDrivingStateService.unregisterDrivingStateChangeListener(
319                 mICarDrivingStateChangeEventListener);
320     }
321 
322     // Binder methods
323 
324     /**
325      * Registers a {@link ICarUxRestrictionsChangeListener} to be notified for changes to the UX
326      * restrictions.
327      *
328      * @param listener  Listener to register
329      * @param displayId UX restrictions on this display will be notified.
330      */
331     @Override
registerUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener, int displayId)332     public synchronized void registerUxRestrictionsChangeListener(
333             ICarUxRestrictionsChangeListener listener, int displayId) {
334         if (listener == null) {
335             Log.e(TAG, "registerUxRestrictionsChangeListener(): listener null");
336             throw new IllegalArgumentException("Listener is null");
337         }
338         // If a new client is registering, create a new DrivingStateClient and add it to the list
339         // of listening clients.
340         UxRestrictionsClient client = findUxRestrictionsClient(listener);
341         if (client == null) {
342             client = new UxRestrictionsClient(listener, displayId);
343             try {
344                 listener.asBinder().linkToDeath(client, 0);
345             } catch (RemoteException e) {
346                 Log.e(TAG, "Cannot link death recipient to binder " + e);
347             }
348             mUxRClients.add(client);
349         }
350         return;
351     }
352 
353     /**
354      * Iterates through the list of registered UX Restrictions clients -
355      * {@link UxRestrictionsClient} and finds if the given client is already registered.
356      *
357      * @param listener Listener to look for.
358      * @return the {@link UxRestrictionsClient} if found, null if not
359      */
360     @Nullable
findUxRestrictionsClient( ICarUxRestrictionsChangeListener listener)361     private UxRestrictionsClient findUxRestrictionsClient(
362             ICarUxRestrictionsChangeListener listener) {
363         IBinder binder = listener.asBinder();
364         for (UxRestrictionsClient client : mUxRClients) {
365             if (client.isHoldingBinder(binder)) {
366                 return client;
367             }
368         }
369         return null;
370     }
371 
372     /**
373      * Unregister the given UX Restrictions listener
374      *
375      * @param listener client to unregister
376      */
377     @Override
unregisterUxRestrictionsChangeListener( ICarUxRestrictionsChangeListener listener)378     public synchronized void unregisterUxRestrictionsChangeListener(
379             ICarUxRestrictionsChangeListener listener) {
380         if (listener == null) {
381             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener null");
382             throw new IllegalArgumentException("Listener is null");
383         }
384 
385         UxRestrictionsClient client = findUxRestrictionsClient(listener);
386         if (client == null) {
387             Log.e(TAG, "unregisterUxRestrictionsChangeListener(): listener was not previously "
388                     + "registered");
389             return;
390         }
391         listener.asBinder().unlinkToDeath(client, 0);
392         mUxRClients.remove(client);
393     }
394 
395     /**
396      * Gets the current UX restrictions for a display.
397      *
398      * @param displayId UX restrictions on this display will be returned.
399      */
400     @Override
getCurrentUxRestrictions(int displayId)401     public synchronized CarUxRestrictions getCurrentUxRestrictions(int displayId) {
402         CarUxRestrictions restrictions = mCurrentUxRestrictions.get(getPhysicalPort(displayId));
403         if (restrictions == null) {
404             Log.e(TAG, String.format(
405                     "Restrictions are null for displayId:%d. Returning full restrictions.",
406                     displayId));
407             restrictions = createFullyRestrictedRestrictions();
408         }
409         return restrictions;
410     }
411 
412     /**
413      * Convenience method to retrieve restrictions for default display.
414      */
415     @Nullable
getCurrentUxRestrictions()416     public synchronized CarUxRestrictions getCurrentUxRestrictions() {
417         return getCurrentUxRestrictions(Display.DEFAULT_DISPLAY);
418     }
419 
420     @Override
saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)421     public synchronized boolean saveUxRestrictionsConfigurationForNextBoot(
422             List<CarUxRestrictionsConfiguration> configs) {
423         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
424 
425         validateConfigs(configs);
426 
427         return persistConfig(configs, CONFIG_FILENAME_STAGED);
428     }
429 
430     @Override
431     @Nullable
getStagedConfigs()432     public List<CarUxRestrictionsConfiguration> getStagedConfigs() {
433         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
434 
435         File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
436         if (stagedConfig.exists()) {
437             logd("Attempting to read staged config");
438             return readPersistedConfig(stagedConfig);
439         } else {
440             return null;
441         }
442     }
443 
444     /**
445      * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to
446      * be applied in the same driving state. Restrictions for each mode can be configured through
447      * {@link CarUxRestrictionsConfiguration}.
448      *
449      * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
450      *
451      * @param mode the restriction mode
452      * @return {@code true} if mode was successfully changed; {@code false} otherwise.
453      * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
454      * @see CarUxRestrictionsConfiguration.Builder
455      */
456     @Override
setRestrictionMode(@onNull String mode)457     public synchronized boolean setRestrictionMode(@NonNull String mode) {
458         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
459         Objects.requireNonNull(mode, "mode must not be null");
460 
461         if (mRestrictionMode.equals(mode)) {
462             return true;
463         }
464 
465         addTransitionLog(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
466                 "Restriction mode");
467         mRestrictionMode = mode;
468         logd("Set restriction mode to: " + mode);
469 
470         handleDispatchUxRestrictions(
471                 mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed());
472         return true;
473     }
474 
475     @Override
476     @NonNull
getRestrictionMode()477     public synchronized String getRestrictionMode() {
478         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
479 
480         return mRestrictionMode;
481     }
482 
483     /**
484      * Writes configuration into the specified file.
485      *
486      * IO access on file is not thread safe. Caller should ensure threading protection.
487      */
persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename)488     private boolean persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename) {
489         File file = getFile(filename);
490         AtomicFile stagedFile = new AtomicFile(file);
491         FileOutputStream fos;
492         try {
493             fos = stagedFile.startWrite();
494         } catch (IOException e) {
495             Log.e(TAG, "Could not open file to persist config", e);
496             return false;
497         }
498         try (JsonWriter jsonWriter = new JsonWriter(
499                 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
500             jsonWriter.beginObject();
501             jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2);
502             jsonWriter.name(JSON_NAME_RESTRICTIONS);
503             jsonWriter.beginArray();
504             for (CarUxRestrictionsConfiguration config : configs) {
505                 config.writeJson(jsonWriter);
506             }
507             jsonWriter.endArray();
508             jsonWriter.endObject();
509         } catch (IOException e) {
510             Log.e(TAG, "Could not persist config", e);
511             stagedFile.failWrite(fos);
512             return false;
513         }
514         stagedFile.finishWrite(fos);
515         return true;
516     }
517 
518     @Nullable
readPersistedConfig(File file)519     private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) {
520         if (!file.exists()) {
521             Log.e(TAG, "Could not find config file: " + file.getName());
522             return null;
523         }
524 
525         // Take one pass at the file to check the version and then a second pass to read the
526         // contents. We could assess the version and read in one pass, but we're preferring
527         // clarity over complexity here.
528         int schemaVersion = readFileSchemaVersion(file);
529 
530         AtomicFile configFile = new AtomicFile(file);
531         try (JsonReader reader = new JsonReader(
532                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
533             List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
534             switch (schemaVersion) {
535                 case JSON_SCHEMA_VERSION_V1:
536                     readV1Json(reader, configs);
537                     break;
538                 case JSON_SCHEMA_VERSION_V2:
539                     readV2Json(reader, configs);
540                     break;
541                 default:
542                     Log.e(TAG, "Unable to parse schema for version " + schemaVersion);
543             }
544 
545             return configs;
546         } catch (IOException e) {
547             Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
548         }
549         return null;
550     }
551 
readV1Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)552     private void readV1Json(JsonReader reader,
553             List<CarUxRestrictionsConfiguration> configs) throws IOException {
554         readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1);
555     }
556 
readV2Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)557     private void readV2Json(JsonReader reader,
558             List<CarUxRestrictionsConfiguration> configs) throws IOException {
559         reader.beginObject();
560         while (reader.hasNext()) {
561             String name = reader.nextName();
562             switch (name) {
563                 case JSON_NAME_RESTRICTIONS:
564                     readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2);
565                     break;
566                 default:
567                     reader.skipValue();
568             }
569         }
570         reader.endObject();
571     }
572 
readFileSchemaVersion(File file)573     private int readFileSchemaVersion(File file) {
574         AtomicFile configFile = new AtomicFile(file);
575         try (JsonReader reader = new JsonReader(
576                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
577             List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
578             if (reader.peek() == JsonToken.BEGIN_ARRAY) {
579                 // only schema V1 beings with an array - no need to keep reading
580                 reader.close();
581                 return JSON_SCHEMA_VERSION_V1;
582             } else {
583                 reader.beginObject();
584                 while (reader.hasNext()) {
585                     String name = reader.nextName();
586                     switch (name) {
587                         case JSON_NAME_SCHEMA_VERSION:
588                             int schemaVersion = reader.nextInt();
589                             // got the version, no need to continue reading
590                             reader.close();
591                             return schemaVersion;
592                         default:
593                             reader.skipValue();
594                     }
595                 }
596                 reader.endObject();
597             }
598         } catch (IOException e) {
599             Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
600         }
601         return UNKNOWN_JSON_SCHEMA_VERSION;
602     }
603 
readRestrictionsArray(JsonReader reader, List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)604     private void readRestrictionsArray(JsonReader reader,
605             List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)
606             throws IOException {
607         reader.beginArray();
608         while (reader.hasNext()) {
609             configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion));
610         }
611         reader.endArray();
612     }
613 
614     /**
615      * Enable/disable UX restrictions change broadcast blocking.
616      * Setting this to true will stop broadcasts of UX restriction change to listeners.
617      * This method works only on debug builds and the caller of this method needs to have the same
618      * signature of the car service.
619      */
setUxRChangeBroadcastEnabled(boolean enable)620     public synchronized void setUxRChangeBroadcastEnabled(boolean enable) {
621         if (!isDebugBuild()) {
622             Log.e(TAG, "Cannot set UX restriction change broadcast.");
623             return;
624         }
625         // Check if the caller has the same signature as that of the car service.
626         if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid())
627                 != PackageManager.SIGNATURE_MATCH) {
628             throw new SecurityException(
629                     "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())
630                             + " does not have the right signature");
631         }
632         if (enable) {
633             // if enabling it back, send the current restrictions
634             mUxRChangeBroadcastEnabled = enable;
635             handleDispatchUxRestrictions(mDrivingStateService.getCurrentDrivingState().eventValue,
636                     getCurrentSpeed());
637         } else {
638             // fake parked state, so if the system is currently restricted, the restrictions are
639             // relaxed.
640             handleDispatchUxRestrictions(DRIVING_STATE_PARKED, 0);
641             mUxRChangeBroadcastEnabled = enable;
642         }
643     }
644 
isDebugBuild()645     private boolean isDebugBuild() {
646         return Build.IS_USERDEBUG || Build.IS_ENG;
647     }
648 
649     /**
650      * Class that holds onto client related information - listener interface, process that hosts the
651      * binder object etc.
652      * It also registers for death notifications of the host.
653      */
654     private class UxRestrictionsClient implements IBinder.DeathRecipient {
655         private final IBinder listenerBinder;
656         private final ICarUxRestrictionsChangeListener listener;
657         private final int mDisplayId;
658 
UxRestrictionsClient(ICarUxRestrictionsChangeListener l, int displayId)659         UxRestrictionsClient(ICarUxRestrictionsChangeListener l, int displayId) {
660             listener = l;
661             listenerBinder = l.asBinder();
662             mDisplayId = displayId;
663         }
664 
665         @Override
binderDied()666         public void binderDied() {
667             logd("Binder died " + listenerBinder);
668             listenerBinder.unlinkToDeath(this, 0);
669             synchronized (CarUxRestrictionsManagerService.this) {
670                 mUxRClients.remove(this);
671             }
672         }
673 
674         /**
675          * Returns if the given binder object matches to what this client info holds.
676          * Used to check if the listener asking to be registered is already registered.
677          *
678          * @return true if matches, false if not
679          */
isHoldingBinder(IBinder binder)680         public boolean isHoldingBinder(IBinder binder) {
681             return listenerBinder == binder;
682         }
683 
684         /**
685          * Dispatch the event to the listener
686          *
687          * @param event {@link CarUxRestrictions}.
688          */
dispatchEventToClients(CarUxRestrictions event)689         public void dispatchEventToClients(CarUxRestrictions event) {
690             if (event == null) {
691                 return;
692             }
693             try {
694                 listener.onUxRestrictionsChanged(event);
695             } catch (RemoteException e) {
696                 Log.e(TAG, "Dispatch to listener failed", e);
697             }
698         }
699     }
700 
701     @Override
dump(PrintWriter writer)702     public void dump(PrintWriter writer) {
703         writer.println("*CarUxRestrictionsManagerService*");
704         for (byte port : mCurrentUxRestrictions.keySet()) {
705             CarUxRestrictions restrictions = mCurrentUxRestrictions.get(port);
706             writer.printf("Port: 0x%02X UXR: %s\n", port, restrictions.toString());
707         }
708         if (isDebugBuild()) {
709             writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled);
710         }
711 
712         writer.println("UX Restriction configurations:");
713         for (CarUxRestrictionsConfiguration config : mCarUxRestrictionsConfigurations.values()) {
714             config.dump(writer);
715         }
716         writer.println("UX Restriction change log:");
717         for (Utils.TransitionLog tlog : mTransitionLogs) {
718             writer.println(tlog);
719         }
720     }
721 
722     /**
723      * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
724      * for getting driving state change notifications.
725      */
726     private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
727             new ICarDrivingStateChangeListener.Stub() {
728                 @Override
729                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
730                     logd("Driving State Changed:" + event.eventValue);
731                     handleDrivingStateEvent(event);
732                 }
733             };
734 
735     /**
736      * Handle the driving state change events coming from the {@link CarDrivingStateService}.
737      * Map the driving state to the corresponding UX Restrictions and dispatch the
738      * UX Restriction change to the registered clients.
739      */
handleDrivingStateEvent(CarDrivingStateEvent event)740     private synchronized void handleDrivingStateEvent(CarDrivingStateEvent event) {
741         if (event == null) {
742             return;
743         }
744         int drivingState = event.eventValue;
745         Float speed = getCurrentSpeed();
746 
747         if (speed != SPEED_NOT_AVAILABLE) {
748             mCurrentMovingSpeed = speed;
749         } else if (drivingState == DRIVING_STATE_PARKED
750                 || drivingState == DRIVING_STATE_UNKNOWN) {
751             // If speed is unavailable, but the driving state is parked or unknown, it can still be
752             // handled.
753             logd("Speed null when driving state is: " + drivingState);
754             mCurrentMovingSpeed = 0;
755         } else {
756             // If we get here with driving state != parked or unknown && speed == null,
757             // something is wrong.  CarDrivingStateService could not have inferred idling or moving
758             // when speed is not available
759             Log.e(TAG, "Unexpected:  Speed null when driving state is: " + drivingState);
760             return;
761         }
762         handleDispatchUxRestrictions(drivingState, mCurrentMovingSpeed);
763     }
764 
765     /**
766      * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
767      * speed change notifications.
768      */
769     private final ICarPropertyEventListener mICarPropertyEventListener =
770             new ICarPropertyEventListener.Stub() {
771                 @Override
772                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
773                     for (CarPropertyEvent event : events) {
774                         if ((event.getEventType()
775                                 == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
776                                 && (event.getCarPropertyValue().getPropertyId()
777                                 == VehicleProperty.PERF_VEHICLE_SPEED)) {
778                             handleSpeedChange((Float) event.getCarPropertyValue().getValue());
779                         }
780                     }
781                 }
782             };
783 
handleSpeedChange(float newSpeed)784     private synchronized void handleSpeedChange(float newSpeed) {
785         if (newSpeed == mCurrentMovingSpeed) {
786             // Ignore if speed hasn't changed
787             return;
788         }
789         int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
790         if (currentDrivingState != DRIVING_STATE_MOVING) {
791             // Ignore speed changes if the vehicle is not moving
792             return;
793         }
794         mCurrentMovingSpeed = newSpeed;
795         handleDispatchUxRestrictions(currentDrivingState, newSpeed);
796     }
797 
798     /**
799      * Handle dispatching UX restrictions change.
800      *
801      * @param currentDrivingState driving state of the vehicle
802      * @param speed               speed of the vehicle
803      */
handleDispatchUxRestrictions(@arDrivingState int currentDrivingState, float speed)804     private synchronized void handleDispatchUxRestrictions(@CarDrivingState int currentDrivingState,
805             float speed) {
806         Preconditions.checkNotNull(mCarUxRestrictionsConfigurations,
807                 "mCarUxRestrictionsConfigurations must be initialized");
808         Preconditions.checkNotNull(mCurrentUxRestrictions,
809                 "mCurrentUxRestrictions must be initialized");
810 
811         if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
812             Log.d(TAG, "Not dispatching UX Restriction due to setting");
813             return;
814         }
815 
816         Map<Byte, CarUxRestrictions> newUxRestrictions = new HashMap<>();
817         for (byte port : mPhysicalPorts) {
818             CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get(port);
819             if (config == null) {
820                 continue;
821             }
822 
823             CarUxRestrictions uxRestrictions = config.getUxRestrictions(
824                     currentDrivingState, speed, mRestrictionMode);
825             logd(String.format("Display port 0x%02x\tDO old->new: %b -> %b",
826                     port,
827                     mCurrentUxRestrictions.get(port).isRequiresDistractionOptimization(),
828                     uxRestrictions.isRequiresDistractionOptimization()));
829             logd(String.format("Display port 0x%02x\tUxR old->new: 0x%x -> 0x%x",
830                     port,
831                     mCurrentUxRestrictions.get(port).getActiveRestrictions(),
832                     uxRestrictions.getActiveRestrictions()));
833             newUxRestrictions.put(port, uxRestrictions);
834         }
835 
836         // Ignore dispatching if the restrictions has not changed.
837         Set<Byte> displayToDispatch = new ArraySet<>();
838         for (byte port : newUxRestrictions.keySet()) {
839             if (!mCurrentUxRestrictions.containsKey(port)) {
840                 // This should never happen.
841                 Log.wtf(TAG, "Unrecognized port:" + port);
842                 continue;
843             }
844             CarUxRestrictions uxRestrictions = newUxRestrictions.get(port);
845             if (!mCurrentUxRestrictions.get(port).isSameRestrictions(uxRestrictions)) {
846                 displayToDispatch.add(port);
847             }
848         }
849         if (displayToDispatch.isEmpty()) {
850             return;
851         }
852 
853         for (byte port : displayToDispatch) {
854             addTransitionLog(
855                     mCurrentUxRestrictions.get(port), newUxRestrictions.get(port));
856         }
857 
858         logd("dispatching to clients");
859         for (UxRestrictionsClient client : mUxRClients) {
860             Byte clientDisplayPort = getPhysicalPort(client.mDisplayId);
861             if (clientDisplayPort == null) {
862                 clientDisplayPort = mDefaultDisplayPhysicalPort;
863             }
864             if (displayToDispatch.contains(clientDisplayPort)) {
865                 client.dispatchEventToClients(newUxRestrictions.get(clientDisplayPort));
866             }
867         }
868 
869         mCurrentUxRestrictions = newUxRestrictions;
870     }
871 
getDefaultDisplayPhysicalPort()872     private byte getDefaultDisplayPhysicalPort() {
873         Display defaultDisplay = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
874         DisplayAddress.Physical address = (DisplayAddress.Physical) defaultDisplay.getAddress();
875 
876         if (address == null) {
877             Log.w(TAG, "Default display does not have physical display port.");
878             return DEFAULT_PORT;
879         }
880         return address.getPort();
881     }
882 
initPhysicalPort()883     private void initPhysicalPort() {
884         for (Display display : mDisplayManager.getDisplays()) {
885             if (display.getType() == Display.TYPE_VIRTUAL) {
886                 continue;
887             }
888 
889             if (display.getDisplayId() == Display.DEFAULT_DISPLAY && display.getAddress() == null) {
890                 // Assume default display is a physical display so assign an address if it
891                 // does not have one (possibly due to lower graphic driver version).
892                 if (Log.isLoggable(TAG, Log.INFO)) {
893                     Log.i(TAG, "Default display does not have display address. Using default.");
894                 }
895                 mPhysicalPorts.add(mDefaultDisplayPhysicalPort);
896             } else if (display.getAddress() instanceof DisplayAddress.Physical) {
897                 byte port = ((DisplayAddress.Physical) display.getAddress()).getPort();
898                 if (Log.isLoggable(TAG, Log.INFO)) {
899                     Log.i(TAG, String.format(
900                             "Display %d uses port %d", display.getDisplayId(), port));
901                 }
902                 mPhysicalPorts.add(port);
903             } else {
904                 Log.w(TAG, "At init non-virtual display has a non-physical display address: "
905                         + display);
906             }
907         }
908     }
909 
convertToMap( List<CarUxRestrictionsConfiguration> configs)910     private Map<Byte, CarUxRestrictionsConfiguration> convertToMap(
911             List<CarUxRestrictionsConfiguration> configs) {
912         validateConfigs(configs);
913 
914         Map<Byte, CarUxRestrictionsConfiguration> result = new HashMap<>();
915         if (configs.size() == 1) {
916             CarUxRestrictionsConfiguration config = configs.get(0);
917             byte port = config.getPhysicalPort() == null
918                     ? mDefaultDisplayPhysicalPort
919                     : config.getPhysicalPort();
920             result.put(port, config);
921         } else {
922             for (CarUxRestrictionsConfiguration config : configs) {
923                 result.put(config.getPhysicalPort(), config);
924             }
925         }
926         return result;
927     }
928 
929     /**
930      * Validates configs for multi-display:
931      * - share the same restrictions parameters;
932      * - each sets display port;
933      * - each has unique display port.
934      */
935     @VisibleForTesting
validateConfigs(List<CarUxRestrictionsConfiguration> configs)936     void validateConfigs(List<CarUxRestrictionsConfiguration> configs) {
937         if (configs.size() == 0) {
938             throw new IllegalArgumentException("Empty configuration.");
939         }
940 
941         if (configs.size() == 1) {
942             return;
943         }
944 
945         CarUxRestrictionsConfiguration first = configs.get(0);
946         Set<Byte> existingPorts = new ArraySet<>();
947         for (CarUxRestrictionsConfiguration config : configs) {
948             if (!config.hasSameParameters(first)) {
949                 // Input should have the same restriction parameters because:
950                 // - it doesn't make sense otherwise; and
951                 // - in format it matches how xml can only specify one set of parameters.
952                 throw new IllegalArgumentException(
953                         "Configurations should have the same restrictions parameters.");
954             }
955 
956             Byte port = config.getPhysicalPort();
957             if (port == null) {
958                 // Size was checked above; safe to assume there are multiple configs.
959                 throw new IllegalArgumentException(
960                         "Input contains multiple configurations; each must set physical port.");
961             }
962             if (existingPorts.contains(port)) {
963                 throw new IllegalArgumentException("Multiple configurations for port " + port);
964             }
965 
966             existingPorts.add(port);
967         }
968     }
969 
970     /**
971      * Returns the physical port byte id for the display or {@code null} if {@link
972      * DisplayManager#getDisplay(int)} is not aware of the provided id.
973      */
974     @Nullable
getPhysicalPort(int displayId)975     private Byte getPhysicalPort(int displayId) {
976         if (!mPortLookup.containsKey(displayId)) {
977             Display display = mDisplayManager.getDisplay(displayId);
978             if (display == null) {
979                 Log.w(TAG, "Could not retrieve display for id: " + displayId);
980                 return null;
981             }
982             byte port = getPhysicalPort(display);
983             mPortLookup.put(displayId, port);
984         }
985         return mPortLookup.get(displayId);
986     }
987 
getPhysicalPort(@onNull Display display)988     private byte getPhysicalPort(@NonNull Display display) {
989         if (display.getType() == Display.TYPE_VIRTUAL) {
990             // We require all virtual displays to be launched on default display.
991             return mDefaultDisplayPhysicalPort;
992         }
993 
994         DisplayAddress address = display.getAddress();
995         if (address == null) {
996             Log.e(TAG, "Display " + display
997                     + " is not a virtual display but has null DisplayAddress.");
998             return mDefaultDisplayPhysicalPort;
999         } else if (!(address instanceof DisplayAddress.Physical)) {
1000             Log.e(TAG, "Display " + display + " has non-physical address: " + address);
1001             return mDefaultDisplayPhysicalPort;
1002         } else {
1003             return ((DisplayAddress.Physical) address).getPort();
1004         }
1005     }
1006 
createUnrestrictedRestrictions()1007     private CarUxRestrictions createUnrestrictedRestrictions() {
1008         return new CarUxRestrictions.Builder(/* reqOpt= */ false,
1009                 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos())
1010                 .build();
1011     }
1012 
createFullyRestrictedRestrictions()1013     private CarUxRestrictions createFullyRestrictedRestrictions() {
1014         return new CarUxRestrictions.Builder(
1015                 /*reqOpt= */ true,
1016                 CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED,
1017                 SystemClock.elapsedRealtimeNanos()).build();
1018     }
1019 
createDefaultConfig(byte port)1020     CarUxRestrictionsConfiguration createDefaultConfig(byte port) {
1021         return new CarUxRestrictionsConfiguration.Builder()
1022                 .setPhysicalPort(port)
1023                 .setUxRestrictions(DRIVING_STATE_PARKED,
1024                         false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
1025                 .setUxRestrictions(DRIVING_STATE_IDLING,
1026                         false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
1027                 .setUxRestrictions(DRIVING_STATE_MOVING,
1028                         true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
1029                 .setUxRestrictions(DRIVING_STATE_UNKNOWN,
1030                         true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
1031                 .build();
1032     }
1033 
addTransitionLog(String name, String from, String to, long timestamp, String extra)1034     private void addTransitionLog(String name, String from, String to, long timestamp,
1035             String extra) {
1036         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
1037             mTransitionLogs.remove();
1038         }
1039 
1040         Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp, extra);
1041         mTransitionLogs.add(tLog);
1042     }
1043 
addTransitionLog( CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions)1044     private void addTransitionLog(
1045             CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions) {
1046         if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
1047             mTransitionLogs.remove();
1048         }
1049         StringBuilder extra = new StringBuilder();
1050         extra.append(oldRestrictions.isRequiresDistractionOptimization() ? "DO -> " : "No DO -> ");
1051         extra.append(newRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO");
1052 
1053         Utils.TransitionLog tLog = new Utils.TransitionLog(TAG,
1054                 oldRestrictions.getActiveRestrictions(), newRestrictions.getActiveRestrictions(),
1055                 System.currentTimeMillis(), extra.toString());
1056         mTransitionLogs.add(tLog);
1057     }
1058 
logd(String msg)1059     private static void logd(String msg) {
1060         if (DBG) {
1061             Slog.d(TAG, msg);
1062         }
1063     }
1064 }
1065