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

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

Merge "Allow non-resizable apps in split-screen (12/n)" into sc-dev

parents 02d28b13 de551ad9
Loading
Loading
Loading
Loading
+45 −28
Original line number Diff line number Diff line
@@ -14,10 +14,23 @@
     See the License for the specific language governing permissions and
     limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<com.android.wm.shell.sizecompatui.SizeCompatHintPopup
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:clipToPadding="false"
        android:padding="@dimen/bubble_elevation">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/background_light"
            android:elevation="@dimen/bubble_elevation"
            android:orientation="vertical">

            <TextView
@@ -46,3 +59,7 @@
                android:textStyle="bold"/>

        </LinearLayout>

    </FrameLayout>

</com.android.wm.shell.sizecompatui.SizeCompatHintPopup>
+71 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.wm.shell.sizecompatui;

import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.Button;
import android.widget.FrameLayout;

import androidx.annotation.Nullable;

import com.android.wm.shell.R;

/** Popup to show the hint about the {@link SizeCompatRestartButton}. */
public class SizeCompatHintPopup extends FrameLayout implements View.OnClickListener {

    private SizeCompatUILayout mLayout;

    public SizeCompatHintPopup(Context context) {
        super(context);
    }

    public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public SizeCompatHintPopup(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public SizeCompatHintPopup(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    void inject(SizeCompatUILayout layout) {
        mLayout = layout;
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        final Button gotItButton = findViewById(R.id.got_it);
        gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
                null /* content */, null /* mask */));
        gotItButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        mLayout.dismissHint();
    }
}
+5 −82
Original line number Diff line number Diff line
@@ -22,19 +22,13 @@ import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.RippleDrawable;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.PopupWindow;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.R;

/** Button to restart the size compat activity. */
@@ -42,10 +36,6 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick
        View.OnLongClickListener {

    private SizeCompatUILayout mLayout;
    private ImageButton mRestartButton;
    @VisibleForTesting
    PopupWindow mShowingHint;
    private WindowManager.LayoutParams mWinParams;

    public SizeCompatRestartButton(@NonNull Context context) {
        super(context);
@@ -67,24 +57,19 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick

    void inject(SizeCompatUILayout layout) {
        mLayout = layout;
        mWinParams = layout.getWindowLayoutParams();
    }

    void remove() {
        dismissHint();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mRestartButton = findViewById(R.id.size_compat_restart_button);
        final ImageButton restartButton = findViewById(R.id.size_compat_restart_button);
        final ColorStateList color = ColorStateList.valueOf(Color.LTGRAY);
        final GradientDrawable mask = new GradientDrawable();
        mask.setShape(GradientDrawable.OVAL);
        mask.setColor(color);
        mRestartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
        mRestartButton.setOnClickListener(this);
        mRestartButton.setOnLongClickListener(this);
        restartButton.setBackground(new RippleDrawable(color, null /* content */, mask));
        restartButton.setOnClickListener(this);
        restartButton.setOnLongClickListener(this);
    }

    @Override
@@ -94,69 +79,7 @@ public class SizeCompatRestartButton extends FrameLayout implements View.OnClick

    @Override
    public boolean onLongClick(View v) {
        showHint();
        mLayout.onRestartButtonLongClicked();
        return true;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mLayout.mShouldShowHint) {
            mLayout.mShouldShowHint = false;
            showHint();
        }
    }

    @Override
    public void setVisibility(@Visibility int visibility) {
        if (visibility == View.GONE && mShowingHint != null) {
            // Also dismiss the popup.
            dismissHint();
        }
        super.setVisibility(visibility);
    }

    @Override
    public void setLayoutDirection(int layoutDirection) {
        final int gravity = SizeCompatUILayout.getGravity(layoutDirection);
        if (mWinParams.gravity != gravity) {
            mWinParams.gravity = gravity;
            getContext().getSystemService(WindowManager.class).updateViewLayout(this,
                    mWinParams);
        }
        super.setLayoutDirection(layoutDirection);
    }

    void showHint() {
        if (mShowingHint != null) {
            return;
        }

        // TODO: popup is not attached to the button surface. Need to handle this differently for
        // non-fullscreen task.
        final View popupView = LayoutInflater.from(getContext()).inflate(
                R.layout.size_compat_mode_hint, null);
        final PopupWindow popupWindow = new PopupWindow(popupView,
                LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
        popupWindow.setWindowLayoutType(mWinParams.type);
        popupWindow.setElevation(getResources().getDimension(R.dimen.bubble_elevation));
        popupWindow.setAnimationStyle(android.R.style.Animation_InputMethod);
        popupWindow.setClippingEnabled(false);
        popupWindow.setOnDismissListener(() -> mShowingHint = null);
        mShowingHint = popupWindow;

        final Button gotItButton = popupView.findViewById(R.id.got_it);
        gotItButton.setBackground(new RippleDrawable(ColorStateList.valueOf(Color.LTGRAY),
                null /* content */, null /* mask */));
        gotItButton.setOnClickListener(view -> dismissHint());
        popupWindow.showAtLocation(mRestartButton, mWinParams.gravity, mLayout.mPopupOffsetX,
                mLayout.mPopupOffsetY);
    }

    void dismissHint() {
        if (mShowingHint != null) {
            mShowingHint.dismiss();
            mShowingHint = null;
        }
    }
}
+8 −8
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
    /** Whether the IME is shown on display id. */
    private final Set<Integer> mDisplaysWithIme = new ArraySet<>(1);

    /** The showing buttons by task id. */
    /** The showing UIs by task id. */
    private final SparseArray<SizeCompatUILayout> mActiveLayouts = new SparseArray<>(0);

    /** Avoid creating display context frequently for non-default display. */
@@ -77,12 +77,12 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
    }

    /**
     * Called when the Task info changed. Creates and updates the restart button if there is an
     * activity in size compat, or removes the restart button if there is no size compat activity.
     * Called when the Task info changed. Creates and updates the size compat UI if there is an
     * activity in size compat, or removes the UI if there is no size compat activity.
     *
     * @param displayId display the task and activity are in.
     * @param taskId task the activity is in.
     * @param taskConfig task config to place the restart button with.
     * @param taskConfig task config to place the size compat UI with.
     * @param sizeCompatActivity the size compat activity in the task. Can be {@code null} if the
     *                           top activity in this Task is not in size compat.
     * @param taskListener listener to handle the Task Surface placement.
@@ -94,10 +94,10 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
            // Null token means the current foreground activity is not in size compatibility mode.
            removeLayout(taskId);
        } else if (mActiveLayouts.contains(taskId)) {
            // Button already exists, update the button layout.
            // UI already exists, update the UI layout.
            updateLayout(taskId, taskConfig, sizeCompatActivity, taskListener);
        } else {
            // Create a new restart button.
            // Create a new size compat UI.
            createLayout(displayId, taskId, taskConfig, sizeCompatActivity, taskListener);
        }
    }
@@ -106,7 +106,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
    public void onDisplayRemoved(int displayId) {
        mDisplayContextCache.remove(displayId);

        // Remove all buttons on the removed display.
        // Remove all size compat UIs on the removed display.
        final List<Integer> toRemoveTaskIds = new ArrayList<>();
        forAllLayoutsOnDisplay(displayId, layout -> toRemoveTaskIds.add(layout.getTaskId()));
        for (int i = toRemoveTaskIds.size() - 1; i >= 0; i--) {
@@ -128,7 +128,7 @@ public class SizeCompatUIController implements DisplayController.OnDisplaysChang
            mDisplaysWithIme.remove(displayId);
        }

        // Hide the button when input method is showing.
        // Hide the size compat UIs when input method is showing.
        forAllLayoutsOnDisplay(displayId, layout -> layout.updateImeVisibility(isShowing));
    }

+117 −32
Original line number Diff line number Diff line
@@ -30,7 +30,6 @@ import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.view.Gravity;
import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowManager;
@@ -43,7 +42,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;

/**
 * Records and handles layout of size compat UI on a task with size compat activity. Helps to
 * calculate proper bounds when configuration or button position changes.
 * calculate proper bounds when configuration or UI position changes.
 */
class SizeCompatUILayout {
    private static final String TAG = "SizeCompatUILayout";
@@ -56,12 +55,18 @@ class SizeCompatUILayout {
    private IBinder mActivityToken;
    private ShellTaskOrganizer.TaskListener mTaskListener;
    private DisplayLayout mDisplayLayout;
    @VisibleForTesting
    final SizeCompatUIWindowManager mWindowManager;

    @VisibleForTesting
    final SizeCompatUIWindowManager mButtonWindowManager;
    @VisibleForTesting
    @Nullable
    SizeCompatUIWindowManager mHintWindowManager;
    @VisibleForTesting
    @Nullable
    SizeCompatRestartButton mButton;
    @VisibleForTesting
    @Nullable
    SizeCompatHintPopup mHint;
    final int mButtonSize;
    final int mPopupOffsetX;
    final int mPopupOffsetY;
@@ -79,7 +84,7 @@ class SizeCompatUILayout {
        mTaskListener = taskListener;
        mDisplayLayout = displayLayout;
        mShouldShowHint = !hasShownHint;
        mWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);
        mButtonWindowManager = new SizeCompatUIWindowManager(mContext, taskConfig, this);

        mButtonSize =
                mContext.getResources().getDimensionPixelSize(R.dimen.size_compat_button_size);
@@ -87,21 +92,52 @@ class SizeCompatUILayout {
        mPopupOffsetY = mButtonSize;
    }

    /** Creates the button window. */
    /** Creates the activity restart button window. */
    void createSizeCompatButton(boolean isImeShowing) {
        if (isImeShowing || mButton != null) {
            // When ime is showing, wait until ime is dismiss to create UI.
            return;
        }
        mButton = mWindowManager.createSizeCompatUI();
        updateSurfacePosition();
        mButton = mButtonWindowManager.createSizeCompatButton();
        updateButtonSurfacePosition();

        if (mShouldShowHint) {
            // Only show by default for the first time.
            mShouldShowHint = false;
            createSizeCompatHint();
        }
    }

    /** Creates the restart button hint window. */
    private void createSizeCompatHint() {
        if (mHint != null) {
            // Hint already shown.
            return;
        }
        mHintWindowManager = createHintWindowManager();
        mHint = mHintWindowManager.createSizeCompatHint();
        updateHintSurfacePosition();
    }

    /** Releases the button window. */
    @VisibleForTesting
    SizeCompatUIWindowManager createHintWindowManager() {
        return new SizeCompatUIWindowManager(mContext, mTaskConfig, this);
    }

    /** Dismisses the hint window. */
    void dismissHint() {
        mHint = null;
        if (mHintWindowManager != null) {
            mHintWindowManager.release();
            mHintWindowManager = null;
        }
    }

    /** Releases the UI windows. */
    void release() {
        mButton.remove();
        dismissHint();
        mButton = null;
        mWindowManager.release();
        mButtonWindowManager.release();
    }

    /** Called when size compat info changed. */
@@ -115,7 +151,10 @@ class SizeCompatUILayout {

        // Update configuration.
        mContext = mContext.createConfigurationContext(taskConfig);
        mWindowManager.setConfiguration(taskConfig);
        mButtonWindowManager.setConfiguration(taskConfig);
        if (mHintWindowManager != null) {
            mHintWindowManager.setConfiguration(taskConfig);
        }

        if (mButton == null || prevTaskListener != taskListener) {
            // TaskListener changed, recreate the button for new surface parent.
@@ -126,14 +165,19 @@ class SizeCompatUILayout {

        if (!taskConfig.windowConfiguration.getBounds()
                .equals(prevTaskConfig.windowConfiguration.getBounds())) {
            // Reposition the button surface.
            updateSurfacePosition();
            // Reposition the UI surfaces.
            updateButtonSurfacePosition();
            updateHintSurfacePosition();
        }

        if (taskConfig.getLayoutDirection() != prevTaskConfig.getLayoutDirection()) {
            // Update layout for RTL.
            mButton.setLayoutDirection(taskConfig.getLayoutDirection());
            updateSurfacePosition();
            updateButtonSurfacePosition();
            if (mHint != null) {
                mHint.setLayoutDirection(taskConfig.getLayoutDirection());
                updateHintSurfacePosition();
            }
        }
    }

@@ -149,8 +193,9 @@ class SizeCompatUILayout {
        displayLayout.getStableBounds(curStableBounds);
        mDisplayLayout = displayLayout;
        if (!prevStableBounds.equals(curStableBounds)) {
            // Stable bounds changed, update button surface position.
            updateSurfacePosition();
            // Stable bounds changed, update UI surface positions.
            updateButtonSurfacePosition();
            updateHintSurfacePosition();
        }
    }

@@ -162,27 +207,46 @@ class SizeCompatUILayout {
            return;
        }

        // Hide size compat UIs when IME is showing.
        final int newVisibility = isImeShowing ? View.GONE : View.VISIBLE;
        if (mButton.getVisibility() != newVisibility) {
            mButton.setVisibility(newVisibility);
        }
        if (mHint != null && mHint.getVisibility() != newVisibility) {
            mHint.setVisibility(newVisibility);
        }
    }

    /** Gets the layout params for restart button. */
    WindowManager.LayoutParams getWindowLayoutParams() {
    WindowManager.LayoutParams getButtonWindowLayoutParams() {
        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
                // Cannot be wrap_content as this determines the actual window size
                mButtonSize, mButtonSize,
                TYPE_APPLICATION_OVERLAY,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
                PixelFormat.TRANSLUCENT);
        winParams.gravity = getGravity(getLayoutDirection());
        winParams.token = new Binder();
        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + mContext.getDisplayId());
        winParams.setTitle(SizeCompatRestartButton.class.getSimpleName() + getTaskId());
        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
        return winParams;
    }

    /** Gets the layout params for hint popup. */
    WindowManager.LayoutParams getHintWindowLayoutParams(SizeCompatHintPopup hint) {
        final WindowManager.LayoutParams winParams = new WindowManager.LayoutParams(
                // Cannot be wrap_content as this determines the actual window size
                hint.getMeasuredWidth(), hint.getMeasuredHeight(),
                TYPE_APPLICATION_OVERLAY,
                FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL,
                PixelFormat.TRANSLUCENT);
        winParams.token = new Binder();
        winParams.setTitle(SizeCompatHintPopup.class.getSimpleName() + getTaskId());
        winParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
        winParams.windowAnimations = android.R.style.Animation_InputMethod;
        return winParams;
    }

    /** Called when it is ready to be placed button surface button. */
    /** Called when it is ready to be placed size compat UI surface. */
    void attachToParentSurface(SurfaceControl.Builder b) {
        mTaskListener.attachChildSurfaceToTask(mTaskId, b);
    }
@@ -192,13 +256,17 @@ class SizeCompatUILayout {
        ActivityClient.getInstance().restartActivityProcessIfVisible(mActivityToken);
    }

    /** Called when the restart button is long clicked. */
    void onRestartButtonLongClicked() {
        createSizeCompatHint();
    }

    @VisibleForTesting
    void updateSurfacePosition() {
        if (mButton == null || mWindowManager.getSurfaceControl() == null) {
    void updateButtonSurfacePosition() {
        if (mButton == null || mButtonWindowManager.getSurfaceControl() == null) {
            return;
        }
        // The hint popup won't be at the correct position.
        mButton.dismissHint();
        final SurfaceControl leash = mButtonWindowManager.getSurfaceControl();

        // Use stable bounds to prevent the button from overlapping with system bars.
        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
@@ -212,8 +280,30 @@ class SizeCompatUILayout {
                : stableBounds.right - taskBounds.left - mButtonSize;
        final int positionY = stableBounds.bottom - taskBounds.top - mButtonSize;

        mSyncQueue.runInSync(t ->
                t.setPosition(mWindowManager.getSurfaceControl(), positionX, positionY));
        mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY));
    }

    void updateHintSurfacePosition() {
        if (mHint == null || mHintWindowManager == null
                || mHintWindowManager.getSurfaceControl() == null) {
            return;
        }
        final SurfaceControl leash = mHintWindowManager.getSurfaceControl();

        // Use stable bounds to prevent the hint from overlapping with system bars.
        final Rect taskBounds = mTaskConfig.windowConfiguration.getBounds();
        final Rect stableBounds = new Rect();
        mDisplayLayout.getStableBounds(stableBounds);
        stableBounds.intersect(taskBounds);

        // Position of the hint in the container coordinate.
        final int positionX = getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
                ? stableBounds.left - taskBounds.left + mPopupOffsetX
                : stableBounds.right - taskBounds.left - mPopupOffsetX - mHint.getMeasuredWidth();
        final int positionY =
                stableBounds.bottom - taskBounds.top - mPopupOffsetY - mHint.getMeasuredHeight();

        mSyncQueue.runInSync(t -> t.setPosition(leash, positionX, positionY));
    }

    int getDisplayId() {
@@ -227,9 +317,4 @@ class SizeCompatUILayout {
    private int getLayoutDirection() {
        return mContext.getResources().getConfiguration().getLayoutDirection();
    }

    static int getGravity(int layoutDirection) {
        return Gravity.BOTTOM
                | (layoutDirection == View.LAYOUT_DIRECTION_RTL ? Gravity.START : Gravity.END);
    }
}
Loading