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
This commit is contained in:
Li Li
2022-12-15 16:41:39 -08:00
parent 541bb486fb
commit 78fecfcf44

View File

@@ -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 {
/**