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

Commit 4f0806e6 authored by Massimo Carli's avatar Massimo Carli Committed by Android (Google) Code Review
Browse files

Merge "[4/n] Implement restart confirmation dialog" into tm-qpr-dev

parents 6a1f582c 1107b8e3
Loading
Loading
Loading
Loading
+38 −5
Original line number Diff line number Diff line
@@ -16,11 +16,12 @@

package com.android.wm.shell.compatui;

import android.annotation.NonNull;
import android.app.TaskInfo;
import android.content.Context;
import android.content.SharedPreferences;
import android.provider.DeviceConfig;

import androidx.annotation.NonNull;

import com.android.wm.shell.R;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellMainThread;
@@ -34,11 +35,23 @@ import javax.inject.Inject;
@WMSingleton
public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedListener {

    static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG = "enable_letterbox_restart_dialog";
    private static final String KEY_ENABLE_LETTERBOX_RESTART_DIALOG =
            "enable_letterbox_restart_dialog";

    static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
    private static final String KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION =
            "enable_letterbox_reachability_education";

    /**
     * The name of the {@link SharedPreferences} that holds which user has seen the Restart
     * confirmation dialog.
     */
    private static final String DONT_SHOW_RESTART_DIALOG_PREF_NAME = "dont_show_restart_dialog";

    /**
     * The {@link SharedPreferences} instance for {@link #DONT_SHOW_RESTART_DIALOG_PREF_NAME}.
     */
    private final SharedPreferences mSharedPreferences;

    // Whether the extended restart dialog is enabled
    private boolean mIsRestartDialogEnabled;

@@ -70,6 +83,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
                false);
        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_APP_COMPAT, mainExecutor,
                this);
        mSharedPreferences = context.getSharedPreferences(DONT_SHOW_RESTART_DIALOG_PREF_NAME,
                Context.MODE_PRIVATE);
    }

    /**
@@ -102,6 +117,20 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
        mIsReachabilityEducationOverrideEnabled = enabled;
    }

    boolean getDontShowRestartDialogAgain(TaskInfo taskInfo) {
        final int userId = taskInfo.userId;
        final String packageName = taskInfo.topActivity.getPackageName();
        return mSharedPreferences.getBoolean(
                getDontShowAgainRestartKey(userId, packageName), /* default= */ false);
    }

    void setDontShowRestartDialogAgain(TaskInfo taskInfo) {
        final int userId = taskInfo.userId;
        final String packageName = taskInfo.topActivity.getPackageName();
        mSharedPreferences.edit().putBoolean(getDontShowAgainRestartKey(userId, packageName),
                true).apply();
    }

    @Override
    public void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
        // TODO(b/263349751): Update flag and default value to true
@@ -116,4 +145,8 @@ public class CompatUIConfiguration implements DeviceConfig.OnPropertiesChangedLi
                    KEY_ENABLE_LETTERBOX_REACHABILITY_EDUCATION, false);
        }
    }

    private String getDontShowAgainRestartKey(int userId, String packageName) {
        return packageName + "@" + userId;
    }
}
 No newline at end of file
+113 −3
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.content.res.Configuration;
import android.hardware.display.DisplayManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import android.view.Display;
import android.view.InsetsSourceControl;
@@ -49,6 +50,7 @@ import com.android.wm.shell.transition.Transitions;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
@@ -90,6 +92,18 @@ public class CompatUIController implements OnDisplaysChangedListener,
     */
    private final SparseArray<CompatUIWindowManager> mActiveCompatLayouts = new SparseArray<>(0);

    /**
     * {@link SparseArray} that maps task ids to {@link RestartDialogWindowManager} that are
     * currently visible
     */
    private final SparseArray<RestartDialogWindowManager> mTaskIdToRestartDialogWindowManagerMap =
            new SparseArray<>(0);

    /**
     * {@link Set} of task ids for which we need to display a restart confirmation dialog
     */
    private Set<Integer> mSetOfTaskIdsShowingRestartDialog = new HashSet<>();

    /**
     * The active Letterbox Education layout if there is one (there can be at most one active).
     *
@@ -111,12 +125,15 @@ public class CompatUIController implements OnDisplaysChangedListener,
    private final ShellExecutor mMainExecutor;
    private final Lazy<Transitions> mTransitionsLazy;
    private final DockStateReader mDockStateReader;
    private final CompatUIConfiguration mCompatUIConfiguration;

    private CompatUICallback mCallback;

    // Only show each hint once automatically in the process life.
    private final CompatUIHintsState mCompatUIHintsState;

    private final CompatUIShellCommandHandler mCompatUIShellCommandHandler;

    // Indicates if the keyguard is currently showing, in which case compat UIs shouldn't
    // be shown.
    private boolean mKeyguardShowing;
@@ -130,7 +147,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
            SyncTransactionQueue syncQueue,
            ShellExecutor mainExecutor,
            Lazy<Transitions> transitionsLazy,
            DockStateReader dockStateReader) {
            DockStateReader dockStateReader,
            CompatUIConfiguration compatUIConfiguration,
            CompatUIShellCommandHandler compatUIShellCommandHandler) {
        mContext = context;
        mShellController = shellController;
        mDisplayController = displayController;
@@ -140,14 +159,17 @@ public class CompatUIController implements OnDisplaysChangedListener,
        mMainExecutor = mainExecutor;
        mTransitionsLazy = transitionsLazy;
        mCompatUIHintsState = new CompatUIHintsState();
        shellInit.addInitCallback(this::onInit, this);
        mDockStateReader = dockStateReader;
        mCompatUIConfiguration = compatUIConfiguration;
        mCompatUIShellCommandHandler = compatUIShellCommandHandler;
        shellInit.addInitCallback(this::onInit, this);
    }

    private void onInit() {
        mShellController.addKeyguardChangeListener(this);
        mDisplayController.addDisplayWindowListener(this);
        mImeController.addPositionProcessor(this);
        mCompatUIShellCommandHandler.onInit();
    }

    /** Sets the callback for UI interactions. */
@@ -164,6 +186,9 @@ public class CompatUIController implements OnDisplaysChangedListener,
     */
    public void onCompatInfoChanged(TaskInfo taskInfo,
            @Nullable ShellTaskOrganizer.TaskListener taskListener) {
        if (taskInfo != null && !taskInfo.topActivityInSizeCompat) {
            mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
        }
        if (taskInfo.configuration == null || taskListener == null) {
            // Null token means the current foreground activity is not in compatibility mode.
            removeLayouts(taskInfo.taskId);
@@ -172,6 +197,7 @@ public class CompatUIController implements OnDisplaysChangedListener,

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

    @Override
@@ -278,7 +304,21 @@ public class CompatUIController implements OnDisplaysChangedListener,
            ShellTaskOrganizer.TaskListener taskListener) {
        return new CompatUIWindowManager(context,
                taskInfo, mSyncQueue, mCallback, taskListener,
                mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState);
                mDisplayController.getDisplayLayout(taskInfo.displayId), mCompatUIHintsState,
                mCompatUIConfiguration, this::onRestartButtonClicked);
    }

    private void onRestartButtonClicked(
            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> taskInfoState) {
        if (mCompatUIConfiguration.isRestartDialogEnabled()
                && !mCompatUIConfiguration.getDontShowRestartDialogAgain(
                taskInfoState.first)) {
            // We need to show the dialog
            mSetOfTaskIdsShowingRestartDialog.add(taskInfoState.first.taskId);
            onCompatInfoChanged(taskInfoState.first, taskInfoState.second);
        } else {
            mCallback.onSizeCompatRestartButtonClicked(taskInfoState.first.taskId);
        }
    }

    private void createOrUpdateLetterboxEduLayout(TaskInfo taskInfo,
@@ -327,6 +367,60 @@ public class CompatUIController implements OnDisplaysChangedListener,
        mActiveLetterboxEduLayout = null;
    }

    private void createOrUpdateRestartDialogLayout(TaskInfo taskInfo,
            ShellTaskOrganizer.TaskListener taskListener) {
        RestartDialogWindowManager layout =
                mTaskIdToRestartDialogWindowManagerMap.get(taskInfo.taskId);
        if (layout != null) {
            // TODO(b/266262111) Handle theme change when taskListener changes
            if (layout.getTaskListener() != taskListener) {
                mSetOfTaskIdsShowingRestartDialog.remove(taskInfo.taskId);
            }
            layout.setRequestRestartDialog(
                    mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
            // 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.
                mTaskIdToRestartDialogWindowManagerMap.remove(taskInfo.taskId);
            }
            return;
        }
        // Create a new UI layout.
        final Context context = getOrCreateDisplayContext(taskInfo.displayId);
        if (context == null) {
            return;
        }
        layout = createRestartDialogWindowManager(context, taskInfo, taskListener);
        layout.setRequestRestartDialog(
                mSetOfTaskIdsShowingRestartDialog.contains(taskInfo.taskId));
        if (layout.createLayout(showOnDisplay(taskInfo.displayId))) {
            // The new layout is eligible to be shown, add it the active layouts.
            mTaskIdToRestartDialogWindowManagerMap.put(taskInfo.taskId, layout);
        }
    }

    @VisibleForTesting
    RestartDialogWindowManager createRestartDialogWindowManager(Context context, TaskInfo taskInfo,
            ShellTaskOrganizer.TaskListener taskListener) {
        return new RestartDialogWindowManager(context, taskInfo, mSyncQueue, taskListener,
                mDisplayController.getDisplayLayout(taskInfo.displayId), mTransitionsLazy.get(),
                this::onRestartDialogCallback, this::onRestartDialogDismissCallback,
                mCompatUIConfiguration);
    }

    private void onRestartDialogCallback(
            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
        mTaskIdToRestartDialogWindowManagerMap.remove(stateInfo.first.taskId);
        mCallback.onSizeCompatRestartButtonClicked(stateInfo.first.taskId);
    }

    private void onRestartDialogDismissCallback(
            Pair<TaskInfo, ShellTaskOrganizer.TaskListener> stateInfo) {
        mSetOfTaskIdsShowingRestartDialog.remove(stateInfo.first.taskId);
        onCompatInfoChanged(stateInfo.first, stateInfo.second);
    }

    private void removeLayouts(int taskId) {
        final CompatUIWindowManager layout = mActiveCompatLayouts.get(taskId);
        if (layout != null) {
@@ -338,6 +432,14 @@ public class CompatUIController implements OnDisplaysChangedListener,
            mActiveLetterboxEduLayout.release();
            mActiveLetterboxEduLayout = null;
        }

        final RestartDialogWindowManager restartLayout =
                mTaskIdToRestartDialogWindowManagerMap.get(taskId);
        if (restartLayout != null) {
            restartLayout.release();
            mTaskIdToRestartDialogWindowManagerMap.remove(taskId);
            mSetOfTaskIdsShowingRestartDialog.remove(taskId);
        }
    }

    private Context getOrCreateDisplayContext(int displayId) {
@@ -382,6 +484,14 @@ public class CompatUIController implements OnDisplaysChangedListener,
        if (mActiveLetterboxEduLayout != null && condition.test(mActiveLetterboxEduLayout)) {
            callback.accept(mActiveLetterboxEduLayout);
        }
        for (int i = 0; i < mTaskIdToRestartDialogWindowManagerMap.size(); i++) {
            final int taskId = mTaskIdToRestartDialogWindowManagerMap.keyAt(i);
            final RestartDialogWindowManager layout =
                    mTaskIdToRestartDialogWindowManagerMap.get(taskId);
            if (layout != null && condition.test(layout)) {
                callback.accept(layout);
            }
        }
    }

    /** An implementation of {@link OnInsetsChangedListener} for a given display id. */
+18 −2
Original line number Diff line number Diff line
@@ -21,12 +21,14 @@ import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_APPLIED;
import static android.app.TaskInfo.CAMERA_COMPAT_CONTROL_TREATMENT_SUGGESTED;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.TaskInfo;
import android.app.TaskInfo.CameraCompatControlState;
import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import android.util.Pair;
import android.view.LayoutInflater;
import android.view.View;

@@ -38,6 +40,8 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.CompatUIController.CompatUICallback;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;

import java.util.function.Consumer;

/**
 * Window manager for the Size Compat restart button and Camera Compat control.
 */
@@ -50,6 +54,13 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {

    private final CompatUICallback mCallback;

    private final CompatUIConfiguration mCompatUIConfiguration;

    private final Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> mOnRestartButtonClicked;

    @NonNull
    private TaskInfo mTaskInfo;

    // Remember the last reported states in case visibility changes due to keyguard or IME updates.
    @VisibleForTesting
    boolean mHasSizeCompat;
@@ -68,12 +79,16 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
    CompatUIWindowManager(Context context, TaskInfo taskInfo,
            SyncTransactionQueue syncQueue, CompatUICallback callback,
            ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
            CompatUIHintsState compatUIHintsState) {
            CompatUIHintsState compatUIHintsState, CompatUIConfiguration compatUIConfiguration,
            Consumer<Pair<TaskInfo, ShellTaskOrganizer.TaskListener>> onRestartButtonClicked) {
        super(context, taskInfo, syncQueue, taskListener, displayLayout);
        mTaskInfo = taskInfo;
        mCallback = callback;
        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
        mCameraCompatControlState = taskInfo.cameraCompatControlState;
        mCompatUIHintsState = compatUIHintsState;
        mCompatUIConfiguration = compatUIConfiguration;
        mOnRestartButtonClicked = onRestartButtonClicked;
    }

    @Override
@@ -119,6 +134,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {
    @Override
    public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
            boolean canShow) {
        mTaskInfo = taskInfo;
        final boolean prevHasSizeCompat = mHasSizeCompat;
        final int prevCameraCompatControlState = mCameraCompatControlState;
        mHasSizeCompat = taskInfo.topActivityInSizeCompat;
@@ -138,7 +154,7 @@ class CompatUIWindowManager extends CompatUIWindowManagerAbstract {

    /** Called when the restart button is clicked. */
    void onRestartButtonClicked() {
        mCallback.onSizeCompatRestartButtonClicked(mTaskId);
        mOnRestartButtonClicked.accept(Pair.create(mTaskInfo, getTaskListener()));
    }

    /** Called when the camera treatment button is clicked. */
+5 −0
Original line number Diff line number Diff line
@@ -151,6 +151,7 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
    @Override
    public void setConfiguration(Configuration configuration) {
        super.setConfiguration(configuration);
        // TODO(b/266262111): Investigate loss of theme configuration when switching TaskListener
        mContext = mContext.createConfigurationContext(configuration);
    }

@@ -169,6 +170,10 @@ public abstract class CompatUIWindowManagerAbstract extends WindowlessWindowMana
        initSurface(mLeash);
    }

    protected ShellTaskOrganizer.TaskListener getTaskListener() {
        return mTaskListener;
    }

    /** Inits the z-order of the surface. */
    private void initSurface(SurfaceControl leash) {
        final int z = getZOrder();
+107 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.compatui;

import android.annotation.Nullable;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.widget.CheckBox;
import android.widget.TextView;

import androidx.constraintlayout.widget.ConstraintLayout;

import com.android.wm.shell.R;

import java.util.function.Consumer;

/**
 * Container for a SCM restart confirmation dialog and background dim.
 */
public class RestartDialogLayout extends ConstraintLayout implements DialogContainerSupplier {

    private View mDialogContainer;
    private TextView mDialogTitle;
    private Drawable mBackgroundDim;

    public RestartDialogLayout(Context context) {
        this(context, null);
    }

    public RestartDialogLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RestartDialogLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

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

    @Override
    public View getDialogContainerView() {
        return mDialogContainer;
    }

    TextView getDialogTitle() {
        return mDialogTitle;
    }

    @Override
    public Drawable getBackgroundDimDrawable() {
        return mBackgroundDim;
    }

    /**
     * Register a callback for the dismiss button and background dim.
     *
     * @param callback The callback to register or null if all on click listeners should be removed.
     */
    void setDismissOnClickListener(@Nullable Runnable callback) {
        final OnClickListener listener = callback == null ? null : view -> callback.run();
        findViewById(R.id.letterbox_restart_dialog_dismiss_button).setOnClickListener(listener);
    }

    /**
     * Register a callback for the restart button
     *
     * @param callback The callback to register or null if all on click listeners should be removed.
     */
    void setRestartOnClickListener(@Nullable Consumer<Boolean> callback) {
        final CheckBox dontShowAgainCheckbox = findViewById(R.id.letterbox_restart_dialog_checkbox);
        final OnClickListener listener = callback == null ? null : view -> callback.accept(
                dontShowAgainCheckbox.isChecked());
        findViewById(R.id.letterbox_restart_dialog_restart_button).setOnClickListener(listener);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mDialogContainer = findViewById(R.id.letterbox_restart_dialog_container);
        mDialogTitle = findViewById(R.id.letterbox_restart_dialog_title);
        mBackgroundDim = getBackground().mutate();
        // Set the alpha of the background dim to 0 for enter animation.
        mBackgroundDim.setAlpha(0);
        // We add a no-op on-click listener to the dialog container so that clicks on it won't
        // propagate to the listener of the layout (which represents the background dim).
        mDialogContainer.setOnClickListener(view -> {});
    }
}
Loading