diff options
| author | 2022-12-15 16:41:39 -0800 | |
|---|---|---|
| committer | 2023-02-03 05:29:56 +0000 | |
| commit | 78fecfcf4456309564456c2aba202f9de185b4d5 (patch) | |
| tree | 0651f8f960870d659d7b80fc83cb06582f68c207 | |
| parent | 541bb486fb26dde8bd5a674c9c712be3ffd72000 (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.java | 129 |
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 { /** |