| /* |
| * 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.app.TaskInfo.PROPERTY_VALUE_UNSET; |
| |
| import android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ObjectAnimator; |
| import android.animation.ValueAnimator; |
| import android.app.TaskInfo; |
| import android.content.Context; |
| import android.util.AttributeSet; |
| import android.view.View; |
| import android.widget.FrameLayout; |
| |
| import com.android.wm.shell.R; |
| |
| import java.util.function.BiConsumer; |
| import java.util.function.Function; |
| |
| /** |
| * Container for reachability education which handles all the show/hide animations. |
| */ |
| public class ReachabilityEduLayout extends FrameLayout { |
| |
| private static final float ALPHA_FULL_TRANSPARENT = 0f; |
| |
| private static final float ALPHA_FULL_OPAQUE = 1f; |
| |
| private static final long VISIBILITY_ANIMATION_DURATION_MS = 400; |
| |
| private static final long MARGINS_ANIMATION_DURATION_MS = 250; |
| |
| private static final String ALPHA_PROPERTY_NAME = "alpha"; |
| |
| private ReachabilityEduWindowManager mWindowManager; |
| |
| private View mMoveLeftButton; |
| private View mMoveRightButton; |
| private View mMoveUpButton; |
| private View mMoveDownButton; |
| |
| private int mLastLeftMargin = PROPERTY_VALUE_UNSET; |
| private int mLastRightMargin = PROPERTY_VALUE_UNSET; |
| private int mLastTopMargin = PROPERTY_VALUE_UNSET; |
| private int mLastBottomMargin = PROPERTY_VALUE_UNSET; |
| |
| public ReachabilityEduLayout(Context context) { |
| this(context, null); |
| } |
| |
| public ReachabilityEduLayout(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr) { |
| this(context, attrs, defStyleAttr, 0); |
| } |
| |
| public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr, |
| int defStyleRes) { |
| super(context, attrs, defStyleAttr, defStyleRes); |
| } |
| |
| void inject(ReachabilityEduWindowManager windowManager) { |
| mWindowManager = windowManager; |
| } |
| |
| void handleVisibility(boolean horizontalEnabled, boolean verticalEnabled, |
| int letterboxVerticalPosition, |
| int letterboxHorizontalPosition, int availableWidth, int availableHeight, |
| CompatUIConfiguration compatUIConfiguration, TaskInfo taskInfo) { |
| hideAllImmediately(); |
| if (horizontalEnabled && letterboxHorizontalPosition != PROPERTY_VALUE_UNSET) { |
| handleLetterboxHorizontalPosition(availableWidth, letterboxHorizontalPosition); |
| compatUIConfiguration.setUserHasSeenHorizontalReachabilityEducation(taskInfo); |
| } else if (verticalEnabled && letterboxVerticalPosition != PROPERTY_VALUE_UNSET) { |
| handleLetterboxVerticalPosition(availableHeight, letterboxVerticalPosition); |
| compatUIConfiguration.setUserHasSeenVerticalReachabilityEducation(taskInfo); |
| } |
| } |
| |
| void hideAllImmediately() { |
| hideImmediately(mMoveLeftButton); |
| hideImmediately(mMoveRightButton); |
| hideImmediately(mMoveUpButton); |
| hideImmediately(mMoveDownButton); |
| mLastLeftMargin = PROPERTY_VALUE_UNSET; |
| mLastRightMargin = PROPERTY_VALUE_UNSET; |
| mLastTopMargin = PROPERTY_VALUE_UNSET; |
| mLastBottomMargin = PROPERTY_VALUE_UNSET; |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| mMoveLeftButton = findViewById(R.id.reachability_move_left_button); |
| mMoveRightButton = findViewById(R.id.reachability_move_right_button); |
| mMoveUpButton = findViewById(R.id.reachability_move_up_button); |
| mMoveDownButton = findViewById(R.id.reachability_move_down_button); |
| mMoveLeftButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
| mMoveRightButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
| mMoveUpButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
| mMoveDownButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
| } |
| |
| private void hideImmediately(View view) { |
| view.setAlpha(0); |
| view.setVisibility(View.INVISIBLE); |
| } |
| |
| private Animator marginAnimator(View view, Function<LayoutParams, Integer> marginSupplier, |
| BiConsumer<LayoutParams, Integer> marginConsumer, int from, int to) { |
| final LayoutParams layoutParams = ((LayoutParams) view.getLayoutParams()); |
| ValueAnimator animator = ValueAnimator.ofInt(marginSupplier.apply(layoutParams), from, to); |
| animator.addUpdateListener(valueAnimator -> { |
| marginConsumer.accept(layoutParams, (Integer) valueAnimator.getAnimatedValue()); |
| view.requestLayout(); |
| }); |
| animator.setDuration(MARGINS_ANIMATION_DURATION_MS); |
| return animator; |
| } |
| |
| private void handleLetterboxHorizontalPosition(int availableWidth, |
| int letterboxHorizontalPosition) { |
| hideItem(mMoveUpButton); |
| hideItem(mMoveDownButton); |
| mLastTopMargin = PROPERTY_VALUE_UNSET; |
| mLastBottomMargin = PROPERTY_VALUE_UNSET; |
| // We calculate the available space on the left and right |
| final int horizontalGap = availableWidth / 2; |
| final int leftAvailableSpace = letterboxHorizontalPosition * horizontalGap; |
| final int rightAvailableSpace = availableWidth - leftAvailableSpace; |
| // We show the button if we have enough space |
| if (leftAvailableSpace >= mMoveLeftButton.getMeasuredWidth()) { |
| int newLeftMargin = (horizontalGap - mMoveLeftButton.getMeasuredWidth()) / 2; |
| if (mLastLeftMargin == PROPERTY_VALUE_UNSET) { |
| mLastLeftMargin = newLeftMargin; |
| } |
| if (mLastLeftMargin != newLeftMargin) { |
| marginAnimator(mMoveLeftButton, layoutParams -> layoutParams.leftMargin, |
| (layoutParams, margin) -> layoutParams.leftMargin = margin, |
| mLastLeftMargin, newLeftMargin).start(); |
| } else { |
| final LayoutParams leftParams = ((LayoutParams) mMoveLeftButton.getLayoutParams()); |
| leftParams.leftMargin = mLastLeftMargin; |
| mMoveLeftButton.setLayoutParams(leftParams); |
| } |
| showItem(mMoveLeftButton); |
| } else { |
| hideItem(mMoveLeftButton); |
| mLastLeftMargin = PROPERTY_VALUE_UNSET; |
| } |
| if (rightAvailableSpace >= mMoveRightButton.getMeasuredWidth()) { |
| int newRightMargin = (horizontalGap - mMoveRightButton.getMeasuredWidth()) / 2; |
| if (mLastRightMargin == PROPERTY_VALUE_UNSET) { |
| mLastRightMargin = newRightMargin; |
| } |
| if (mLastRightMargin != newRightMargin) { |
| marginAnimator(mMoveRightButton, layoutParams -> layoutParams.rightMargin, |
| (layoutParams, margin) -> layoutParams.rightMargin = margin, |
| mLastRightMargin, newRightMargin).start(); |
| } else { |
| final LayoutParams rightParams = |
| ((LayoutParams) mMoveRightButton.getLayoutParams()); |
| rightParams.rightMargin = mLastRightMargin; |
| mMoveRightButton.setLayoutParams(rightParams); |
| } |
| showItem(mMoveRightButton); |
| } else { |
| hideItem(mMoveRightButton); |
| mLastRightMargin = PROPERTY_VALUE_UNSET; |
| } |
| } |
| |
| private void handleLetterboxVerticalPosition(int availableHeight, |
| int letterboxVerticalPosition) { |
| hideItem(mMoveLeftButton); |
| hideItem(mMoveRightButton); |
| mLastLeftMargin = PROPERTY_VALUE_UNSET; |
| mLastRightMargin = PROPERTY_VALUE_UNSET; |
| // We calculate the available space on the left and right |
| final int verticalGap = availableHeight / 2; |
| final int topAvailableSpace = letterboxVerticalPosition * verticalGap; |
| final int bottomAvailableSpace = availableHeight - topAvailableSpace; |
| if (topAvailableSpace >= mMoveUpButton.getMeasuredHeight()) { |
| int newTopMargin = (verticalGap - mMoveUpButton.getMeasuredHeight()) / 2; |
| if (mLastTopMargin == PROPERTY_VALUE_UNSET) { |
| mLastTopMargin = newTopMargin; |
| } |
| if (mLastTopMargin != newTopMargin) { |
| marginAnimator(mMoveUpButton, layoutParams -> layoutParams.topMargin, |
| (layoutParams, margin) -> layoutParams.topMargin = margin, |
| mLastTopMargin, newTopMargin).start(); |
| } else { |
| final LayoutParams topParams = ((LayoutParams) mMoveUpButton.getLayoutParams()); |
| topParams.topMargin = mLastTopMargin; |
| mMoveUpButton.setLayoutParams(topParams); |
| } |
| showItem(mMoveUpButton); |
| } else { |
| hideItem(mMoveUpButton); |
| mLastTopMargin = PROPERTY_VALUE_UNSET; |
| } |
| if (bottomAvailableSpace >= mMoveDownButton.getMeasuredHeight()) { |
| int newBottomMargin = (verticalGap - mMoveDownButton.getMeasuredHeight()) / 2; |
| if (mLastBottomMargin == PROPERTY_VALUE_UNSET) { |
| mLastBottomMargin = newBottomMargin; |
| } |
| if (mLastBottomMargin != newBottomMargin) { |
| marginAnimator(mMoveDownButton, layoutParams -> layoutParams.bottomMargin, |
| (layoutParams, margin) -> layoutParams.bottomMargin = margin, |
| mLastBottomMargin, newBottomMargin).start(); |
| } else { |
| final LayoutParams bottomParams = |
| ((LayoutParams) mMoveDownButton.getLayoutParams()); |
| bottomParams.bottomMargin = mLastBottomMargin; |
| mMoveDownButton.setLayoutParams(bottomParams); |
| } |
| showItem(mMoveDownButton); |
| } else { |
| hideItem(mMoveDownButton); |
| mLastBottomMargin = PROPERTY_VALUE_UNSET; |
| } |
| } |
| |
| private void showItem(View view) { |
| view.setVisibility(View.VISIBLE); |
| ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, |
| ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE); |
| fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS); |
| fadeIn.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| view.setVisibility(View.VISIBLE); |
| } |
| }); |
| fadeIn.start(); |
| } |
| |
| private void hideItem(View view) { |
| ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, |
| ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT); |
| fadeOut.setDuration(VISIBILITY_ANIMATION_DURATION_MS); |
| fadeOut.addListener(new AnimatorListenerAdapter() { |
| @Override |
| public void onAnimationEnd(Animator animation) { |
| view.setVisibility(View.INVISIBLE); |
| } |
| }); |
| fadeOut.start(); |
| } |
| |
| } |