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

Commit 8c37fec1 authored by Piotr Wilczyński's avatar Piotr Wilczyński Committed by Android (Google) Code Review
Browse files

Merge "Send topology updates to frozen processes" into main

parents 9d8c6297 6792eda4
Loading
Loading
Loading
Loading
+75 −34
Original line number Diff line number Diff line
@@ -684,8 +684,9 @@ public final class DisplayManagerService extends SystemService {
            final var backupManager = new BackupManager(mContext);
            Consumer<Pair<DisplayTopology, DisplayTopologyGraph>> topologyChangedCallback =
                    update -> {
                        if (mInputManagerInternal != null) {
                            mInputManagerInternal.setDisplayTopology(update.second);
                        DisplayTopologyGraph graph = update.second;
                        if (mInputManagerInternal != null && graph != null) {
                            mInputManagerInternal.setDisplayTopology(graph);
                        }
                        deliverTopologyUpdate(update.first);
                    };
@@ -3647,7 +3648,7 @@ public final class DisplayManagerService extends SystemService {

    private void deliverTopologyUpdate(DisplayTopology topology) {
        if (DEBUG) {
            Slog.d(TAG, "Delivering topology update");
            Slog.d(TAG, "Delivering topology update: " + topology);
        }
        if (Trace.isTagEnabled(Trace.TRACE_TAG_POWER)) {
            Trace.instant(Trace.TRACE_TAG_POWER, "deliverTopologyUpdate");
@@ -4209,13 +4210,18 @@ public final class DisplayManagerService extends SystemService {

        public boolean mWifiDisplayScanRequested;

        // A single pending event.
        // A single pending display event.
        private record Event(int displayId, @DisplayEvent int event) { };

        // The list of pending events.  This is null until there is a pending event to be saved.
        // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
        // The list of pending display events. This is null until there is a pending event to be
        // saved. This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
        @GuardedBy("mCallback")
        private ArrayList<Event> mPendingEvents;
        @Nullable
        private ArrayList<Event> mPendingDisplayEvents;

        @GuardedBy("mCallback")
        @Nullable
        private DisplayTopology mPendingTopology;

        // Process states: a process is ready to receive events if it is neither cached nor
        // frozen.
@@ -4285,7 +4291,10 @@ public final class DisplayManagerService extends SystemService {
         */
        @GuardedBy("mCallback")
        private boolean hasPendingAndIsReadyLocked() {
            return isReadyLocked() && mPendingEvents != null && !mPendingEvents.isEmpty() && mAlive;
            boolean pendingDisplayEvents = mPendingDisplayEvents != null
                    && !mPendingDisplayEvents.isEmpty();
            boolean pendingTopology = mPendingTopology != null;
            return isReadyLocked() && (pendingDisplayEvents || pendingTopology) && mAlive;
        }

        /**
@@ -4366,7 +4375,8 @@ public final class DisplayManagerService extends SystemService {
                    // occurs as the client is transitioning to ready but pending events have not
                    // been dispatched.  The new event must be added to the pending list to
                    // preserve event ordering.
                    if (!isReadyLocked() || (mPendingEvents != null && !mPendingEvents.isEmpty())) {
                    if (!isReadyLocked() || (mPendingDisplayEvents != null
                            && !mPendingDisplayEvents.isEmpty())) {
                        // The client is interested in the event but is not ready to receive it.
                        // Put the event on the pending list.
                        addDisplayEvent(displayId, event);
@@ -4453,13 +4463,13 @@ public final class DisplayManagerService extends SystemService {
        // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
        @GuardedBy("mCallback")
        private void addDisplayEvent(int displayId, int event) {
            if (mPendingEvents == null) {
                mPendingEvents = new ArrayList<>();
            if (mPendingDisplayEvents == null) {
                mPendingDisplayEvents = new ArrayList<>();
            }
            if (!mPendingEvents.isEmpty()) {
            if (!mPendingDisplayEvents.isEmpty()) {
                // Ignore redundant events. Further optimization is possible by merging adjacent
                // events.
                Event last = mPendingEvents.get(mPendingEvents.size() - 1);
                Event last = mPendingDisplayEvents.get(mPendingDisplayEvents.size() - 1);
                if (last.displayId == displayId && last.event == event) {
                    if (DEBUG) {
                        Slog.d(TAG, "Ignore redundant display event " + displayId + "/" + event
@@ -4468,12 +4478,13 @@ public final class DisplayManagerService extends SystemService {
                    return;
                }
            }
            mPendingEvents.add(new Event(displayId, event));
            mPendingDisplayEvents.add(new Event(displayId, event));
        }

        /**
         * @return {@code false} if RemoteException happens; otherwise {@code true} for
         * success.
         * success. This returns true even if the update was deferred because the remote client is
         * cached or frozen.
         */
        boolean notifyTopologyUpdateAsync(DisplayTopology topology) {
            if ((mInternalEventFlagsMask.get()
@@ -4490,6 +4501,18 @@ public final class DisplayManagerService extends SystemService {
                // The client is not interested in this event, so do nothing.
                return true;
            }

            if (deferDisplayEventsWhenFrozen()) {
                synchronized (mCallback) {
                    // Save the new update if the client frozen or cached (not ready).
                    if (!isReadyLocked()) {
                        // The client is interested in the update but is not ready to receive it.
                        mPendingTopology = topology;
                        return true;
                    }
                }
            }

            return transmitTopologyUpdate(topology);
        }

@@ -4514,21 +4537,29 @@ public final class DisplayManagerService extends SystemService {
        // would be unusual to do so.  The method returns true on success.
        // This is only used if {@link deferDisplayEventsWhenFrozen()} is true.
        public boolean dispatchPending() {
            Event[] pending;
            Event[] pendingDisplayEvents = null;
            DisplayTopology pendingTopology;
            synchronized (mCallback) {
                if (mPendingEvents == null || mPendingEvents.isEmpty() || !mAlive) {
                if (!mAlive) {
                    return true;
                }
                if (!isReadyLocked()) {
                    return false;
                }
                pending = new Event[mPendingEvents.size()];
                pending = mPendingEvents.toArray(pending);
                mPendingEvents.clear();

                if (mPendingDisplayEvents != null && !mPendingDisplayEvents.isEmpty()) {
                    pendingDisplayEvents = new Event[mPendingDisplayEvents.size()];
                    pendingDisplayEvents = mPendingDisplayEvents.toArray(pendingDisplayEvents);
                    mPendingDisplayEvents.clear();
                }

                pendingTopology = mPendingTopology;
                mPendingTopology = null;
            }
            try {
                for (int i = 0; i < pending.length; i++) {
                    Event displayEvent = pending[i];
                if (pendingDisplayEvents != null) {
                    for (int i = 0; i < pendingDisplayEvents.length; i++) {
                        Event displayEvent = pendingDisplayEvents[i];
                        if (DEBUG) {
                            Slog.d(TAG, "Send pending display event #" + i + " "
                                    + displayEvent.displayId + "/"
@@ -4541,10 +4572,19 @@ public final class DisplayManagerService extends SystemService {

                        transmitDisplayEvent(displayEvent.displayId, displayEvent.event);
                    }
                }

                if (pendingTopology != null) {
                    if (DEBUG) {
                        Slog.d(TAG, "Send pending topology: " + pendingTopology
                                + " to " + mUid + "/" + mPid);
                    }
                    mCallback.onTopologyChanged(pendingTopology);
                }

                return true;
            } catch (RemoteException ex) {
                Slog.w(TAG, "Failed to notify process "
                        + mPid + " that display topology changed, assuming it died.", ex);
                Slog.w(TAG, "Failed to notify process " + mPid + ", assuming it died.", ex);
                binderDied();
                return false;

@@ -4556,11 +4596,12 @@ public final class DisplayManagerService extends SystemService {
            if (deferDisplayEventsWhenFrozen()) {
                final String fmt =
                        "mPid=%d mUid=%d mWifiDisplayScanRequested=%s"
                        + " cached=%s frozen=%s pending=%d";
                        + " cached=%s frozen=%s pendingDisplayEvents=%d pendingTopology=%b";
                synchronized (mCallback) {
                    return formatSimple(fmt,
                            mPid, mUid, mWifiDisplayScanRequested, mCached, mFrozen,
                            (mPendingEvents == null) ? 0 : mPendingEvents.size());
                            (mPendingDisplayEvents == null) ? 0 : mPendingDisplayEvents.size(),
                            mPendingTopology != null);
                }
            } else {
                final String fmt =
+1 −0
Original line number Diff line number Diff line
@@ -51,6 +51,7 @@ android_test {

    data: [
        ":DisplayManagerTestApp",
        ":TopologyTestApp",
    ],

    certificate: "platform",
+1 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@
    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
    <uses-permission android:name="android.permission.MANAGE_USB" />
    <uses-permission android:name="android.permission.MANAGE_DISPLAYS" />

    <!-- Permissions needed for DisplayTransformManagerTest -->
    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@
        <option name="cleanup-apks" value="true" />
        <option name="install-arg" value="-t" />
        <option name="test-file-name" value="DisplayManagerTestApp.apk" />
        <option name="test-file-name" value="TopologyTestApp.apk" />
    </target_preparer>

    <option name="test-tag" value="DisplayServiceTests" />
+32 −212
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.server.display;

import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
import static android.util.DisplayMetrics.DENSITY_HIGH;
@@ -27,19 +26,11 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;

import android.app.ActivityManager;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.os.BinderProxy;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.platform.test.annotations.AppModeSdkSandbox;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -48,10 +39,7 @@ import android.util.SparseArray;

import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.test.platform.app.InstrumentationRegistry;

import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
import com.android.server.am.Flags;

import org.junit.After;
@@ -63,9 +51,7 @@ import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

@@ -73,8 +59,7 @@ import java.util.concurrent.TimeUnit;
 * Tests that applications can receive display events correctly.
 */
@RunWith(Parameterized.class)
@AppModeSdkSandbox(reason = "Allow test in the SDK sandbox (does not prevent other modes).")
public class DisplayEventDeliveryTest {
public class DisplayEventDeliveryTest extends EventDeliveryTestBase {
    private static final String TAG = "DisplayEventDeliveryTest";

    @Rule
@@ -85,37 +70,17 @@ public class DisplayEventDeliveryTest {
    private static final int WIDTH = 720;
    private static final int HEIGHT = 480;

    private static final int MESSAGE_LAUNCHED = 1;
    private static final int MESSAGE_CALLBACK = 2;

    private static final int DISPLAY_ADDED = 1;
    private static final int DISPLAY_CHANGED = 2;
    private static final int DISPLAY_REMOVED = 3;

    private static final long DISPLAY_EVENT_TIMEOUT_MSEC = 100;
    private static final long TEST_FAILURE_TIMEOUT_MSEC = 10000;

    private static final String TEST_PACKAGE =
            "com.android.servicestests.apps.displaymanagertestapp";
    private static final String TEST_ACTIVITY = TEST_PACKAGE + ".DisplayEventActivity";
    private static final String TEST_DISPLAYS = "DISPLAYS";
    private static final String TEST_MESSENGER = "MESSENGER";

    private final Object mLock = new Object();

    private Instrumentation mInstrumentation;
    private Context mContext;
    private DisplayManager mDisplayManager;
    private ActivityManager mActivityManager;
    private ActivityManager.OnUidImportanceListener mUidImportanceListener;
    private CountDownLatch mLatchActivityLaunch;
    private CountDownLatch mLatchActivityCached;
    private HandlerThread mHandlerThread;
    private Handler mHandler;
    private Messenger mMessenger;
    private int mPid;
    private int mUid;

    /**
     * Array of DisplayBundle. The test handler uses it to check if certain display events have
     * been sent to DisplayEventActivity.
@@ -167,7 +132,7 @@ public class DisplayEventDeliveryTest {
         */
        public void assertNoDisplayEvents() {
            try {
                assertNull(mExpectations.poll(DISPLAY_EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
                assertNull(mExpectations.poll(EVENT_TIMEOUT_MSEC, TimeUnit.MILLISECONDS));
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
@@ -239,37 +204,17 @@ public class DisplayEventDeliveryTest {
    }

    @Before
    public void setUp() throws Exception {
        mInstrumentation = InstrumentationRegistry.getInstrumentation();
        mContext = mInstrumentation.getContext();
        mDisplayManager = mContext.getSystemService(DisplayManager.class);
        mLatchActivityLaunch = new CountDownLatch(1);
        mLatchActivityCached = new CountDownLatch(1);
        mActivityManager = mContext.getSystemService(ActivityManager.class);
        mUidImportanceListener = (uid, importance) -> {
            if (uid == mUid && importance == IMPORTANCE_CACHED) {
                Log.d(TAG, "Listener " + uid + " becomes " + importance);
                mLatchActivityCached.countDown();
            }
        };
        SystemUtil.runWithShellPermissionIdentity(() ->
                mActivityManager.addOnUidImportanceListener(mUidImportanceListener,
                        IMPORTANCE_CACHED));
    public void setUp() {
        super.setUp();
        // The lock is not functionally necessary but eliminates lint error messages.
        synchronized (mLock) {
            mDisplayBundles = new SparseArray<>();
        }
        mHandlerThread = new HandlerThread("handler");
        mHandlerThread.start();
        mHandler = new TestHandler(mHandlerThread.getLooper());
        mMessenger = new Messenger(mHandler);
        mPid = 0;
    }

    @After
    public void tearDown() throws Exception {
        mActivityManager.removeOnUidImportanceListener(mUidImportanceListener);
        mHandlerThread.quitSafely();
        super.tearDown();
        synchronized (mLock) {
            for (int i = 0; i < mDisplayBundles.size(); i++) {
                DisplayBundle bundle = mDisplayBundles.valueAt(i);
@@ -278,55 +223,45 @@ public class DisplayEventDeliveryTest {
            }
            mDisplayBundles.clear();
        }
        SystemUtil.runShellCommand(mInstrumentation, "am force-stop " + TEST_PACKAGE);
    }

    /**
     * Return a display bundle at the stated index.  The bundle is retrieved under lock.
     */
    private DisplayBundle displayBundleAt(int i) {
        synchronized (mLock) {
            return mDisplayBundles.valueAt(i);
    @Override
    protected String getTag() {
        return TAG;
    }

    @Override
    protected Handler getHandler(Looper looper) {
        return new TestHandler(looper);
    }

    /**
     * Return true if the freezer is enabled on this platform and if freezer notifications are
     * supported.  It is not enough to test that the freezer notification feature is enabled
     * because some devices do not have the necessary kernel support.
     */
    private boolean isAppFreezerEnabled() {
        try {
            return mActivityManager.getService().isAppFreezerEnabled()
                    && android.os.Flags.binderFrozenStateChangeCallback()
                    && BinderProxy.isFrozenStateChangeCallbackSupported();
        } catch (Exception e) {
            Log.e(TAG, "isAppFreezerEnabled() failed: " + e);
            return false;
    @Override
    protected String getTestPackage() {
        return TEST_PACKAGE;
    }

    @Override
    protected String getTestActivity() {
        return TEST_ACTIVITY;
    }

    private void waitForProcessFreeze(int pid, long timeoutMs) {
        // TODO: Add a listener to monitor freezer state changes.
        SystemUtil.runWithShellPermissionIdentity(() -> {
            TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
                    (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
                    () -> mActivityManager.isProcessFrozen(pid));
        });
    @Override
    protected void putExtra(Intent intent) {
        intent.putExtra(TEST_DISPLAYS, mDisplayCount);
    }

    private void waitForProcessUnfreeze(int pid, long timeoutMs) {
        // TODO: Add a listener to monitor freezer state changes.
        SystemUtil.runWithShellPermissionIdentity(() -> {
            TestUtils.waitUntil("Timed out waiting for test process to be frozen; pid=" + pid,
                    (int) TimeUnit.MILLISECONDS.toSeconds(timeoutMs),
                    () -> !mActivityManager.isProcessFrozen(pid));
        });
    /**
     * Return a display bundle at the stated index.  The bundle is retrieved under lock.
     */
    private DisplayBundle displayBundleAt(int i) {
        synchronized (mLock) {
            return mDisplayBundles.valueAt(i);
        }
    }

    /**
     * Create virtual displays, change their configurations and release them. The number of
     * displays is set by the {@link #mDisplays} variable.
     * displays is set by the {@link #data()} parameter.
     */
    private void testDisplayEventsInternal(boolean cached, boolean frozen) {
        Log.d(TAG, "Start test testDisplayEvents " + mDisplayCount + " " + cached + " " + frozen);
@@ -444,110 +379,6 @@ public class DisplayEventDeliveryTest {
        testDisplayEventsInternal(true, true);
    }

    /**
     * Launch the test activity that would listen to display events. Return its process ID.
     */
    private int launchTestActivity() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
        intent.putExtra(TEST_MESSENGER, mMessenger);
        intent.putExtra(TEST_DISPLAYS, mDisplayCount);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        SystemUtil.runWithShellPermissionIdentity(
                () -> {
                    mContext.startActivity(intent);
                },
                android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
        waitLatch(mLatchActivityLaunch);

        try {
            String cmd = "pidof " + TEST_PACKAGE;
            String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
            return Integer.parseInt(result.trim());
        } catch (IOException e) {
            fail("failed to get pid of test package");
            return 0;
        } catch (NumberFormatException e) {
            fail("failed to parse pid " + e);
            return 0;
        }
    }

    /**
     * Bring the test activity back to top
     */
    private void bringTestActivityTop() {
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClassName(TEST_PACKAGE, TEST_ACTIVITY);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
        SystemUtil.runWithShellPermissionIdentity(
                () -> {
                    mContext.startActivity(intent);
                },
                android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
    }

    /**
     * Bring the test activity into cached mode by launching another 2 apps
     */
    private void makeTestActivityCached() {
        // Launch another activity to bring the test activity into background
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.setClass(mContext, SimpleActivity.class);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);

        // Launch another activity to bring the test activity into cached mode
        Intent intent2 = new Intent(Intent.ACTION_MAIN);
        intent2.setClass(mContext, SimpleActivity2.class);
        intent2.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        SystemUtil.runWithShellPermissionIdentity(
                () -> {
                    mInstrumentation.startActivitySync(intent);
                    mInstrumentation.startActivitySync(intent2);
                },
                android.Manifest.permission.START_ACTIVITIES_FROM_SDK_SANDBOX);
        waitLatch(mLatchActivityCached);
    }

    // Sleep, ignoring interrupts.
    private void pause(int s) {
        try { Thread.sleep(s * 1000); } catch (Exception e) { }
    }

    /**
     * Freeze the test activity.
     */
    private void makeTestActivityFrozen(int pid) {
        // The delay here is meant to allow pending binder transactions to drain.  A process
        // cannot be frozen if it has pending binder transactions, and attempting to freeze such a
        // process more than a few times will result in the system killing the process.
        pause(5);
        try {
            String cmd = "am freeze --sticky ";
            SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
        } catch (IOException e) {
            fail(e.toString());
        }
        // Wait for the freeze to complete in the kernel and for the frozen process
        // notification to settle out.
        waitForProcessFreeze(pid, 5 * 1000);
    }

    /**
     * Freeze the test activity.
     */
    private void makeTestActivityUnfrozen(int pid) {
        try {
            String cmd = "am unfreeze --sticky ";
            SystemUtil.runShellCommand(mInstrumentation, cmd + TEST_PACKAGE);
        } catch (IOException e) {
            fail(e.toString());
        }
        // Wait for the freeze to complete in the kernel and for the frozen process
        // notification to settle out.
        waitForProcessUnfreeze(pid, 5 * 1000);
    }

    /**
     * Create a virtual display
     *
@@ -560,15 +391,4 @@ public class DisplayEventDeliveryTest {
                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
                /* flags: a public virtual display that another app can access */);
    }

    /**
     * Wait for CountDownLatch with timeout
     */
    private void waitLatch(CountDownLatch latch) {
        try {
            latch.await(TEST_FAILURE_TIMEOUT_MSEC, TimeUnit.MILLISECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
Loading