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