1 /* 2 * Copyright (C) 2020 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.onehanded; 18 19 import android.animation.Animator; 20 import android.animation.ValueAnimator; 21 import android.annotation.IntDef; 22 import android.annotation.Nullable; 23 import android.content.Context; 24 import android.graphics.Rect; 25 import android.view.SurfaceControl; 26 import android.view.animation.BaseInterpolator; 27 import android.window.WindowContainerToken; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.VisibleForTesting; 31 32 import java.io.PrintWriter; 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.util.ArrayList; 36 import java.util.HashMap; 37 import java.util.List; 38 39 /** 40 * Controller class of OneHanded animations (both from and to OneHanded mode). 41 */ 42 public class OneHandedAnimationController { 43 private static final String TAG = "OneHandedAnimationController"; 44 private static final float FRACTION_START = 0f; 45 private static final float FRACTION_END = 1f; 46 47 public static final int TRANSITION_DIRECTION_NONE = 0; 48 public static final int TRANSITION_DIRECTION_TRIGGER = 1; 49 public static final int TRANSITION_DIRECTION_EXIT = 2; 50 51 @IntDef(prefix = {"TRANSITION_DIRECTION_"}, value = { 52 TRANSITION_DIRECTION_NONE, 53 TRANSITION_DIRECTION_TRIGGER, 54 TRANSITION_DIRECTION_EXIT, 55 }) 56 @Retention(RetentionPolicy.SOURCE) 57 public @interface TransitionDirection { 58 } 59 60 private final OneHandedInterpolator mInterpolator; 61 private final OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper; 62 private final HashMap<WindowContainerToken, OneHandedTransitionAnimator> mAnimatorMap = 63 new HashMap<>(); 64 65 /** 66 * Constructor of OneHandedAnimationController 67 */ OneHandedAnimationController(Context context)68 public OneHandedAnimationController(Context context) { 69 mSurfaceTransactionHelper = new OneHandedSurfaceTransactionHelper(context); 70 mInterpolator = new OneHandedInterpolator(); 71 } 72 73 @SuppressWarnings("unchecked") getAnimator(WindowContainerToken token, SurfaceControl leash, float startPos, float endPos, Rect displayBounds)74 OneHandedTransitionAnimator getAnimator(WindowContainerToken token, SurfaceControl leash, 75 float startPos, float endPos, Rect displayBounds) { 76 final OneHandedTransitionAnimator animator = mAnimatorMap.get(token); 77 if (animator == null) { 78 mAnimatorMap.put(token, setupOneHandedTransitionAnimator( 79 OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos, 80 displayBounds))); 81 } else if (animator.isRunning()) { 82 animator.updateEndValue(endPos); 83 } else { 84 animator.cancel(); 85 mAnimatorMap.put(token, setupOneHandedTransitionAnimator( 86 OneHandedTransitionAnimator.ofYOffset(token, leash, startPos, endPos, 87 displayBounds))); 88 } 89 return mAnimatorMap.get(token); 90 } 91 getAnimatorMap()92 HashMap<WindowContainerToken, OneHandedTransitionAnimator> getAnimatorMap() { 93 return mAnimatorMap; 94 } 95 isAnimatorsConsumed()96 boolean isAnimatorsConsumed() { 97 return mAnimatorMap.isEmpty(); 98 } 99 removeAnimator(WindowContainerToken token)100 void removeAnimator(WindowContainerToken token) { 101 final OneHandedTransitionAnimator animator = mAnimatorMap.remove(token); 102 if (animator != null && animator.isRunning()) { 103 animator.cancel(); 104 } 105 } 106 setupOneHandedTransitionAnimator( OneHandedTransitionAnimator animator)107 OneHandedTransitionAnimator setupOneHandedTransitionAnimator( 108 OneHandedTransitionAnimator animator) { 109 animator.setSurfaceTransactionHelper(mSurfaceTransactionHelper); 110 animator.setInterpolator(mInterpolator); 111 animator.setFloatValues(FRACTION_START, FRACTION_END); 112 return animator; 113 } 114 115 /** 116 * Animator for OneHanded transition animation which supports both alpha and bounds animation. 117 */ 118 // TODO: Refactoring to use SpringAnimation and DynamicAnimation instead of using ValueAnimator 119 // to implement One-Handed transition animation. (b/185129031) 120 public abstract static class OneHandedTransitionAnimator extends ValueAnimator implements 121 ValueAnimator.AnimatorUpdateListener, 122 ValueAnimator.AnimatorListener { 123 124 private final SurfaceControl mLeash; 125 private final WindowContainerToken mToken; 126 private float mStartValue; 127 private float mEndValue; 128 private float mCurrentValue; 129 130 private final List<OneHandedAnimationCallback> mOneHandedAnimationCallbacks = 131 new ArrayList<>(); 132 private OneHandedSurfaceTransactionHelper mSurfaceTransactionHelper; 133 private OneHandedSurfaceTransactionHelper.SurfaceControlTransactionFactory 134 mSurfaceControlTransactionFactory; 135 136 private @TransitionDirection int mTransitionDirection; 137 OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash, float startValue, float endValue)138 private OneHandedTransitionAnimator(WindowContainerToken token, SurfaceControl leash, 139 float startValue, float endValue) { 140 mLeash = leash; 141 mToken = token; 142 mStartValue = startValue; 143 mEndValue = endValue; 144 addListener(this); 145 addUpdateListener(this); 146 mSurfaceControlTransactionFactory = SurfaceControl.Transaction::new; 147 mTransitionDirection = TRANSITION_DIRECTION_NONE; 148 } 149 150 @Override onAnimationStart(Animator animation)151 public void onAnimationStart(Animator animation) { 152 mCurrentValue = mStartValue; 153 mOneHandedAnimationCallbacks.forEach( 154 (callback) -> callback.onOneHandedAnimationStart(this) 155 ); 156 } 157 158 @Override onAnimationEnd(Animator animation)159 public void onAnimationEnd(Animator animation) { 160 mCurrentValue = mEndValue; 161 final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); 162 onEndTransaction(mLeash, tx); 163 mOneHandedAnimationCallbacks.forEach( 164 (callback) -> callback.onOneHandedAnimationEnd(tx, this) 165 ); 166 mOneHandedAnimationCallbacks.clear(); 167 } 168 169 @Override onAnimationCancel(Animator animation)170 public void onAnimationCancel(Animator animation) { 171 mCurrentValue = mEndValue; 172 mOneHandedAnimationCallbacks.forEach( 173 (callback) -> callback.onOneHandedAnimationCancel(this) 174 ); 175 mOneHandedAnimationCallbacks.clear(); 176 } 177 178 @Override onAnimationRepeat(Animator animation)179 public void onAnimationRepeat(Animator animation) { 180 } 181 182 @Override onAnimationUpdate(ValueAnimator animation)183 public void onAnimationUpdate(ValueAnimator animation) { 184 final SurfaceControl.Transaction tx = newSurfaceControlTransaction(); 185 mOneHandedAnimationCallbacks.forEach( 186 (callback) -> callback.onAnimationUpdate(tx, 0f, mCurrentValue) 187 ); 188 applySurfaceControlTransaction(mLeash, tx, animation.getAnimatedFraction()); 189 } 190 onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)191 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 192 } 193 onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx)194 void onEndTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 195 } 196 applySurfaceControlTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, float fraction)197 abstract void applySurfaceControlTransaction(SurfaceControl leash, 198 SurfaceControl.Transaction tx, float fraction); 199 getSurfaceTransactionHelper()200 OneHandedSurfaceTransactionHelper getSurfaceTransactionHelper() { 201 return mSurfaceTransactionHelper; 202 } 203 setSurfaceTransactionHelper(OneHandedSurfaceTransactionHelper helper)204 void setSurfaceTransactionHelper(OneHandedSurfaceTransactionHelper helper) { 205 mSurfaceTransactionHelper = helper; 206 } 207 addOneHandedAnimationCallback( @ullable OneHandedAnimationCallback callback)208 OneHandedTransitionAnimator addOneHandedAnimationCallback( 209 @Nullable OneHandedAnimationCallback callback) { 210 if (callback != null) { 211 mOneHandedAnimationCallbacks.add(callback); 212 } 213 return this; 214 } 215 getToken()216 WindowContainerToken getToken() { 217 return mToken; 218 } 219 getDestinationOffset()220 float getDestinationOffset() { 221 return (mEndValue - mStartValue); 222 } 223 224 @TransitionDirection getTransitionDirection()225 int getTransitionDirection() { 226 return mTransitionDirection; 227 } 228 setTransitionDirection(int direction)229 OneHandedTransitionAnimator setTransitionDirection(int direction) { 230 mTransitionDirection = direction; 231 return this; 232 } 233 getStartValue()234 float getStartValue() { 235 return mStartValue; 236 } 237 getEndValue()238 float getEndValue() { 239 return mEndValue; 240 } 241 setCurrentValue(float value)242 void setCurrentValue(float value) { 243 mCurrentValue = value; 244 } 245 246 /** 247 * Updates the {@link #mEndValue}. 248 */ updateEndValue(float endValue)249 void updateEndValue(float endValue) { 250 mEndValue = endValue; 251 } 252 newSurfaceControlTransaction()253 SurfaceControl.Transaction newSurfaceControlTransaction() { 254 return mSurfaceControlTransactionFactory.getTransaction(); 255 } 256 257 @VisibleForTesting ofYOffset(WindowContainerToken token, SurfaceControl leash, float startValue, float endValue, Rect displayBounds)258 static OneHandedTransitionAnimator ofYOffset(WindowContainerToken token, 259 SurfaceControl leash, float startValue, float endValue, Rect displayBounds) { 260 261 return new OneHandedTransitionAnimator(token, leash, startValue, endValue) { 262 263 private final Rect mTmpRect = new Rect(displayBounds); 264 265 private float getCastedFractionValue(float start, float end, float fraction) { 266 return (start * (1 - fraction) + end * fraction + .5f); 267 } 268 269 @Override 270 void applySurfaceControlTransaction(SurfaceControl leash, 271 SurfaceControl.Transaction tx, float fraction) { 272 final float start = getStartValue(); 273 final float end = getEndValue(); 274 final float currentValue = getCastedFractionValue(start, end, fraction); 275 mTmpRect.set( 276 mTmpRect.left, 277 mTmpRect.top + Math.round(currentValue), 278 mTmpRect.right, 279 mTmpRect.bottom + Math.round(currentValue)); 280 setCurrentValue(currentValue); 281 getSurfaceTransactionHelper() 282 .crop(tx, leash, mTmpRect) 283 .round(tx, leash) 284 .translate(tx, leash, currentValue); 285 tx.apply(); 286 } 287 288 @Override 289 void onStartTransaction(SurfaceControl leash, SurfaceControl.Transaction tx) { 290 getSurfaceTransactionHelper() 291 .crop(tx, leash, mTmpRect) 292 .round(tx, leash) 293 .translate(tx, leash, getStartValue()); 294 tx.apply(); 295 } 296 }; 297 } 298 } 299 300 /** 301 * An Interpolator for One-Handed transition animation. 302 */ 303 public class OneHandedInterpolator extends BaseInterpolator { 304 @Override getInterpolation(float input)305 public float getInterpolation(float input) { 306 return (float) (Math.pow(2, -10 * input) * Math.sin(((input - 4.0f) / 4.0f) 307 * (2.0f * Math.PI) / 4.0f) + 1.0f); 308 } 309 } 310 dump(@onNull PrintWriter pw)311 void dump(@NonNull PrintWriter pw) { 312 final String innerPrefix = " "; 313 pw.println(TAG + "states: "); 314 pw.print(innerPrefix + "mAnimatorMap="); 315 pw.println(mAnimatorMap); 316 317 if (mSurfaceTransactionHelper != null) { 318 mSurfaceTransactionHelper.dump(pw); 319 } 320 } 321 } 322