/* * 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 mOnDismissCallback; private final Function mDisappearTimeSupplier; @Nullable @VisibleForTesting ReachabilityEduLayout mLayout; ReachabilityEduWindowManager(Context context, TaskInfo taskInfo, SyncTransactionQueue syncQueue, ShellTaskOrganizer.TaskListener taskListener, DisplayLayout displayLayout, CompatUIConfiguration compatUIConfiguration, ShellExecutor mainExecutor, BiConsumer onDismissCallback, Function 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; } }