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