322 lines
13 KiB
Java
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;
|
|
}
|
|
}
|