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

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

Merge "Hook EmbeddedActivityWindowInfo APIs to ClientTransactionListener" into main

parents 17b89a1b 25255265
Loading
Loading
Loading
Loading
+113 −6
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.Application;
import android.app.Instrumentation;
import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -103,6 +104,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;

/**
 * Main controller class that manages split states and presentation.
@@ -178,6 +180,20 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen

    private final List<ActivityStack> mLastReportedActivityStacks = new ArrayList<>();

    /** WM Jetpack set callback for {@link EmbeddedActivityWindowInfo}. */
    @GuardedBy("mLock")
    @Nullable
    private Pair<Executor, Consumer<EmbeddedActivityWindowInfo>>
            mEmbeddedActivityWindowInfoCallback;

    /** Listener registered to {@link ClientTransactionListenerController}. */
    @GuardedBy("mLock")
    @Nullable
    private final BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener =
            Flags.activityWindowInfoFlag()
                    ? this::onActivityWindowInfoChanged
                    : null;

    private final Handler mHandler;
    final Object mLock = new Object();
    private final ActivityStartMonitor mActivityStartMonitor;
@@ -2455,6 +2471,13 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        return ActivityThread.currentActivityThread().getActivity(activityToken);
    }

    @VisibleForTesting
    @Nullable
    ActivityThread.ActivityClientRecord getActivityClientRecord(@NonNull Activity activity) {
        return ActivityThread.currentActivityThread()
                .getActivityClient(activity.getActivityToken());
    }

    @VisibleForTesting
    ActivityStartMonitor getActivityStartMonitor() {
        return mActivityStartMonitor;
@@ -2468,8 +2491,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
    @VisibleForTesting
    @Nullable
    IBinder getTaskFragmentTokenFromActivityClientRecord(@NonNull Activity activity) {
        final ActivityThread.ActivityClientRecord record = ActivityThread.currentActivityThread()
                .getActivityClient(activity.getActivityToken());
        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
        return record != null ? record.mTaskFragmentToken : null;
    }

@@ -2876,17 +2898,102 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
        }
    }

    @Override
    public void setEmbeddedActivityWindowInfoCallback(@NonNull Executor executor,
            @NonNull Consumer<EmbeddedActivityWindowInfo> callback) {
        if (!Flags.activityWindowInfoFlag()) {
            return;
        }
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);
        synchronized (mLock) {
            if (mEmbeddedActivityWindowInfoCallback == null) {
                ClientTransactionListenerController.getInstance()
                        .registerActivityWindowInfoChangedListener(getActivityWindowInfoListener());
            }
            mEmbeddedActivityWindowInfoCallback = new Pair<>(executor, callback);
        }
    }

    @Override
    public void clearEmbeddedActivityWindowInfoCallback() {
        if (!Flags.activityWindowInfoFlag()) {
            return;
        }
        synchronized (mLock) {
            if (mEmbeddedActivityWindowInfoCallback == null) {
                return;
            }
            mEmbeddedActivityWindowInfoCallback = null;
            ClientTransactionListenerController.getInstance()
                    .unregisterActivityWindowInfoChangedListener(getActivityWindowInfoListener());
        }
    }

    @VisibleForTesting
    @GuardedBy("mLock")
    @Nullable
    BiConsumer<IBinder, ActivityWindowInfo> getActivityWindowInfoListener() {
        return mActivityWindowInfoListener;
    }

    @Nullable
    private static ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
    @Override
    public EmbeddedActivityWindowInfo getEmbeddedActivityWindowInfo(@NonNull Activity activity) {
        if (!Flags.activityWindowInfoFlag()) {
            return null;
        }
        synchronized (mLock) {
            final ActivityWindowInfo activityWindowInfo = getActivityWindowInfo(activity);
            return activityWindowInfo != null
                    ? translateActivityWindowInfo(activity, activityWindowInfo)
                    : null;
        }
    }

    @VisibleForTesting
    void onActivityWindowInfoChanged(@NonNull IBinder activityToken,
            @NonNull ActivityWindowInfo activityWindowInfo) {
        synchronized (mLock) {
            if (mEmbeddedActivityWindowInfoCallback == null) {
                return;
            }
            final Executor executor = mEmbeddedActivityWindowInfoCallback.first;
            final Consumer<EmbeddedActivityWindowInfo> callback =
                    mEmbeddedActivityWindowInfoCallback.second;

            final Activity activity = getActivity(activityToken);
            if (activity == null) {
                return;
            }
            final EmbeddedActivityWindowInfo info = translateActivityWindowInfo(
                    activity, activityWindowInfo);

            executor.execute(() -> callback.accept(info));
        }
    }

    @Nullable
    private ActivityWindowInfo getActivityWindowInfo(@NonNull Activity activity) {
        if (activity.isFinishing()) {
            return null;
        }
        final ActivityThread.ActivityClientRecord record =
                ActivityThread.currentActivityThread()
                        .getActivityClient(activity.getActivityToken());
        final ActivityThread.ActivityClientRecord record = getActivityClientRecord(activity);
        return record != null ? record.getActivityWindowInfo() : null;
    }

    @NonNull
    private static EmbeddedActivityWindowInfo translateActivityWindowInfo(
            @NonNull Activity activity, @NonNull ActivityWindowInfo activityWindowInfo) {
        final boolean isEmbedded = activityWindowInfo.isEmbedded();
        final Rect activityBounds = new Rect(activity.getResources().getConfiguration()
                .windowConfiguration.getBounds());
        final Rect taskBounds = new Rect(activityWindowInfo.getTaskBounds());
        final Rect activityStackBounds = new Rect(activityWindowInfo.getTaskFragmentBounds());
        return new EmbeddedActivityWindowInfo(activity, isEmbedded, activityBounds, taskBounds,
                activityStackBounds);
    }

    /**
     * If the two rules have the same presentation, and the calculated {@link SplitAttributes}
     * matches the {@link SplitAttributes} of {@link SplitContainer}, we can reuse the same
+90 −0
Original line number Diff line number Diff line
@@ -72,6 +72,8 @@ import static org.mockito.Mockito.times;
import android.annotation.NonNull;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.ActivityThread;
import android.app.servertransaction.ClientTransactionListenerController;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -83,9 +85,11 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
import android.platform.test.flag.junit.SetFlagsRule;
import android.util.ArraySet;
import android.view.WindowInsets;
import android.view.WindowMetrics;
import android.window.ActivityWindowInfo;
import android.window.TaskFragmentInfo;
import android.window.TaskFragmentOrganizer;
import android.window.TaskFragmentParentInfo;
@@ -99,7 +103,10 @@ import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.extensions.layout.WindowLayoutComponentImpl;
import androidx.window.extensions.layout.WindowLayoutInfo;

import com.android.window.flags.Flags;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -110,6 +117,8 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

/**
@@ -127,6 +136,9 @@ public class SplitControllerTest {
    private static final Intent PLACEHOLDER_INTENT = new Intent().setComponent(
            new ComponentName("test", "placeholder"));

    @Rule
    public final SetFlagsRule mSetFlagRule = new SetFlagsRule();

    private Activity mActivity;
    @Mock
    private Resources mActivityResources;
@@ -138,6 +150,13 @@ public class SplitControllerTest {
    private Handler mHandler;
    @Mock
    private WindowLayoutComponentImpl mWindowLayoutComponent;
    @Mock
    private ActivityWindowInfo mActivityWindowInfo;
    @Mock
    private BiConsumer<IBinder, ActivityWindowInfo> mActivityWindowInfoListener;
    @Mock
    private androidx.window.extensions.core.util.function.Consumer<EmbeddedActivityWindowInfo>
            mEmbeddedActivityWindowInfoCallback;

    private SplitController mSplitController;
    private SplitPresenter mSplitPresenter;
@@ -1529,6 +1548,73 @@ public class SplitControllerTest {
                .getTopNonFinishingActivity(), secondaryActivity);
    }

    @Test
    public void testIsActivityEmbedded() {
        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        assertFalse(mSplitController.isActivityEmbedded(mActivity));

        doReturn(true).when(mActivityWindowInfo).isEmbedded();

        assertTrue(mSplitController.isActivityEmbedded(mActivity));
    }

    @Test
    public void testGetEmbeddedActivityWindowInfo() {
        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        final boolean isEmbedded = true;
        final Rect activityBounds = mActivity.getResources().getConfiguration().windowConfiguration
                .getBounds();
        final Rect taskBounds = new Rect(0, 0, 1000, 2000);
        final Rect activityStackBounds = new Rect(0, 0, 500, 2000);
        doReturn(isEmbedded).when(mActivityWindowInfo).isEmbedded();
        doReturn(taskBounds).when(mActivityWindowInfo).getTaskBounds();
        doReturn(activityStackBounds).when(mActivityWindowInfo).getTaskFragmentBounds();

        final EmbeddedActivityWindowInfo expected = new EmbeddedActivityWindowInfo(mActivity,
                isEmbedded, activityBounds, taskBounds, activityStackBounds);
        assertEquals(expected, mSplitController.getEmbeddedActivityWindowInfo(mActivity));
    }

    @Test
    public void testSetEmbeddedActivityWindowInfoCallback() {
        mSetFlagRule.enableFlags(Flags.FLAG_ACTIVITY_WINDOW_INFO_FLAG);

        final ClientTransactionListenerController controller = ClientTransactionListenerController
                .getInstance();
        spyOn(controller);
        doNothing().when(controller).registerActivityWindowInfoChangedListener(any());
        doReturn(mActivityWindowInfoListener).when(mSplitController)
                .getActivityWindowInfoListener();
        final Executor executor = Runnable::run;

        // Register to ClientTransactionListenerController
        mSplitController.setEmbeddedActivityWindowInfoCallback(executor,
                mEmbeddedActivityWindowInfoCallback);

        verify(controller).registerActivityWindowInfoChangedListener(mActivityWindowInfoListener);
        verify(mEmbeddedActivityWindowInfoCallback, never()).accept(any());

        // Test onActivityWindowInfoChanged triggered.
        mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
                mActivityWindowInfo);

        verify(mEmbeddedActivityWindowInfoCallback).accept(any());

        // Unregister to ClientTransactionListenerController
        mSplitController.clearEmbeddedActivityWindowInfoCallback();

        verify(controller).unregisterActivityWindowInfoChangedListener(mActivityWindowInfoListener);

        // Test onActivityWindowInfoChanged triggered as no-op after clear callback.
        clearInvocations(mEmbeddedActivityWindowInfoCallback);
        mSplitController.onActivityWindowInfoChanged(mActivity.getActivityToken(),
                mActivityWindowInfo);

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

    /** Creates a mock activity in the organizer process. */
    private Activity createMockActivity() {
        return createMockActivity(TASK_ID);
@@ -1537,13 +1623,17 @@ public class SplitControllerTest {
    /** Creates a mock activity in the organizer process. */
    private Activity createMockActivity(int taskId) {
        final Activity activity = mock(Activity.class);
        final ActivityThread.ActivityClientRecord activityClientRecord =
                mock(ActivityThread.ActivityClientRecord.class);
        doReturn(mActivityResources).when(activity).getResources();
        final IBinder activityToken = new Binder();
        doReturn(activityToken).when(activity).getActivityToken();
        doReturn(activity).when(mSplitController).getActivity(activityToken);
        doReturn(activityClientRecord).when(mSplitController).getActivityClientRecord(activity);
        doReturn(taskId).when(activity).getTaskId();
        doReturn(new ActivityInfo()).when(activity).getActivityInfo();
        doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
        doReturn(mActivityWindowInfo).when(activityClientRecord).getActivityWindowInfo();
        return activity;
    }