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

Commit 17b89a1b authored by Chris Li's avatar Chris Li Committed by Android (Google) Code Review
Browse files

Merge "Introduce ActivityWindowInfoChangedListener" into main

parents ec3d9489 b8a6fa9f
Loading
Loading
Loading
Loading
+24 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import static android.window.ConfigurationHelper.shouldUpdateResources;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
import static com.android.window.flags.Flags.activityWindowInfoFlag;

import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -63,6 +64,7 @@ import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ActivityResultItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.DestroyActivityItem;
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PendingTransactionActions;
@@ -606,6 +608,8 @@ public final class ActivityThread extends ClientTransactionHandler
        Configuration overrideConfig;
        @NonNull
        private ActivityWindowInfo mActivityWindowInfo;
        @Nullable
        private ActivityWindowInfo mLastReportedActivityWindowInfo;

        // Used for consolidating configs before sending on to Activity.
        private final Configuration tmpConfig = new Configuration();
@@ -4180,6 +4184,9 @@ public final class ActivityThread extends ClientTransactionHandler
                pendingActions.setRestoreInstanceState(true);
                pendingActions.setCallOnPostCreate(true);
            }

            // Trigger ActivityWindowInfo callback if first launch or change from relaunch.
            handleActivityWindowInfoChanged(r);
        } else {
            // If there was an error, for any reason, tell the activity manager to stop us.
            ActivityClient.getInstance().finishActivity(r.token, Activity.RESULT_CANCELED,
@@ -6740,7 +6747,7 @@ public final class ActivityThread extends ClientTransactionHandler
        // Perform updates.
        r.overrideConfig = overrideConfig;
        r.mActivityWindowInfo = activityWindowInfo;
        // TODO(b/287582673): notify on ActivityWindowInfo change

        final ViewRootImpl viewRoot = r.activity.mDecor != null
            ? r.activity.mDecor.getViewRootImpl() : null;

@@ -6763,6 +6770,22 @@ public final class ActivityThread extends ClientTransactionHandler
            viewRoot.updateConfiguration(displayId);
        }
        mSomeActivitiesChanged = true;

        // Trigger ActivityWindowInfo callback if changed.
        handleActivityWindowInfoChanged(r);
    }

    private void handleActivityWindowInfoChanged(@NonNull ActivityClientRecord r) {
        if (!activityWindowInfoFlag()) {
            return;
        }
        if (r.mActivityWindowInfo == null
                || r.mActivityWindowInfo.equals(r.mLastReportedActivityWindowInfo)) {
            return;
        }
        r.mLastReportedActivityWindowInfo = r.mActivityWindowInfo;
        ClientTransactionListenerController.getInstance().onActivityWindowInfoChanged(r.token,
                r.mActivityWindowInfo);
    }

    final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
+65 −0
Original line number Diff line number Diff line
@@ -16,16 +16,24 @@

package android.app.servertransaction;

import static com.android.window.flags.Flags.activityWindowInfoFlag;
import static com.android.window.flags.Flags.bundleClientTransactionFlag;

import static java.util.Objects.requireNonNull;

import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityThread;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.util.ArraySet;
import android.window.ActivityWindowInfo;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;

import java.util.function.BiConsumer;

/**
 * Singleton controller to manage listeners to individual {@link ClientTransaction}.
 *
@@ -35,8 +43,14 @@ public class ClientTransactionListenerController {

    private static ClientTransactionListenerController sController;

    private final Object mLock = new Object();
    private final DisplayManagerGlobal mDisplayManager;

    /** Listeners registered via {@link #registerActivityWindowInfoChangedListener(BiConsumer)}. */
    @GuardedBy("mLock")
    private final ArraySet<BiConsumer<IBinder, ActivityWindowInfo>>
            mActivityWindowInfoChangedListeners = new ArraySet<>();

    /** Gets the singleton controller. */
    @NonNull
    public static ClientTransactionListenerController getInstance() {
@@ -61,6 +75,57 @@ public class ClientTransactionListenerController {
        mDisplayManager = requireNonNull(displayManager);
    }

    /**
     * Registers to listen on activity {@link ActivityWindowInfo} change.
     * The listener will be invoked with two parameters: {@link Activity#getActivityToken()} and
     * {@link ActivityWindowInfo}.
     */
    public void registerActivityWindowInfoChangedListener(
            @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
        if (!activityWindowInfoFlag()) {
            return;
        }
        synchronized (mLock) {
            mActivityWindowInfoChangedListeners.add(listener);
        }
    }

    /**
     * Unregisters the listener that was previously registered via
     * {@link #registerActivityWindowInfoChangedListener(BiConsumer)}
     */
    public void unregisterActivityWindowInfoChangedListener(
            @NonNull BiConsumer<IBinder, ActivityWindowInfo> listener) {
        if (!activityWindowInfoFlag()) {
            return;
        }
        synchronized (mLock) {
            mActivityWindowInfoChangedListeners.remove(listener);
        }
    }

    /**
     * Called when receives a {@link ClientTransaction} that is updating an activity's
     * {@link ActivityWindowInfo}.
     */
    public void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
            @NonNull ActivityWindowInfo activityWindowInfo) {
        if (!activityWindowInfoFlag()) {
            return;
        }
        final Object[] activityWindowInfoChangedListeners;
        synchronized (mLock) {
            if (mActivityWindowInfoChangedListeners.isEmpty()) {
                return;
            }
            activityWindowInfoChangedListeners = mActivityWindowInfoChangedListeners.toArray();
        }
        for (Object activityWindowInfoChangedListener : activityWindowInfoChangedListeners) {
            ((BiConsumer<IBinder, ActivityWindowInfo>) activityWindowInfoChangedListener)
                    .accept(activityToken, activityWindowInfo);
        }
    }

    /**
     * Called when receives a {@link ClientTransaction} that is updating display-related
     * window configuration.
+118 −1
Original line number Diff line number Diff line
@@ -21,16 +21,22 @@ import static android.content.Intent.ACTION_EDIT;
import static android.content.Intent.ACTION_VIEW;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;

import static com.android.window.flags.Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.annotation.NonNull;
@@ -46,6 +52,7 @@ import android.app.servertransaction.ActivityConfigurationChangeItem;
import android.app.servertransaction.ActivityRelaunchItem;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.ClientTransactionItem;
import android.app.servertransaction.ClientTransactionListenerController;
import android.app.servertransaction.ConfigurationChangeItem;
import android.app.servertransaction.NewIntentItem;
import android.app.servertransaction.ResumeActivityItem;
@@ -60,7 +67,9 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.DisplayMetrics;
import android.util.MergedConfiguration;
import android.view.Display;
@@ -81,11 +90,14 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
@@ -103,11 +115,17 @@ public class ActivityThreadTest {
    // few sequence numbers the framework used to launch the test activity.
    private static final int BASE_SEQ = 10000000;

    @Rule
    @Rule(order = 0)
    public final ActivityTestRule<TestActivity> mActivityTestRule =
            new ActivityTestRule<>(TestActivity.class, true /* initialTouchMode */,
                    false /* launchActivity */);

    @Rule(order = 1)
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);

    @Mock
    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;

    private WindowTokenClientController mOriginalWindowTokenClientController;
    private Configuration mOriginalAppConfig;

@@ -115,6 +133,8 @@ public class ActivityThreadTest {

    @Before
    public void setup() {
        MockitoAnnotations.initMocks(this);

        // Keep track of the original controller, so that it can be used to restore in tearDown()
        // when there is override in some test cases.
        mOriginalWindowTokenClientController = WindowTokenClientController.getInstance();
@@ -129,6 +149,8 @@ public class ActivityThreadTest {
            mCreatedVirtualDisplays = null;
        }
        WindowTokenClientController.overrideForTesting(mOriginalWindowTokenClientController);
        ClientTransactionListenerController.getInstance()
                .unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);
        InstrumentationRegistry.getInstrumentation().runOnMainSync(
                () -> restoreConfig(ActivityThread.currentActivityThread(), mOriginalAppConfig));
    }
@@ -783,6 +805,101 @@ public class ActivityThreadTest {
        verify(windowTokenClientController).onWindowContextWindowRemoved(clientToken);
    }

    @Test
    public void testActivityWindowInfoChanged_activityLaunch() {
        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
                mActivityWindowInfoListener);

        final Activity activity = mActivityTestRule.launchActivity(new Intent());
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
        final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);

        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
                activityClientRecord.getActivityWindowInfo());
    }

    @Test
    public void testActivityWindowInfoChanged_activityRelaunch() throws RemoteException {
        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
                mActivityWindowInfoListener);

        final Activity activity = mActivityTestRule.launchActivity(new Intent());
        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
        appThread.scheduleTransaction(newRelaunchResumeTransaction(activity));
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
        final ActivityClientRecord activityClientRecord = getActivityClientRecord(activity);

        // The same ActivityWindowInfo won't trigger duplicated callback.
        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
                activityClientRecord.getActivityWindowInfo());

        final Configuration currentConfig = activity.getResources().getConfiguration();
        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
                new Rect(0, 0, 1000, 1000));
        final ActivityRelaunchItem relaunchItem = ActivityRelaunchItem.obtain(
                activity.getActivityToken(), null, null, 0,
                new MergedConfiguration(currentConfig, currentConfig),
                false /* preserveWindow */, activityWindowInfo);
        final ClientTransaction transaction = newTransaction(activity);
        transaction.addTransactionItem(relaunchItem);
        appThread.scheduleTransaction(transaction);
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        verify(mActivityWindowInfoListener).accept(activityClientRecord.token,
                activityWindowInfo);
    }

    @Test
    public void testActivityWindowInfoChanged_activityConfigurationChanged()
            throws RemoteException {
        mSetFlagsRule.enableFlags(FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        ClientTransactionListenerController.getInstance().registerActivityWindowInfoChangedListener(
                mActivityWindowInfoListener);

        final Activity activity = mActivityTestRule.launchActivity(new Intent());
        final IApplicationThread appThread = activity.getActivityThread().getApplicationThread();
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        clearInvocations(mActivityWindowInfoListener);
        final Configuration config = new Configuration(activity.getResources().getConfiguration());
        config.seq++;
        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
        final Rect taskFragmentBounds = new Rect(0, 0, 1000, 1000);
        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
        activityWindowInfo.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
        final ActivityConfigurationChangeItem activityConfigurationChangeItem =
                ActivityConfigurationChangeItem.obtain(
                        activity.getActivityToken(), config, activityWindowInfo);
        final ClientTransaction transaction = newTransaction(activity);
        transaction.addTransactionItem(activityConfigurationChangeItem);
        appThread.scheduleTransaction(transaction);
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        verify(mActivityWindowInfoListener).accept(activity.getActivityToken(),
                activityWindowInfo);

        clearInvocations(mActivityWindowInfoListener);
        final ActivityWindowInfo activityWindowInfo2 = new ActivityWindowInfo();
        activityWindowInfo2.set(true /* isEmbedded */, taskBounds, taskFragmentBounds);
        config.seq++;
        final ActivityConfigurationChangeItem activityConfigurationChangeItem2 =
                ActivityConfigurationChangeItem.obtain(
                        activity.getActivityToken(), config, activityWindowInfo2);
        final ClientTransaction transaction2 = newTransaction(activity);
        transaction2.addTransactionItem(activityConfigurationChangeItem2);
        appThread.scheduleTransaction(transaction);
        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        // The same ActivityWindowInfo won't trigger duplicated callback.
        verify(mActivityWindowInfoListener, never()).accept(any(), any());
    }

    /**
     * Calls {@link ActivityThread#handleActivityConfigurationChanged(ActivityClientRecord,
     * Configuration, int, ActivityWindowInfo)} to try to push activity configuration to the
+34 −0
Original line number Diff line number Diff line
@@ -22,21 +22,29 @@ import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentat

import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManagerGlobal;
import android.hardware.display.IDisplayManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.DisplayInfo;
import android.window.ActivityWindowInfo;

import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.window.flags.Flags;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -44,6 +52,8 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.function.BiConsumer;

/**
 * Tests for {@link ClientTransactionListenerController}.
 *
@@ -62,6 +72,10 @@ public class ClientTransactionListenerControllerTest {
    private IDisplayManager mIDisplayManager;
    @Mock
    private DisplayManager.DisplayListener mListener;
    @Mock
    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
    @Mock
    private IBinder mActivityToken;

    private DisplayManagerGlobal mDisplayManager;
    private Handler mHandler;
@@ -91,4 +105,24 @@ public class ClientTransactionListenerControllerTest {

        verify(mListener).onDisplayChanged(123);
    }

    @Test
    public void testActivityWindowInfoChangedListener() {
        mSetFlagsRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        mController.registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
        final ActivityWindowInfo activityWindowInfo = new ActivityWindowInfo();
        activityWindowInfo.set(true /* isEmbedded */, new Rect(0, 0, 1000, 2000),
                new Rect(0, 0, 1000, 1000));
        mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);

        verify(mActivityWindowInfoListener).accept(mActivityToken, activityWindowInfo);

        clearInvocations(mActivityWindowInfoListener);
        mController.unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);

        mController.onActivityWindowInfoChanged(mActivityToken, activityWindowInfo);

        verify(mActivityWindowInfoListener, never()).accept(any(), any());
    }
}