getStagedConfigs() {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
File stagedConfig = getFile(CONFIG_FILENAME_STAGED);
if (stagedConfig.exists()) {
logd("Attempting to read staged config");
return readPersistedConfig(stagedConfig);
} else {
return null;
}
}
/**
* Sets the restriction mode to use. Restriction mode allows a different set of restrictions to
* be applied in the same driving state. Restrictions for each mode can be configured through
* {@link CarUxRestrictionsConfiguration}.
*
* Defaults to {@link CarUxRestrictionsManager#UX_RESTRICTION_MODE_BASELINE}.
*
* @param mode the restriction mode
* @return {@code true} if mode was successfully changed; {@code false} otherwise.
* @see CarUxRestrictionsConfiguration.DrivingStateRestrictions
* @see CarUxRestrictionsConfiguration.Builder
*/
@Override
public boolean setRestrictionMode(@NonNull String mode) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
Objects.requireNonNull(mode, "mode must not be null");
synchronized (mLock) {
if (mRestrictionMode.equals(mode)) {
return true;
}
addTransitionLogLocked(TAG, mRestrictionMode, mode, System.currentTimeMillis(),
"Restriction mode");
mRestrictionMode = mode;
logd("Set restriction mode to: " + mode);
handleDispatchUxRestrictionsLocked(
mDrivingStateService.getCurrentDrivingState().eventValue, getCurrentSpeed());
}
return true;
}
@Override
@NonNull
public String getRestrictionMode() {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_UX_RESTRICTIONS_CONFIGURATION);
synchronized (mLock) {
return mRestrictionMode;
}
}
/**
* Writes configuration into the specified file.
*
* IO access on file is not thread safe. Caller should ensure threading protection.
*/
private boolean persistConfig(List configs, String filename) {
File file = getFile(filename);
AtomicFile stagedFile = new AtomicFile(file);
FileOutputStream fos;
try {
fos = stagedFile.startWrite();
} catch (IOException e) {
Slog.e(TAG, "Could not open file to persist config", e);
return false;
}
try (JsonWriter jsonWriter = new JsonWriter(
new OutputStreamWriter(fos, StandardCharsets.UTF_8))) {
writeJson(jsonWriter, configs);
} catch (IOException e) {
Slog.e(TAG, "Could not persist config", e);
stagedFile.failWrite(fos);
return false;
}
stagedFile.finishWrite(fos);
return true;
}
@VisibleForTesting
void writeJson(JsonWriter jsonWriter, List configs)
throws IOException {
jsonWriter.beginObject();
jsonWriter.name(JSON_NAME_SCHEMA_VERSION).value(JSON_SCHEMA_VERSION_V2);
jsonWriter.name(JSON_NAME_RESTRICTIONS);
jsonWriter.beginArray();
for (CarUxRestrictionsConfiguration config : configs) {
config.writeJson(jsonWriter);
}
jsonWriter.endArray();
jsonWriter.endObject();
}
@Nullable
private List readPersistedConfig(File file) {
if (!file.exists()) {
Slog.e(TAG, "Could not find config file: " + file.getName());
return null;
}
// Take one pass at the file to check the version and then a second pass to read the
// contents. We could assess the version and read in one pass, but we're preferring
// clarity over complexity here.
int schemaVersion = readFileSchemaVersion(file);
AtomicFile configFile = new AtomicFile(file);
try (JsonReader reader = new JsonReader(
new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
List configs = new ArrayList<>();
switch (schemaVersion) {
case JSON_SCHEMA_VERSION_V1:
readV1Json(reader, configs);
break;
case JSON_SCHEMA_VERSION_V2:
readV2Json(reader, configs);
break;
default:
Slog.e(TAG, "Unable to parse schema for version " + schemaVersion);
}
return configs;
} catch (IOException e) {
Slog.e(TAG, "Could not read persisted config file " + file.getName(), e);
}
return null;
}
private void readV1Json(JsonReader reader,
List configs) throws IOException {
readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V1);
}
private void readV2Json(JsonReader reader,
List configs) throws IOException {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
switch (name) {
case JSON_NAME_RESTRICTIONS:
readRestrictionsArray(reader, configs, JSON_SCHEMA_VERSION_V2);
break;
default:
reader.skipValue();
}
}
reader.endObject();
}
private int readFileSchemaVersion(File file) {
AtomicFile configFile = new AtomicFile(file);
try (JsonReader reader = new JsonReader(
new InputStreamReader(configFile.openRead(), StandardCharsets.UTF_8))) {
List configs = new ArrayList<>();
if (reader.peek() == JsonToken.BEGIN_ARRAY) {
// only schema V1 beings with an array - no need to keep reading
reader.close();
return JSON_SCHEMA_VERSION_V1;
} else {
reader.beginObject();
while (reader.hasNext()) {
String name = reader.nextName();
switch (name) {
case JSON_NAME_SCHEMA_VERSION:
int schemaVersion = reader.nextInt();
// got the version, no need to continue reading
reader.close();
return schemaVersion;
default:
reader.skipValue();
}
}
reader.endObject();
}
} catch (IOException e) {
Slog.e(TAG, "Could not read persisted config file " + file.getName(), e);
}
return UNKNOWN_JSON_SCHEMA_VERSION;
}
private void readRestrictionsArray(JsonReader reader,
List configs, @JsonSchemaVersion int schemaVersion)
throws IOException {
reader.beginArray();
while (reader.hasNext()) {
configs.add(CarUxRestrictionsConfiguration.readJson(reader, schemaVersion));
}
reader.endArray();
}
/**
* Enable/disable UX restrictions change broadcast blocking.
* Setting this to true will stop broadcasts of UX restriction change to listeners.
* This method works only on debug builds and the caller of this method needs to have the same
* signature of the car service.
*/
public void setUxRChangeBroadcastEnabled(boolean enable) {
if (!isDebugBuild()) {
Slog.e(TAG, "Cannot set UX restriction change broadcast.");
return;
}
// Check if the caller has the same signature as that of the car service.
if (mContext.getPackageManager().checkSignatures(Process.myUid(), Binder.getCallingUid())
!= PackageManager.SIGNATURE_MATCH) {
throw new SecurityException(
"Caller " + mContext.getPackageManager().getNameForUid(Binder.getCallingUid())
+ " does not have the right signature");
}
synchronized (mLock) {
if (enable) {
// if enabling it back, send the current restrictions
mUxRChangeBroadcastEnabled = enable;
handleDispatchUxRestrictionsLocked(
mDrivingStateService.getCurrentDrivingState().eventValue,
getCurrentSpeed());
} else {
// fake parked state, so if the system is currently restricted, the restrictions are
// relaxed.
handleDispatchUxRestrictionsLocked(DRIVING_STATE_PARKED, 0);
mUxRChangeBroadcastEnabled = enable;
}
}
}
private boolean isDebugBuild() {
return Build.IS_USERDEBUG || Build.IS_ENG;
}
@Override
public void dump(IndentingPrintWriter writer) {
synchronized (mLock) {
writer.println("*CarUxRestrictionsManagerService*");
mUxRClients.dump(writer, "UX Restrictions Clients ");
for (int port : mCurrentUxRestrictions.keySet()) {
CarUxRestrictions restrictions = mCurrentUxRestrictions.get(port);
writer.printf("Port: 0x%02X UXR: %s\n", port, restrictions.toString());
}
if (isDebugBuild()) {
writer.println("mUxRChangeBroadcastEnabled? " + mUxRChangeBroadcastEnabled);
}
writer.println("UX Restriction configurations:");
for (CarUxRestrictionsConfiguration config :
mCarUxRestrictionsConfigurations.values()) {
config.dump(writer);
}
writer.println("UX Restriction change log:");
for (Utils.TransitionLog tlog : mTransitionLogs) {
writer.println(tlog);
}
writer.println("UX Restriction display info:");
for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
writer.printf("Display%d: physicalDisplayId=%d, owner=%s\n",
mActivityViewDisplayInfoMap.keyAt(i), info.mPhysicalDisplayId, info.mOwner);
}
}
}
/**
* {@link CarDrivingStateEvent} listener registered with the {@link CarDrivingStateService}
* for getting driving state change notifications.
*/
private final ICarDrivingStateChangeListener mICarDrivingStateChangeEventListener =
new ICarDrivingStateChangeListener.Stub() {
@Override
public void onDrivingStateChanged(CarDrivingStateEvent event) {
logd("Driving State Changed:" + event.eventValue);
synchronized (mLock) {
handleDrivingStateEventLocked(event);
}
}
};
/**
* Handle the driving state change events coming from the {@link CarDrivingStateService}.
* Map the driving state to the corresponding UX Restrictions and dispatch the
* UX Restriction change to the registered clients.
*/
@VisibleForTesting
@GuardedBy("mLock")
void handleDrivingStateEventLocked(CarDrivingStateEvent event) {
if (event == null) {
return;
}
int drivingState = event.eventValue;
Float speed = getCurrentSpeed();
if (speed != SPEED_NOT_AVAILABLE) {
mCurrentMovingSpeed = speed;
} else if (drivingState == DRIVING_STATE_PARKED
|| drivingState == DRIVING_STATE_UNKNOWN) {
// If speed is unavailable, but the driving state is parked or unknown, it can still be
// handled.
logd("Speed null when driving state is: " + drivingState);
mCurrentMovingSpeed = 0;
} else {
// If we get here with driving state != parked or unknown && speed == null,
// something is wrong. CarDrivingStateService could not have inferred idling or moving
// when speed is not available
Slog.e(TAG, "Unexpected: Speed null when driving state is: " + drivingState);
return;
}
handleDispatchUxRestrictionsLocked(drivingState, mCurrentMovingSpeed);
}
/**
* {@link CarPropertyEvent} listener registered with the {@link CarPropertyService} for getting
* speed change notifications.
*/
private final ICarPropertyEventListener mICarPropertyEventListener =
new ICarPropertyEventListener.Stub() {
@Override
public void onEvent(List events) throws RemoteException {
synchronized (mLock) {
for (CarPropertyEvent event : events) {
if ((event.getEventType()
== CarPropertyEvent.PROPERTY_EVENT_PROPERTY_CHANGE)
&& (event.getCarPropertyValue().getPropertyId()
== VehicleProperty.PERF_VEHICLE_SPEED)) {
handleSpeedChangeLocked(
(Float) event.getCarPropertyValue().getValue());
}
}
}
}
};
@GuardedBy("mLock")
private void handleSpeedChangeLocked(float newSpeed) {
if (newSpeed == mCurrentMovingSpeed) {
// Ignore if speed hasn't changed
return;
}
int currentDrivingState = mDrivingStateService.getCurrentDrivingState().eventValue;
if (currentDrivingState != DRIVING_STATE_MOVING) {
// Ignore speed changes if the vehicle is not moving
return;
}
mCurrentMovingSpeed = newSpeed;
handleDispatchUxRestrictionsLocked(currentDrivingState, newSpeed);
}
/**
* Handle dispatching UX restrictions change.
*
* @param currentDrivingState driving state of the vehicle
* @param speed speed of the vehicle
*/
@GuardedBy("mLock")
private void handleDispatchUxRestrictionsLocked(@CarDrivingState int currentDrivingState,
float speed) {
Objects.requireNonNull(mCarUxRestrictionsConfigurations,
"mCarUxRestrictionsConfigurations must be initialized");
Objects.requireNonNull(mCurrentUxRestrictions,
"mCurrentUxRestrictions must be initialized");
if (isDebugBuild() && !mUxRChangeBroadcastEnabled) {
Slog.d(TAG, "Not dispatching UX Restriction due to setting");
return;
}
Map newUxRestrictions = new HashMap<>();
for (int port : mPhysicalPorts) {
CarUxRestrictionsConfiguration config = mCarUxRestrictionsConfigurations.get(port);
if (config == null) {
continue;
}
CarUxRestrictions uxRestrictions = config.getUxRestrictions(
currentDrivingState, speed, mRestrictionMode);
logd(String.format("Display port 0x%02x\tDO old->new: %b -> %b",
port,
mCurrentUxRestrictions.get(port).isRequiresDistractionOptimization(),
uxRestrictions.isRequiresDistractionOptimization()));
logd(String.format("Display port 0x%02x\tUxR old->new: 0x%x -> 0x%x",
port,
mCurrentUxRestrictions.get(port).getActiveRestrictions(),
uxRestrictions.getActiveRestrictions()));
newUxRestrictions.put(port, uxRestrictions);
}
// Ignore dispatching if the restrictions has not changed.
Set displayToDispatch = new ArraySet<>();
for (int port : newUxRestrictions.keySet()) {
if (!mCurrentUxRestrictions.containsKey(port)) {
// This should never happen.
Slog.wtf(TAG, "Unrecognized port:" + port);
continue;
}
CarUxRestrictions uxRestrictions = newUxRestrictions.get(port);
if (!mCurrentUxRestrictions.get(port).isSameRestrictions(uxRestrictions)) {
displayToDispatch.add(port);
}
}
if (displayToDispatch.isEmpty()) {
return;
}
for (int port : displayToDispatch) {
addTransitionLogLocked(
mCurrentUxRestrictions.get(port), newUxRestrictions.get(port));
}
dispatchRestrictionsToClients(newUxRestrictions, displayToDispatch);
mCurrentUxRestrictions = newUxRestrictions;
}
private void dispatchRestrictionsToClients(Map displayRestrictions,
Set displayToDispatch) {
logd("dispatching to clients");
boolean success = mClientDispatchHandler.post(() -> {
int numClients = mUxRClients.beginBroadcast();
for (int i = 0; i < numClients; i++) {
ICarUxRestrictionsChangeListener callback = mUxRClients.getBroadcastItem(i);
RemoteCallbackListCookie cookie =
(RemoteCallbackListCookie) mUxRClients.getBroadcastCookie(i);
if (!displayToDispatch.contains(cookie.mPhysicalPort)) {
continue;
}
CarUxRestrictions restrictions = displayRestrictions.get(cookie.mPhysicalPort);
if (restrictions == null) {
// don't dispatch to displays without configurations
continue;
}
try {
callback.onUxRestrictionsChanged(restrictions);
} catch (RemoteException e) {
Slog.e(TAG,
String.format("Dispatch to listener %s failed for restrictions (%s)",
callback, restrictions));
}
}
mUxRClients.finishBroadcast();
});
if (!success) {
Slog.e(TAG, "Unable to post (" + displayRestrictions + ") event to dispatch handler");
}
}
@VisibleForTesting
static int getDefaultDisplayPhysicalPort(DisplayManager displayManager) {
Display defaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
DisplayAddress.Physical address = (DisplayAddress.Physical) defaultDisplay.getAddress();
if (address == null) {
Slog.e(TAG, "Default display does not have physical display port. Using 0 as port.");
return DEFAULT_PORT;
}
return address.getPort();
}
private void initPhysicalPort() {
for (Display display : mDisplayManager.getDisplays()) {
if (display.getType() == Display.TYPE_VIRTUAL) {
continue;
}
if (display.getDisplayId() == Display.DEFAULT_DISPLAY && display.getAddress() == null) {
// Assume default display is a physical display so assign an address if it
// does not have one (possibly due to lower graphic driver version).
if (Log.isLoggable(TAG, Log.INFO)) {
Slog.i(TAG, "Default display does not have display address. Using default.");
}
synchronized (mLock) {
mPhysicalPorts.add(mDefaultDisplayPhysicalPort);
}
} else if (display.getAddress() instanceof DisplayAddress.Physical) {
int port = ((DisplayAddress.Physical) display.getAddress()).getPort();
if (Log.isLoggable(TAG, Log.INFO)) {
Slog.i(TAG, "Display " + display.getDisplayId() + " uses port " + port);
}
synchronized (mLock) {
mPhysicalPorts.add(port);
}
} else {
Slog.w(TAG, "At init non-virtual display has a non-physical display address: "
+ display);
}
}
}
private Map convertToMap(
List configs) {
validateConfigs(configs);
Map result = new HashMap<>();
if (configs.size() == 1) {
CarUxRestrictionsConfiguration config = configs.get(0);
synchronized (mLock) {
int port = config.getPhysicalPort() == null
? mDefaultDisplayPhysicalPort
: config.getPhysicalPort();
result.put(port, config);
}
} else {
for (CarUxRestrictionsConfiguration config : configs) {
result.put(config.getPhysicalPort(), config);
}
}
return result;
}
/**
* Validates configs for multi-display:
* - share the same restrictions parameters;
* - each sets display port;
* - each has unique display port.
*/
@VisibleForTesting
void validateConfigs(List configs) {
if (configs.size() == 0) {
throw new IllegalArgumentException("Empty configuration.");
}
if (configs.size() == 1) {
return;
}
CarUxRestrictionsConfiguration first = configs.get(0);
Set existingPorts = new ArraySet<>();
for (CarUxRestrictionsConfiguration config : configs) {
if (!config.hasSameParameters(first)) {
// Input should have the same restriction parameters because:
// - it doesn't make sense otherwise; and
// - in format it matches how xml can only specify one set of parameters.
throw new IllegalArgumentException(
"Configurations should have the same restrictions parameters.");
}
Integer port = config.getPhysicalPort();
if (port == null) {
// Size was checked above; safe to assume there are multiple configs.
throw new IllegalArgumentException(
"Input contains multiple configurations; each must set physical port.");
}
if (existingPorts.contains(port)) {
throw new IllegalArgumentException("Multiple configurations for port " + port);
}
existingPorts.add(port);
}
}
/**
* Returns the physical port id for the display or {@code null} if {@link
* DisplayManager#getDisplay(int)} is not aware of the provided id.
*/
@Nullable
@GuardedBy("mLock")
private Integer getPhysicalPortLocked(int displayId) {
if (!mPortLookup.containsKey(displayId)) {
Display display = mDisplayManager.getDisplay(displayId);
if (display == null) {
Slog.w(TAG, "Could not retrieve display for id: " + displayId);
return null;
}
int port = doGetPhysicalPortLocked(display);
mPortLookup.put(displayId, port);
}
return mPortLookup.get(displayId);
}
@GuardedBy("mLock")
private int doGetPhysicalPortLocked(@NonNull Display display) {
if (display.getType() == Display.TYPE_VIRTUAL) {
Slog.e(TAG, "Display " + display
+ " is a virtual display and does not have a known port.");
return mDefaultDisplayPhysicalPort;
}
DisplayAddress address = display.getAddress();
if (address == null) {
Slog.e(TAG, "Display " + display
+ " is not a virtual display but has null DisplayAddress.");
return mDefaultDisplayPhysicalPort;
} else if (!(address instanceof DisplayAddress.Physical)) {
Slog.e(TAG, "Display " + display + " has non-physical address: " + address);
return mDefaultDisplayPhysicalPort;
} else {
return ((DisplayAddress.Physical) address).getPort();
}
}
private CarUxRestrictions createUnrestrictedRestrictions() {
return new CarUxRestrictions.Builder(/* reqOpt= */ false,
CarUxRestrictions.UX_RESTRICTIONS_BASELINE, SystemClock.elapsedRealtimeNanos())
.build();
}
private CarUxRestrictions createFullyRestrictedRestrictions() {
return new CarUxRestrictions.Builder(
/*reqOpt= */ true,
CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED,
SystemClock.elapsedRealtimeNanos()).build();
}
CarUxRestrictionsConfiguration createDefaultConfig(int port) {
return new CarUxRestrictionsConfiguration.Builder()
.setPhysicalPort(port)
.setUxRestrictions(DRIVING_STATE_PARKED,
false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
.setUxRestrictions(DRIVING_STATE_IDLING,
false, CarUxRestrictions.UX_RESTRICTIONS_BASELINE)
.setUxRestrictions(DRIVING_STATE_MOVING,
true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
.setUxRestrictions(DRIVING_STATE_UNKNOWN,
true, CarUxRestrictions.UX_RESTRICTIONS_FULLY_RESTRICTED)
.build();
}
@GuardedBy("mLock")
private void addTransitionLogLocked(String name, String from, String to, long timestamp,
String extra) {
if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
mTransitionLogs.remove();
}
Utils.TransitionLog tLog = new Utils.TransitionLog(name, from, to, timestamp, extra);
mTransitionLogs.add(tLog);
}
@GuardedBy("mLock")
private void addTransitionLogLocked(
CarUxRestrictions oldRestrictions, CarUxRestrictions newRestrictions) {
if (mTransitionLogs.size() >= MAX_TRANSITION_LOG_SIZE) {
mTransitionLogs.remove();
}
StringBuilder extra = new StringBuilder();
extra.append(oldRestrictions.isRequiresDistractionOptimization() ? "DO -> " : "No DO -> ");
extra.append(newRestrictions.isRequiresDistractionOptimization() ? "DO" : "No DO");
Utils.TransitionLog tLog = new Utils.TransitionLog(TAG,
oldRestrictions.getActiveRestrictions(), newRestrictions.getActiveRestrictions(),
System.currentTimeMillis(), extra.toString());
mTransitionLogs.add(tLog);
}
private static void logd(String msg) {
if (DBG) {
Slog.d(TAG, msg);
}
}
private static final class DisplayInfo {
final IRemoteCallback mOwner;
final int mPhysicalDisplayId;
DisplayInfo(IRemoteCallback owner, int physicalDisplayId) {
mOwner = owner;
mPhysicalDisplayId = physicalDisplayId;
}
}
@GuardedBy("mLock")
private final SparseArray mActivityViewDisplayInfoMap = new SparseArray<>();
@GuardedBy("mLock")
private final RemoteCallbackList mRemoteCallbackList =
new RemoteCallbackList<>() {
@Override
public void onCallbackDied(IRemoteCallback callback) {
synchronized (mLock) {
// Descending order to delete items safely from SpareArray.gc().
for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
if (info.mOwner == callback) {
logd("onCallbackDied: clean up callback=" + callback);
mActivityViewDisplayInfoMap.removeAt(i);
mPortLookup.remove(mActivityViewDisplayInfoMap.keyAt(i));
}
}
}
}
};
@Override
public void reportVirtualDisplayToPhysicalDisplay(IRemoteCallback callback,
int virtualDisplayId, int physicalDisplayId) {
logd("reportVirtualDisplayToPhysicalDisplay: callback=" + callback
+ ", virtualDisplayId=" + virtualDisplayId
+ ", physicalDisplayId=" + physicalDisplayId);
boolean release = physicalDisplayId == Display.INVALID_DISPLAY;
checkCallerOwnsDisplay(virtualDisplayId, release);
synchronized (mLock) {
if (release) {
mRemoteCallbackList.unregister(callback);
mActivityViewDisplayInfoMap.delete(virtualDisplayId);
mPortLookup.remove(virtualDisplayId);
return;
}
mRemoteCallbackList.register(callback);
mActivityViewDisplayInfoMap.put(virtualDisplayId,
new DisplayInfo(callback, physicalDisplayId));
Integer physicalPort = getPhysicalPortLocked(physicalDisplayId);
if (physicalPort == null) {
// This should not happen.
Slog.wtf(TAG, "No known physicalPort for displayId:" + physicalDisplayId);
physicalPort = mDefaultDisplayPhysicalPort;
}
mPortLookup.put(virtualDisplayId, physicalPort);
}
}
@Override
public int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
logd("getMappedPhysicalDisplayOfVirtualDisplay: displayId=" + displayId);
synchronized (mLock) {
DisplayInfo foundInfo = mActivityViewDisplayInfoMap.get(displayId);
if (foundInfo == null) {
return Display.INVALID_DISPLAY;
}
// ActivityView can be placed in another ActivityView, so we should repeat the process
// until no parent is found (reached to the physical display).
while (foundInfo != null) {
displayId = foundInfo.mPhysicalDisplayId;
foundInfo = mActivityViewDisplayInfoMap.get(displayId);
}
}
return displayId;
}
private void checkCallerOwnsDisplay(int displayId, boolean release) {
Display display = mDisplayManager.getDisplay(displayId);
if (display == null) {
// Bypasses the permission check for non-existing display when releasing it, since
// reportVirtualDisplayToPhysicalDisplay() and releasing display happens simultaneously
// and it's no harm to release the information on the non-existing display.
if (release) return;
throw new IllegalArgumentException(
"Cannot find display for non-existent displayId: " + displayId);
}
int callingUid = Binder.getCallingUid();
int displayOwnerUid = display.getOwnerUid();
if (callingUid != displayOwnerUid) {
throw new SecurityException("The caller doesn't own the display: callingUid="
+ callingUid + ", displayOwnerUid=" + displayOwnerUid);
}
}
}