Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 78fecfcf authored by Li Li's avatar Li Li
Browse files

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
parent 541bb486
Loading
Loading
Loading
Loading
+125 −4
Original line number Diff line number Diff line
@@ -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 {
        /**