Files
Lawnchair/wmshell/src/com/android/wm/shell/compatui/ReachabilityEduWindowManager.java
T
2024-11-02 10:55:28 +08:00

322 lines
13 KiB
Java

/*
* Copyright (C) 2023 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 static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.window.TaskConstants.TASK_CHILD_LAYER_COMPAT_UI;
import android.annotation.Nullable;
import android.app.AppCompatTaskInfo;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import com.android.internal.annotations.VisibleForTesting;
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.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import java.util.function.BiConsumer;
import java.util.function.Function;
/**
* Window manager for the reachability education
*/
class ReachabilityEduWindowManager extends CompatUIWindowManagerAbstract {
private static final int REACHABILITY_LEFT_OR_UP_POSITION = 0;
private static final int REACHABILITY_RIGHT_OR_BOTTOM_POSITION = 2;
private final CompatUIConfiguration mCompatUIConfiguration;
private final ShellExecutor mMainExecutor;
private boolean mIsLetterboxDoubleTapEnabled;
private int mLetterboxVerticalPosition;
private int mLetterboxHorizontalPosition;
private int mTopActivityLetterboxWidth;
private int mTopActivityLetterboxHeight;
private long mNextHideTime = -1L;
private boolean mForceUpdate = false;
// We decided to force the visualization of the double-tap animated icons every time the user
// double-taps.
private boolean mHasUserDoubleTapped;
// When the size of the letterboxed app changes and the icons are visible
// we need to animate them.
private boolean mHasLetterboxSizeChanged;
private final BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> mOnDismissCallback;
private final Function<Integer, Integer> mDisappearTimeSupplier;
@Nullable
@VisibleForTesting
ReachabilityEduLayout mLayout;
ReachabilityEduWindowManager(Context context, TaskInfo taskInfo,
SyncTransactionQueue syncQueue,
ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout,
CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor,
BiConsumer<TaskInfo, ShellTaskOrganizer.TaskListener> onDismissCallback,
Function<Integer, Integer> disappearTimeSupplier) {
super(context, taskInfo, syncQueue, taskListener, displayLayout);
final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
mTopActivityLetterboxHeight = appCompatTaskInfo.topActivityLetterboxHeight;
mCompatUIConfiguration = compatUIConfiguration;
mMainExecutor = mainExecutor;
mOnDismissCallback = onDismissCallback;
mDisappearTimeSupplier = disappearTimeSupplier;
}
@Override
protected int getZOrder() {
return TASK_CHILD_LAYER_COMPAT_UI + 1;
}
@Override
protected @Nullable View getLayout() {
return mLayout;
}
@Override
protected void removeLayout() {
mLayout = null;
}
@Override
protected boolean eligibleToShowLayout() {
return mIsLetterboxDoubleTapEnabled
&& (mLetterboxVerticalPosition != -1 || mLetterboxHorizontalPosition != -1);
}
@Override
protected View createLayout() {
mLayout = inflateLayout();
mLayout.inject(this);
updateVisibilityOfViews();
return mLayout;
}
@VisibleForTesting
ReachabilityEduLayout inflateLayout() {
return (ReachabilityEduLayout) LayoutInflater.from(mContext).inflate(
R.layout.reachability_ui_layout, null);
}
@Override
public boolean updateCompatInfo(TaskInfo taskInfo, ShellTaskOrganizer.TaskListener taskListener,
boolean canShow) {
final boolean prevIsLetterboxDoubleTapEnabled = mIsLetterboxDoubleTapEnabled;
final int prevLetterboxVerticalPosition = mLetterboxVerticalPosition;
final int prevLetterboxHorizontalPosition = mLetterboxHorizontalPosition;
final int prevTopActivityLetterboxWidth = mTopActivityLetterboxWidth;
final int prevTopActivityLetterboxHeight = mTopActivityLetterboxHeight;
final AppCompatTaskInfo appCompatTaskInfo = taskInfo.appCompatTaskInfo;
mIsLetterboxDoubleTapEnabled = appCompatTaskInfo.isLetterboxDoubleTapEnabled;
mLetterboxVerticalPosition = appCompatTaskInfo.topActivityLetterboxVerticalPosition;
mLetterboxHorizontalPosition = appCompatTaskInfo.topActivityLetterboxHorizontalPosition;
mTopActivityLetterboxWidth = appCompatTaskInfo.topActivityLetterboxWidth;
mTopActivityLetterboxHeight = appCompatTaskInfo.topActivityLetterboxHeight;
mHasUserDoubleTapped = appCompatTaskInfo.isFromLetterboxDoubleTap;
if (!super.updateCompatInfo(taskInfo, taskListener, canShow)) {
return false;
}
mHasLetterboxSizeChanged = prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
|| prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight;
if (mHasUserDoubleTapped || prevIsLetterboxDoubleTapEnabled != mIsLetterboxDoubleTapEnabled
|| prevLetterboxVerticalPosition != mLetterboxVerticalPosition
|| prevLetterboxHorizontalPosition != mLetterboxHorizontalPosition
|| prevTopActivityLetterboxWidth != mTopActivityLetterboxWidth
|| prevTopActivityLetterboxHeight != mTopActivityLetterboxHeight) {
updateVisibilityOfViews();
}
return true;
}
void forceUpdate(boolean forceUpdate) {
mForceUpdate = forceUpdate;
}
@Override
protected void onParentBoundsChanged() {
if (mLayout == null) {
return;
}
// Both the layout dimensions and dialog margins depend on the parent bounds.
WindowManager.LayoutParams windowLayoutParams = getWindowLayoutParams();
mLayout.setLayoutParams(windowLayoutParams);
relayout(windowLayoutParams);
}
/** Gets the layout params. */
protected WindowManager.LayoutParams getWindowLayoutParams() {
View layout = getLayout();
if (layout == null) {
return new WindowManager.LayoutParams();
}
// Measure how big the hint is since its size depends on the text size.
final Rect taskBounds = getTaskBounds();
layout.measure(View.MeasureSpec.makeMeasureSpec(taskBounds.width(),
View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(taskBounds.height(),
View.MeasureSpec.EXACTLY));
return getWindowLayoutParams(layout.getMeasuredWidth(), layout.getMeasuredHeight());
}
/**
* @return Flags to use for the WindowManager layout
*/
@Override
protected int getWindowManagerLayoutParamsFlags() {
return FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE;
}
@Override
@VisibleForTesting
public void updateSurfacePosition() {
if (mLayout == null) {
return;
}
updateSurfacePosition(0, 0);
}
void updateHideTime() {
mNextHideTime = SystemClock.uptimeMillis() + getDisappearTimeMs();
}
private long getDisappearTimeMs() {
return mDisappearTimeSupplier.apply(
AccessibilityManager.FLAG_CONTENT_ICONS | AccessibilityManager.FLAG_CONTENT_TEXT);
}
private void updateVisibilityOfViews() {
if (mLayout == null) {
return;
}
final TaskInfo lastTaskInfo = getLastTaskInfo();
final boolean hasSeenHorizontalReachabilityEdu =
mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(lastTaskInfo);
final boolean hasSeenVerticalReachabilityEdu =
mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(lastTaskInfo);
final boolean eligibleForDisplayHorizontalEducation = mForceUpdate
|| !hasSeenHorizontalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxHorizontalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxHorizontalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
final boolean eligibleForDisplayVerticalEducation = mForceUpdate
|| !hasSeenVerticalReachabilityEdu
|| (mHasUserDoubleTapped
&& (mLetterboxVerticalPosition == REACHABILITY_LEFT_OR_UP_POSITION
|| mLetterboxVerticalPosition == REACHABILITY_RIGHT_OR_BOTTOM_POSITION));
if (mIsLetterboxDoubleTapEnabled && (eligibleForDisplayHorizontalEducation
|| eligibleForDisplayVerticalEducation)) {
int availableWidth = getTaskBounds().width() - mTopActivityLetterboxWidth;
int availableHeight = getTaskBounds().height() - mTopActivityLetterboxHeight;
mLayout.handleVisibility(eligibleForDisplayHorizontalEducation,
eligibleForDisplayVerticalEducation,
mLetterboxVerticalPosition, mLetterboxHorizontalPosition, availableWidth,
availableHeight, mCompatUIConfiguration, lastTaskInfo);
if (!mHasLetterboxSizeChanged) {
updateHideTime();
final long disappearTimeMs = getDisappearTimeMs();
mMainExecutor.executeDelayed(this::hideReachability, disappearTimeMs);
// If reachability education has been seen for the first time, trigger callback to
// display aspect ratio settings button once reachability education disappears
if (hasShownHorizontalReachabilityEduFirstTime(hasSeenHorizontalReachabilityEdu)
|| hasShownVerticalReachabilityEduFirstTime(
hasSeenVerticalReachabilityEdu)) {
mMainExecutor.executeDelayed(this::triggerOnDismissCallback,
disappearTimeMs);
}
}
mHasUserDoubleTapped = false;
} else {
mLayout.hideAllImmediately();
}
}
/**
* Compares the value of
* {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} before and after the
* layout is shown. Horizontal reachability education is considered seen for the first time if
* prior to viewing the layout,
* {@link CompatUIConfiguration#hasSeenHorizontalReachabilityEducation} is {@code false}
* but becomes {@code true} once the current layout is shown.
*/
private boolean hasShownHorizontalReachabilityEduFirstTime(
boolean previouslyShownHorizontalReachabilityEducation) {
return !previouslyShownHorizontalReachabilityEducation
&& mCompatUIConfiguration.hasSeenHorizontalReachabilityEducation(getLastTaskInfo());
}
/**
* Compares the value of
* {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} before and after the
* layout is shown. Horizontal reachability education is considered seen for the first time if
* prior to viewing the layout,
* {@link CompatUIConfiguration#hasSeenVerticalReachabilityEducation} is {@code false}
* but becomes {@code true} once the current layout is shown.
*/
private boolean hasShownVerticalReachabilityEduFirstTime(
boolean previouslyShownVerticalReachabilityEducation) {
return !previouslyShownVerticalReachabilityEducation
&& mCompatUIConfiguration.hasSeenVerticalReachabilityEducation(getLastTaskInfo());
}
private void triggerOnDismissCallback() {
mOnDismissCallback.accept(getLastTaskInfo(), getTaskListener());
}
private void hideReachability() {
if (mLayout == null || !shouldHideEducation()) {
return;
}
mLayout.hideAllImmediately();
}
private boolean shouldHideEducation() {
return SystemClock.uptimeMillis() >= mNextHideTime;
}
}