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.JsonReader;
58 import android.util.JsonToken;
59 import android.util.JsonWriter;
60 import android.util.Log;
61 import android.util.Slog;
62 import android.util.SparseArray;
63 import android.view.Display;
64 import android.view.DisplayAddress;
65 
66 import com.android.car.systeminterface.SystemInterface;
67 import com.android.internal.annotations.GuardedBy;
68 import com.android.internal.annotations.VisibleForTesting;
69 
70 import org.xmlpull.v1.XmlPullParserException;
71 
72 import java.io.File;
73 import java.io.FileOutputStream;
74 import java.io.IOException;
75 import java.io.InputStreamReader;
76 import java.io.OutputStreamWriter;
77 import java.io.PrintWriter;
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 = "CarUxR";
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 byte 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 Byte mPhysicalPort;
146 
RemoteCallbackListCookie(Byte physicalPort)147         RemoteCallbackListCookie(Byte 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 a byte that represents a physical
156      * port. It includes mappings for virtual displays.
157      */
158     @GuardedBy("mLock")
159     private final Map<Integer, Byte> mPortLookup = new HashMap<>();
160 
161     @GuardedBy("mLock")
162     private Map<Byte, CarUxRestrictionsConfiguration> mCarUxRestrictionsConfigurations;
163 
164     @GuardedBy("mLock")
165     private Map<Byte, CarUxRestrictions> mCurrentUxRestrictions;
166 
167     @GuardedBy("mLock")
168     private String mRestrictionMode = UX_RESTRICTION_MODE_BASELINE;
169 
170     @GuardedBy("mLock")
171     private float mCurrentMovingSpeed;
172 
173     // Byte represents a physical port for display.
174     @GuardedBy("mLock")
175     private byte mDefaultDisplayPhysicalPort;
176 
177     @GuardedBy("mLock")
178     private final List<Byte> 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 (byte 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         Log.w(TAG, "Creating default config");
272 
273         configs = new ArrayList<>();
274         synchronized (mLock) {
275             for (byte 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             Log.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                 Log.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.getProperty(VehicleProperty.PERF_VEHICLE_SPEED,
348                 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             Log.e(TAG, "registerUxRestrictionsChangeListener(): listener null");
387             throw new IllegalArgumentException("Listener is null");
388         }
389         Byte 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             Log.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             restrictions = mCurrentUxRestrictions.get(getPhysicalPortLocked(displayId));
424         }
425         if (restrictions == null) {
426             Log.e(TAG, String.format(
427                     "Restrictions are null for displayId:%d. Returning full restrictions.",
428                     displayId));
429             restrictions = createFullyRestrictedRestrictions();
430         }
431         return restrictions;
432     }
433 
434     /**
435      * Convenience method to retrieve restrictions for default display.
436      */
437     @Nullable
getCurrentUxRestrictions()438     public CarUxRestrictions getCurrentUxRestrictions() {
439         return getCurrentUxRestrictions(Display.DEFAULT_DISPLAY);
440     }
441 
442     @Override
saveUxRestrictionsConfigurationForNextBoot( List<CarUxRestrictionsConfiguration> configs)443     public boolean saveUxRestrictionsConfigurationForNextBoot(
444             List<CarUxRestrictionsConfiguration> configs) {
445         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
446 
447         validateConfigs(configs);
448 
449         return persistConfig(configs, CONFIG_FILENAME_STAGED);
450     }
451 
452     @Override
453     @Nullable
getStagedConfigs()454     public List<CarUxRestrictionsConfiguration> getStagedConfigs() {
455         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
456 
457         File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
458         if (stagedConfig.exists()) {
459             logd("Attempting to read staged config");
460             return readPersistedConfig(stagedConfig);
461         } else {
462             return null;
463         }
464     }
465 
466     /**
467      * Sets the restriction mode to use. Restriction mode allows a different set of restrictions to
468      * be applied in the same driving state. Restrictions for each mode can be configured through
469      * {@link CarUxRestrictionsConfiguration}.
470      *
471      * <p>Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
472      *
473      * @param mode the restriction mode
474      * @return {@code true} if mode was successfully changed; {@code false} otherwise.
475      * @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
476      * @see CarUxRestrictionsConfiguration.Builder
477      */
478     @Override
setRestrictionMode(@onNull String mode)479     public boolean setRestrictionMode(@NonNull String mode) {
480         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
481         Objects.requireNonNull(mode, "mode must not be null");
482 
483         synchronized (mLock) {
484             if (mRestrictionMode.equals(mode)) {
485                 return true;
486             }
487 
488             addTransitionLogLocked(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
489                     "Restriction mode");
490             mRestrictionMode = mode;
491             logd("Set restriction mode to: " + mode);
492 
493             handleDispatchUxRestrictionsLocked(
494                     mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed());
495         }
496         return true;
497     }
498 
499     @Override
500     @NonNull
getRestrictionMode()501     public String getRestrictionMode() {
502         ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
503 
504         synchronized (mLock) {
505             return mRestrictionMode;
506         }
507     }
508 
509     /**
510      * Writes configuration into the specified file.
511      *
512      * IO access on file is not thread safe. Caller should ensure threading protection.
513      */
persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename)514     private boolean persistConfig(List<CarUxRestrictionsConfiguration> configs, String filename) {
515         File file = getFile(filename);
516         AtomicFile stagedFile = new AtomicFile(file);
517         FileOutputStream fos;
518         try {
519             fos = stagedFile.startWrite();
520         } catch (IOException e) {
521             Log.e(TAG, "Could not open file to persist config", e);
522             return false;
523         }
524         try (JsonWriter jsonWriter = new JsonWriter(
525                 new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
526             writeJson(jsonWriter, configs);
527         } catch (IOException e) {
528             Log.e(TAG, "Could not persist config", e);
529             stagedFile.failWrite(fos);
530             return false;
531         }
532         stagedFile.finishWrite(fos);
533         return true;
534     }
535 
536     @VisibleForTesting
writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)537     void writeJson(JsonWriter jsonWriter, List<CarUxRestrictionsConfiguration> configs)
538             throws IOException {
539         jsonWriter.beginObject();
540         jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2);
541         jsonWriter.name(JSON_NAME_RESTRICTIONS);
542         jsonWriter.beginArray();
543         for (CarUxRestrictionsConfiguration config : configs) {
544             config.writeJson(jsonWriter);
545         }
546         jsonWriter.endArray();
547         jsonWriter.endObject();
548     }
549 
550     @Nullable
readPersistedConfig(File file)551     private List<CarUxRestrictionsConfiguration> readPersistedConfig(File file) {
552         if (!file.exists()) {
553             Log.e(TAG, "Could not find config file: " + file.getName());
554             return null;
555         }
556 
557         // Take one pass at the file to check the version and then a second pass to read the
558         // contents. We could assess the version and read in one pass, but we're preferring
559         // clarity over complexity here.
560         int schemaVersion = readFileSchemaVersion(file);
561 
562         AtomicFile configFile = new AtomicFile(file);
563         try (JsonReader reader = new JsonReader(
564                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
565             List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
566             switch (schemaVersion) {
567                 case JSON_SCHEMA_VERSION_V1:
568                     readV1Json(reader, configs);
569                     break;
570                 case JSON_SCHEMA_VERSION_V2:
571                     readV2Json(reader, configs);
572                     break;
573                 default:
574                     Log.e(TAG, "Unable to parse schema for version " + schemaVersion);
575             }
576 
577             return configs;
578         } catch (IOException e) {
579             Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
580         }
581         return null;
582     }
583 
readV1Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)584     private void readV1Json(JsonReader reader,
585             List<CarUxRestrictionsConfiguration> configs) throws IOException {
586         readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1);
587     }
588 
readV2Json(JsonReader reader, List<CarUxRestrictionsConfiguration> configs)589     private void readV2Json(JsonReader reader,
590             List<CarUxRestrictionsConfiguration> configs) throws IOException {
591         reader.beginObject();
592         while (reader.hasNext()) {
593             String name = reader.nextName();
594             switch (name) {
595                 case JSON_NAME_RESTRICTIONS:
596                     readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2);
597                     break;
598                 default:
599                     reader.skipValue();
600             }
601         }
602         reader.endObject();
603     }
604 
readFileSchemaVersion(File file)605     private int readFileSchemaVersion(File file) {
606         AtomicFile configFile = new AtomicFile(file);
607         try (JsonReader reader = new JsonReader(
608                 new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
609             List<CarUxRestrictionsConfiguration> configs = new ArrayList<>();
610             if (reader.peek() == JsonToken.BEGIN_ARRAY) {
611                 // only schema V1 beings with an array - no need to keep reading
612                 reader.close();
613                 return JSON_SCHEMA_VERSION_V1;
614             } else {
615                 reader.beginObject();
616                 while (reader.hasNext()) {
617                     String name = reader.nextName();
618                     switch (name) {
619                         case JSON_NAME_SCHEMA_VERSION:
620                             int schemaVersion = reader.nextInt();
621                             // got the version, no need to continue reading
622                             reader.close();
623                             return schemaVersion;
624                         default:
625                             reader.skipValue();
626                     }
627                 }
628                 reader.endObject();
629             }
630         } catch (IOException e) {
631             Log.e(TAG, "Could not read persisted config file " + file.getName(), e);
632         }
633         return UNKNOWN_JSON_SCHEMA_VERSION;
634     }
635 
readRestrictionsArray(JsonReader reader, List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)636     private void readRestrictionsArray(JsonReader reader,
637             List<CarUxRestrictionsConfiguration> configs, @JsonSchemaVersion int schemaVersion)
638             throws IOException {
639         reader.beginArray();
640         while (reader.hasNext()) {
641             configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion));
642         }
643         reader.endArray();
644     }
645 
646     /**
647      * Enable/disable UX restrictions change broadcast blocking.
648      * Setting this to true will stop broadcasts of UX restriction change to listeners.
649      * This method works only on debug builds and the caller of this method needs to have the same
650      * signature of the car service.
651      */
setUxRChangeBroadcastEnabled(boolean enable)652     public void setUxRChangeBroadcastEnabled(boolean enable) {
653         if (!isDebugBuild()) {
654             Log.e(TAG, "Cannot set UX restriction change broadcast.");
655             return;
656         }
657         // Check if the caller has the same signature as that of the car service.
658         if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid())
659                 != PackageManager.SIGNATURE_MATCH) {
660             throw new SecurityException(
661                     "Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())
662                             + " does not have the right signature");
663         }
664 
665         synchronized (mLock) {
666             if (enable) {
667                 // if enabling it back, send the current restrictions
668                 mUxRChangeBroadcastEnabled = enable;
669                 handleDispatchUxRestrictionsLocked(
670                         mDrivingStateService.getCurrentDrivingState().eventValue,
671                         getCurrentSpeed());
672             } else {
673                 // fake parked state, so if the system is currently restricted, the restrictions are
674                 // relaxed.
675                 handleDispatchUxRestrictionsLocked(DRIVING_STATE_PARKED, 0);
676                 mUxRChangeBroadcastEnabled = enable;
677             }
678         }
679     }
680 
isDebugBuild()681     private boolean isDebugBuild() {
682         return Build.IS_USERDEBUG || Build.IS_ENG;
683     }
684 
685     @Override
dump(PrintWriter writer)686     public void dump(PrintWriter writer) {
687         synchronized (mLock) {
688             writer.println("*CarUxRestrictionsManagerService*");
689             mUxRClients.dump(writer, "UX Restrictions Clients ");
690             for (byte port : mCurrentUxRestrictions.keySet()) {
691                 CarUxRestrictions restrictions = mCurrentUxRestrictions.get(port);
692                 writer.printf("Port: 0x%02X UXR: %s\n", port, restrictions.toString());
693             }
694             if (isDebugBuild()) {
695                 writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled);
696             }
697             writer.println("UX Restriction configurations:");
698             for (CarUxRestrictionsConfiguration config :
699                     mCarUxRestrictionsConfigurations.values()) {
700                 config.dump(writer);
701             }
702             writer.println("UX Restriction change log:");
703             for (Utils.TransitionLog tlog : mTransitionLogs) {
704                 writer.println(tlog);
705             }
706             writer.println("UX Restriction display info:");
707             for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
708                 DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
709                 writer.printf("Display%d: physicalDisplayId=%d, owner=%s\n",
710                         mActivityViewDisplayInfoMap.keyAt(i), info.mPhysicalDisplayId, info.mOwner);
711             }
712         }
713     }
714 
715     /**
716      * {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
717      * for getting driving state change notifications.
718      */
719     private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
720             new ICarDrivingStateChangeListener.Stub() {
721                 @Override
722                 public void onDrivingStateChanged(CarDrivingStateEvent event) {
723                     logd("Driving State Changed:" + event.eventValue);
724                     synchronized (mLock) {
725                         handleDrivingStateEventLocked(event);
726                     }
727                 }
728             };
729 
730     /**
731      * Handle the driving state change events coming from the {@link CarDrivingStateService}.
732      * Map the driving state to the corresponding UX Restrictions and dispatch the
733      * UX Restriction change to the registered clients.
734      */
735     @VisibleForTesting
736     @GuardedBy("mLock")
handleDrivingStateEventLocked(CarDrivingStateEvent event)737     void handleDrivingStateEventLocked(CarDrivingStateEvent event) {
738         if (event == null) {
739             return;
740         }
741         int drivingState = event.eventValue;
742         Float speed = getCurrentSpeed();
743 
744         if (speed != SPEED_NOT_AVAILABLE) {
745             mCurrentMovingSpeed = speed;
746         } else if (drivingState == DRIVING_STATE_PARKED
747                 || drivingState == DRIVING_STATE_UNKNOWN) {
748             // If speed is unavailable, but the driving state is parked or unknown, it can still be
749             // handled.
750             logd("Speed null when driving state is: " + drivingState);
751             mCurrentMovingSpeed = 0;
752         } else {
753             // If we get here with driving state != parked or unknown && speed == null,
754             // something is wrong.  CarDrivingStateService could not have inferred idling or moving
755             // when speed is not available
756             Log.e(TAG, "Unexpected:  Speed null when driving state is: " + drivingState);
757             return;
758         }
759         handleDispatchUxRestrictionsLocked(drivingState, mCurrentMovingSpeed);
760     }
761 
762     /**
763      * {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
764      * speed change notifications.
765      */
766     private final ICarPropertyEventListener mICarPropertyEventListener =
767             new ICarPropertyEventListener.Stub() {
768                 @Override
769                 public void onEvent(List<CarPropertyEvent> events) throws RemoteException {
770                     synchronized (mLock) {
771                         for (CarPropertyEvent event : events) {
772                             if ((event.getEventType()
773                                     == CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
774                                     && (event.getCarPropertyValue().getPropertyId()
775                                     == VehicleProperty.PERF_VEHICLE_SPEED)) {
776                                 handleSpeedChangeLocked(
777                                         (Float) event.getCarPropertyValue().getValue());
778                             }
779                         }
780                     }
781                 }
782             };
783 
784     @GuardedBy("mLock")
handleSpeedChangeLocked(float newSpeed)785     private void handleSpeedChangeLocked(float newSpeed) {
786         if (newSpeed == mCurrentMovingSpeed) {
787             // Ignore if speed hasn't changed
788             return;
789         }
790         int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
791         if (currentDrivingState != DRIVING_STATE_MOVING) {
792             // Ignore speed changes if the vehicle is not moving
793             return;
794         }
795         mCurrentMovingSpeed = newSpeed;
796         handleDispatchUxRestrictionsLocked(currentDrivingState, newSpeed);
797     }
798 
799     /**
800      * Handle dispatching UX restrictions change.
801      *
802      * @param currentDrivingState driving state of the vehicle
803      * @param speed               speed of the vehicle
804      */
805     @GuardedBy("mLock")
handleDispatchUxRestrictionsLocked(@arDrivingState int currentDrivingState, float speed)806     private void handleDispatchUxRestrictionsLocked(@CarDrivingState int currentDrivingState,
807             float speed) {
808         Objects.requireNonNull(mCarUxRestrictionsConfigurations,
809                 "mCarUxRestrictionsConfigurations must be initialized");
810         Objects.requireNonNull(mCurrentUxRestrictions,
811                 "mCurrentUxRestrictions must be initialized");
812 
813         if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
814             Log.d(TAG, "Not dispatching UX Restriction due to setting");
815             return;
816         }
817 
818         Map<Byte, CarUxRestrictions> newUxRestrictions = new HashMap<>();
819         for (byte port : mPhysicalPorts) {
820             CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get(port);
821             if (config == null) {
822                 continue;
823             }
824 
825             CarUxRestrictions uxRestrictions = config.getUxRestrictions(
826                     currentDrivingState, speed, mRestrictionMode);
827             logd(String.format("Display port 0x%02x\tDO old->new: %b -> %b",
828                     port,
829                     mCurrentUxRestrictions.get(port).isRequiresDistractionOptimization(),
830                     uxRestrictions.isRequiresDistractionOptimization()));
831             logd(String.format("Display port 0x%02x\tUxR old->new: 0x%x -> 0x%x",
832                     port,
833                     mCurrentUxRestrictions.get(port).getActiveRestrictions(),
834                     uxRestrictions.getActiveRestrictions()));
835             newUxRestrictions.put(port, uxRestrictions);
836         }
837 
838         // Ignore dispatching if the restrictions has not changed.
839         Set<Byte> displayToDispatch = new ArraySet<>();
840         for (byte port : newUxRestrictions.keySet()) {
841             if (!mCurrentUxRestrictions.containsKey(port)) {
842                 // This should never happen.
843                 Log.wtf(TAG, "Unrecognized port:" + port);
844                 continue;
845             }
846             CarUxRestrictions uxRestrictions = newUxRestrictions.get(port);
847             if (!mCurrentUxRestrictions.get(port).isSameRestrictions(uxRestrictions)) {
848                 displayToDispatch.add(port);
849             }
850         }
851         if (displayToDispatch.isEmpty()) {
852             return;
853         }
854 
855         for (byte port : displayToDispatch) {
856             addTransitionLogLocked(
857                     mCurrentUxRestrictions.get(port), newUxRestrictions.get(port));
858         }
859 
860         dispatchRestrictionsToClients(newUxRestrictions, displayToDispatch);
861 
862         mCurrentUxRestrictions = newUxRestrictions;
863     }
864 
dispatchRestrictionsToClients(Map<Byte, CarUxRestrictions> displayRestrictions, Set<Byte> displayToDispatch)865     private void dispatchRestrictionsToClients(Map<Byte, CarUxRestrictions> displayRestrictions,
866             Set<Byte> displayToDispatch) {
867         logd("dispatching to clients");
868         boolean success = mClientDispatchHandler.post(() -> {
869             int numClients = mUxRClients.beginBroadcast();
870             for (int i = 0; i < numClients; i++) {
871                 ICarUxRestrictionsChangeListener callback = mUxRClients.getBroadcastItem(i);
872                 RemoteCallbackListCookie cookie =
873                         (RemoteCallbackListCookie) mUxRClients.getBroadcastCookie(i);
874                 if (!displayToDispatch.contains(cookie.mPhysicalPort)) {
875                     continue;
876                 }
877                 CarUxRestrictions restrictions = displayRestrictions.get(cookie.mPhysicalPort);
878                 if (restrictions == null) {
879                     // don't dispatch to displays without configurations
880                     continue;
881                 }
882                 try {
883                     callback.onUxRestrictionsChanged(restrictions);
884                 } catch (RemoteException e) {
885                     Log.e(TAG,
886                             String.format("Dispatch to listener %s failed for restrictions (%s)",
887                                     callback, restrictions));
888                 }
889             }
890             mUxRClients.finishBroadcast();
891         });
892 
893         if (!success) {
894             Log.e(TAG, String.format("Unable to post (%s) event to dispatch handler",
895                     displayRestrictions));
896         }
897     }
898 
899     @VisibleForTesting
getDefaultDisplayPhysicalPort(DisplayManager displayManager)900     static byte getDefaultDisplayPhysicalPort(DisplayManager displayManager) {
901         Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
902         DisplayAddress.Physical address = (DisplayAddress.Physical) defaultDisplay.getAddress();
903 
904         if (address == null) {
905             Log.e(TAG, "Default display does not have physical display port. Using 0 as port.");
906             return DEFAULT_PORT;
907         }
908         return address.getPort();
909     }
910 
initPhysicalPort()911     private void initPhysicalPort() {
912         for (Display display : mDisplayManager.getDisplays()) {
913             if (display.getType() == Display.TYPE_VIRTUAL) {
914                 continue;
915             }
916 
917             if (display.getDisplayId() == Display.DEFAULT_DISPLAY && display.getAddress() == null) {
918                 // Assume default display is a physical display so assign an address if it
919                 // does not have one (possibly due to lower graphic driver version).
920                 if (Log.isLoggable(TAG, Log.INFO)) {
921                     Log.i(TAG, "Default display does not have display address. Using default.");
922                 }
923                 synchronized (mLock) {
924                     mPhysicalPorts.add(mDefaultDisplayPhysicalPort);
925                 }
926             } else if (display.getAddress() instanceof DisplayAddress.Physical) {
927                 byte port = ((DisplayAddress.Physical) display.getAddress()).getPort();
928                 if (Log.isLoggable(TAG, Log.INFO)) {
929                     Log.i(TAG, String.format(
930                             "Display %d uses port %d", display.getDisplayId(),
931                             Byte.toUnsignedInt(port)));
932                 }
933                 synchronized (mLock) {
934                     mPhysicalPorts.add(port);
935                 }
936             } else {
937                 Log.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<Byte, CarUxRestrictionsConfiguration> convertToMap(
944             List<CarUxRestrictionsConfiguration> configs) {
945         validateConfigs(configs);
946 
947         Map<Byte, CarUxRestrictionsConfiguration> result = new HashMap<>();
948         if (configs.size() == 1) {
949             CarUxRestrictionsConfiguration config = configs.get(0);
950             synchronized (mLock) {
951                 byte 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<Byte> 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             Byte 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 byte 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 Byte getPhysicalPortLocked(int displayId) {
1012         if (!mPortLookup.containsKey(displayId)) {
1013             Display display = mDisplayManager.getDisplay(displayId);
1014             if (display == null) {
1015                 Log.w(TAG, "Could not retrieve display for id: " + displayId);
1016                 return null;
1017             }
1018             byte 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 byte doGetPhysicalPortLocked(@NonNull Display display) {
1026         if (display.getType() == Display.TYPE_VIRTUAL) {
1027             Log.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             Log.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             Log.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(byte port)1058     CarUxRestrictionsConfiguration createDefaultConfig(byte 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             Byte physicalPort = getPhysicalPortLocked(physicalDisplayId);
1156             if (physicalPort == null) {
1157                 // This should not happen.
1158                 Log.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