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

Commit 185483b2 authored by Charles Chen's avatar Charles Chen Committed by Automerger Merge Worker
Browse files

Merge changes from topic "window_context_2.0" into sc-dev am: 582247ee

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/13435721

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: Ic993a19f7cca3f10693d8313ab8d51a6a544cc19
parents be56db82 582247ee
Loading
Loading
Loading
Loading
+10 −22
Original line number Diff line number Diff line
@@ -15,8 +15,7 @@
 */
package android.app;

import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerGlobal.ADD_TOO_MANY_TOKENS;
import static android.view.WindowManagerImpl.createWindowContextWindowManager;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -28,8 +27,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.view.Display;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerImpl;

import com.android.internal.annotations.VisibleForTesting;

@@ -46,10 +45,10 @@ import java.lang.ref.Reference;
 */
@UiContext
public class WindowContext extends ContextWrapper {
    private final WindowManagerImpl mWindowManager;
    private final WindowManager mWindowManager;
    private final IWindowManager mWms;
    private final WindowTokenClient mToken;
    private boolean mOwnsToken;
    private boolean mListenerRegistered;

    /**
     * Default constructor. Will generate a {@link WindowTokenClient} and attach this context to
@@ -86,25 +85,14 @@ public class WindowContext extends ContextWrapper {

        mToken.attachContext(this);

        mWindowManager = new WindowManagerImpl(this);
        mWindowManager.setDefaultToken(mToken);
        mWindowManager = createWindowContextWindowManager(this);

        int result;
        try {
            // Register the token with WindowManager. This will also call back with the current
            // config back to the client.
            result = mWms.addWindowTokenWithOptions(
                    mToken, type, getDisplayId(), options, getPackageName());
            mListenerRegistered = mWms.registerWindowContextListener(mToken, type, getDisplayId(),
                    options);
        }  catch (RemoteException e) {
            mOwnsToken = false;
            throw e.rethrowFromSystemServer();
        }
        if (result == ADD_TOO_MANY_TOKENS) {
            throw new UnsupportedOperationException("createWindowContext failed! Too many unused "
                    + "window contexts. Please see Context#createWindowContext documentation for "
                    + "detail.");
        }
        mOwnsToken = result == ADD_OKAY;
        Reference.reachabilityFence(this);
    }

@@ -131,10 +119,10 @@ public class WindowContext extends ContextWrapper {
    /** Used for test to invoke because we can't invoke finalize directly. */
    @VisibleForTesting
    public void release() {
        if (mOwnsToken) {
        if (mListenerRegistered) {
            mListenerRegistered = false;
            try {
                mWms.removeWindowToken(mToken, getDisplayId());
                mOwnsToken = false;
                mWms.unregisterWindowContextListener(mToken);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
+45 −9
Original line number Diff line number Diff line
@@ -75,6 +75,7 @@ import android.view.Display;
import android.view.DisplayAdjustments;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import android.view.autofill.AutofillManager.AutofillClient;
@@ -6110,18 +6111,19 @@ public abstract class Context {
     *
     * // WindowManager.LayoutParams initialization
     * ...
     * // The types used in addView and createWindowContext must match.
     * mParams.type = TYPE_APPLICATION_OVERLAY;
     * ...
     *
     * mWindowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
     * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
     * </pre>
     *
     * <p>
     * This context's configuration and resources are adjusted to a display area where the windows
     * with provided type will be added. <b>Note that all windows associated with the same context
     * will have an affinity and can only be moved together between different displays or areas on a
     * display.</b> If there is a need to add different window types, or non-associated windows,
     * separate Contexts should be used.
     * This context's configuration and resources are adjusted to an area of the display where
     * the windows with provided type will be added. <b>Note that all windows associated with the
     * same context will have an affinity and can only be moved together between different displays
     * or areas on a display.</b> If there is a need to add different window types, or
     * non-associated windows, separate Contexts should be used.
     * </p>
     * <p>
     * Creating a window context is an expensive operation. Misuse of this API may lead to a huge
@@ -6129,7 +6131,43 @@ public abstract class Context {
     * An approach is to create one window context with specific window type and display and
     * use it everywhere it's needed.
     * </p>
     * <p>
     * After {@link Build.VERSION_CODES#S}, window context provides the capability to receive
     * configuration changes for existing token by overriding the
     * {@link android.view.WindowManager.LayoutParams#token token} of the
     * {@link android.view.WindowManager.LayoutParams} passed in
     * {@link WindowManager#addView(View, LayoutParams)}. This is useful when an application needs
     * to attach its window to an existing activity for window token sharing use-case.
     * </p>
     * <p>
     * Note that the window context in {@link Build.VERSION_CODES#R} didn't have this
     * capability. This is a no-op for the window context in {@link Build.VERSION_CODES#R}.
     * </p>
     * Below is sample code to <b>attach an existing token to a window context:</b>
     * <pre class="prettyprint">
     * final DisplayManager dm = anyContext.getSystemService(DisplayManager.class);
     * final Display primaryDisplay = dm.getDisplay(DEFAULT_DISPLAY);
     * final Context windowContext = anyContext.createWindowContext(primaryDisplay,
     *         TYPE_APPLICATION, null);
     *
     * // Get an existing token.
     * final IBinder existingToken = activity.getWindow().getAttributes().token;
     *
     * // The types used in addView() and createWindowContext() must match.
     * final WindowManager.LayoutParams params = new WindowManager.LayoutParams(TYPE_APPLICATION);
     * params.token = existingToken;
     *
     * // After WindowManager#addView(), the server side will extract the provided token from
     * // LayoutParams#token (existingToken in the sample code), and switch to propagate
     * // configuration changes from the node associated with the provided token.
     * windowContext.getSystemService(WindowManager.class).addView(overlayView, mParams);
     * </pre>
     * <p>
     * Note that using {@link android.app.Application} or {@link android.app.Service} context for
     * UI-related queries may result in layout or continuity issues on devices with variable screen
     * sizes (e.g. foldables) or in multi-window modes, since these non-UI contexts may not reflect
     * the {@link Configuration} changes for the visual container.
     * </p>
     * @param type Window type in {@link WindowManager.LayoutParams}
     * @param options A bundle used to pass window-related options
     * @return A {@link Context} that can be used to create
@@ -6141,9 +6179,7 @@ public abstract class Context {
     * @see #LAYOUT_INFLATER_SERVICE
     * @see #WALLPAPER_SERVICE
     * @throws UnsupportedOperationException if this {@link Context} does not attach to a display,
     * such as {@link android.app.Application Application} or {@link android.app.Service Service},
     * or the current number of window contexts without adding any view by
     * {@link WindowManager#addView} <b>exceeds five</b>.
     * such as {@link android.app.Application Application} or {@link android.app.Service Service}.
     */
    @UiContext
    @NonNull
+1 −1
Original line number Diff line number Diff line
@@ -117,7 +117,7 @@ interface IWindowManager
    // These can only be called when holding the MANAGE_APP_TOKENS permission.
    void setEventDispatching(boolean enabled);

    /** @return {@code true} if this binder is a registered window token. */
    /** Returns {@code true} if this binder is a registered window token. */
    boolean isWindowToken(in IBinder binder);
    /**
     * Adds window token for a given type.
+3 −0
Original line number Diff line number Diff line
@@ -147,6 +147,9 @@
    <!-- WindowMetricsTest permissions -->
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

    <!-- WindowContextTest permissions -->
    <uses-permission android:name="android.permission.MANAGE_APP_TOKENS" />

    <!-- Allow use of PendingIntent.getIntent() -->
    <uses-permission android:name="android.permission.GET_INTENT_SENDER_INTENT" />

+138 −14
Original line number Diff line number Diff line
@@ -18,29 +18,38 @@ package android.app;

import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.content.Context;
import android.content.Intent;
import android.hardware.display.DisplayManager;
import android.os.Binder;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
import android.view.IWindowManager;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.WindowType;
import android.view.WindowManagerGlobal;
import android.view.WindowManagerImpl;

import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;

import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * Tests for {@link WindowContext}
 *
@@ -54,41 +63,156 @@ import org.junit.runner.RunWith;
@SmallTest
@Presubmit
public class WindowContextTest {
    @Rule
    public ActivityTestRule<EmptyActivity> mActivityRule =
            new ActivityTestRule<>(EmptyActivity.class, false /* initialTouchMode */,
                    false /* launchActivity */);

    private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation();
    private final WindowContext mWindowContext = createWindowContext();
    private final IWindowManager mWms = WindowManagerGlobal.getWindowManagerService();

    @Test
    public void testCreateWindowContextWindowManagerAttachClientToken() {
        final WindowManager windowContextWm = WindowManagerImpl
                .createWindowContextWindowManager(mWindowContext);
        final WindowManager.LayoutParams params =
                new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
        mInstrumentation.runOnMainSync(() -> {
            final View view = new View(mWindowContext);
            windowContextWm.addView(view, params);
        });

        assertEquals(mWindowContext.getWindowContextToken(), params.mWindowContextToken);
    }

    /**
     * Test the {@link WindowContext} life cycle behavior to add a new window token:
     * <ul>
     *  <li>The window token is created before adding the first view.</li>
     *  <li>The window token is registered after adding the first view.</li>
     *  <li>The window token is removed after {@link WindowContext}'s release.</li>
     * </ul>
     */
    @Test
    public void testWindowContextRelease_doRemoveWindowToken() throws Throwable {
    public void testCreateWindowContextNewTokenFromClient() throws Throwable {
        final IBinder token = mWindowContext.getWindowContextToken();
        final IWindowManager wms = WindowManagerGlobal.getWindowManagerService();

        assertTrue("Token must be registered to WMS", wms.isWindowToken(token));
        // Test that the window token is not created yet.
        assertFalse("Token must not be registered until adding the first window",
                mWms.isWindowToken(token));

        final WindowManager.LayoutParams params =
                new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
        final View testView = new View(mWindowContext);

        final CountDownLatch latch = new CountDownLatch(1);
        testView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
            @Override
            public void onViewAttachedToWindow(View v) {
                latch.countDown();
            }

            @Override
            public void onViewDetachedFromWindow(View v) {}
        });

        mInstrumentation.runOnMainSync(() -> {
            mWindowContext.getSystemService(WindowManager.class).addView(testView, params);

            assertEquals(token, params.mWindowContextToken);
        });


        assertTrue(latch.await(4, TimeUnit.SECONDS));


        // Verify that the window token of the window context is created after first addView().
        assertTrue("Token must exist after adding the first view.",
                mWms.isWindowToken(token));

        mWindowContext.release();

        assertFalse("Token must be unregistered to WMS", wms.isWindowToken(token));
        // After the window context's release, the window token is also removed.
        assertFalse("Token must be removed after release.", mWms.isWindowToken(token));
    }

    /**
     * Verifies the behavior when window context attaches an {@link Activity} by override
     * {@link WindowManager.LayoutParams#token}.
     *
     * The window context token should be overridden to
     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must
     * not be removed regardless of the release of window context.
     */
    @Test
    public void testCreateWindowContextWindowManagerAttachClientToken() {
        final WindowManager windowContextWm = WindowManagerImpl
                .createWindowContextWindowManager(mWindowContext);
    public void testCreateWindowContext_AttachActivity_TokenNotRemovedAfterRelease()
            throws Throwable {
        mActivityRule.launchActivity(new Intent());
        final Activity activity = mActivityRule.getActivity();
        final WindowManager.LayoutParams params = activity.getWindow().getAttributes();

        final WindowContext windowContext = createWindowContext(params.type);
        final IBinder token = windowContext.getWindowContextToken();

        final View testView = new View(windowContext);

        mInstrumentation.runOnMainSync(() -> {
            windowContext.getSystemService(WindowManager.class).addView(testView, params);

            assertEquals(token, params.mWindowContextToken);
        });
        windowContext.release();

        // Even if the window context is released, the activity should still exist.
        assertTrue("Token must exist even if the window context is released.",
                mWms.isWindowToken(activity.getActivityToken()));
    }

    /**
     * Verifies the behavior when window context attaches an existing token by override
     * {@link WindowManager.LayoutParams#token}.
     *
     * The window context token should be overridden to
     * {@link android.view.WindowManager.LayoutParams} and the {@link Activity}'s token must not be
     * removed regardless of release of window context.
     */
    @Test
    public void testCreateWindowContext_AttachWindowToken_TokenNotRemovedAfterRelease()
            throws Throwable {
        final WindowContext windowContext = createWindowContext(TYPE_INPUT_METHOD);
        final IBinder token = windowContext.getWindowContextToken();

        final IBinder existingToken = new Binder();
        mWms.addWindowToken(existingToken, TYPE_INPUT_METHOD, windowContext.getDisplayId());

        final WindowManager.LayoutParams params =
                new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
                new WindowManager.LayoutParams(TYPE_INPUT_METHOD);
        params.token = existingToken;
        final View testView = new View(windowContext);

        mInstrumentation.runOnMainSync(() -> {
            final View view = new View(mWindowContext);
            windowContextWm.addView(view, params);
            windowContext.getSystemService(WindowManager.class).addView(testView, params);

            assertEquals(token, params.mWindowContextToken);
        });
        windowContext.release();

        assertEquals(mWindowContext.getWindowContextToken(), params.mWindowContextToken);
        // Even if the window context is released, the existing token should still exist.
        assertTrue("Token must exist even if the window context is released.",
                mWms.isWindowToken(existingToken));

        mWms.removeWindowToken(existingToken, DEFAULT_DISPLAY);
    }

    private WindowContext createWindowContext() {
        return createWindowContext(TYPE_APPLICATION_OVERLAY);
    }

    private WindowContext createWindowContext(@WindowType int type) {
        final Context instContext = mInstrumentation.getTargetContext();
        final Display display = instContext.getSystemService(DisplayManager.class)
                .getDisplay(DEFAULT_DISPLAY);
        final Context context = instContext.createDisplayContext(display);
        return new WindowContext(context, TYPE_APPLICATION_OVERLAY,
                null /* options */);
        return (WindowContext) instContext.createWindowContext(display, type,  null /* options */);
    }
}
Loading