1 /* 2 * Copyright (C) 2019 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 static android.view.WindowInsets.Type.systemGestures; 20 import static android.view.WindowManager.LayoutParams; 21 22 import static com.android.systemui.accessibility.WindowMagnificationSettings.MagnificationSize; 23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_MAGNIFICATION_OVERLAP; 24 25 import static java.lang.Math.abs; 26 27 import android.animation.ObjectAnimator; 28 import android.animation.PropertyValuesHolder; 29 import android.annotation.MainThread; 30 import android.annotation.NonNull; 31 import android.annotation.Nullable; 32 import android.annotation.UiContext; 33 import android.content.ComponentCallbacks; 34 import android.content.Context; 35 import android.content.pm.ActivityInfo; 36 import android.content.res.Configuration; 37 import android.content.res.Resources; 38 import android.graphics.Insets; 39 import android.graphics.Matrix; 40 import android.graphics.PixelFormat; 41 import android.graphics.PorterDuff; 42 import android.graphics.PorterDuffColorFilter; 43 import android.graphics.Rect; 44 import android.graphics.RectF; 45 import android.graphics.Region; 46 import android.os.Build; 47 import android.os.Bundle; 48 import android.os.Handler; 49 import android.os.RemoteException; 50 import android.os.UserHandle; 51 import android.provider.Settings; 52 import android.util.Log; 53 import android.util.Range; 54 import android.util.Size; 55 import android.util.SparseArray; 56 import android.util.TypedValue; 57 import android.view.Choreographer; 58 import android.view.Display; 59 import android.view.Gravity; 60 import android.view.IWindow; 61 import android.view.IWindowSession; 62 import android.view.LayoutInflater; 63 import android.view.MotionEvent; 64 import android.view.Surface; 65 import android.view.SurfaceControl; 66 import android.view.SurfaceControlViewHost; 67 import android.view.SurfaceHolder; 68 import android.view.SurfaceView; 69 import android.view.View; 70 import android.view.WindowManager; 71 import android.view.WindowManagerGlobal; 72 import android.view.WindowMetrics; 73 import android.view.accessibility.AccessibilityManager; 74 import android.view.accessibility.AccessibilityNodeInfo; 75 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; 76 import android.view.accessibility.IRemoteMagnificationAnimationCallback; 77 import android.widget.FrameLayout; 78 import android.widget.ImageView; 79 80 import androidx.annotation.UiThread; 81 import androidx.core.math.MathUtils; 82 83 import com.android.internal.accessibility.common.MagnificationConstants; 84 import com.android.internal.annotations.VisibleForTesting; 85 import com.android.internal.graphics.SfVsyncFrameCallbackProvider; 86 import com.android.systemui.Flags; 87 import com.android.systemui.model.SysUiState; 88 import com.android.systemui.res.R; 89 import com.android.systemui.util.settings.SecureSettings; 90 91 import java.io.PrintWriter; 92 import java.text.NumberFormat; 93 import java.util.Collections; 94 import java.util.Locale; 95 import java.util.function.Supplier; 96 97 /** 98 * Class to handle adding and removing a window magnification. 99 */ 100 class WindowMagnificationController implements View.OnTouchListener, SurfaceHolder.Callback, 101 MirrorWindowControl.MirrorWindowDelegate, MagnificationGestureDetector.OnGestureListener, 102 ComponentCallbacks { 103 104 private static final String TAG = "WindowMagnificationController"; 105 @SuppressWarnings("isloggabletaglength") 106 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG) || Build.IS_DEBUGGABLE; 107 // Delay to avoid updating state description too frequently. 108 private static final int UPDATE_STATE_DESCRIPTION_DELAY_MS = 100; 109 // It should be consistent with the value defined in WindowMagnificationGestureHandler. 110 private static final Range<Float> A11Y_ACTION_SCALE_RANGE = new Range<>( 111 MagnificationConstants.SCALE_MIN_VALUE, 112 MagnificationConstants.SCALE_MAX_VALUE); 113 private static final float A11Y_CHANGE_SCALE_DIFFERENCE = 1.0f; 114 private static final float ANIMATION_BOUNCE_EFFECT_SCALE = 1.05f; 115 private final SparseArray<Float> mMagnificationSizeScaleOptions = new SparseArray<>(); 116 117 private final Context mContext; 118 private final Resources mResources; 119 private final Handler mHandler; 120 private final Rect mWindowBounds; 121 private final int mDisplayId; 122 @Surface.Rotation 123 @VisibleForTesting 124 int mRotation; 125 private final SurfaceControl.Transaction mTransaction; 126 127 private final WindowManager mWm; 128 129 private float mScale; 130 131 /** 132 * MagnificationFrame represents the bound of {@link #mMirrorSurfaceView} and is constrained 133 * by the {@link #mMagnificationFrameBoundary}. 134 * We use MagnificationFrame to calculate the position of {@link #mMirrorView}. 135 * We combine MagnificationFrame with {@link #mMagnificationFrameOffsetX} and 136 * {@link #mMagnificationFrameOffsetY} to calculate the position of {@link #mSourceBounds}. 137 */ 138 private final Rect mMagnificationFrame = new Rect(); 139 private final Rect mTmpRect = new Rect(); 140 141 /** 142 * MirrorViewBounds is the bound of the {@link #mMirrorView} which displays the magnified 143 * content. 144 * {@link #mMirrorView}'s center is equal to {@link #mMagnificationFrame}'s center. 145 */ 146 private final Rect mMirrorViewBounds = new Rect(); 147 148 /** 149 * SourceBound is the bound of the magnified region which projects the magnified content. 150 * SourceBound's center is equal to the parameters centerX and centerY in 151 * {@link WindowMagnificationController#updateWindowMagnificationInternal(float, float, float)}} 152 * but it is calculated from {@link #mMagnificationFrame}'s center in the runtime. 153 */ 154 private final Rect mSourceBounds = new Rect(); 155 156 /** 157 * The relation of centers between {@link #mSourceBounds} and {@link #mMagnificationFrame} is 158 * calculated in {@link #calculateSourceBounds(Rect, float)} and the equations are as following: 159 * MagnificationFrame = SourceBound (e.g., centerX & centerY) + MagnificationFrameOffset 160 * SourceBound = MagnificationFrame - MagnificationFrameOffset 161 */ 162 private int mMagnificationFrameOffsetX = 0; 163 private int mMagnificationFrameOffsetY = 0; 164 165 @Nullable private Supplier<SurfaceControlViewHost> mScvhSupplier; 166 167 /** 168 * SurfaceControlViewHost is used to control the position of the window containing 169 * {@link #mMirrorView}. Using SurfaceControlViewHost instead of a regular window enables 170 * changing the window's position and setting {@link #mMirrorSurface}'s geometry atomically. 171 */ 172 private SurfaceControlViewHost mSurfaceControlViewHost; 173 174 // The root of the mirrored content 175 private SurfaceControl mMirrorSurface; 176 177 private ImageView mDragView; 178 private ImageView mCloseView; 179 private View mLeftDrag; 180 private View mTopDrag; 181 private View mRightDrag; 182 private View mBottomDrag; 183 private ImageView mTopLeftCornerView; 184 private ImageView mTopRightCornerView; 185 private ImageView mBottomLeftCornerView; 186 private ImageView mBottomRightCornerView; 187 private final Configuration mConfiguration; 188 189 @NonNull 190 private final WindowMagnifierCallback mWindowMagnifierCallback; 191 192 private final View.OnLayoutChangeListener mMirrorViewLayoutChangeListener; 193 private final View.OnLayoutChangeListener mMirrorSurfaceViewLayoutChangeListener; 194 private final Runnable mMirrorViewRunnable; 195 private final Runnable mUpdateStateDescriptionRunnable; 196 private final Runnable mWindowInsetChangeRunnable; 197 // MirrorView is the mirror window which displays the magnified content. 198 private View mMirrorView; 199 private View mMirrorBorderView; 200 private SurfaceView mMirrorSurfaceView; 201 private int mMirrorSurfaceMargin; 202 private int mBorderDragSize; 203 private int mOuterBorderSize; 204 205 /** 206 * How far from the right edge of the screen you need to drag the window before the button 207 * repositions to the other side. 208 */ 209 private int mButtonRepositionThresholdFromEdge; 210 211 // The boundary of magnification frame. 212 private final Rect mMagnificationFrameBoundary = new Rect(); 213 // The top Y of the system gesture rect at the bottom. Set to -1 if it is invalid. 214 private int mSystemGestureTop = -1; 215 private int mMinWindowSize; 216 217 private final WindowMagnificationAnimationController mAnimationController; 218 private final Supplier<IWindowSession> mGlobalWindowSessionSupplier; 219 private final SfVsyncFrameCallbackProvider mSfVsyncFrameProvider; 220 private final MagnificationGestureDetector mGestureDetector; 221 private int mBounceEffectDuration; 222 private final Choreographer.FrameCallback mMirrorViewGeometryVsyncCallback; 223 private Locale mLocale; 224 private NumberFormat mPercentFormat; 225 private float mBounceEffectAnimationScale; 226 private final SysUiState mSysUiState; 227 // Set it to true when the view is overlapped with the gesture insets at the bottom. 228 private boolean mOverlapWithGestureInsets; 229 private boolean mIsDragging; 230 231 private static final int MAX_HORIZONTAL_MOVE_ANGLE = 50; 232 private static final int HORIZONTAL = 1; 233 private static final int VERTICAL = 0; 234 235 @VisibleForTesting 236 static final double HORIZONTAL_LOCK_BASE = 237 Math.tan(Math.toRadians(MAX_HORIZONTAL_MOVE_ANGLE)); 238 239 private boolean mAllowDiagonalScrolling = false; 240 private boolean mEditSizeEnable = false; 241 private boolean mSettingsPanelVisibility = false; 242 @VisibleForTesting 243 WindowMagnificationFrameSizePrefs mWindowMagnificationFrameSizePrefs; 244 245 @Nullable 246 private final MirrorWindowControl mMirrorWindowControl; 247 WindowMagnificationController( @iContext Context context, @NonNull Handler handler, @NonNull WindowMagnificationAnimationController animationController, MirrorWindowControl mirrorWindowControl, SurfaceControl.Transaction transaction, @NonNull WindowMagnifierCallback callback, SysUiState sysUiState, SecureSettings secureSettings, Supplier<SurfaceControlViewHost> scvhSupplier, SfVsyncFrameCallbackProvider sfVsyncFrameProvider, Supplier<IWindowSession> globalWindowSessionSupplier)248 WindowMagnificationController( 249 @UiContext Context context, 250 @NonNull Handler handler, 251 @NonNull WindowMagnificationAnimationController animationController, 252 MirrorWindowControl mirrorWindowControl, 253 SurfaceControl.Transaction transaction, 254 @NonNull WindowMagnifierCallback callback, 255 SysUiState sysUiState, 256 SecureSettings secureSettings, 257 Supplier<SurfaceControlViewHost> scvhSupplier, 258 SfVsyncFrameCallbackProvider sfVsyncFrameProvider, 259 Supplier<IWindowSession> globalWindowSessionSupplier) { 260 mContext = context; 261 mHandler = handler; 262 mAnimationController = animationController; 263 mAnimationController.setOnAnimationEndRunnable(() -> { 264 if (Flags.createWindowlessWindowMagnifier()) { 265 notifySourceBoundsChanged(); 266 } 267 }); 268 mAnimationController.setWindowMagnificationController(this); 269 mWindowMagnifierCallback = callback; 270 mSysUiState = sysUiState; 271 mScvhSupplier = scvhSupplier; 272 mConfiguration = new Configuration(context.getResources().getConfiguration()); 273 mWindowMagnificationFrameSizePrefs = new WindowMagnificationFrameSizePrefs(mContext); 274 275 final Display display = mContext.getDisplay(); 276 mDisplayId = mContext.getDisplayId(); 277 mRotation = display.getRotation(); 278 279 mWm = context.getSystemService(WindowManager.class); 280 mWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds()); 281 282 mResources = mContext.getResources(); 283 mScale = secureSettings.getFloatForUser( 284 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 285 mResources.getInteger(R.integer.magnification_default_scale), 286 UserHandle.USER_CURRENT); 287 mAllowDiagonalScrolling = secureSettings.getIntForUser( 288 Settings.Secure.ACCESSIBILITY_ALLOW_DIAGONAL_SCROLLING, 1, 289 UserHandle.USER_CURRENT) == 1; 290 291 setupMagnificationSizeScaleOptions(); 292 293 setBounceEffectDuration(mResources.getInteger( 294 com.android.internal.R.integer.config_shortAnimTime)); 295 updateDimensions(); 296 297 final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible(); 298 setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(), 299 mWindowBounds.width() / 2, mWindowBounds.height() / 2); 300 computeBounceAnimationScale(); 301 302 mMirrorWindowControl = mirrorWindowControl; 303 if (mMirrorWindowControl != null) { 304 mMirrorWindowControl.setWindowDelegate(this); 305 } 306 mTransaction = transaction; 307 mGestureDetector = 308 new MagnificationGestureDetector(mContext, handler, this); 309 mWindowInsetChangeRunnable = this::onWindowInsetChanged; 310 mGlobalWindowSessionSupplier = globalWindowSessionSupplier; 311 mSfVsyncFrameProvider = sfVsyncFrameProvider; 312 313 // Initialize listeners. 314 if (Flags.createWindowlessWindowMagnifier()) { 315 mMirrorViewRunnable = new Runnable() { 316 final Rect mPreviousBounds = new Rect(); 317 318 @Override 319 public void run() { 320 if (mMirrorView != null) { 321 if (mPreviousBounds.width() != mMirrorViewBounds.width() 322 || mPreviousBounds.height() != mMirrorViewBounds.height()) { 323 mMirrorView.setSystemGestureExclusionRects(Collections.singletonList( 324 new Rect(0, 0, mMirrorViewBounds.width(), 325 mMirrorViewBounds.height()))); 326 mPreviousBounds.set(mMirrorViewBounds); 327 } 328 updateSystemUIStateIfNeeded(); 329 mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( 330 mDisplayId, mMirrorViewBounds); 331 } 332 } 333 }; 334 335 mMirrorSurfaceViewLayoutChangeListener = 336 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> 337 mMirrorView.post(this::applyTapExcludeRegion); 338 339 mMirrorViewGeometryVsyncCallback = null; 340 } else { 341 mMirrorViewRunnable = () -> { 342 if (mMirrorView != null) { 343 final Rect oldViewBounds = new Rect(mMirrorViewBounds); 344 mMirrorView.getBoundsOnScreen(mMirrorViewBounds); 345 if (oldViewBounds.width() != mMirrorViewBounds.width() 346 || oldViewBounds.height() != mMirrorViewBounds.height()) { 347 mMirrorView.setSystemGestureExclusionRects(Collections.singletonList( 348 new Rect(0, 0, 349 mMirrorViewBounds.width(), mMirrorViewBounds.height()))); 350 } 351 updateSystemUIStateIfNeeded(); 352 mWindowMagnifierCallback.onWindowMagnifierBoundsChanged( 353 mDisplayId, mMirrorViewBounds); 354 } 355 }; 356 357 mMirrorSurfaceViewLayoutChangeListener = 358 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> 359 mMirrorView.post(this::applyTapExcludeRegion); 360 361 mMirrorViewGeometryVsyncCallback = 362 l -> { 363 if (isActivated() && mMirrorSurface != null && calculateSourceBounds( 364 mMagnificationFrame, mScale)) { 365 // The final destination for the magnification surface should be at 0,0 366 // since the ViewRootImpl's position will change 367 mTmpRect.set(0, 0, mMagnificationFrame.width(), 368 mMagnificationFrame.height()); 369 mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect, 370 Surface.ROTATION_0).apply(); 371 372 // Notify source bounds change when the magnifier is not animating. 373 if (!mAnimationController.isAnimating()) { 374 mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, 375 mSourceBounds); 376 } 377 } 378 }; 379 } 380 381 mMirrorViewLayoutChangeListener = 382 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { 383 if (!mHandler.hasCallbacks(mMirrorViewRunnable)) { 384 mHandler.post(mMirrorViewRunnable); 385 } 386 }; 387 388 mUpdateStateDescriptionRunnable = () -> { 389 if (isActivated()) { 390 mMirrorView.setStateDescription(formatStateDescription(mScale)); 391 } 392 }; 393 } 394 setupMagnificationSizeScaleOptions()395 private void setupMagnificationSizeScaleOptions() { 396 mMagnificationSizeScaleOptions.clear(); 397 mMagnificationSizeScaleOptions.put(MagnificationSize.SMALL, 1.4f); 398 mMagnificationSizeScaleOptions.put(MagnificationSize.MEDIUM, 1.8f); 399 mMagnificationSizeScaleOptions.put(MagnificationSize.LARGE, 2.5f); 400 } 401 updateDimensions()402 private void updateDimensions() { 403 mMirrorSurfaceMargin = mResources.getDimensionPixelSize( 404 R.dimen.magnification_mirror_surface_margin); 405 mBorderDragSize = mResources.getDimensionPixelSize( 406 R.dimen.magnification_border_drag_size); 407 mOuterBorderSize = mResources.getDimensionPixelSize( 408 R.dimen.magnification_outer_border_margin); 409 mButtonRepositionThresholdFromEdge = 410 mResources.getDimensionPixelSize( 411 R.dimen.magnification_button_reposition_threshold_from_edge); 412 mMinWindowSize = mResources.getDimensionPixelSize( 413 com.android.internal.R.dimen.accessibility_window_magnifier_min_size); 414 } 415 computeBounceAnimationScale()416 private void computeBounceAnimationScale() { 417 final float windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; 418 final float visibleWindowWidth = windowWidth - 2 * mOuterBorderSize; 419 final float animationScaleMax = windowWidth / visibleWindowWidth; 420 mBounceEffectAnimationScale = Math.min(animationScaleMax, ANIMATION_BOUNCE_EFFECT_SCALE); 421 } 422 updateSystemGestureInsetsTop()423 private boolean updateSystemGestureInsetsTop() { 424 final WindowMetrics windowMetrics = mWm.getCurrentWindowMetrics(); 425 final Insets insets = windowMetrics.getWindowInsets().getInsets(systemGestures()); 426 final int gestureTop = 427 insets.bottom != 0 ? windowMetrics.getBounds().bottom - insets.bottom : -1; 428 if (gestureTop != mSystemGestureTop) { 429 mSystemGestureTop = gestureTop; 430 return true; 431 } 432 return false; 433 } 434 changeMagnificationSize(@agnificationSize int index)435 void changeMagnificationSize(@MagnificationSize int index) { 436 if (!mMagnificationSizeScaleOptions.contains(index)) { 437 return; 438 } 439 int size = getMagnificationWindowSizeFromIndex(index); 440 setWindowSize(size, size); 441 } 442 getMagnificationWindowSizeFromIndex(@agnificationSize int index)443 int getMagnificationWindowSizeFromIndex(@MagnificationSize int index) { 444 final float scale = mMagnificationSizeScaleOptions.get(index, 1.0f); 445 int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3; 446 return (int) (initSize * scale) - (int) (initSize * scale) % 2; 447 } 448 setEditMagnifierSizeMode(boolean enable)449 void setEditMagnifierSizeMode(boolean enable) { 450 mEditSizeEnable = enable; 451 applyResourcesValues(); 452 453 if (isActivated()) { 454 updateDimensions(); 455 applyTapExcludeRegion(); 456 } 457 458 if (!enable) { 459 // Keep the magnifier size when exiting edit mode 460 mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity( 461 new Size(mMagnificationFrame.width(), mMagnificationFrame.height())); 462 } 463 } 464 setDiagonalScrolling(boolean enable)465 void setDiagonalScrolling(boolean enable) { 466 mAllowDiagonalScrolling = enable; 467 } 468 469 /** 470 * Wraps {@link WindowMagnificationController#deleteWindowMagnification()}} with transition 471 * animation. If the window magnification is enabling, it runs the animation in reverse. 472 * 473 * @param animationCallback Called when the transition is complete, the given arguments 474 * are as same as current values, or the transition is interrupted 475 * due to the new transition request. 476 */ deleteWindowMagnification( @ullable IRemoteMagnificationAnimationCallback animationCallback)477 void deleteWindowMagnification( 478 @Nullable IRemoteMagnificationAnimationCallback animationCallback) { 479 mAnimationController.deleteWindowMagnification(animationCallback); 480 } 481 482 /** 483 * Deletes the magnification window. 484 */ deleteWindowMagnification()485 void deleteWindowMagnification() { 486 if (!isActivated()) { 487 return; 488 } 489 490 if (mMirrorSurface != null) { 491 mTransaction.remove(mMirrorSurface).apply(); 492 mMirrorSurface = null; 493 } 494 495 if (mMirrorSurfaceView != null) { 496 mMirrorSurfaceView.removeOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener); 497 } 498 499 if (mMirrorView != null) { 500 mHandler.removeCallbacks(mMirrorViewRunnable); 501 mMirrorView.removeOnLayoutChangeListener(mMirrorViewLayoutChangeListener); 502 if (!Flags.createWindowlessWindowMagnifier()) { 503 mWm.removeView(mMirrorView); 504 } 505 mMirrorView = null; 506 } 507 508 if (mMirrorWindowControl != null) { 509 mMirrorWindowControl.destroyControl(); 510 } 511 512 if (mSurfaceControlViewHost != null) { 513 mSurfaceControlViewHost.release(); 514 mSurfaceControlViewHost = null; 515 } 516 517 mMirrorViewBounds.setEmpty(); 518 mSourceBounds.setEmpty(); 519 updateSystemUIStateIfNeeded(); 520 setEditMagnifierSizeMode(false); 521 522 mContext.unregisterComponentCallbacks(this); 523 // Notify source bounds empty when magnification is deleted. 524 mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, new Rect()); 525 } 526 527 @Override onConfigurationChanged(@onNull Configuration newConfig)528 public void onConfigurationChanged(@NonNull Configuration newConfig) { 529 final int configDiff = newConfig.diff(mConfiguration); 530 mConfiguration.setTo(newConfig); 531 onConfigurationChanged(configDiff); 532 } 533 534 @Override onLowMemory()535 public void onLowMemory() { 536 } 537 538 /** 539 * Called when the configuration has changed, and it updates window magnification UI. 540 * 541 * @param configDiff a bit mask of the differences between the configurations 542 */ onConfigurationChanged(int configDiff)543 void onConfigurationChanged(int configDiff) { 544 if (DEBUG) { 545 Log.d(TAG, "onConfigurationChanged = " + Configuration.configurationDiffToString( 546 configDiff)); 547 } 548 if (configDiff == 0) { 549 return; 550 } 551 if ((configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) { 552 onRotate(); 553 } 554 555 if ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0) { 556 updateAccessibilityWindowTitleIfNeeded(); 557 } 558 559 boolean reCreateWindow = false; 560 if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0) { 561 updateDimensions(); 562 computeBounceAnimationScale(); 563 reCreateWindow = true; 564 } 565 566 if ((configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0) { 567 reCreateWindow |= handleScreenSizeChanged(); 568 } 569 570 // Recreate the window again to correct the window appearance due to density or 571 // window size changed not caused by rotation. 572 if (isActivated() && reCreateWindow) { 573 deleteWindowMagnification(); 574 updateWindowMagnificationInternal(Float.NaN, Float.NaN, Float.NaN); 575 } 576 } 577 578 /** 579 * Calculates the magnification frame if the window bounds is changed. 580 * Note that the orientation also changes the wind bounds, so it should be handled first. 581 * 582 * @return {@code true} if the magnification frame is changed with the new window bounds. 583 */ handleScreenSizeChanged()584 private boolean handleScreenSizeChanged() { 585 final Rect oldWindowBounds = new Rect(mWindowBounds); 586 final Rect currentWindowBounds = mWm.getCurrentWindowMetrics().getBounds(); 587 588 if (currentWindowBounds.equals(oldWindowBounds)) { 589 if (DEBUG) { 590 Log.d(TAG, "handleScreenSizeChanged -- window bounds is not changed"); 591 } 592 return false; 593 } 594 mWindowBounds.set(currentWindowBounds); 595 final Size windowFrameSize = restoreMagnificationWindowFrameSizeIfPossible(); 596 final float newCenterX = (getCenterX()) * mWindowBounds.width() / oldWindowBounds.width(); 597 final float newCenterY = (getCenterY()) * mWindowBounds.height() / oldWindowBounds.height(); 598 599 setMagnificationFrame(windowFrameSize.getWidth(), windowFrameSize.getHeight(), 600 (int) newCenterX, (int) newCenterY); 601 calculateMagnificationFrameBoundary(); 602 return true; 603 } 604 updateSystemUIStateIfNeeded()605 private void updateSystemUIStateIfNeeded() { 606 updateSysUIState(false); 607 } 608 updateAccessibilityWindowTitleIfNeeded()609 private void updateAccessibilityWindowTitleIfNeeded() { 610 if (!isActivated()) return; 611 LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams(); 612 params.accessibilityTitle = getAccessibilityWindowTitle(); 613 if (Flags.createWindowlessWindowMagnifier()) { 614 mSurfaceControlViewHost.relayout(params); 615 } else { 616 mWm.updateViewLayout(mMirrorView, params); 617 } 618 } 619 620 /** 621 * Keep MirrorWindow position on the screen unchanged when device rotates 90° clockwise or 622 * anti-clockwise. 623 */ onRotate()624 private void onRotate() { 625 final Display display = mContext.getDisplay(); 626 final int oldRotation = mRotation; 627 mRotation = display.getRotation(); 628 final int rotationDegree = getDegreeFromRotation(mRotation, oldRotation); 629 if (rotationDegree == 0 || rotationDegree == 180) { 630 Log.w(TAG, "onRotate -- rotate with the device. skip it"); 631 return; 632 } 633 final Rect currentWindowBounds = new Rect(mWm.getCurrentWindowMetrics().getBounds()); 634 if (currentWindowBounds.width() != mWindowBounds.height() 635 || currentWindowBounds.height() != mWindowBounds.width()) { 636 Log.w(TAG, "onRotate -- unexpected window height/width"); 637 return; 638 } 639 640 mWindowBounds.set(currentWindowBounds); 641 642 // Keep MirrorWindow position on the screen unchanged when device rotates 90° 643 // clockwise or anti-clockwise. 644 645 final Matrix matrix = new Matrix(); 646 matrix.setRotate(rotationDegree); 647 if (rotationDegree == 90) { 648 matrix.postTranslate(mWindowBounds.width(), 0); 649 } else if (rotationDegree == 270) { 650 matrix.postTranslate(0, mWindowBounds.height()); 651 } 652 653 final RectF transformedRect = new RectF(mMagnificationFrame); 654 // The window frame is going to be transformed by the rotation matrix. 655 transformedRect.inset(-mMirrorSurfaceMargin, -mMirrorSurfaceMargin); 656 matrix.mapRect(transformedRect); 657 setWindowSizeAndCenter((int) transformedRect.width(), (int) transformedRect.height(), 658 (int) transformedRect.centerX(), (int) transformedRect.centerY()); 659 } 660 661 /** Returns the rotation degree change of two {@link Surface.Rotation} */ getDegreeFromRotation(@urface.Rotation int newRotation, @Surface.Rotation int oldRotation)662 private int getDegreeFromRotation(@Surface.Rotation int newRotation, 663 @Surface.Rotation int oldRotation) { 664 return (oldRotation - newRotation + 4) % 4 * 90; 665 } 666 createMirrorWindow()667 private void createMirrorWindow() { 668 if (Flags.createWindowlessWindowMagnifier()) { 669 createWindowlessMirrorWindow(); 670 return; 671 } 672 673 // The window should be the size the mirrored surface will be but also add room for the 674 // border and the drag handle. 675 int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; 676 int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; 677 678 LayoutParams params = new LayoutParams( 679 windowWidth, windowHeight, 680 LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, 681 LayoutParams.FLAG_NOT_TOUCH_MODAL 682 | LayoutParams.FLAG_NOT_FOCUSABLE, 683 PixelFormat.TRANSPARENT); 684 params.gravity = Gravity.TOP | Gravity.LEFT; 685 params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; 686 params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; 687 params.layoutInDisplayCutoutMode = LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 688 params.receiveInsetsIgnoringZOrder = true; 689 params.setTitle(mContext.getString(R.string.magnification_window_title)); 690 params.accessibilityTitle = getAccessibilityWindowTitle(); 691 692 mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null); 693 mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view); 694 695 mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border); 696 697 // Allow taps to go through to the mirror SurfaceView below. 698 mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener); 699 700 mMirrorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE 701 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 702 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 703 | View.SYSTEM_UI_FLAG_FULLSCREEN 704 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 705 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); 706 mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener); 707 mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate()); 708 mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> { 709 if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) { 710 mHandler.post(mWindowInsetChangeRunnable); 711 } 712 return v.onApplyWindowInsets(insets); 713 }); 714 715 mWm.addView(mMirrorView, params); 716 717 SurfaceHolder holder = mMirrorSurfaceView.getHolder(); 718 holder.addCallback(this); 719 holder.setFormat(PixelFormat.RGBA_8888); 720 addDragTouchListeners(); 721 } 722 createWindowlessMirrorWindow()723 private void createWindowlessMirrorWindow() { 724 // The window should be the size the mirrored surface will be but also add room for the 725 // border and the drag handle. 726 int windowWidth = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; 727 int windowHeight = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; 728 729 // TODO: b/335440685 - Move to TYPE_ACCESSIBILITY_OVERLAY after the issues with 730 // that type preventing swipe to navigate are resolved. 731 LayoutParams params = new LayoutParams( 732 windowWidth, windowHeight, 733 LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY, 734 LayoutParams.FLAG_NOT_TOUCH_MODAL 735 | LayoutParams.FLAG_NOT_FOCUSABLE, 736 PixelFormat.TRANSPARENT); 737 params.receiveInsetsIgnoringZOrder = true; 738 params.setTitle(mContext.getString(R.string.magnification_window_title)); 739 params.accessibilityTitle = getAccessibilityWindowTitle(); 740 params.setTrustedOverlay(); 741 742 mMirrorView = LayoutInflater.from(mContext).inflate(R.layout.window_magnifier_view, null); 743 mMirrorSurfaceView = mMirrorView.findViewById(R.id.surface_view); 744 745 mMirrorBorderView = mMirrorView.findViewById(R.id.magnification_inner_border); 746 747 // Allow taps to go through to the mirror SurfaceView below. 748 mMirrorSurfaceView.addOnLayoutChangeListener(mMirrorSurfaceViewLayoutChangeListener); 749 750 mMirrorView.addOnLayoutChangeListener(mMirrorViewLayoutChangeListener); 751 mMirrorView.setAccessibilityDelegate(new MirrorWindowA11yDelegate()); 752 mMirrorView.setOnApplyWindowInsetsListener((v, insets) -> { 753 if (!mHandler.hasCallbacks(mWindowInsetChangeRunnable)) { 754 mHandler.post(mWindowInsetChangeRunnable); 755 } 756 return v.onApplyWindowInsets(insets); 757 }); 758 759 mSurfaceControlViewHost = mScvhSupplier.get(); 760 mSurfaceControlViewHost.setView(mMirrorView, params); 761 SurfaceControl surfaceControl = mSurfaceControlViewHost 762 .getSurfacePackage().getSurfaceControl(); 763 764 int x = mMagnificationFrame.left - mMirrorSurfaceMargin; 765 int y = mMagnificationFrame.top - mMirrorSurfaceMargin; 766 mTransaction 767 .setCrop(surfaceControl, new Rect(0, 0, windowWidth, windowHeight)) 768 .setPosition(surfaceControl, x, y) 769 .setLayer(surfaceControl, Integer.MAX_VALUE) 770 .show(surfaceControl) 771 .apply(); 772 773 mMirrorViewBounds.set(x, y, x + windowWidth, y + windowHeight); 774 775 AccessibilityManager accessibilityManager = mContext 776 .getSystemService(AccessibilityManager.class); 777 accessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl); 778 779 SurfaceHolder holder = mMirrorSurfaceView.getHolder(); 780 holder.addCallback(this); 781 holder.setFormat(PixelFormat.RGBA_8888); 782 addDragTouchListeners(); 783 } 784 onWindowInsetChanged()785 private void onWindowInsetChanged() { 786 if (updateSystemGestureInsetsTop()) { 787 updateSystemUIStateIfNeeded(); 788 } 789 } 790 applyTapExcludeRegion()791 private void applyTapExcludeRegion() { 792 if (Flags.createWindowlessWindowMagnifier()) { 793 applyTouchableRegion(); 794 return; 795 } 796 797 // Sometimes this can get posted and run after deleteWindowMagnification() is called. 798 if (mMirrorView == null) return; 799 800 final Region tapExcludeRegion = calculateTapExclude(); 801 final IWindow window = IWindow.Stub.asInterface(mMirrorView.getWindowToken()); 802 try { 803 IWindowSession session = mGlobalWindowSessionSupplier.get(); 804 session.updateTapExcludeRegion(window, tapExcludeRegion); 805 } catch (RemoteException e) { 806 } 807 } 808 calculateTapExclude()809 private Region calculateTapExclude() { 810 Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize, 811 mMirrorView.getWidth() - mBorderDragSize, 812 mMirrorView.getHeight() - mBorderDragSize); 813 814 Region tapExcludeRegion = new Region(); 815 816 Rect dragArea = new Rect(); 817 mDragView.getHitRect(dragArea); 818 819 Rect topLeftArea = new Rect(); 820 mTopLeftCornerView.getHitRect(topLeftArea); 821 822 Rect topRightArea = new Rect(); 823 mTopRightCornerView.getHitRect(topRightArea); 824 825 Rect bottomLeftArea = new Rect(); 826 mBottomLeftCornerView.getHitRect(bottomLeftArea); 827 828 Rect bottomRightArea = new Rect(); 829 mBottomRightCornerView.getHitRect(bottomRightArea); 830 831 Rect closeArea = new Rect(); 832 mCloseView.getHitRect(closeArea); 833 834 // add tapExcludeRegion for Drag or close 835 tapExcludeRegion.op(dragArea, Region.Op.UNION); 836 tapExcludeRegion.op(topLeftArea, Region.Op.UNION); 837 tapExcludeRegion.op(topRightArea, Region.Op.UNION); 838 tapExcludeRegion.op(bottomLeftArea, Region.Op.UNION); 839 tapExcludeRegion.op(bottomRightArea, Region.Op.UNION); 840 tapExcludeRegion.op(closeArea, Region.Op.UNION); 841 842 regionInsideDragBorder.op(tapExcludeRegion, Region.Op.DIFFERENCE); 843 844 return regionInsideDragBorder; 845 } 846 applyTouchableRegion()847 private void applyTouchableRegion() { 848 // Sometimes this can get posted and run after deleteWindowMagnification() is called. 849 if (mMirrorView == null) return; 850 851 var surfaceControl = mSurfaceControlViewHost.getRootSurfaceControl(); 852 surfaceControl.setTouchableRegion(calculateTouchableRegion()); 853 } 854 calculateTouchableRegion()855 private Region calculateTouchableRegion() { 856 Region touchableRegion = new Region(0, 0, mMirrorView.getWidth(), mMirrorView.getHeight()); 857 858 Region regionInsideDragBorder = new Region(mBorderDragSize, mBorderDragSize, 859 mMirrorView.getWidth() - mBorderDragSize, 860 mMirrorView.getHeight() - mBorderDragSize); 861 touchableRegion.op(regionInsideDragBorder, Region.Op.DIFFERENCE); 862 863 Rect dragArea = new Rect(); 864 mDragView.getHitRect(dragArea); 865 866 Rect topLeftArea = new Rect(); 867 mTopLeftCornerView.getHitRect(topLeftArea); 868 869 Rect topRightArea = new Rect(); 870 mTopRightCornerView.getHitRect(topRightArea); 871 872 Rect bottomLeftArea = new Rect(); 873 mBottomLeftCornerView.getHitRect(bottomLeftArea); 874 875 Rect bottomRightArea = new Rect(); 876 mBottomRightCornerView.getHitRect(bottomRightArea); 877 878 Rect closeArea = new Rect(); 879 mCloseView.getHitRect(closeArea); 880 881 // Add touchable regions for drag and close 882 touchableRegion.op(dragArea, Region.Op.UNION); 883 touchableRegion.op(topLeftArea, Region.Op.UNION); 884 touchableRegion.op(topRightArea, Region.Op.UNION); 885 touchableRegion.op(bottomLeftArea, Region.Op.UNION); 886 touchableRegion.op(bottomRightArea, Region.Op.UNION); 887 touchableRegion.op(closeArea, Region.Op.UNION); 888 889 return touchableRegion; 890 } 891 getAccessibilityWindowTitle()892 private String getAccessibilityWindowTitle() { 893 return mResources.getString(com.android.internal.R.string.android_system_label); 894 } 895 showControls()896 private void showControls() { 897 if (mMirrorWindowControl != null) { 898 mMirrorWindowControl.showControl(); 899 } 900 } 901 902 /** 903 * Sets the window frame size with given width and height in pixels without changing the 904 * window center. 905 * 906 * @param width the window frame width in pixels 907 * @param height the window frame height in pixels. 908 */ 909 @MainThread setMagnificationFrameSize(int width, int height)910 private void setMagnificationFrameSize(int width, int height) { 911 setWindowSize(width + 2 * mMirrorSurfaceMargin, height + 2 * mMirrorSurfaceMargin); 912 } 913 914 /** 915 * Sets the window size with given width and height in pixels without changing the 916 * window center. The width or the height will be clamped in the range 917 * [{@link #mMinWindowSize}, screen width or height]. 918 * 919 * @param width the window width in pixels 920 * @param height the window height in pixels. 921 */ setWindowSize(int width, int height)922 public void setWindowSize(int width, int height) { 923 setWindowSizeAndCenter(width, height, Float.NaN, Float.NaN); 924 } 925 setWindowSizeAndCenter(int width, int height, float centerX, float centerY)926 void setWindowSizeAndCenter(int width, int height, float centerX, float centerY) { 927 width = MathUtils.clamp(width, mMinWindowSize, mWindowBounds.width()); 928 height = MathUtils.clamp(height, mMinWindowSize, mWindowBounds.height()); 929 930 if (Float.isNaN(centerX)) { 931 centerX = mMagnificationFrame.centerX(); 932 } 933 if (Float.isNaN(centerY)) { 934 centerY = mMagnificationFrame.centerY(); 935 } 936 937 final int frameWidth = width - 2 * mMirrorSurfaceMargin; 938 final int frameHeight = height - 2 * mMirrorSurfaceMargin; 939 setMagnificationFrame(frameWidth, frameHeight, (int) centerX, (int) centerY); 940 calculateMagnificationFrameBoundary(); 941 // Correct the frame position to ensure it is inside the boundary. 942 updateMagnificationFramePosition(0, 0); 943 modifyWindowMagnification(true); 944 } 945 setMagnificationFrame(int width, int height, int centerX, int centerY)946 private void setMagnificationFrame(int width, int height, int centerX, int centerY) { 947 mWindowMagnificationFrameSizePrefs.saveSizeForCurrentDensity(new Size(width, height)); 948 949 // Sets the initial frame area for the mirror and place it to the given center on the 950 // display. 951 final int initX = centerX - width / 2; 952 final int initY = centerY - height / 2; 953 mMagnificationFrame.set(initX, initY, initX + width, initY + height); 954 } 955 restoreMagnificationWindowFrameSizeIfPossible()956 private Size restoreMagnificationWindowFrameSizeIfPossible() { 957 if (!mWindowMagnificationFrameSizePrefs.isPreferenceSavedForCurrentDensity()) { 958 return getDefaultMagnificationWindowFrameSize(); 959 } 960 961 return mWindowMagnificationFrameSizePrefs.getSizeForCurrentDensity(); 962 } 963 getDefaultMagnificationWindowFrameSize()964 private Size getDefaultMagnificationWindowFrameSize() { 965 final int defaultSize = getMagnificationWindowSizeFromIndex(MagnificationSize.MEDIUM) 966 - 2 * mMirrorSurfaceMargin; 967 return new Size(defaultSize, defaultSize); 968 } 969 970 /** 971 * This is called once the surfaceView is created so the mirrored content can be placed as a 972 * child of the surfaceView. 973 */ createMirror()974 private void createMirror() { 975 mMirrorSurface = mirrorDisplay(mDisplayId); 976 if (!mMirrorSurface.isValid()) { 977 return; 978 } 979 mTransaction.show(mMirrorSurface) 980 .reparent(mMirrorSurface, mMirrorSurfaceView.getSurfaceControl()); 981 modifyWindowMagnification(false); 982 } 983 984 /** 985 * Mirrors a specified display. The SurfaceControl returned is the root of the mirrored 986 * hierarchy. 987 * 988 * @param displayId The id of the display to mirror 989 * @return The SurfaceControl for the root of the mirrored hierarchy. 990 */ mirrorDisplay(final int displayId)991 private SurfaceControl mirrorDisplay(final int displayId) { 992 try { 993 SurfaceControl outSurfaceControl = new SurfaceControl(); 994 WindowManagerGlobal.getWindowManagerService().mirrorDisplay(displayId, 995 outSurfaceControl); 996 return outSurfaceControl; 997 } catch (RemoteException e) { 998 Log.e(TAG, "Unable to reach window manager", e); 999 } 1000 return null; 1001 } 1002 addDragTouchListeners()1003 private void addDragTouchListeners() { 1004 mDragView = mMirrorView.findViewById(R.id.drag_handle); 1005 mLeftDrag = mMirrorView.findViewById(R.id.left_handle); 1006 mTopDrag = mMirrorView.findViewById(R.id.top_handle); 1007 mRightDrag = mMirrorView.findViewById(R.id.right_handle); 1008 mBottomDrag = mMirrorView.findViewById(R.id.bottom_handle); 1009 mCloseView = mMirrorView.findViewById(R.id.close_button); 1010 mTopRightCornerView = mMirrorView.findViewById(R.id.top_right_corner); 1011 mTopLeftCornerView = mMirrorView.findViewById(R.id.top_left_corner); 1012 mBottomRightCornerView = mMirrorView.findViewById(R.id.bottom_right_corner); 1013 mBottomLeftCornerView = mMirrorView.findViewById(R.id.bottom_left_corner); 1014 1015 mDragView.setOnTouchListener(this); 1016 mLeftDrag.setOnTouchListener(this); 1017 mTopDrag.setOnTouchListener(this); 1018 mRightDrag.setOnTouchListener(this); 1019 mBottomDrag.setOnTouchListener(this); 1020 mCloseView.setOnTouchListener(this); 1021 mTopLeftCornerView.setOnTouchListener(this); 1022 mTopRightCornerView.setOnTouchListener(this); 1023 mBottomLeftCornerView.setOnTouchListener(this); 1024 mBottomRightCornerView.setOnTouchListener(this); 1025 } 1026 1027 /** 1028 * Modifies the placement of the mirrored content when the position or size of mMirrorView is 1029 * updated. 1030 * 1031 * @param computeWindowSize set to {@code true} to compute window size with 1032 * {@link #mMagnificationFrame}. 1033 */ modifyWindowMagnification(boolean computeWindowSize)1034 private void modifyWindowMagnification(boolean computeWindowSize) { 1035 if (Flags.createWindowlessWindowMagnifier()) { 1036 updateMirrorSurfaceGeometry(); 1037 updateWindowlessMirrorViewLayout(computeWindowSize); 1038 } else { 1039 mSfVsyncFrameProvider.postFrameCallback(mMirrorViewGeometryVsyncCallback); 1040 updateMirrorViewLayout(computeWindowSize); 1041 } 1042 } 1043 1044 /** 1045 * Updates {@link #mMirrorSurface}'s geometry. This modifies {@link #mTransaction} but does not 1046 * apply it. 1047 */ 1048 @UiThread updateMirrorSurfaceGeometry()1049 private void updateMirrorSurfaceGeometry() { 1050 if (isActivated() && mMirrorSurface != null 1051 && calculateSourceBounds(mMagnificationFrame, mScale)) { 1052 // The final destination for the magnification surface should be at 0,0 1053 // since the ViewRootImpl's position will change 1054 mTmpRect.set(0, 0, mMagnificationFrame.width(), mMagnificationFrame.height()); 1055 mTransaction.setGeometry(mMirrorSurface, mSourceBounds, mTmpRect, Surface.ROTATION_0); 1056 1057 // Notify source bounds change when the magnifier is not animating. 1058 if (!mAnimationController.isAnimating()) { 1059 notifySourceBoundsChanged(); 1060 } 1061 } 1062 } 1063 notifySourceBoundsChanged()1064 private void notifySourceBoundsChanged() { 1065 mWindowMagnifierCallback.onSourceBoundsChanged(mDisplayId, mSourceBounds); 1066 } 1067 1068 /** 1069 * Updates the position of {@link mSurfaceControlViewHost} and layout params of MirrorView based 1070 * on the position and size of {@link #mMagnificationFrame}. 1071 * 1072 * @param computeWindowSize set to {@code true} to compute window size with 1073 * {@link #mMagnificationFrame}. 1074 */ 1075 @UiThread updateWindowlessMirrorViewLayout(boolean computeWindowSize)1076 private void updateWindowlessMirrorViewLayout(boolean computeWindowSize) { 1077 if (!isActivated()) { 1078 return; 1079 } 1080 1081 final int width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; 1082 final int height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; 1083 1084 final int minX = -mOuterBorderSize; 1085 final int maxX = mWindowBounds.right - width + mOuterBorderSize; 1086 final int x = MathUtils.clamp(mMagnificationFrame.left - mMirrorSurfaceMargin, minX, maxX); 1087 1088 final int minY = -mOuterBorderSize; 1089 final int maxY = mWindowBounds.bottom - height + mOuterBorderSize; 1090 final int y = MathUtils.clamp(mMagnificationFrame.top - mMirrorSurfaceMargin, minY, maxY); 1091 1092 if (computeWindowSize) { 1093 LayoutParams params = (LayoutParams) mMirrorView.getLayoutParams(); 1094 params.width = width; 1095 params.height = height; 1096 mSurfaceControlViewHost.relayout(params); 1097 mTransaction.setCrop(mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl(), 1098 new Rect(0, 0, width, height)); 1099 } 1100 1101 mMirrorViewBounds.set(x, y, x + width, y + height); 1102 mTransaction.setPosition( 1103 mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl(), x, y); 1104 if (computeWindowSize) { 1105 mSurfaceControlViewHost.getRootSurfaceControl().applyTransactionOnDraw(mTransaction); 1106 } else { 1107 mTransaction.apply(); 1108 } 1109 1110 // If they are not dragging the handle, we can move the drag handle immediately without 1111 // disruption. But if they are dragging it, we avoid moving until the end of the drag. 1112 if (!mIsDragging) { 1113 mMirrorView.post(this::maybeRepositionButton); 1114 } 1115 1116 mMirrorViewRunnable.run(); 1117 } 1118 1119 /** 1120 * Updates the layout params of MirrorView based on the size of {@link #mMagnificationFrame} 1121 * and translates MirrorView position when the view is moved close to the screen edges; 1122 * 1123 * @param computeWindowSize set to {@code true} to compute window size with 1124 * {@link #mMagnificationFrame}. 1125 */ updateMirrorViewLayout(boolean computeWindowSize)1126 private void updateMirrorViewLayout(boolean computeWindowSize) { 1127 if (!isActivated()) { 1128 return; 1129 } 1130 final int maxMirrorViewX = mWindowBounds.width() - mMirrorView.getWidth(); 1131 final int maxMirrorViewY = mWindowBounds.height() - mMirrorView.getHeight(); 1132 1133 LayoutParams params = 1134 (LayoutParams) mMirrorView.getLayoutParams(); 1135 params.x = mMagnificationFrame.left - mMirrorSurfaceMargin; 1136 params.y = mMagnificationFrame.top - mMirrorSurfaceMargin; 1137 if (computeWindowSize) { 1138 params.width = mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin; 1139 params.height = mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin; 1140 } 1141 1142 // Translates MirrorView position to make MirrorSurfaceView that is inside MirrorView 1143 // able to move close to the screen edges. 1144 final float translationX; 1145 final float translationY; 1146 if (params.x < 0) { 1147 translationX = Math.max(params.x, -mOuterBorderSize); 1148 } else if (params.x > maxMirrorViewX) { 1149 translationX = Math.min(params.x - maxMirrorViewX, mOuterBorderSize); 1150 } else { 1151 translationX = 0; 1152 } 1153 if (params.y < 0) { 1154 translationY = Math.max(params.y, -mOuterBorderSize); 1155 } else if (params.y > maxMirrorViewY) { 1156 translationY = Math.min(params.y - maxMirrorViewY, mOuterBorderSize); 1157 } else { 1158 translationY = 0; 1159 } 1160 mMirrorView.setTranslationX(translationX); 1161 mMirrorView.setTranslationY(translationY); 1162 mWm.updateViewLayout(mMirrorView, params); 1163 1164 // If they are not dragging the handle, we can move the drag handle immediately without 1165 // disruption. But if they are dragging it, we avoid moving until the end of the drag. 1166 if (!mIsDragging) { 1167 mMirrorView.post(this::maybeRepositionButton); 1168 } 1169 } 1170 1171 @Override onTouch(View v, MotionEvent event)1172 public boolean onTouch(View v, MotionEvent event) { 1173 if (v == mDragView 1174 || v == mLeftDrag 1175 || v == mTopDrag 1176 || v == mRightDrag 1177 || v == mBottomDrag 1178 || v == mTopLeftCornerView 1179 || v == mTopRightCornerView 1180 || v == mBottomLeftCornerView 1181 || v == mBottomRightCornerView 1182 || v == mCloseView) { 1183 return mGestureDetector.onTouch(v, event); 1184 } 1185 return false; 1186 } 1187 updateSysUIStateFlag()1188 public void updateSysUIStateFlag() { 1189 updateSysUIState(true); 1190 } 1191 1192 /** 1193 * Calculates the desired source bounds. This will be the area under from the center of the 1194 * displayFrame, factoring in scale. 1195 * 1196 * @return {@code true} if the source bounds is changed. 1197 */ calculateSourceBounds(Rect displayFrame, float scale)1198 private boolean calculateSourceBounds(Rect displayFrame, float scale) { 1199 final Rect oldSourceBounds = mTmpRect; 1200 oldSourceBounds.set(mSourceBounds); 1201 int halfWidth = displayFrame.width() / 2; 1202 int halfHeight = displayFrame.height() / 2; 1203 int left = displayFrame.left + (halfWidth - (int) (halfWidth / scale)); 1204 int right = displayFrame.right - (halfWidth - (int) (halfWidth / scale)); 1205 int top = displayFrame.top + (halfHeight - (int) (halfHeight / scale)); 1206 int bottom = displayFrame.bottom - (halfHeight - (int) (halfHeight / scale)); 1207 1208 mSourceBounds.set(left, top, right, bottom); 1209 1210 // SourceBound's center is equal to center[X,Y] but calculated from MagnificationFrame's 1211 // center. The relation between SourceBound and MagnificationFrame is as following: 1212 // MagnificationFrame = SourceBound (center[X,Y]) + MagnificationFrameOffset 1213 // SourceBound = MagnificationFrame - MagnificationFrameOffset 1214 mSourceBounds.offset(-mMagnificationFrameOffsetX, -mMagnificationFrameOffsetY); 1215 1216 if (mSourceBounds.left < 0) { 1217 mSourceBounds.offsetTo(0, mSourceBounds.top); 1218 } else if (mSourceBounds.right > mWindowBounds.width()) { 1219 mSourceBounds.offsetTo(mWindowBounds.width() - mSourceBounds.width(), 1220 mSourceBounds.top); 1221 } 1222 1223 if (mSourceBounds.top < 0) { 1224 mSourceBounds.offsetTo(mSourceBounds.left, 0); 1225 } else if (mSourceBounds.bottom > mWindowBounds.height()) { 1226 mSourceBounds.offsetTo(mSourceBounds.left, 1227 mWindowBounds.height() - mSourceBounds.height()); 1228 } 1229 return !mSourceBounds.equals(oldSourceBounds); 1230 } 1231 calculateMagnificationFrameBoundary()1232 private void calculateMagnificationFrameBoundary() { 1233 // Calculates width and height for magnification frame could exceed out the screen. 1234 // TODO : re-calculating again when scale is changed. 1235 // The half width of magnification frame. 1236 final int halfWidth = mMagnificationFrame.width() / 2; 1237 // The half height of magnification frame. 1238 final int halfHeight = mMagnificationFrame.height() / 2; 1239 // The scaled half width of magnified region. 1240 final int scaledWidth = (int) (halfWidth / mScale); 1241 // The scaled half height of magnified region. 1242 final int scaledHeight = (int) (halfHeight / mScale); 1243 1244 // MagnificationFrameBoundary constrain the space of MagnificationFrame, and it also has 1245 // to leave enough space for SourceBound to magnify the whole screen space. 1246 // However, there is an offset between SourceBound and MagnificationFrame. 1247 // The relation between SourceBound and MagnificationFrame is as following: 1248 // SourceBound = MagnificationFrame - MagnificationFrameOffset 1249 // Therefore, we have to adjust the exceededBoundary based on the offset. 1250 // 1251 // We have to increase the offset space for the SourceBound edges which are located in 1252 // the MagnificationFrame. For example, if the offsetX and offsetY are negative, which 1253 // means SourceBound is at right-bottom size of MagnificationFrame, the left and top 1254 // edges of SourceBound are located in MagnificationFrame. So, we have to leave extra 1255 // offset space at left and top sides and don't have to leave extra space at right and 1256 // bottom sides. 1257 final int exceededLeft = Math.max(halfWidth - scaledWidth - mMagnificationFrameOffsetX, 0); 1258 final int exceededRight = Math.max(halfWidth - scaledWidth + mMagnificationFrameOffsetX, 0); 1259 final int exceededTop = Math.max(halfHeight - scaledHeight - mMagnificationFrameOffsetY, 0); 1260 final int exceededBottom = Math.max(halfHeight - scaledHeight + mMagnificationFrameOffsetY, 1261 0); 1262 1263 mMagnificationFrameBoundary.set( 1264 -exceededLeft, 1265 -exceededTop, 1266 mWindowBounds.width() + exceededRight, 1267 mWindowBounds.height() + exceededBottom); 1268 } 1269 1270 /** 1271 * Calculates and sets the real position of magnification frame based on the magnified region 1272 * should be limited by the region of the display. 1273 */ updateMagnificationFramePosition(int xOffset, int yOffset)1274 private boolean updateMagnificationFramePosition(int xOffset, int yOffset) { 1275 mTmpRect.set(mMagnificationFrame); 1276 mTmpRect.offset(xOffset, yOffset); 1277 1278 if (mTmpRect.left < mMagnificationFrameBoundary.left) { 1279 mTmpRect.offsetTo(mMagnificationFrameBoundary.left, mTmpRect.top); 1280 } else if (mTmpRect.right > mMagnificationFrameBoundary.right) { 1281 final int leftOffset = mMagnificationFrameBoundary.right - mMagnificationFrame.width(); 1282 mTmpRect.offsetTo(leftOffset, mTmpRect.top); 1283 } 1284 1285 if (mTmpRect.top < mMagnificationFrameBoundary.top) { 1286 mTmpRect.offsetTo(mTmpRect.left, mMagnificationFrameBoundary.top); 1287 } else if (mTmpRect.bottom > mMagnificationFrameBoundary.bottom) { 1288 final int topOffset = mMagnificationFrameBoundary.bottom - mMagnificationFrame.height(); 1289 mTmpRect.offsetTo(mTmpRect.left, topOffset); 1290 } 1291 1292 if (!mTmpRect.equals(mMagnificationFrame)) { 1293 mMagnificationFrame.set(mTmpRect); 1294 return true; 1295 } 1296 return false; 1297 } 1298 updateSysUIState(boolean force)1299 private void updateSysUIState(boolean force) { 1300 final boolean overlap = isActivated() && mSystemGestureTop > 0 1301 && mMirrorViewBounds.bottom > mSystemGestureTop; 1302 if (force || overlap != mOverlapWithGestureInsets) { 1303 mOverlapWithGestureInsets = overlap; 1304 mSysUiState.setFlag(SYSUI_STATE_MAGNIFICATION_OVERLAP, mOverlapWithGestureInsets) 1305 .commitUpdate(mDisplayId); 1306 } 1307 } 1308 1309 @Override surfaceCreated(SurfaceHolder holder)1310 public void surfaceCreated(SurfaceHolder holder) { 1311 createMirror(); 1312 } 1313 1314 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)1315 public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { 1316 } 1317 1318 @Override surfaceDestroyed(SurfaceHolder holder)1319 public void surfaceDestroyed(SurfaceHolder holder) { 1320 } 1321 1322 @Override move(int xOffset, int yOffset)1323 public void move(int xOffset, int yOffset) { 1324 moveWindowMagnifier(xOffset, yOffset); 1325 mWindowMagnifierCallback.onMove(mDisplayId); 1326 } 1327 1328 /** 1329 * Wraps {@link WindowMagnificationController#updateWindowMagnificationInternal(float, float, 1330 * float, float, float)} 1331 * with transition animation. If the window magnification is not enabled, the scale will start 1332 * from 1.0 and the center won't be changed during the animation. If animator is 1333 * {@code STATE_DISABLING}, the animation runs in reverse. 1334 * 1335 * @param scale The target scale, or {@link Float#NaN} to leave unchanged. 1336 * @param centerX The screen-relative X coordinate around which to center for magnification, 1337 * or {@link Float#NaN} to leave unchanged. 1338 * @param centerY The screen-relative Y coordinate around which to center for magnification, 1339 * or {@link Float#NaN} to leave unchanged. 1340 * @param magnificationFrameOffsetRatioX Indicate the X coordinate offset 1341 * between frame position X and centerX 1342 * @param magnificationFrameOffsetRatioY Indicate the Y coordinate offset 1343 * between frame position Y and centerY 1344 * @param animationCallback Called when the transition is complete, the given arguments 1345 * are as same as current values, or the transition is interrupted 1346 * due to the new transition request. 1347 */ enableWindowMagnification(float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, @Nullable IRemoteMagnificationAnimationCallback animationCallback)1348 public void enableWindowMagnification(float scale, float centerX, float centerY, 1349 float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, 1350 @Nullable IRemoteMagnificationAnimationCallback animationCallback) { 1351 mAnimationController.enableWindowMagnification(scale, centerX, centerY, 1352 magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY, animationCallback); 1353 } 1354 1355 /** 1356 * Updates window magnification status with specified parameters. If the given scale is 1357 * <strong>less than 1.0f</strong>, then 1358 * {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to 1359 * be consistent with the behavior of display magnification. If the given scale is 1360 * <strong>larger than or equal to 1.0f</strong>, and the window magnification is not activated 1361 * yet, window magnification will be enabled. 1362 * 1363 * @param scale the target scale, or {@link Float#NaN} to leave unchanged 1364 * @param centerX the screen-relative X coordinate around which to center for magnification, 1365 * or {@link Float#NaN} to leave unchanged. 1366 * @param centerY the screen-relative Y coordinate around which to center for magnification, 1367 * or {@link Float#NaN} to leave unchanged. 1368 */ updateWindowMagnificationInternal(float scale, float centerX, float centerY)1369 void updateWindowMagnificationInternal(float scale, float centerX, float centerY) { 1370 updateWindowMagnificationInternal(scale, centerX, centerY, Float.NaN, Float.NaN); 1371 } 1372 1373 /** 1374 * Updates window magnification status with specified parameters. If the given scale is 1375 * <strong>less than 1.0f</strong>, then 1376 * {@link WindowMagnificationController#deleteWindowMagnification()} will be called instead to 1377 * be consistent with the behavior of display magnification. If the given scale is 1378 * <strong>larger than or equal to 1.0f</strong>, and the window magnification is not activated 1379 * yet, window magnification will be enabled. 1380 * @param scale the target scale, or {@link Float#NaN} to leave unchanged 1381 * @param centerX the screen-relative X coordinate around which to center for magnification, 1382 * or {@link Float#NaN} to leave unchanged. 1383 * @param centerY the screen-relative Y coordinate around which to center for magnification, 1384 * or {@link Float#NaN} to leave unchanged. 1385 * @param magnificationFrameOffsetRatioX Indicate the X coordinate offset 1386 * between frame position X and centerX, 1387 * or {@link Float#NaN} to leave unchanged. 1388 * @param magnificationFrameOffsetRatioY Indicate the Y coordinate offset 1389 * between frame position Y and centerY, 1390 * or {@link Float#NaN} to leave unchanged. 1391 */ updateWindowMagnificationInternal(float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY)1392 void updateWindowMagnificationInternal(float scale, float centerX, float centerY, 1393 float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY) { 1394 if (Float.compare(scale, 1.0f) < 0) { 1395 deleteWindowMagnification(); 1396 return; 1397 } 1398 if (!isActivated()) { 1399 onConfigurationChanged(mResources.getConfiguration()); 1400 mContext.registerComponentCallbacks(this); 1401 } 1402 1403 mMagnificationFrameOffsetX = Float.isNaN(magnificationFrameOffsetRatioX) 1404 ? mMagnificationFrameOffsetX 1405 : (int) (mMagnificationFrame.width() / 2 * magnificationFrameOffsetRatioX); 1406 mMagnificationFrameOffsetY = Float.isNaN(magnificationFrameOffsetRatioY) 1407 ? mMagnificationFrameOffsetY 1408 : (int) (mMagnificationFrame.height() / 2 * magnificationFrameOffsetRatioY); 1409 1410 // The relation of centers between SourceBound and MagnificationFrame is as following: 1411 // MagnificationFrame = SourceBound (e.g., centerX & centerY) + MagnificationFrameOffset 1412 final float newMagnificationFrameCenterX = centerX + mMagnificationFrameOffsetX; 1413 final float newMagnificationFrameCenterY = centerY + mMagnificationFrameOffsetY; 1414 1415 final float offsetX = Float.isNaN(centerX) ? 0 1416 : newMagnificationFrameCenterX - mMagnificationFrame.exactCenterX(); 1417 final float offsetY = Float.isNaN(centerY) ? 0 1418 : newMagnificationFrameCenterY - mMagnificationFrame.exactCenterY(); 1419 mScale = Float.isNaN(scale) ? mScale : scale; 1420 1421 calculateMagnificationFrameBoundary(); 1422 updateMagnificationFramePosition((int) offsetX, (int) offsetY); 1423 if (!isActivated()) { 1424 createMirrorWindow(); 1425 showControls(); 1426 applyResourcesValues(); 1427 } else { 1428 modifyWindowMagnification(false); 1429 } 1430 } 1431 1432 // The magnifier is activated when the window is visible, 1433 // and the window is visible when it is existed. isActivated()1434 boolean isActivated() { 1435 return mMirrorView != null; 1436 } 1437 1438 /** 1439 * Sets the scale of the magnified region if it's visible. 1440 * 1441 * @param scale the target scale, or {@link Float#NaN} to leave unchanged 1442 */ setScale(float scale)1443 void setScale(float scale) { 1444 if (mAnimationController.isAnimating() || !isActivated() || mScale == scale) { 1445 return; 1446 } 1447 1448 updateWindowMagnificationInternal(scale, Float.NaN, Float.NaN); 1449 mHandler.removeCallbacks(mUpdateStateDescriptionRunnable); 1450 mHandler.postDelayed(mUpdateStateDescriptionRunnable, UPDATE_STATE_DESCRIPTION_DELAY_MS); 1451 } 1452 1453 /** 1454 * Moves the window magnifier with specified offset in pixels unit. 1455 * 1456 * @param offsetX the amount in pixels to offset the window magnifier in the X direction, in 1457 * current screen pixels. 1458 * @param offsetY the amount in pixels to offset the window magnifier in the Y direction, in 1459 * current screen pixels. 1460 */ moveWindowMagnifier(float offsetX, float offsetY)1461 void moveWindowMagnifier(float offsetX, float offsetY) { 1462 if (mAnimationController.isAnimating() || mMirrorSurfaceView == null) { 1463 return; 1464 } 1465 1466 if (!mAllowDiagonalScrolling) { 1467 int direction = selectDirectionForMove(abs(offsetX), abs(offsetY)); 1468 1469 if (direction == HORIZONTAL) { 1470 offsetY = 0; 1471 } else { 1472 offsetX = 0; 1473 } 1474 } 1475 1476 if (updateMagnificationFramePosition((int) offsetX, (int) offsetY)) { 1477 modifyWindowMagnification(false); 1478 } 1479 } 1480 moveWindowMagnifierToPosition(float positionX, float positionY, IRemoteMagnificationAnimationCallback callback)1481 void moveWindowMagnifierToPosition(float positionX, float positionY, 1482 IRemoteMagnificationAnimationCallback callback) { 1483 if (mMirrorSurfaceView == null) { 1484 return; 1485 } 1486 mAnimationController.moveWindowMagnifierToPosition(positionX, positionY, callback); 1487 } 1488 selectDirectionForMove(float diffX, float diffY)1489 private int selectDirectionForMove(float diffX, float diffY) { 1490 int direction = 0; 1491 float result = diffY / diffX; 1492 1493 if (result <= HORIZONTAL_LOCK_BASE) { 1494 direction = HORIZONTAL; // horizontal move 1495 } else { 1496 direction = VERTICAL; // vertical move 1497 } 1498 return direction; 1499 } 1500 1501 /** 1502 * Gets the scale. 1503 * 1504 * @return {@link Float#NaN} if the window is invisible. 1505 */ getScale()1506 float getScale() { 1507 return isActivated() ? mScale : Float.NaN; 1508 } 1509 1510 /** 1511 * Returns the screen-relative X coordinate of the center of the magnified bounds. 1512 * 1513 * @return the X coordinate. {@link Float#NaN} if the window is invisible. 1514 */ getCenterX()1515 float getCenterX() { 1516 return isActivated() ? mMagnificationFrame.exactCenterX() : Float.NaN; 1517 } 1518 1519 /** 1520 * Returns the screen-relative Y coordinate of the center of the magnified bounds. 1521 * 1522 * @return the Y coordinate. {@link Float#NaN} if the window is invisible. 1523 */ getCenterY()1524 float getCenterY() { 1525 return isActivated() ? mMagnificationFrame.exactCenterY() : Float.NaN; 1526 } 1527 1528 1529 @VisibleForTesting isDiagonalScrollingEnabled()1530 boolean isDiagonalScrollingEnabled() { 1531 return mAllowDiagonalScrolling; 1532 } 1533 formatStateDescription(float scale)1534 private CharSequence formatStateDescription(float scale) { 1535 // Cache the locale-appropriate NumberFormat. Configuration locale is guaranteed 1536 // non-null, so the first time this is called we will always get the appropriate 1537 // NumberFormat, then never regenerate it unless the locale changes on the fly. 1538 final Locale curLocale = mContext.getResources().getConfiguration().getLocales().get(0); 1539 if (!curLocale.equals(mLocale)) { 1540 mLocale = curLocale; 1541 mPercentFormat = NumberFormat.getPercentInstance(curLocale); 1542 } 1543 return mPercentFormat.format(scale); 1544 } 1545 1546 @Override onSingleTap(View view)1547 public boolean onSingleTap(View view) { 1548 handleSingleTap(view); 1549 return true; 1550 } 1551 1552 @Override onDrag(View view, float offsetX, float offsetY)1553 public boolean onDrag(View view, float offsetX, float offsetY) { 1554 if (mEditSizeEnable) { 1555 return changeWindowSize(view, offsetX, offsetY); 1556 } else { 1557 move((int) offsetX, (int) offsetY); 1558 } 1559 return true; 1560 } 1561 handleSingleTap(View view)1562 private void handleSingleTap(View view) { 1563 int id = view.getId(); 1564 if (id == R.id.drag_handle) { 1565 mWindowMagnifierCallback.onClickSettingsButton(mDisplayId); 1566 } else if (id == R.id.close_button) { 1567 setEditMagnifierSizeMode(false); 1568 } else { 1569 animateBounceEffectIfNeeded(); 1570 } 1571 } 1572 applyResourcesValues()1573 private void applyResourcesValues() { 1574 // Sets the border appearance for the magnifier window 1575 mMirrorBorderView.setBackground(mResources.getDrawable(mEditSizeEnable 1576 ? R.drawable.accessibility_window_magnification_background_change 1577 : R.drawable.accessibility_window_magnification_background)); 1578 1579 // Changes the corner radius of the mMirrorSurfaceView 1580 mMirrorSurfaceView.setCornerRadius( 1581 TypedValue.applyDimension( 1582 TypedValue.COMPLEX_UNIT_DIP, 1583 mEditSizeEnable ? 16f : 28f, 1584 mContext.getResources().getDisplayMetrics())); 1585 1586 // Sets visibility of components for the magnifier window 1587 if (mEditSizeEnable) { 1588 mDragView.setVisibility(View.GONE); 1589 mCloseView.setVisibility(View.VISIBLE); 1590 mTopRightCornerView.setVisibility(View.VISIBLE); 1591 mTopLeftCornerView.setVisibility(View.VISIBLE); 1592 mBottomRightCornerView.setVisibility(View.VISIBLE); 1593 mBottomLeftCornerView.setVisibility(View.VISIBLE); 1594 } else { 1595 mDragView.setVisibility(View.VISIBLE); 1596 mCloseView.setVisibility(View.GONE); 1597 mTopRightCornerView.setVisibility(View.GONE); 1598 mTopLeftCornerView.setVisibility(View.GONE); 1599 mBottomRightCornerView.setVisibility(View.GONE); 1600 mBottomLeftCornerView.setVisibility(View.GONE); 1601 } 1602 } 1603 changeWindowSize(View view, float offsetX, float offsetY)1604 private boolean changeWindowSize(View view, float offsetX, float offsetY) { 1605 if (view == mLeftDrag) { 1606 changeMagnificationFrameSize(offsetX, 0, 0, 0); 1607 } else if (view == mRightDrag) { 1608 changeMagnificationFrameSize(0, 0, offsetX, 0); 1609 } else if (view == mTopDrag) { 1610 changeMagnificationFrameSize(0, offsetY, 0, 0); 1611 } else if (view == mBottomDrag) { 1612 changeMagnificationFrameSize(0, 0, 0, offsetY); 1613 } else if (view == mTopLeftCornerView) { 1614 changeMagnificationFrameSize(offsetX, offsetY, 0, 0); 1615 } else if (view == mTopRightCornerView) { 1616 changeMagnificationFrameSize(0, offsetY, offsetX, 0); 1617 } else if (view == mBottomLeftCornerView) { 1618 changeMagnificationFrameSize(offsetX, 0, 0, offsetY); 1619 } else if (view == mBottomRightCornerView) { 1620 changeMagnificationFrameSize(0, 0, offsetX, offsetY); 1621 } else { 1622 return false; 1623 } 1624 1625 return true; 1626 } 1627 changeMagnificationFrameSize( float leftOffset, float topOffset, float rightOffset, float bottomOffset)1628 private void changeMagnificationFrameSize( 1629 float leftOffset, float topOffset, float rightOffset, 1630 float bottomOffset) { 1631 boolean bRTL = isRTL(mContext); 1632 final int initSize = Math.min(mWindowBounds.width(), mWindowBounds.height()) / 3; 1633 1634 final int maxHeightSize = mWindowBounds.height() - 2 * mMirrorSurfaceMargin; 1635 final int maxWidthSize = mWindowBounds.width() - 2 * mMirrorSurfaceMargin; 1636 1637 Rect tempRect = new Rect(); 1638 tempRect.set(mMagnificationFrame); 1639 1640 if (bRTL) { 1641 tempRect.left += (int) (rightOffset); 1642 tempRect.right += (int) (leftOffset); 1643 } else { 1644 tempRect.right += (int) (rightOffset); 1645 tempRect.left += (int) (leftOffset); 1646 } 1647 tempRect.top += (int) (topOffset); 1648 tempRect.bottom += (int) (bottomOffset); 1649 1650 if (tempRect.width() < initSize || tempRect.height() < initSize 1651 || tempRect.width() > maxWidthSize || tempRect.height() > maxHeightSize) { 1652 return; 1653 } 1654 mMagnificationFrame.set(tempRect); 1655 1656 computeBounceAnimationScale(); 1657 calculateMagnificationFrameBoundary(); 1658 1659 modifyWindowMagnification(true); 1660 } 1661 isRTL(Context context)1662 private static boolean isRTL(Context context) { 1663 Configuration config = context.getResources().getConfiguration(); 1664 if (config == null) { 1665 return false; 1666 } 1667 return (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) 1668 == Configuration.SCREENLAYOUT_LAYOUTDIR_RTL; 1669 } 1670 1671 @Override onStart(float x, float y)1672 public boolean onStart(float x, float y) { 1673 mIsDragging = true; 1674 return true; 1675 } 1676 1677 @Override onFinish(float x, float y)1678 public boolean onFinish(float x, float y) { 1679 maybeRepositionButton(); 1680 mIsDragging = false; 1681 return false; 1682 } 1683 1684 /** Moves the button to the opposite edge if the frame is against the edge of the screen. */ maybeRepositionButton()1685 private void maybeRepositionButton() { 1686 if (mMirrorView == null) return; 1687 1688 final float screenEdgeX = mWindowBounds.right - mButtonRepositionThresholdFromEdge; 1689 final FrameLayout.LayoutParams layoutParams = 1690 (FrameLayout.LayoutParams) mDragView.getLayoutParams(); 1691 1692 final int newGravity; 1693 if (mMirrorViewBounds.right >= screenEdgeX) { 1694 newGravity = Gravity.BOTTOM | Gravity.LEFT; 1695 } else { 1696 newGravity = Gravity.BOTTOM | Gravity.RIGHT; 1697 } 1698 if (newGravity != layoutParams.gravity) { 1699 layoutParams.gravity = newGravity; 1700 mDragView.setLayoutParams(layoutParams); 1701 mDragView.post(this::applyTapExcludeRegion); 1702 } 1703 } 1704 updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown)1705 void updateDragHandleResourcesIfNeeded(boolean settingsPanelIsShown) { 1706 if (!isActivated()) { 1707 return; 1708 } 1709 1710 mSettingsPanelVisibility = settingsPanelIsShown; 1711 1712 mDragView.setBackground(mContext.getResources().getDrawable(settingsPanelIsShown 1713 ? R.drawable.accessibility_window_magnification_drag_handle_background_change_inset 1714 : R.drawable.accessibility_window_magnification_drag_handle_background_inset)); 1715 1716 PorterDuffColorFilter filter = new PorterDuffColorFilter( 1717 mContext.getColor(settingsPanelIsShown 1718 ? R.color.magnification_border_color 1719 : R.color.magnification_drag_handle_stroke), 1720 PorterDuff.Mode.SRC_ATOP); 1721 1722 mDragView.setColorFilter(filter); 1723 } 1724 setBounceEffectDuration(int duration)1725 private void setBounceEffectDuration(int duration) { 1726 mBounceEffectDuration = duration; 1727 } 1728 animateBounceEffectIfNeeded()1729 private void animateBounceEffectIfNeeded() { 1730 if (mMirrorView == null) { 1731 // run the animation only if the mirror view is not null 1732 return; 1733 } 1734 1735 final ObjectAnimator scaleAnimator = ObjectAnimator.ofPropertyValuesHolder(mMirrorView, 1736 PropertyValuesHolder.ofFloat(View.SCALE_X, 1, mBounceEffectAnimationScale, 1), 1737 PropertyValuesHolder.ofFloat(View.SCALE_Y, 1, mBounceEffectAnimationScale, 1)); 1738 scaleAnimator.setDuration(mBounceEffectDuration); 1739 scaleAnimator.start(); 1740 } 1741 dump(PrintWriter pw)1742 public void dump(PrintWriter pw) { 1743 pw.println("WindowMagnificationController (displayId=" + mDisplayId + "):"); 1744 pw.println(" mOverlapWithGestureInsets:" + mOverlapWithGestureInsets); 1745 pw.println(" mScale:" + mScale); 1746 pw.println(" mWindowBounds:" + mWindowBounds); 1747 pw.println(" mMirrorViewBounds:" + (isActivated() ? mMirrorViewBounds : "empty")); 1748 pw.println(" mMagnificationFrameBoundary:" 1749 + (isActivated() ? mMagnificationFrameBoundary : "empty")); 1750 pw.println(" mMagnificationFrame:" 1751 + (isActivated() ? mMagnificationFrame : "empty")); 1752 pw.println(" mSourceBounds:" 1753 + (mSourceBounds.isEmpty() ? "empty" : mSourceBounds)); 1754 pw.println(" mSystemGestureTop:" + mSystemGestureTop); 1755 pw.println(" mMagnificationFrameOffsetX:" + mMagnificationFrameOffsetX); 1756 pw.println(" mMagnificationFrameOffsetY:" + mMagnificationFrameOffsetY); 1757 } 1758 1759 private class MirrorWindowA11yDelegate extends View.AccessibilityDelegate { 1760 getClickAccessibilityActionLabel()1761 private CharSequence getClickAccessibilityActionLabel() { 1762 if (mEditSizeEnable) { 1763 // Perform click action to exit edit mode 1764 return mContext.getResources().getString( 1765 R.string.magnification_exit_edit_mode_click_label); 1766 } 1767 1768 return mSettingsPanelVisibility 1769 ? mContext.getResources().getString( 1770 R.string.magnification_close_settings_click_label) 1771 : mContext.getResources().getString( 1772 R.string.magnification_open_settings_click_label); 1773 } 1774 1775 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)1776 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { 1777 super.onInitializeAccessibilityNodeInfo(host, info); 1778 final AccessibilityAction clickAction = new AccessibilityAction( 1779 AccessibilityAction.ACTION_CLICK.getId(), getClickAccessibilityActionLabel()); 1780 info.addAction(clickAction); 1781 info.setClickable(true); 1782 1783 info.addAction( 1784 new AccessibilityAction(R.id.accessibility_action_zoom_in, 1785 mContext.getString(R.string.accessibility_control_zoom_in))); 1786 info.addAction(new AccessibilityAction(R.id.accessibility_action_zoom_out, 1787 mContext.getString(R.string.accessibility_control_zoom_out))); 1788 1789 if (!mEditSizeEnable) { 1790 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up, 1791 mContext.getString(R.string.accessibility_control_move_up))); 1792 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down, 1793 mContext.getString(R.string.accessibility_control_move_down))); 1794 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left, 1795 mContext.getString(R.string.accessibility_control_move_left))); 1796 info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right, 1797 mContext.getString(R.string.accessibility_control_move_right))); 1798 } else { 1799 if ((mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin) 1800 < mWindowBounds.width()) { 1801 info.addAction(new AccessibilityAction( 1802 R.id.accessibility_action_increase_window_width, 1803 mContext.getString( 1804 R.string.accessibility_control_increase_window_width))); 1805 } 1806 if ((mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin) 1807 < mWindowBounds.height()) { 1808 info.addAction(new AccessibilityAction( 1809 R.id.accessibility_action_increase_window_height, 1810 mContext.getString( 1811 R.string.accessibility_control_increase_window_height))); 1812 } 1813 if ((mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin) > mMinWindowSize) { 1814 info.addAction(new AccessibilityAction( 1815 R.id.accessibility_action_decrease_window_width, 1816 mContext.getString( 1817 R.string.accessibility_control_decrease_window_width))); 1818 } 1819 if ((mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin) > mMinWindowSize) { 1820 info.addAction(new AccessibilityAction( 1821 R.id.accessibility_action_decrease_window_height, 1822 mContext.getString( 1823 R.string.accessibility_control_decrease_window_height))); 1824 } 1825 } 1826 1827 info.setContentDescription(mContext.getString(R.string.magnification_window_title)); 1828 info.setStateDescription(formatStateDescription(getScale())); 1829 } 1830 1831 @Override performAccessibilityAction(View host, int action, Bundle args)1832 public boolean performAccessibilityAction(View host, int action, Bundle args) { 1833 if (performA11yAction(action)) { 1834 return true; 1835 } 1836 return super.performAccessibilityAction(host, action, args); 1837 } 1838 performA11yAction(int action)1839 private boolean performA11yAction(int action) { 1840 final float changeWindowSizeAmount = mContext.getResources().getFraction( 1841 R.fraction.magnification_resize_window_size_amount, 1842 /* base= */ 1, 1843 /* pbase= */ 1); 1844 1845 if (action == AccessibilityAction.ACTION_CLICK.getId()) { 1846 if (mEditSizeEnable) { 1847 // When edit mode is enabled, click the magnifier to exit edit mode. 1848 setEditMagnifierSizeMode(false); 1849 } else { 1850 // Simulate tapping the drag view so it opens the Settings. 1851 handleSingleTap(mDragView); 1852 } 1853 1854 } else if (action == R.id.accessibility_action_zoom_in) { 1855 performScale(mScale + A11Y_CHANGE_SCALE_DIFFERENCE); 1856 } else if (action == R.id.accessibility_action_zoom_out) { 1857 performScale(mScale - A11Y_CHANGE_SCALE_DIFFERENCE); 1858 } else if (action == R.id.accessibility_action_move_up) { 1859 move(0, -mSourceBounds.height()); 1860 } else if (action == R.id.accessibility_action_move_down) { 1861 move(0, mSourceBounds.height()); 1862 } else if (action == R.id.accessibility_action_move_left) { 1863 move(-mSourceBounds.width(), 0); 1864 } else if (action == R.id.accessibility_action_move_right) { 1865 move(mSourceBounds.width(), 0); 1866 } else if (action == R.id.accessibility_action_increase_window_width) { 1867 int newFrameWidth = 1868 (int) (mMagnificationFrame.width() * (1 + changeWindowSizeAmount)); 1869 setMagnificationFrameSize(newFrameWidth, mMagnificationFrame.height()); 1870 } else if (action == R.id.accessibility_action_increase_window_height) { 1871 int newFrameHeight = 1872 (int) (mMagnificationFrame.height() * (1 + changeWindowSizeAmount)); 1873 setMagnificationFrameSize(mMagnificationFrame.width(), newFrameHeight); 1874 } else if (action == R.id.accessibility_action_decrease_window_width) { 1875 int newFrameWidth = 1876 (int) (mMagnificationFrame.width() * (1 - changeWindowSizeAmount)); 1877 setMagnificationFrameSize(newFrameWidth, mMagnificationFrame.height()); 1878 } else if (action == R.id.accessibility_action_decrease_window_height) { 1879 int newFrameHeight = 1880 (int) (mMagnificationFrame.height() * (1 - changeWindowSizeAmount)); 1881 setMagnificationFrameSize(mMagnificationFrame.width(), newFrameHeight); 1882 } else { 1883 return false; 1884 } 1885 1886 mWindowMagnifierCallback.onAccessibilityActionPerformed(mDisplayId); 1887 return true; 1888 } 1889 performScale(float scale)1890 private void performScale(float scale) { 1891 scale = A11Y_ACTION_SCALE_RANGE.clamp(scale); 1892 mWindowMagnifierCallback.onPerformScaleAction( 1893 mDisplayId, scale, /* updatePersistence= */ true); 1894 } 1895 } 1896 1897 }