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

Commit 25255265 authored by Chris Li's avatar Chris Li
Browse files

Hook EmbeddedActivityWindowInfo APIs to ClientTransactionListener

Bug: 287582673
Test: atest WMJetpackUnitTests:SplitControllerTest
Change-Id: I84dbfb5f90326fac30096ba94dd83e4bda5cfe47
parent b8a6fa9f
Loading
Loading
Loading
Loading
+113 −6
Original line number Diff line number Diff line
@@ -55,6 +55,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;
@@ -102,6 +103,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.
@@ -181,6 +183,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;
@@ -2456,6 +2472,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;
@@ -2469,8 +2492,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;
    }

@@ -2875,17 +2897,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;
    }