summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Li Li <dualli@google.com> 2022-12-15 16:41:39 -0800
committer Li Li <dualli@google.com> 2023-02-03 05:29:56 +0000
commit78fecfcf4456309564456c2aba202f9de185b4d5 (patch)
tree0651f8f960870d659d7b80fc83cb06582f68c207
parent541bb486fb26dde8bd5a674c9c712be3ffd72000 (diff)
Defer sending display events to cached apps
When the target app is in cached mode, the display events won't be sent out until the app becomes non-cached later. Those redundant display events can be collapsed (e.g. repeated display/brightness change) to improve the efficiency. Events belonging to different displays won't impact each other. They will still reach the target apps in the original order. Bug: 217609394 Test: atest DisplayEventTest Test: logcat to confirm the interleaved display events are correctly sent to the cached applications after they become non-cached Change-Id: Icf25b87f2bf0b14b3f5a3f6df8fb42e1b599bba7
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java129
1 files changed, 125 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 70069c60c330..baf4f140c383 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -21,6 +21,8 @@ import static android.Manifest.permission.ADD_TRUSTED_DISPLAY;
import static android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT;
import static android.Manifest.permission.CAPTURE_VIDEO_OUTPUT;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_GONE;
import static android.hardware.display.DisplayManager.EventsMask;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
@@ -43,7 +45,10 @@ import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.compat.CompatChanges;
import android.companion.virtual.IVirtualDevice;
@@ -233,6 +238,9 @@ public final class DisplayManagerService extends SystemService {
private final DisplayModeDirector mDisplayModeDirector;
private WindowManagerInternal mWindowManagerInternal;
private InputManagerInternal mInputManagerInternal;
+ private ActivityManagerInternal mActivityManagerInternal;
+ private ActivityManager mActivityManager;
+ private UidImportanceListener mUidImportanceListener = new UidImportanceListener();
private IMediaProjectionManager mProjectionService;
private DeviceStateManagerInternal mDeviceStateManager;
@GuardedBy("mSyncRoot")
@@ -412,6 +420,11 @@ public final class DisplayManagerService extends SystemService {
// May be used outside of the lock but only on the handler thread.
private final ArrayList<CallbackRecord> mTempCallbacks = new ArrayList<CallbackRecord>();
+ // Pending callback records indexed by calling process uid.
+ // Must be used outside of the lock mSyncRoot and should be selflocked.
+ @GuardedBy("mPendingCallbackSelfLocked")
+ public final SparseArray<PendingCallback> mPendingCallbackSelfLocked = new SparseArray<>();
+
// Temporary viewports, used when sending new viewport information to the
// input system. May be used outside of the lock but only on the handler thread.
private final ArrayList<DisplayViewport> mTempViewports = new ArrayList<>();
@@ -619,11 +632,18 @@ public final class DisplayManagerService extends SystemService {
}
}
- // TODO: Use dependencies or a boot phase
+ /**
+ * The 2nd stage initialization
+ * TODO: Use dependencies or a boot phase
+ */
+ @SuppressLint("AndroidFrameworkRequiresPermission")
public void windowManagerAndInputReady() {
synchronized (mSyncRoot) {
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
+ mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mActivityManager.addOnUidImportanceListener(mUidImportanceListener, IMPORTANCE_CACHED);
mDeviceStateManager = LocalServices.getService(DeviceStateManagerInternal.class);
mContext.getSystemService(DeviceStateManager.class).registerCallback(
@@ -825,6 +845,36 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private class UidImportanceListener implements ActivityManager.OnUidImportanceListener {
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ synchronized (mPendingCallbackSelfLocked) {
+ if (importance >= IMPORTANCE_GONE) {
+ // Clean up as the app is already gone
+ Slog.d(TAG, "Drop pending events for gone uid " + uid);
+ mPendingCallbackSelfLocked.delete(uid);
+ return;
+ } else if (importance >= IMPORTANCE_CACHED) {
+ // Nothing to do as the app is still in cached mode
+ return;
+ }
+
+ // Do we care about this uid?
+ PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+ if (pendingCallback == null) {
+ return;
+ }
+
+ // Send the pending events out when a certain uid becomes non-cached
+ if (DEBUG) {
+ Slog.d(TAG, "Uid " + uid + " becomes " + importance);
+ }
+ pendingCallback.sendPendingDisplayEvent();
+ mPendingCallbackSelfLocked.delete(uid);
+ }
+ }
+ }
+
private class SettingsObserver extends ContentObserver {
SettingsObserver() {
super(mHandler);
@@ -2538,6 +2588,16 @@ public final class DisplayManagerService extends SystemService {
}
}
+ // Check if the target app is in cached mode
+ private boolean isUidCached(int uid) {
+ if (mActivityManagerInternal == null) {
+ return false;
+ }
+ int procState = mActivityManagerInternal.getUidProcessState(uid);
+ int importance = ActivityManager.RunningAppProcessInfo.procStateToImportance(procState);
+ return importance >= IMPORTANCE_CACHED;
+ }
+
// Runs on Handler thread.
// Delivers display event notifications to callbacks.
private void deliverDisplayEvent(int displayId, ArraySet<Integer> uids,
@@ -2561,7 +2621,22 @@ public final class DisplayManagerService extends SystemService {
// After releasing the lock, send the notifications out.
for (int i = 0; i < mTempCallbacks.size(); i++) {
- mTempCallbacks.get(i).notifyDisplayEventAsync(displayId, event);
+ CallbackRecord callbackRecord = mTempCallbacks.get(i);
+ final int uid = callbackRecord.mUid;
+ if (isUidCached(uid)) {
+ // For cached apps, save the pending event until it becomes non-cached
+ synchronized (mPendingCallbackSelfLocked) {
+ PendingCallback pendingCallback = mPendingCallbackSelfLocked.get(uid);
+ if (pendingCallback == null) {
+ mPendingCallbackSelfLocked.put(uid,
+ new PendingCallback(callbackRecord, displayId, event));
+ } else {
+ pendingCallback.addDisplayEvent(displayId, event);
+ }
+ }
+ } else {
+ callbackRecord.notifyDisplayEventAsync(displayId, event);
+ }
}
mTempCallbacks.clear();
}
@@ -2997,17 +3072,22 @@ public final class DisplayManagerService extends SystemService {
onCallbackDied(this);
}
- public void notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
+ /**
+ * @return {@code false} if RemoteException happens; otherwise {@code true} for success.
+ */
+ public boolean notifyDisplayEventAsync(int displayId, @DisplayEvent int event) {
if (!shouldSendEvent(event)) {
- return;
+ return true;
}
try {
mCallback.onDisplayEvent(displayId, event);
+ return true;
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to notify process "
+ mPid + " that displays changed, assuming it died.", ex);
binderDied();
+ return false;
}
}
@@ -3030,6 +3110,47 @@ public final class DisplayManagerService extends SystemService {
}
}
+ private static final class PendingCallback {
+ private final CallbackRecord mCallbackRecord;
+ private final ArrayList<Pair<Integer, Integer>> mDisplayEvents;
+
+ PendingCallback(CallbackRecord cr, int displayId, int event) {
+ mCallbackRecord = cr;
+ mDisplayEvents = new ArrayList<>();
+ mDisplayEvents.add(new Pair<>(displayId, event));
+ }
+
+ public void addDisplayEvent(int displayId, int event) {
+ // Ignore redundant events. Further optimization is possible by merging adjacent events.
+ Pair<Integer, Integer> last = mDisplayEvents.get(mDisplayEvents.size() - 1);
+ if (last.first == displayId && last.second == event) {
+ Slog.d(TAG,
+ "Ignore redundant display event " + displayId + "/" + event + " to "
+ + mCallbackRecord.mUid + "/" + mCallbackRecord.mPid);
+ return;
+ }
+
+ mDisplayEvents.add(new Pair<>(displayId, event));
+ }
+
+ public void sendPendingDisplayEvent() {
+ for (int i = 0; i < mDisplayEvents.size(); i++) {
+ Pair<Integer, Integer> displayEvent = mDisplayEvents.get(i);
+ if (DEBUG) {
+ Slog.d(TAG, "Send pending display event #" + i + " " + displayEvent.first + "/"
+ + displayEvent.second + " to " + mCallbackRecord.mUid + "/"
+ + mCallbackRecord.mPid);
+ }
+ if (!mCallbackRecord.notifyDisplayEventAsync(displayEvent.first,
+ displayEvent.second)) {
+ Slog.d(TAG, "Drop pending events for dead process " + mCallbackRecord.mPid);
+ break;
+ }
+ }
+ mDisplayEvents.clear();
+ }
+ }
+
@VisibleForTesting
final class BinderService extends IDisplayManager.Stub {
/**