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

Commit 2d609769 authored by tomnatan's avatar tomnatan
Browse files

[6/n] Letterbox Education: add window manager and connect to controller.

Bug: 207010227
Test: atest WMShellUnitTests:CompatUIWindowManagerTest
Test: atest WMShellUnitTests:CompatUIControllerTest
Change-Id: I1f85245180ffa9b3abe409a0f18bf923bea3a442
parent dc7a4d59
Loading
Loading
Loading
Loading
+99 −33
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -74,8 +75,22 @@ public class CompatUIController implements OnDisplaysChangedListener,
    private final SparseArray<PerDisplayOnInsetsChangedListener> mOnInsetsChangedListeners =
            new SparseArray<>(0);

    /** The showing UIs by task id. */
    private final SparseArray<CompatUIWindowManager> mActiveLayouts = new SparseArray<>(0);
    /**
     * The active Compat Control UI layouts by task id.
     *
     * <p>An active layout is a layout that is eligible to be shown for the associated task but
     * isn't necessarily shown at a given time.
     */
    private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0);

    /**
     * The active Letterbox Education layout if there is one (there can be at most one active).
     *
     * <p>An active layout is a layout that is eligible to be shown for the associated task but
     * isn't necessarily shown at a given time.
     */
    @Nullable
    private LetterboxEduWindowManager mActiveLetterboxEduLayout;

    /** Avoid creating display context frequently for non-default display. */
    private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
@@ -135,14 +150,12 @@ public class CompatUIController implements OnDisplaysChangedListener,
            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
        if (taskInfo.configuration == null || taskListener == null) {
            // Null token means the current foreground activity is not in compatibility mode.
            removeLayout(taskInfo.taskId);
        } else if (mActiveLayouts.contains(taskInfo.taskId)) {
            // UI already exists, update the UI layout.
            updateLayout(taskInfo, taskListener);
        } else {
            // Create a new compat UI.
            createLayout(taskInfo, taskListener);
            removeLayouts(taskInfo.taskId);
            return;
        }

        createOrUpdateCompatLayout(taskInfo, taskListener);
        createOrUpdateLetterboxEduLayout(taskInfo, taskListener);
    }

    @Override
@@ -159,7 +172,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
        final List<Integer> toRemoveTaskIds = new ArrayList<>();
        forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
        for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
            removeLayout(toRemoveTaskIds.get(i));
            removeLayouts(toRemoveTaskIds.get(i));
        }
    }

@@ -218,26 +231,39 @@ public class CompatUIController implements OnDisplaysChangedListener,
        return mDisplaysWithIme.contains(displayId);
    }

    private void createLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
    private void createOrUpdateCompatLayout(TaskInfo taskInfo,
            ShellTaskOrganizer.TaskListener taskListener) {
        CompatUIWindowManager layout = mActiveCompatLayouts.get(taskInfo.taskId);
        if (layout != null) {
            // UI already exists, update the UI layout.
            if (!layout.updateCompatInfo(taskInfo, taskListener,
                    showOnDisplay(layout.getDisplayId()))) {
                // The layout is no longer eligible to be shown, remove from active layouts.
                mActiveCompatLayouts.remove(taskInfo.taskId);
            }
            return;
        }

        // Create a new UI layout.
        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
        if (context == null) {
            Log.e(TAG, "Cannot get context for display " + taskInfo.displayId);
            return;
        }

        final CompatUIWindowManager compatUIWindowManager =
                createLayout(context, taskInfo, taskListener);
        mActiveLayouts.put(taskInfo.taskId, compatUIWindowManager);
        compatUIWindowManager.createLayout(showOnDisplay(taskInfo.displayId), taskInfo);
        layout = createCompatUiWindowManager(context, taskInfo, taskListener);
        if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
            // The new layout is eligible to be shown, add it the active layouts.
            mActiveCompatLayouts.put(taskInfo.taskId, layout);
        }
    }

    @VisibleForTesting
    CompatUIWindowManager createLayout(Context context, TaskInfo taskInfo,
    CompatUIWindowManager createCompatUiWindowManager(Context context, TaskInfo taskInfo,
            ShellTaskOrganizer.TaskListener taskListener) {
        final CompatUIWindowManager compatUIWindowManager = new CompatUIWindowManager(context,
                taskInfo.configuration, mSyncQueue, mCallback, taskInfo.taskId, taskListener,
                taskInfo, mSyncQueue, mCallback, taskListener,
                mDisplayController.getDisplayLayout(taskInfo.displayId), mHasShownSizeCompatHint,
                mHasShownCameraCompatHint);
        // TODO(b/218304113): updates values only if hints are actually shown to the user.
        // Only show hints for the first time.
        if (taskInfo.topActivityInSizeCompat) {
            mHasShownSizeCompatHint = true;
@@ -248,19 +274,53 @@ public class CompatUIController implements OnDisplaysChangedListener,
        return compatUIWindowManager;
    }

    private void updateLayout(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener) {
        final CompatUIWindowManager layout = mActiveLayouts.get(taskInfo.taskId);
        if (layout == null) {
    private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
            ShellTaskOrganizer.TaskListener taskListener) {
        if (mActiveLetterboxEduLayout != null
                && mActiveLetterboxEduLayout.getTaskId() == taskInfo.taskId) {
            // UI already exists, update the UI layout.
            if (!mActiveLetterboxEduLayout.updateCompatInfo(taskInfo, taskListener,
                    showOnDisplay(mActiveLetterboxEduLayout.getDisplayId()))) {
                // The layout is no longer eligible to be shown, clear active layout.
                mActiveLetterboxEduLayout = null;
            }
            return;
        }

        // Create a new UI layout.
        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
        if (context == null) {
            return;
        }
        layout.updateCompatInfo(taskInfo, taskListener, showOnDisplay(layout.getDisplayId()));
        LetterboxEduWindowManager newLayout = new LetterboxEduWindowManager(context, taskInfo,
                mSyncQueue, taskListener, mDisplayController.getDisplayLayout(taskInfo.displayId),
                this::onLetterboxEduDismissed);
        if (newLayout.createLayout(showOnDisplay(taskInfo.displayId))) {
            // The new layout is eligible to be shown, make it the active layout.
            if (mActiveLetterboxEduLayout != null) {
                // Release the previous layout since at most one can be active.
                // Since letterbox education is only shown once to the user, releasing the previous
                // layout is only a precaution.
                mActiveLetterboxEduLayout.release();
            }
            mActiveLetterboxEduLayout = newLayout;
        }
    }

    private void onLetterboxEduDismissed() {
        mActiveLetterboxEduLayout = null;
    }

    private void removeLayout(int taskId) {
        final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
    private void removeLayouts(int taskId) {
        final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
        if (layout != null) {
            layout.release();
            mActiveLayouts.remove(taskId);
            mActiveCompatLayouts.remove(taskId);
        }

        if (mActiveLetterboxEduLayout != null && mActiveLetterboxEduLayout.getTaskId() == taskId) {
            mActiveLetterboxEduLayout.release();
            mActiveLetterboxEduLayout = null;
        }
    }

@@ -278,28 +338,34 @@ public class CompatUIController implements OnDisplaysChangedListener,
            if (display != null) {
                context = mContext.createDisplayContext(display);
                mDisplayContextCache.put(displayId, new WeakReference<>(context));
            } else {
                Log.e(TAG, "Cannot get context for display " + displayId);
            }
        }
        return context;
    }

    private void forAllLayoutsOnDisplay(int displayId, Consumer<CompatUIWindowManager> callback) {
    private void forAllLayoutsOnDisplay(int displayId,
            Consumer<CompatUIWindowManagerAbstract> callback) {
        forAllLayouts(layout -> layout.getDisplayId() == displayId, callback);
    }

    private void forAllLayouts(Consumer<CompatUIWindowManager> callback) {
    private void forAllLayouts(Consumer<CompatUIWindowManagerAbstract> callback) {
        forAllLayouts(layout -> true, callback);
    }

    private void forAllLayouts(Predicate<CompatUIWindowManager> condition,
            Consumer<CompatUIWindowManager> callback) {
        for (int i = 0; i < mActiveLayouts.size(); i++) {
            final int taskId = mActiveLayouts.keyAt(i);
            final CompatUIWindowManager layout = mActiveLayouts.get(taskId);
    private void forAllLayouts(Predicate<CompatUIWindowManagerAbstract> condition,
            Consumer<CompatUIWindowManagerAbstract> callback) {
        for (int i = 0; i < mActiveCompatLayouts.size(); i++) {
            final int taskId = mActiveCompatLayouts.keyAt(i);
            final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
            if (layout != null && condition.test(layout)) {
                callback.accept(layout);
            }
        }
        if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
            callback.accept(mActiveLetterboxEduLayout);
        }
    }

    /**
+20 −23
Original line number Diff line number Diff line
@@ -25,7 +25,6 @@ import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.util.Log;
import android.view.LayoutInflater;
@@ -36,6 +35,8 @@ import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;

/**
 * Window manager for the Size Compat restart button and Camera Compat control.
@@ -43,18 +44,19 @@ import com.android.wm.shell.common.SyncTransactionQueue;
class CompatUIWindowManager extends CompatUIWindowManagerAbstract {

    /**
     * The Compat UI should be the topmost child of the Task in case there can be more than one
     * child.
     * The Compat UI should be below the Letterbox Education.
     */
    private static final int Z_ORDER = Integer.MAX_VALUE;
    private static final int Z_ORDER = LetterboxEduWindowManager.Z_ORDER - 1;

    private final CompatUIController.CompatUICallback mCallback;
    private final CompatUICallback mCallback;

    // Remember the last reported states in case visibility changes due to keyguard or IME updates.
    @VisibleForTesting
    boolean mHasSizeCompat;

    @VisibleForTesting
    @CameraCompatControlState
    private int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;
    int mCameraCompatControlState = CAMERA_COMPAT_CONTROL_HIDDEN;

    @VisibleForTesting
    boolean mShouldShowSizeCompatHint;
@@ -65,12 +67,14 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
    @VisibleForTesting
    CompatUILayout mLayout;

    CompatUIWindowManager(Context context, Configuration taskConfig,
            SyncTransactionQueue syncQueue, CompatUIController.CompatUICallback callback,
            int taskId, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
    CompatUIWindowManager(Context context, TaskInfo taskInfo,
            SyncTransactionQueue syncQueue, CompatUICallback callback,
            ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
            boolean hasShownSizeCompatHint, boolean hasShownCameraCompatHint) {
        super(context, taskConfig, syncQueue, taskId, taskListener, displayLayout);
        super(context, taskInfo, syncQueue, taskListener, displayLayout);
        mCallback = callback;
        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
        mCameraCompatControlState = taskInfo.cameraCompatControlState;
        mShouldShowSizeCompatHint = !hasShownSizeCompatHint;
        mShouldShowCameraCompatHint = !hasShownCameraCompatHint;
    }
@@ -80,7 +84,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
        return Z_ORDER;
    }


    @Override
    protected @Nullable View getLayout() {
        return mLayout;
@@ -96,16 +99,6 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
        return mHasSizeCompat || shouldShowCameraControl();
    }

    /**
     * Updates the internal state with respect to {@code taskInfo} and calls {@link
     * #createLayout(boolean)}.
     */
    void createLayout(boolean canShow, TaskInfo taskInfo) {
        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
        mCameraCompatControlState = taskInfo.cameraCompatControlState;
        createLayout(canShow);
    }

    @Override
    protected View createLayout() {
        mLayout = inflateLayout();
@@ -127,19 +120,23 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
    }

    @Override
    public void updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
    public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
            boolean canShow) {
        final boolean prevHasSizeCompat = mHasSizeCompat;
        final int prevCameraCompatControlState = mCameraCompatControlState;
        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
        mCameraCompatControlState = taskInfo.cameraCompatControlState;

        super.updateCompatInfo(taskInfo, taskListener, canShow);
        if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
            return false;
        }

        if (prevHasSizeCompat != mHasSizeCompat
                || prevCameraCompatControlState != mCameraCompatControlState) {
            updateVisibilityOfViews();
        }

        return true;
    }

    /** Called when the restart button is clicked. */
+37 −20
Original line number Diff line number Diff line
@@ -49,7 +49,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
 *
 * <p>Holds view hierarchy of a root surface and helps to inflate and manage layout.
 */
abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {

    protected final SyncTransactionQueue mSyncQueue;
    protected final int mDisplayId;
@@ -75,15 +75,15 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
    @Nullable
    protected SurfaceControl mLeash;

    protected CompatUIWindowManagerAbstract(Context context, Configuration taskConfig,
            SyncTransactionQueue syncQueue, int taskId,
            ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout) {
        super(taskConfig, null /* rootSurface */, null /* hostInputToken */);
    protected CompatUIWindowManagerAbstract(Context context, TaskInfo taskInfo,
            SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener,
            DisplayLayout displayLayout) {
        super(taskInfo.configuration, null /* rootSurface */, null /* hostInputToken */);
        mContext = context;
        mSyncQueue = syncQueue;
        mTaskConfig = taskConfig;
        mTaskConfig = taskInfo.configuration;
        mDisplayId = mContext.getDisplayId();
        mTaskId = taskId;
        mTaskId = taskInfo.taskId;
        mTaskListener = taskListener;
        mDisplayLayout = displayLayout;
        mStableBounds = new Rect();
@@ -105,12 +105,18 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
     * Inflates and inits the layout of this window manager on to the root surface if both {@code
     * canShow} and {@link #eligibleToShowLayout} are true.
     *
     * <p>Doesn't do anything if layout is not eligible to be shown.
     *
     * @param canShow whether the layout is allowed to be shown by the parent controller.
     * @return whether the layout is eligible to be shown.
     */
    void createLayout(boolean canShow) {
        if (!canShow || !eligibleToShowLayout() || getLayout() != null) {
            // Wait until layout should be visible.
            return;
    protected boolean createLayout(boolean canShow) {
        if (!eligibleToShowLayout()) {
            return false;
        }
        if (!canShow || getLayout() != null) {
            // Wait until layout should be visible, or layout was already created.
            return true;
        }

        if (mViewHost != null) {
@@ -123,6 +129,8 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
        mViewHost.setView(createLayout(), getWindowLayoutParams());

        updateSurfacePosition();

        return true;
    }

    /** Inflates and inits the layout of this window manager. */
@@ -174,9 +182,12 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
    /**
     * Called when compat info changed.
     *
     * <p>The window manager is released if the layout is no longer eligible to be shown.
     *
     * @param canShow whether the layout is allowed to be shown by the parent controller.
     * @return whether the layout is eligible to be shown.
     */
    void updateCompatInfo(TaskInfo taskInfo,
    protected boolean updateCompatInfo(TaskInfo taskInfo,
            ShellTaskOrganizer.TaskListener taskListener, boolean canShow) {
        final Configuration prevTaskConfig = mTaskConfig;
        final ShellTaskOrganizer.TaskListener prevTaskListener = mTaskListener;
@@ -186,12 +197,16 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
        // Update configuration.
        setConfiguration(mTaskConfig);

        if (!eligibleToShowLayout()) {
            release();
            return false;
        }

        View layout = getLayout();
        if (layout == null || prevTaskListener != taskListener) {
            // TaskListener changed, recreate the layout for new surface parent.
            release();
            createLayout(canShow);
            return;
            return createLayout(canShow);
        }

        boolean boundsUpdated = !mTaskConfig.windowConfiguration.getBounds().equals(
@@ -207,6 +222,8 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
            // Update layout for RTL.
            layout.setLayoutDirection(mTaskConfig.getLayoutDirection());
        }

        return true;
    }


@@ -248,16 +265,16 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
        mTaskListener.attachChildSurfaceToTask(mTaskId, b);
    }

    int getDisplayId() {
    public int getDisplayId() {
        return mDisplayId;
    }

    int getTaskId() {
    public int getTaskId() {
        return mTaskId;
    }

    /** Releases the surface control and tears down the view hierarchy. */
    void release() {
    public void release() {
        // Hiding before releasing to avoid flickering when transitioning to the Home screen.
        View layout = getLayout();
        if (layout != null) {
@@ -278,7 +295,7 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
    }

    /** Re-layouts the view host and updates the surface position. */
    void relayout() {
    public void relayout() {
        if (mViewHost == null) {
            return;
        }
@@ -334,7 +351,7 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
    }

    /** Gets the layout params. */
    private WindowManager.LayoutParams getWindowLayoutParams() {
    protected WindowManager.LayoutParams getWindowLayoutParams() {
        View layout = getLayout();
        if (layout == null) {
            return new WindowManager.LayoutParams();
@@ -345,7 +362,7 @@ abstract class CompatUIWindowManagerAbstract extends WindowlessWindowManager {
    }

    /** Gets the layout params given the width and height of the layout. */
    private WindowManager.LayoutParams getWindowLayoutParams(int width, int height) {
    protected WindowManager.LayoutParams getWindowLayoutParams(int width, int height) {
        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
                // Cannot be wrap_content as this determines the actual window size
                width, height,
+13 −0
Original line number Diff line number Diff line
@@ -35,6 +35,8 @@ class LetterboxEduDialogLayout extends FrameLayout {
    // 204 is simply 255 * 0.8.
    private static final int BACKGROUND_DIM_ALPHA = 204;

    private LetterboxEduWindowManager mWindowManager;

    public LetterboxEduDialogLayout(Context context) {
        this(context, null);
    }
@@ -52,6 +54,17 @@ class LetterboxEduDialogLayout extends FrameLayout {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    void inject(LetterboxEduWindowManager windowManager) {
        mWindowManager = windowManager;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        // Need to relayout after visibility changes since they affect size.
        mWindowManager.relayout();
    }

    /**
     * Register a callback for the dismiss button and background dim.
     *
+150 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading