1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.wm.shell.compatui; 18 19 import static android.app.TaskInfo.PROPERTY_VALUE_UNSET; 20 21 import android.animation.Animator; 22 import android.animation.AnimatorListenerAdapter; 23 import android.animation.ObjectAnimator; 24 import android.animation.ValueAnimator; 25 import android.app.TaskInfo; 26 import android.content.Context; 27 import android.util.AttributeSet; 28 import android.view.View; 29 import android.widget.FrameLayout; 30 31 import com.android.wm.shell.R; 32 33 import java.util.function.BiConsumer; 34 import java.util.function.Function; 35 36 /** 37 * Container for reachability education which handles all the show/hide animations. 38 */ 39 public class ReachabilityEduLayout extends FrameLayout { 40 41 private static final float ALPHA_FULL_TRANSPARENT = 0f; 42 43 private static final float ALPHA_FULL_OPAQUE = 1f; 44 45 private static final long VISIBILITY_ANIMATION_DURATION_MS = 400; 46 47 private static final long MARGINS_ANIMATION_DURATION_MS = 250; 48 49 private static final String ALPHA_PROPERTY_NAME = "alpha"; 50 51 private ReachabilityEduWindowManager mWindowManager; 52 53 private View mMoveLeftButton; 54 private View mMoveRightButton; 55 private View mMoveUpButton; 56 private View mMoveDownButton; 57 58 private int mLastLeftMargin = PROPERTY_VALUE_UNSET; 59 private int mLastRightMargin = PROPERTY_VALUE_UNSET; 60 private int mLastTopMargin = PROPERTY_VALUE_UNSET; 61 private int mLastBottomMargin = PROPERTY_VALUE_UNSET; 62 ReachabilityEduLayout(Context context)63 public ReachabilityEduLayout(Context context) { 64 this(context, null); 65 } 66 ReachabilityEduLayout(Context context, AttributeSet attrs)67 public ReachabilityEduLayout(Context context, AttributeSet attrs) { 68 this(context, attrs, 0); 69 } 70 ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr)71 public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr) { 72 this(context, attrs, defStyleAttr, 0); 73 } 74 ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)75 public ReachabilityEduLayout(Context context, AttributeSet attrs, int defStyleAttr, 76 int defStyleRes) { 77 super(context, attrs, defStyleAttr, defStyleRes); 78 } 79 inject(ReachabilityEduWindowManager windowManager)80 void inject(ReachabilityEduWindowManager windowManager) { 81 mWindowManager = windowManager; 82 } 83 handleVisibility(boolean horizontalEnabled, boolean verticalEnabled, int letterboxVerticalPosition, int letterboxHorizontalPosition, int availableWidth, int availableHeight, CompatUIConfiguration compatUIConfiguration, TaskInfo taskInfo)84 void handleVisibility(boolean horizontalEnabled, boolean verticalEnabled, 85 int letterboxVerticalPosition, 86 int letterboxHorizontalPosition, int availableWidth, int availableHeight, 87 CompatUIConfiguration compatUIConfiguration, TaskInfo taskInfo) { 88 hideAllImmediately(); 89 if (horizontalEnabled && letterboxHorizontalPosition != PROPERTY_VALUE_UNSET) { 90 handleLetterboxHorizontalPosition(availableWidth, letterboxHorizontalPosition); 91 compatUIConfiguration.setUserHasSeenHorizontalReachabilityEducation(taskInfo); 92 } else if (verticalEnabled && letterboxVerticalPosition != PROPERTY_VALUE_UNSET) { 93 handleLetterboxVerticalPosition(availableHeight, letterboxVerticalPosition); 94 compatUIConfiguration.setUserHasSeenVerticalReachabilityEducation(taskInfo); 95 } 96 } 97 hideAllImmediately()98 void hideAllImmediately() { 99 hideImmediately(mMoveLeftButton); 100 hideImmediately(mMoveRightButton); 101 hideImmediately(mMoveUpButton); 102 hideImmediately(mMoveDownButton); 103 mLastLeftMargin = PROPERTY_VALUE_UNSET; 104 mLastRightMargin = PROPERTY_VALUE_UNSET; 105 mLastTopMargin = PROPERTY_VALUE_UNSET; 106 mLastBottomMargin = PROPERTY_VALUE_UNSET; 107 } 108 109 @Override onFinishInflate()110 protected void onFinishInflate() { 111 super.onFinishInflate(); 112 mMoveLeftButton = findViewById(R.id.reachability_move_left_button); 113 mMoveRightButton = findViewById(R.id.reachability_move_right_button); 114 mMoveUpButton = findViewById(R.id.reachability_move_up_button); 115 mMoveDownButton = findViewById(R.id.reachability_move_down_button); 116 mMoveLeftButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 117 mMoveRightButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 118 mMoveUpButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 119 mMoveDownButton.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 120 } 121 hideImmediately(View view)122 private void hideImmediately(View view) { 123 view.setAlpha(0); 124 view.setVisibility(View.INVISIBLE); 125 } 126 marginAnimator(View view, Function<LayoutParams, Integer> marginSupplier, BiConsumer<LayoutParams, Integer> marginConsumer, int from, int to)127 private Animator marginAnimator(View view, Function<LayoutParams, Integer> marginSupplier, 128 BiConsumer<LayoutParams, Integer> marginConsumer, int from, int to) { 129 final LayoutParams layoutParams = ((LayoutParams) view.getLayoutParams()); 130 ValueAnimator animator = ValueAnimator.ofInt(marginSupplier.apply(layoutParams), from, to); 131 animator.addUpdateListener(valueAnimator -> { 132 marginConsumer.accept(layoutParams, (Integer) valueAnimator.getAnimatedValue()); 133 view.requestLayout(); 134 }); 135 animator.setDuration(MARGINS_ANIMATION_DURATION_MS); 136 return animator; 137 } 138 handleLetterboxHorizontalPosition(int availableWidth, int letterboxHorizontalPosition)139 private void handleLetterboxHorizontalPosition(int availableWidth, 140 int letterboxHorizontalPosition) { 141 hideItem(mMoveUpButton); 142 hideItem(mMoveDownButton); 143 mLastTopMargin = PROPERTY_VALUE_UNSET; 144 mLastBottomMargin = PROPERTY_VALUE_UNSET; 145 // We calculate the available space on the left and right 146 final int horizontalGap = availableWidth / 2; 147 final int leftAvailableSpace = letterboxHorizontalPosition * horizontalGap; 148 final int rightAvailableSpace = availableWidth - leftAvailableSpace; 149 // We show the button if we have enough space 150 if (leftAvailableSpace >= mMoveLeftButton.getMeasuredWidth()) { 151 int newLeftMargin = (horizontalGap - mMoveLeftButton.getMeasuredWidth()) / 2; 152 if (mLastLeftMargin == PROPERTY_VALUE_UNSET) { 153 mLastLeftMargin = newLeftMargin; 154 } 155 if (mLastLeftMargin != newLeftMargin) { 156 marginAnimator(mMoveLeftButton, layoutParams -> layoutParams.leftMargin, 157 (layoutParams, margin) -> layoutParams.leftMargin = margin, 158 mLastLeftMargin, newLeftMargin).start(); 159 } else { 160 final LayoutParams leftParams = ((LayoutParams) mMoveLeftButton.getLayoutParams()); 161 leftParams.leftMargin = mLastLeftMargin; 162 mMoveLeftButton.setLayoutParams(leftParams); 163 } 164 showItem(mMoveLeftButton); 165 } else { 166 hideItem(mMoveLeftButton); 167 mLastLeftMargin = PROPERTY_VALUE_UNSET; 168 } 169 if (rightAvailableSpace >= mMoveRightButton.getMeasuredWidth()) { 170 int newRightMargin = (horizontalGap - mMoveRightButton.getMeasuredWidth()) / 2; 171 if (mLastRightMargin == PROPERTY_VALUE_UNSET) { 172 mLastRightMargin = newRightMargin; 173 } 174 if (mLastRightMargin != newRightMargin) { 175 marginAnimator(mMoveRightButton, layoutParams -> layoutParams.rightMargin, 176 (layoutParams, margin) -> layoutParams.rightMargin = margin, 177 mLastRightMargin, newRightMargin).start(); 178 } else { 179 final LayoutParams rightParams = 180 ((LayoutParams) mMoveRightButton.getLayoutParams()); 181 rightParams.rightMargin = mLastRightMargin; 182 mMoveRightButton.setLayoutParams(rightParams); 183 } 184 showItem(mMoveRightButton); 185 } else { 186 hideItem(mMoveRightButton); 187 mLastRightMargin = PROPERTY_VALUE_UNSET; 188 } 189 } 190 handleLetterboxVerticalPosition(int availableHeight, int letterboxVerticalPosition)191 private void handleLetterboxVerticalPosition(int availableHeight, 192 int letterboxVerticalPosition) { 193 hideItem(mMoveLeftButton); 194 hideItem(mMoveRightButton); 195 mLastLeftMargin = PROPERTY_VALUE_UNSET; 196 mLastRightMargin = PROPERTY_VALUE_UNSET; 197 // We calculate the available space on the left and right 198 final int verticalGap = availableHeight / 2; 199 final int topAvailableSpace = letterboxVerticalPosition * verticalGap; 200 final int bottomAvailableSpace = availableHeight - topAvailableSpace; 201 if (topAvailableSpace >= mMoveUpButton.getMeasuredHeight()) { 202 int newTopMargin = (verticalGap - mMoveUpButton.getMeasuredHeight()) / 2; 203 if (mLastTopMargin == PROPERTY_VALUE_UNSET) { 204 mLastTopMargin = newTopMargin; 205 } 206 if (mLastTopMargin != newTopMargin) { 207 marginAnimator(mMoveUpButton, layoutParams -> layoutParams.topMargin, 208 (layoutParams, margin) -> layoutParams.topMargin = margin, 209 mLastTopMargin, newTopMargin).start(); 210 } else { 211 final LayoutParams topParams = ((LayoutParams) mMoveUpButton.getLayoutParams()); 212 topParams.topMargin = mLastTopMargin; 213 mMoveUpButton.setLayoutParams(topParams); 214 } 215 showItem(mMoveUpButton); 216 } else { 217 hideItem(mMoveUpButton); 218 mLastTopMargin = PROPERTY_VALUE_UNSET; 219 } 220 if (bottomAvailableSpace >= mMoveDownButton.getMeasuredHeight()) { 221 int newBottomMargin = (verticalGap - mMoveDownButton.getMeasuredHeight()) / 2; 222 if (mLastBottomMargin == PROPERTY_VALUE_UNSET) { 223 mLastBottomMargin = newBottomMargin; 224 } 225 if (mLastBottomMargin != newBottomMargin) { 226 marginAnimator(mMoveDownButton, layoutParams -> layoutParams.bottomMargin, 227 (layoutParams, margin) -> layoutParams.bottomMargin = margin, 228 mLastBottomMargin, newBottomMargin).start(); 229 } else { 230 final LayoutParams bottomParams = 231 ((LayoutParams) mMoveDownButton.getLayoutParams()); 232 bottomParams.bottomMargin = mLastBottomMargin; 233 mMoveDownButton.setLayoutParams(bottomParams); 234 } 235 showItem(mMoveDownButton); 236 } else { 237 hideItem(mMoveDownButton); 238 mLastBottomMargin = PROPERTY_VALUE_UNSET; 239 } 240 } 241 showItem(View view)242 private void showItem(View view) { 243 view.setVisibility(View.VISIBLE); 244 ObjectAnimator fadeIn = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, 245 ALPHA_FULL_TRANSPARENT, ALPHA_FULL_OPAQUE); 246 fadeIn.setDuration(VISIBILITY_ANIMATION_DURATION_MS); 247 fadeIn.addListener(new AnimatorListenerAdapter() { 248 @Override 249 public void onAnimationEnd(Animator animation) { 250 view.setVisibility(View.VISIBLE); 251 } 252 }); 253 fadeIn.start(); 254 } 255 hideItem(View view)256 private void hideItem(View view) { 257 ObjectAnimator fadeOut = ObjectAnimator.ofFloat(view, ALPHA_PROPERTY_NAME, 258 ALPHA_FULL_OPAQUE, ALPHA_FULL_TRANSPARENT); 259 fadeOut.setDuration(VISIBILITY_ANIMATION_DURATION_MS); 260 fadeOut.addListener(new AnimatorListenerAdapter() { 261 @Override 262 public void onAnimationEnd(Animator animation) { 263 view.setVisibility(View.INVISIBLE); 264 } 265 }); 266 fadeOut.start(); 267 } 268 269 } 270