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 }