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.systemui.accessibility; 18 19 import android.animation.Animator; 20 import android.animation.ValueAnimator; 21 import android.annotation.IntDef; 22 import android.annotation.Nullable; 23 import android.annotation.UiContext; 24 import android.content.Context; 25 import android.content.res.Resources; 26 import android.os.RemoteException; 27 import android.util.Log; 28 import android.view.accessibility.IRemoteMagnificationAnimationCallback; 29 import android.view.animation.AccelerateInterpolator; 30 31 import com.android.internal.annotations.VisibleForTesting; 32 import com.android.systemui.R; 33 34 import java.io.PrintWriter; 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 38 /** 39 * Provides same functionality of {@link WindowMagnificationController}. Some methods run with 40 * the animation. 41 */ 42 class WindowMagnificationAnimationController implements ValueAnimator.AnimatorUpdateListener, 43 Animator.AnimatorListener { 44 45 private static final String TAG = "WindowMagnificationAnimationController"; 46 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); 47 48 @Retention(RetentionPolicy.SOURCE) 49 @IntDef({STATE_DISABLED, STATE_ENABLED, STATE_DISABLING, STATE_ENABLING}) 50 @interface MagnificationState {} 51 52 // The window magnification is disabled. 53 private static final int STATE_DISABLED = 0; 54 // The window magnification is enabled. 55 private static final int STATE_ENABLED = 1; 56 // The window magnification is going to be disabled when the animation is end. 57 private static final int STATE_DISABLING = 2; 58 // The animation is running for enabling the window magnification. 59 private static final int STATE_ENABLING = 3; 60 61 private final WindowMagnificationController mController; 62 private final ValueAnimator mValueAnimator; 63 private final AnimationSpec mStartSpec = new AnimationSpec(); 64 private final AnimationSpec mEndSpec = new AnimationSpec(); 65 private final Context mContext; 66 // Called when the animation is ended successfully without cancelling or mStartSpec and 67 // mEndSpec are equal. 68 private IRemoteMagnificationAnimationCallback mAnimationCallback; 69 // The flag to ignore the animation end callback. 70 private boolean mEndAnimationCanceled = false; 71 @MagnificationState 72 private int mState = STATE_DISABLED; 73 WindowMagnificationAnimationController(@iContext Context context, WindowMagnificationController controller)74 WindowMagnificationAnimationController(@UiContext Context context, 75 WindowMagnificationController controller) { 76 this(context, controller, newValueAnimator(context.getResources())); 77 } 78 79 @VisibleForTesting WindowMagnificationAnimationController(Context context, WindowMagnificationController controller, ValueAnimator valueAnimator)80 WindowMagnificationAnimationController(Context context, 81 WindowMagnificationController controller, ValueAnimator valueAnimator) { 82 mContext = context; 83 mController = controller; 84 mValueAnimator = valueAnimator; 85 mValueAnimator.addUpdateListener(this); 86 mValueAnimator.addListener(this); 87 } 88 89 /** 90 * Wraps {@link WindowMagnificationController#enableWindowMagnification(float, float, float)} 91 * with transition animation. If the window magnification is not enabled, the scale will start 92 * from 1.0 and the center won't be changed during the animation. If {@link #mState} is 93 * {@code STATE_DISABLING}, the animation runs in reverse. 94 * 95 * @param scale The target scale, or {@link Float#NaN} to leave unchanged. 96 * @param centerX The screen-relative X coordinate around which to center, 97 * or {@link Float#NaN} to leave unchanged. 98 * @param centerY The screen-relative Y coordinate around which to center, 99 * or {@link Float#NaN} to leave unchanged. 100 * @param animationCallback Called when the transition is complete, the given arguments 101 * are as same as current values, or the transition is interrupted 102 * due to the new transition request. 103 * 104 * @see #onAnimationUpdate(ValueAnimator) 105 */ enableWindowMagnification(float scale, float centerX, float centerY, @Nullable IRemoteMagnificationAnimationCallback animationCallback)106 void enableWindowMagnification(float scale, float centerX, float centerY, 107 @Nullable IRemoteMagnificationAnimationCallback animationCallback) { 108 sendAnimationCallback(false); 109 mAnimationCallback = animationCallback; 110 setupEnableAnimationSpecs(scale, centerX, centerY); 111 if (mEndSpec.equals(mStartSpec)) { 112 if (mState == STATE_DISABLED) { 113 mController.enableWindowMagnification(scale, centerX, centerY); 114 } else if (mState == STATE_ENABLING || mState == STATE_DISABLING) { 115 mValueAnimator.cancel(); 116 } 117 sendAnimationCallback(true); 118 setState(STATE_ENABLED); 119 } else { 120 if (mState == STATE_DISABLING) { 121 mValueAnimator.reverse(); 122 } else { 123 if (mState == STATE_ENABLING) { 124 mValueAnimator.cancel(); 125 } 126 mValueAnimator.start(); 127 } 128 setState(STATE_ENABLING); 129 } 130 } 131 setupEnableAnimationSpecs(float scale, float centerX, float centerY)132 private void setupEnableAnimationSpecs(float scale, float centerX, float centerY) { 133 final float currentScale = mController.getScale(); 134 final float currentCenterX = mController.getCenterX(); 135 final float currentCenterY = mController.getCenterY(); 136 137 if (mState == STATE_DISABLED) { 138 // We don't need to offset the center during the animation. 139 mStartSpec.set(/* scale*/ 1.0f, centerX, centerY); 140 mEndSpec.set(Float.isNaN(scale) ? mContext.getResources().getInteger( 141 R.integer.magnification_default_scale) : scale, centerX, centerY); 142 } else { 143 mStartSpec.set(currentScale, currentCenterX, currentCenterY); 144 mEndSpec.set(Float.isNaN(scale) ? currentScale : scale, 145 Float.isNaN(centerX) ? currentCenterX : centerX, 146 Float.isNaN(centerY) ? currentCenterY : centerY); 147 } 148 if (DEBUG) { 149 Log.d(TAG, "SetupEnableAnimationSpecs : mStartSpec = " + mStartSpec + ", endSpec = " 150 + mEndSpec); 151 } 152 } 153 154 /** 155 * Wraps {@link WindowMagnificationController#setScale(float)}. If the animation is 156 * running, it has no effect. 157 */ setScale(float scale)158 void setScale(float scale) { 159 if (mValueAnimator.isRunning()) { 160 return; 161 } 162 mController.setScale(scale); 163 } 164 165 /** 166 * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition 167 * animation. If the window magnification is enabling, it runs the animation in reverse. 168 * 169 * @param animationCallback Called when the transition is complete, the given arguments 170 * are as same as current values, or the transition is interrupted 171 * due to the new transition request. 172 */ deleteWindowMagnification( @ullable IRemoteMagnificationAnimationCallback animationCallback)173 void deleteWindowMagnification( 174 @Nullable IRemoteMagnificationAnimationCallback animationCallback) { 175 sendAnimationCallback(false); 176 mAnimationCallback = animationCallback; 177 if (mState == STATE_DISABLED || mState == STATE_DISABLING) { 178 if (mState == STATE_DISABLED) { 179 sendAnimationCallback(true); 180 } 181 return; 182 } 183 mStartSpec.set(/* scale*/ 1.0f, Float.NaN, Float.NaN); 184 mEndSpec.set(/* scale*/ mController.getScale(), Float.NaN, Float.NaN); 185 186 mValueAnimator.reverse(); 187 setState(STATE_DISABLING); 188 } 189 190 /** 191 * Wraps {@link WindowMagnificationController#moveWindowMagnifier(float, float)}. If the 192 * animation is running, it has no effect. 193 * @param offsetX The amount in pixels to offset the window magnifier in the X direction, in 194 * current screen pixels. 195 * @param offsetY The amount in pixels to offset the window magnifier in the Y direction, in 196 * current screen pixels. 197 */ moveWindowMagnifier(float offsetX, float offsetY)198 void moveWindowMagnifier(float offsetX, float offsetY) { 199 if (mValueAnimator.isRunning()) { 200 return; 201 } 202 mController.moveWindowMagnifier(offsetX, offsetY); 203 } 204 onConfigurationChanged(int configDiff)205 void onConfigurationChanged(int configDiff) { 206 mController.onConfigurationChanged(configDiff); 207 } 208 setState(@agnificationState int state)209 private void setState(@MagnificationState int state) { 210 if (DEBUG) { 211 Log.d(TAG, "setState from " + mState + " to " + state); 212 } 213 mState = state; 214 } 215 216 @Override onAnimationStart(Animator animation)217 public void onAnimationStart(Animator animation) { 218 mEndAnimationCanceled = false; 219 } 220 221 @Override onAnimationEnd(Animator animation, boolean isReverse)222 public void onAnimationEnd(Animator animation, boolean isReverse) { 223 if (mEndAnimationCanceled) { 224 return; 225 } 226 if (isReverse) { 227 mController.deleteWindowMagnification(); 228 setState(STATE_DISABLED); 229 } else { 230 setState(STATE_ENABLED); 231 } 232 sendAnimationCallback(true); 233 } 234 235 @Override onAnimationEnd(Animator animation)236 public void onAnimationEnd(Animator animation) { 237 } 238 239 @Override onAnimationCancel(Animator animation)240 public void onAnimationCancel(Animator animation) { 241 mEndAnimationCanceled = true; 242 } 243 244 @Override onAnimationRepeat(Animator animation)245 public void onAnimationRepeat(Animator animation) { 246 } 247 sendAnimationCallback(boolean success)248 private void sendAnimationCallback(boolean success) { 249 if (mAnimationCallback != null) { 250 try { 251 mAnimationCallback.onResult(success); 252 if (DEBUG) { 253 Log.d(TAG, "sendAnimationCallback success = " + success); 254 } 255 } catch (RemoteException e) { 256 Log.w(TAG, "sendAnimationCallback failed : " + e); 257 } 258 mAnimationCallback = null; 259 } 260 } 261 262 @Override onAnimationUpdate(ValueAnimator animation)263 public void onAnimationUpdate(ValueAnimator animation) { 264 final float fract = animation.getAnimatedFraction(); 265 final float sentScale = mStartSpec.mScale + (mEndSpec.mScale - mStartSpec.mScale) * fract; 266 final float centerX = 267 mStartSpec.mCenterX + (mEndSpec.mCenterX - mStartSpec.mCenterX) * fract; 268 final float centerY = 269 mStartSpec.mCenterY + (mEndSpec.mCenterY - mStartSpec.mCenterY) * fract; 270 mController.enableWindowMagnification(sentScale, centerX, centerY); 271 } 272 updateSysUiStateFlag()273 public void updateSysUiStateFlag() { 274 mController.updateSysUIStateFlag(); 275 } 276 dump(PrintWriter pw)277 void dump(PrintWriter pw) { 278 mController.dump(pw); 279 } 280 newValueAnimator(Resources resources)281 private static ValueAnimator newValueAnimator(Resources resources) { 282 final ValueAnimator valueAnimator = new ValueAnimator(); 283 valueAnimator.setDuration( 284 resources.getInteger(com.android.internal.R.integer.config_longAnimTime)); 285 valueAnimator.setInterpolator(new AccelerateInterpolator(2.5f)); 286 valueAnimator.setFloatValues(0.0f, 1.0f); 287 return valueAnimator; 288 } 289 290 private static class AnimationSpec { 291 private float mScale = Float.NaN; 292 private float mCenterX = Float.NaN; 293 private float mCenterY = Float.NaN; 294 295 @Override equals(Object other)296 public boolean equals(Object other) { 297 if (this == other) { 298 return true; 299 } 300 301 if (other == null || getClass() != other.getClass()) { 302 return false; 303 } 304 305 final AnimationSpec s = (AnimationSpec) other; 306 return mScale == s.mScale && mCenterX == s.mCenterX && mCenterY == s.mCenterY; 307 } 308 309 @Override hashCode()310 public int hashCode() { 311 int result = (mScale != +0.0f ? Float.floatToIntBits(mScale) : 0); 312 result = 31 * result + (mCenterX != +0.0f ? Float.floatToIntBits(mCenterX) : 0); 313 result = 31 * result + (mCenterY != +0.0f ? Float.floatToIntBits(mCenterY) : 0); 314 return result; 315 } 316 set(float scale, float centerX, float centerY)317 void set(float scale, float centerX, float centerY) { 318 mScale = scale; 319 mCenterX = centerX; 320 mCenterY = centerY; 321 } 322 323 @Override toString()324 public String toString() { 325 return "AnimationSpec{" 326 + "mScale=" + mScale 327 + ", mCenterX=" + mCenterX 328 + ", mCenterY=" + mCenterY 329 + '}'; 330 } 331 } 332 } 333