summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Kenneth Ford <kennethford@google.com> 2022-11-12 00:21:09 +0000
committer Kenneth Ford <kennethford@google.com> 2022-11-17 22:36:42 +0000
commite65409d819cee98077942a242b874b4d4261b79b (patch)
treef5b5b4ade0e4a09e36526e48ffb1a307c5cc9dad
parentdc078773aa8e01e1f4e03965f364eda5a8a0d604 (diff)
Adds call to rear display overlay intent
When a request is made to enable rear display mode, and it's from a process that doesn't hold the CONTROL_DEVICE_STATE permission, we should launch the educational overlay to alert the user of what's about to happen. This change also introduces a method on DeviceStateManagerGlobal and Service to allow the overlay activity to communicate back to the system service if the overlay has been dismissed or acted on Bug: 207686851 Test: DeviceStateManagerServiceTest && DeviceStateManagerGlobalTest Change-Id: Ife7d642563005a2571bdeacbd7a3baaa9aad5e25
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateManager.java16
-rw-r--r--core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java18
-rw-r--r--core/java/android/hardware/devicestate/IDeviceStateManager.aidl11
-rw-r--r--core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java5
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java135
5 files changed, 181 insertions, 4 deletions
diff --git a/core/java/android/hardware/devicestate/DeviceStateManager.java b/core/java/android/hardware/devicestate/DeviceStateManager.java
index bdd45e6df448..dba1a5e8dfc6 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManager.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManager.java
@@ -52,6 +52,22 @@ public final class DeviceStateManager {
/** The maximum allowed device state identifier. */
public static final int MAXIMUM_DEVICE_STATE = 255;
+ /**
+ * Intent needed to launch the rear display overlay activity from SysUI
+ *
+ * @hide
+ */
+ public static final String ACTION_SHOW_REAR_DISPLAY_OVERLAY =
+ "com.android.intent.action.SHOW_REAR_DISPLAY_OVERLAY";
+
+ /**
+ * Intent extra sent to the rear display overlay activity of the current base state
+ *
+ * @hide
+ */
+ public static final String EXTRA_ORIGINAL_DEVICE_BASE_STATE =
+ "original_device_base_state";
+
private final DeviceStateManagerGlobal mGlobal;
/** @hide */
diff --git a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
index 738045dafdf1..7756b9ca7e5a 100644
--- a/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
+++ b/core/java/android/hardware/devicestate/DeviceStateManagerGlobal.java
@@ -51,7 +51,7 @@ public final class DeviceStateManagerGlobal {
* connection with the device state service couldn't be established.
*/
@Nullable
- static DeviceStateManagerGlobal getInstance() {
+ public static DeviceStateManagerGlobal getInstance() {
synchronized (DeviceStateManagerGlobal.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.DEVICE_STATE_SERVICE);
@@ -259,6 +259,22 @@ public final class DeviceStateManagerGlobal {
}
}
+ /**
+ * Provides notification to the system server that a device state feature overlay
+ * was dismissed. This should only be called from the {@link android.app.Activity} that
+ * was showing the overlay corresponding to the feature.
+ *
+ * Validation of there being an overlay visible and pending state request is handled on the
+ * system server.
+ */
+ public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+ try {
+ mDeviceStateManager.onStateRequestOverlayDismissed(shouldCancelRequest);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private void registerCallbackIfNeededLocked() {
if (mCallback == null) {
mCallback = new DeviceStateManagerCallback();
diff --git a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
index 7175eae58a26..099316099738 100644
--- a/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
+++ b/core/java/android/hardware/devicestate/IDeviceStateManager.aidl
@@ -103,4 +103,15 @@ interface IDeviceStateManager {
@JavaPassthrough(annotation=
"@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
void cancelBaseStateOverride();
+
+ /**
+ * Notifies the system service that the educational overlay that was launched
+ * before entering a requested state was dismissed or closed, and provides
+ * the system information on if the pairing mode should be canceled or not.
+ *
+ * This should only be called from the overlay itself.
+ */
+ @JavaPassthrough(annotation=
+ "@android.annotation.RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_STATE)")
+ void onStateRequestOverlayDismissed(boolean shouldCancelRequest);
}
diff --git a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
index 9e39e13265bd..3e3c77b7b21c 100644
--- a/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
+++ b/core/tests/devicestatetests/src/android/hardware/devicestate/DeviceStateManagerGlobalTest.java
@@ -377,6 +377,11 @@ public final class DeviceStateManagerGlobalTest {
notifyDeviceStateInfoChanged();
}
+ // No-op in the test since DeviceStateManagerGlobal just calls into the system server with
+ // no business logic around it.
+ @Override
+ public void onStateRequestOverlayDismissed(boolean shouldCancelMode) {}
+
public void setSupportedStates(int[] states) {
mSupportedStates = states;
notifyDeviceStateInfoChanged();
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 44c8e18a22cf..925fc21737e5 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -17,6 +17,11 @@
package com.android.server.devicestate;
import static android.Manifest.permission.CONTROL_DEVICE_STATE;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.hardware.devicestate.DeviceStateManager.ACTION_SHOW_REAR_DISPLAY_OVERLAY;
+import static android.hardware.devicestate.DeviceStateManager.EXTRA_ORIGINAL_DEVICE_BASE_STATE;
+import static android.hardware.devicestate.DeviceStateManager.INVALID_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -31,7 +36,10 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.WindowConfiguration;
import android.content.Context;
+import android.content.Intent;
import android.hardware.devicestate.DeviceStateInfo;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManagerInternal;
@@ -157,6 +165,15 @@ public final class DeviceStateManagerService extends SystemService {
private Set<Integer> mDeviceStatesAvailableForAppRequests;
+ private Set<Integer> mFoldedDeviceStates;
+
+ @Nullable
+ private DeviceState mRearDisplayState;
+
+ // TODO(259328837) Generalize for all pending feature requests in the future
+ @Nullable
+ private OverrideRequest mRearDisplayPendingOverrideRequest;
+
@VisibleForTesting
interface SystemPropertySetter {
void setDebugTracingDeviceStateProperty(String value);
@@ -201,6 +218,7 @@ public final class DeviceStateManagerService extends SystemService {
synchronized (mLock) {
readStatesAvailableForRequestFromApps();
+ mFoldedDeviceStates = readFoldedStates();
}
}
@@ -350,6 +368,8 @@ public final class DeviceStateManagerService extends SystemService {
mOverrideRequestController.handleNewSupportedStates(newStateIdentifiers);
updatePendingStateLocked();
+ setRearDisplayStateLocked();
+
if (!mPendingState.isPresent()) {
// If the change in the supported states didn't result in a change of the pending
// state commitPendingState() will never be called and the callbacks will never be
@@ -361,6 +381,15 @@ public final class DeviceStateManagerService extends SystemService {
}
}
+ @GuardedBy("mLock")
+ private void setRearDisplayStateLocked() {
+ int rearDisplayIdentifier = getContext().getResources().getInteger(
+ R.integer.config_deviceStateRearDisplay);
+ if (rearDisplayIdentifier != INVALID_DEVICE_STATE) {
+ mRearDisplayState = mDeviceStates.get(rearDisplayIdentifier);
+ }
+ }
+
/**
* Returns {@code true} if the provided state is supported. Requires that
* {@link #mDeviceStates} is sorted prior to calling.
@@ -398,6 +427,10 @@ public final class DeviceStateManagerService extends SystemService {
// Base state hasn't changed. Nothing to do.
return;
}
+ // There is a pending rear display request, so we check if the overlay should be closed
+ if (mRearDisplayPendingOverrideRequest != null) {
+ handleRearDisplayBaseStateChangedLocked(identifier);
+ }
mBaseState = Optional.of(baseState);
if (baseState.hasFlag(FLAG_CANCEL_OVERRIDE_REQUESTS)) {
@@ -663,7 +696,7 @@ public final class DeviceStateManagerService extends SystemService {
}
private void requestStateInternal(int state, int flags, int callingPid,
- @NonNull IBinder token) {
+ @NonNull IBinder token, boolean hasControlDeviceStatePermission) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
if (processRecord == null) {
@@ -685,10 +718,34 @@ public final class DeviceStateManagerService extends SystemService {
OverrideRequest request = new OverrideRequest(token, callingPid, state, flags,
OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
- mOverrideRequestController.addRequest(request);
+
+ // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
+ if (!hasControlDeviceStatePermission && mRearDisplayState != null
+ && state == mRearDisplayState.getIdentifier()) {
+ showRearDisplayEducationalOverlayLocked(request);
+ } else {
+ mOverrideRequestController.addRequest(request);
+ }
}
}
+ /**
+ * If we get a request to enter rear display mode, we need to display an educational
+ * overlay to let the user know what will happen. This creates the pending request and then
+ * launches the {@link RearDisplayEducationActivity}
+ */
+ @GuardedBy("mLock")
+ private void showRearDisplayEducationalOverlayLocked(OverrideRequest request) {
+ mRearDisplayPendingOverrideRequest = request;
+
+ Intent intent = new Intent(ACTION_SHOW_REAR_DISPLAY_OVERLAY);
+ intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_ORIGINAL_DEVICE_BASE_STATE, mBaseState.get().getIdentifier());
+ final ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ getUiContext().startActivity(intent, options.toBundle());
+ }
+
private void cancelStateRequestInternal(int callingPid) {
synchronized (mLock) {
final ProcessRecord processRecord = mProcessRecords.get(callingPid);
@@ -738,6 +795,27 @@ public final class DeviceStateManagerService extends SystemService {
}
}
+ /**
+ * Adds the rear display state request to the {@link OverrideRequestController} if the
+ * educational overlay was closed in a way that should enable the feature, and cancels the
+ * request if it was dismissed in a way that should cancel the feature.
+ */
+ private void onStateRequestOverlayDismissedInternal(boolean shouldCancelRequest) {
+ if (mRearDisplayPendingOverrideRequest != null) {
+ synchronized (mLock) {
+ if (shouldCancelRequest) {
+ ProcessRecord processRecord = mProcessRecords.get(
+ mRearDisplayPendingOverrideRequest.getPid());
+ processRecord.notifyRequestCanceledAsync(
+ mRearDisplayPendingOverrideRequest.getToken());
+ } else {
+ mOverrideRequestController.addRequest(mRearDisplayPendingOverrideRequest);
+ }
+ mRearDisplayPendingOverrideRequest = null;
+ }
+ }
+ }
+
private void dumpInternal(PrintWriter pw) {
pw.println("DEVICE STATE MANAGER (dumpsys device_state)");
@@ -823,6 +901,16 @@ public final class DeviceStateManagerService extends SystemService {
}
}
+ private Set<Integer> readFoldedStates() {
+ Set<Integer> foldedStates = new HashSet();
+ int[] mFoldedStatesArray = getContext().getResources().getIntArray(
+ com.android.internal.R.array.config_foldedDeviceStates);
+ for (int i = 0; i < mFoldedStatesArray.length; i++) {
+ foldedStates.add(mFoldedStatesArray[i]);
+ }
+ return foldedStates;
+ }
+
@GuardedBy("mLock")
private boolean isValidState(int state) {
for (int i = 0; i < mDeviceStates.size(); i++) {
@@ -833,6 +921,28 @@ public final class DeviceStateManagerService extends SystemService {
return false;
}
+ /**
+ * If the device is being opened, in response to the rear display educational overlay, we should
+ * dismiss the overlay and enter the mode.
+ */
+ @GuardedBy("mLock")
+ private void handleRearDisplayBaseStateChangedLocked(int newBaseState) {
+ if (isDeviceOpeningLocked(newBaseState)) {
+ onStateRequestOverlayDismissedInternal(false);
+ }
+ }
+
+ /**
+ * Determines if the device is being opened and if we are going from a folded state to a
+ * non-folded state.
+ */
+ @GuardedBy("mLock")
+ private boolean isDeviceOpeningLocked(int newBaseState) {
+ return mBaseState.filter(
+ deviceState -> mFoldedDeviceStates.contains(deviceState.getIdentifier())
+ && !mFoldedDeviceStates.contains(newBaseState)).isPresent();
+ }
+
private final class DeviceStateProviderListener implements DeviceStateProvider.Listener {
@IntRange(from = MINIMUM_DEVICE_STATE, to = MAXIMUM_DEVICE_STATE) int mCurrentBaseState;
@@ -850,6 +960,7 @@ public final class DeviceStateManagerService extends SystemService {
if (identifier < MINIMUM_DEVICE_STATE || identifier > MAXIMUM_DEVICE_STATE) {
throw new IllegalArgumentException("Invalid identifier: " + identifier);
}
+
mCurrentBaseState = identifier;
setBaseState(identifier);
}
@@ -977,9 +1088,12 @@ public final class DeviceStateManagerService extends SystemService {
throw new IllegalArgumentException("Request token must not be null.");
}
+ boolean hasControlStatePermission = getContext().checkCallingOrSelfPermission(
+ CONTROL_DEVICE_STATE) == PERMISSION_GRANTED;
+
final long callingIdentity = Binder.clearCallingIdentity();
try {
- requestStateInternal(state, flags, callingPid, token);
+ requestStateInternal(state, flags, callingPid, token, hasControlStatePermission);
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -1034,6 +1148,21 @@ public final class DeviceStateManagerService extends SystemService {
}
@Override // Binder call
+ public void onStateRequestOverlayDismissed(boolean shouldCancelRequest) {
+
+ getContext().enforceCallingOrSelfPermission(CONTROL_DEVICE_STATE,
+ "CONTROL_DEVICE_STATE permission required to control the state request "
+ + "overlay");
+
+ final long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ onStateRequestOverlayDismissedInternal(shouldCancelRequest);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override // Binder call
public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
String[] args, ShellCallback callback, ResultReceiver result) {
new DeviceStateManagerShellCommand(DeviceStateManagerService.this)