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.server.accessibility.magnification;
18 
19 import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION;
20 import static android.accessibilityservice.AccessibilityTrace.FLAGS_MAGNIFICATION_CONNECTION_CALLBACK;
21 import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
22 import static android.view.accessibility.MagnificationAnimationCallback.STUB_ANIMATION_CALLBACK;
23 
24 import static com.android.server.accessibility.AccessibilityManagerService.INVALID_SERVICE_ID;
25 import static com.android.server.accessibility.AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID;
26 
27 import android.annotation.IntDef;
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.RequiresNoPermission;
31 import android.content.BroadcastReceiver;
32 import android.content.Context;
33 import android.content.Intent;
34 import android.content.IntentFilter;
35 import android.graphics.PointF;
36 import android.graphics.Rect;
37 import android.graphics.Region;
38 import android.os.Binder;
39 import android.os.IBinder;
40 import android.os.RemoteException;
41 import android.os.SystemClock;
42 import android.util.MathUtils;
43 import android.util.Slog;
44 import android.util.SparseArray;
45 import android.util.SparseBooleanArray;
46 import android.view.MotionEvent;
47 import android.view.accessibility.IMagnificationConnection;
48 import android.view.accessibility.IMagnificationConnectionCallback;
49 import android.view.accessibility.MagnificationAnimationCallback;
50 
51 import com.android.internal.accessibility.common.MagnificationConstants;
52 import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
53 import com.android.internal.annotations.GuardedBy;
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.server.LocalServices;
56 import com.android.server.accessibility.AccessibilityTraceManager;
57 import com.android.server.statusbar.StatusBarManagerInternal;
58 import com.android.server.wm.WindowManagerInternal;
59 
60 import java.lang.annotation.Retention;
61 import java.lang.annotation.RetentionPolicy;
62 import java.util.concurrent.atomic.AtomicLongFieldUpdater;
63 
64 /**
65  * A class to manipulate magnification through {@link MagnificationConnectionWrapper}
66  * create by {@link #setConnection(IMagnificationConnection)}. To set the connection with
67  * SysUI, call {@code StatusBarManagerInternal#requestMagnificationConnection(boolean)}.
68  * The applied magnification scale is constrained by
69  * {@link MagnificationScaleProvider#constrainScale(float)}
70  */
71 public class MagnificationConnectionManager implements
72         PanningScalingHandler.MagnificationDelegate,
73         WindowManagerInternal.AccessibilityControllerInternal.UiChangesForAccessibilityCallbacks {
74 
75     private static final boolean DBG = false;
76 
77     private static final String TAG = "MagnificationConnectionManager";
78 
79     /**
80      * Indicate that the magnification window is at the magnification center.
81      */
82     public static final int WINDOW_POSITION_AT_CENTER = 0;
83 
84     /**
85      * Indicate that the magnification window is at the top-left side of the magnification
86      * center. The offset is equal to a half of MirrorSurfaceView. So, the bottom-right corner
87      * of the window is at the magnification center.
88      */
89     public static final int WINDOW_POSITION_AT_TOP_LEFT = 1;
90 
91     @Retention(RetentionPolicy.SOURCE)
92     @IntDef(prefix = { "WINDOW_POSITION_AT_" }, value = {
93             WINDOW_POSITION_AT_CENTER,
94             WINDOW_POSITION_AT_TOP_LEFT
95     })
96     public @interface WindowPosition {}
97 
98     /** Magnification connection is connecting. */
99     private static final int CONNECTING = 0;
100     /** Magnification connection is connected. */
101     private static final int CONNECTED = 1;
102     /** Magnification connection is disconnecting. */
103     private static final int DISCONNECTING = 2;
104     /** Magnification connection is disconnected. */
105     private static final int DISCONNECTED = 3;
106 
107     @Retention(RetentionPolicy.SOURCE)
108     @IntDef(prefix = {"CONNECTION_STATE"}, value = {
109             CONNECTING,
110             CONNECTED,
111             DISCONNECTING,
112             DISCONNECTED
113     })
114     private @interface ConnectionState {
115     }
116 
connectionStateToString(@onnectionState int state)117     private static String connectionStateToString(@ConnectionState int state) {
118         switch (state) {
119             case CONNECTING: return "CONNECTING";
120             case CONNECTED: return "CONNECTED";
121             case DISCONNECTING: return "DISCONNECTING";
122             case DISCONNECTED: return "DISCONNECTED";
123             default:
124                 return "UNKNOWN:" + state;
125         }
126     }
127 
128     @ConnectionState
129     private int mConnectionState = DISCONNECTED;
130 
131     private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 200 * HW_TIMEOUT_MULTIPLIER;
132 
133     private final Object mLock;
134     private final Context mContext;
135     @VisibleForTesting
136     @GuardedBy("mLock")
137     @Nullable
138     MagnificationConnectionWrapper mConnectionWrapper;
139     @GuardedBy("mLock")
140     private ConnectionCallback mConnectionCallback;
141     @GuardedBy("mLock")
142     private SparseArray<WindowMagnifier> mWindowMagnifiers = new SparseArray<>();
143     // Whether the following typing focus feature for magnification is enabled.
144     private boolean mMagnificationFollowTypingEnabled = true;
145     @GuardedBy("mLock")
146     private final SparseBooleanArray mIsImeVisibleArray = new SparseBooleanArray();
147     @GuardedBy("mLock")
148     private final SparseArray<Float> mLastActivatedScale = new SparseArray<>();
149 
150     private boolean mReceiverRegistered = false;
151     @VisibleForTesting
152     protected final BroadcastReceiver mScreenStateReceiver = new BroadcastReceiver() {
153         @Override
154         public void onReceive(Context context, Intent intent) {
155             final int displayId = context.getDisplayId();
156             removeMagnificationButton(displayId);
157             disableWindowMagnification(displayId, false, null);
158         }
159     };
160 
161     /**
162      * Callback to handle magnification actions from system UI.
163      */
164     public interface Callback {
165 
166         /**
167          * Called when the accessibility action of scale requests to be performed.
168          * It is invoked from System UI. And the action is provided by the mirror window.
169          *
170          * @param displayId The logical display id.
171          * @param scale the target scale, or {@link Float#NaN} to leave unchanged
172          * @param updatePersistence whether the scale should be persisted
173          */
onPerformScaleAction(int displayId, float scale, boolean updatePersistence)174         void onPerformScaleAction(int displayId, float scale, boolean updatePersistence);
175 
176         /**
177          * Called when the accessibility action is performed.
178          *
179          * @param displayId The logical display id.
180          */
onAccessibilityActionPerformed(int displayId)181         void onAccessibilityActionPerformed(int displayId);
182 
183         /**
184          * Called when the state of the magnification activation is changed.
185          *
186          * @param displayId The logical display id.
187          * @param activated {@code true} if the magnification is activated, otherwise {@code false}.
188          */
onWindowMagnificationActivationState(int displayId, boolean activated)189         void onWindowMagnificationActivationState(int displayId, boolean activated);
190 
191         /**
192          * Called when the magnification source bounds are changed.
193          *
194          * @param displayId The logical display id.
195          * @param bounds    The magnified source bounds on the display.
196          */
onSourceBoundsChanged(int displayId, Rect bounds)197         void onSourceBoundsChanged(int displayId, Rect bounds);
198 
199         /**
200          * Called from {@link IMagnificationConnection} to request changing the magnification
201          * mode on the given display.
202          *
203          * @param displayId the logical display id
204          * @param magnificationMode the target magnification mode
205          */
onChangeMagnificationMode(int displayId, int magnificationMode)206         void onChangeMagnificationMode(int displayId, int magnificationMode);
207     }
208 
209     private final Callback mCallback;
210     private final AccessibilityTraceManager mTrace;
211     private final MagnificationScaleProvider mScaleProvider;
212 
MagnificationConnectionManager(Context context, Object lock, @NonNull Callback callback, AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider)213     public MagnificationConnectionManager(Context context, Object lock, @NonNull Callback callback,
214             AccessibilityTraceManager trace, MagnificationScaleProvider scaleProvider) {
215         mContext = context;
216         mLock = lock;
217         mCallback = callback;
218         mTrace = trace;
219         mScaleProvider = scaleProvider;
220     }
221 
222     /**
223      * Sets {@link IMagnificationConnection}.
224      *
225      * @param connection {@link IMagnificationConnection}
226      */
setConnection(@ullable IMagnificationConnection connection)227     public void setConnection(@Nullable IMagnificationConnection connection) {
228         if (DBG) {
229             Slog.d(TAG, "setConnection :" + connection + ", mConnectionState="
230                     + connectionStateToString(mConnectionState));
231         }
232         synchronized (mLock) {
233             // Reset connectionWrapper.
234             if (mConnectionWrapper != null) {
235                 mConnectionWrapper.setConnectionCallback(null);
236                 if (mConnectionCallback != null) {
237                     mConnectionCallback.mExpiredDeathRecipient = true;
238                 }
239                 mConnectionWrapper.unlinkToDeath(mConnectionCallback);
240                 mConnectionWrapper = null;
241                 // The connection is still connecting so it is no need to reset the
242                 // connection state to disconnected.
243                 // TODO b/220086369 will reset the connection immediately when requestConnection
244                 //  is called
245                 if (mConnectionState != CONNECTING) {
246                     setConnectionState(DISCONNECTED);
247                 }
248             }
249             if (connection != null) {
250                 mConnectionWrapper = new MagnificationConnectionWrapper(connection, mTrace);
251             }
252 
253             if (mConnectionWrapper != null) {
254                 try {
255                     mConnectionCallback = new ConnectionCallback();
256                     mConnectionWrapper.linkToDeath(mConnectionCallback);
257                     mConnectionWrapper.setConnectionCallback(mConnectionCallback);
258                     setConnectionState(CONNECTED);
259                 } catch (RemoteException e) {
260                     Slog.e(TAG, "setConnection failed", e);
261                     mConnectionWrapper = null;
262                     setConnectionState(DISCONNECTED);
263                 } finally {
264                     mLock.notify();
265                 }
266             }
267         }
268     }
269 
270     /**
271      * @return {@code true} if {@link IMagnificationConnection} is available
272      */
isConnected()273     public boolean isConnected() {
274         synchronized (mLock) {
275             return mConnectionWrapper != null;
276         }
277     }
278 
279     /**
280      * Requests {@link IMagnificationConnection} through
281      * {@link StatusBarManagerInternal#requestMagnificationConnection(boolean)} and
282      * destroys all window magnifications if necessary.
283      *
284      * @param connect {@code true} if needs connection, otherwise set the connection to null and
285      *                destroy all window magnifications.
286      * @return {@code true} if {@link IMagnificationConnection} state is going to change.
287      */
requestConnection(boolean connect)288     public boolean requestConnection(boolean connect) {
289         if (DBG) {
290             Slog.d(TAG, "requestConnection :" + connect);
291         }
292         if (mTrace.isA11yTracingEnabledForTypes(FLAGS_MAGNIFICATION_CONNECTION)) {
293             mTrace.logTrace(TAG + ".requestMagnificationConnection",
294                     FLAGS_MAGNIFICATION_CONNECTION, "connect=" + connect);
295         }
296         synchronized (mLock) {
297             if ((connect && (mConnectionState == CONNECTED || mConnectionState == CONNECTING))
298                     || (!connect && (mConnectionState == DISCONNECTED
299                     || mConnectionState == DISCONNECTING))) {
300                 Slog.w(TAG, "requestConnection duplicated request: connect=" + connect
301                         + ", mConnectionState=" + connectionStateToString(mConnectionState));
302                 return false;
303             }
304 
305             if (connect) {
306                 final IntentFilter intentFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
307                 if (!mReceiverRegistered) {
308                     mContext.registerReceiver(mScreenStateReceiver, intentFilter);
309                     mReceiverRegistered = true;
310                 }
311             } else {
312                 disableAllWindowMagnifiers();
313                 if (mReceiverRegistered) {
314                     mContext.unregisterReceiver(mScreenStateReceiver);
315                     mReceiverRegistered = false;
316                 }
317             }
318         }
319         if (requestConnectionInternal(connect)) {
320             setConnectionState(connect ? CONNECTING : DISCONNECTING);
321             return true;
322         } else {
323             setConnectionState(DISCONNECTED);
324             return false;
325         }
326     }
327 
requestConnectionInternal(boolean connect)328     private boolean requestConnectionInternal(boolean connect) {
329         final long identity = Binder.clearCallingIdentity();
330         try {
331             final StatusBarManagerInternal service = LocalServices.getService(
332                     StatusBarManagerInternal.class);
333             if (service != null) {
334                 return service.requestMagnificationConnection(connect);
335             }
336         } finally {
337             Binder.restoreCallingIdentity(identity);
338         }
339         return false;
340     }
341 
342     /**
343      * Returns window magnification connection state.
344      */
getConnectionState()345     public String getConnectionState() {
346         return connectionStateToString(mConnectionState);
347     }
348 
setConnectionState(@onnectionState int state)349     private void setConnectionState(@ConnectionState int state) {
350         if (DBG) {
351             Slog.d(TAG, "setConnectionState : state=" + state + ", mConnectionState="
352                     + connectionStateToString(mConnectionState));
353         }
354         mConnectionState = state;
355     }
356 
357     /**
358      * Disables window magnifier on all displays without animation.
359      */
disableAllWindowMagnifiers()360     void disableAllWindowMagnifiers() {
361         synchronized (mLock) {
362             for (int i = 0; i < mWindowMagnifiers.size(); i++) {
363                 final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
364                 magnifier.disableWindowMagnificationInternal(null);
365             }
366             mWindowMagnifiers.clear();
367         }
368     }
369 
370     /**
371      * Resets the window magnifier on all displays that had been controlled by the
372      * specified service connection. Called when the service connection is unbound
373      * or binder died.
374      *
375      * @param connectionId The connection id
376      */
resetAllIfNeeded(int connectionId)377     public void resetAllIfNeeded(int connectionId) {
378         synchronized (mLock) {
379             for (int i = 0; i < mWindowMagnifiers.size(); i++) {
380                 final WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
381                 if (magnifier != null
382                         && magnifier.mEnabled
383                         && connectionId == magnifier.getIdOfLastServiceToControl()) {
384                     magnifier.disableWindowMagnificationInternal(null);
385                 }
386             }
387         }
388     }
389 
resetWindowMagnifiers()390     private void resetWindowMagnifiers() {
391         synchronized (mLock) {
392             for (int i = 0; i < mWindowMagnifiers.size(); i++) {
393                 WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
394                 magnifier.reset();
395             }
396         }
397     }
398 
399     @Override
onRectangleOnScreenRequested(int displayId, int left, int top, int right, int bottom)400     public void onRectangleOnScreenRequested(int displayId, int left, int top, int right,
401             int bottom) {
402         if (!mMagnificationFollowTypingEnabled) {
403             return;
404         }
405 
406         float toCenterX = (float) (left + right) / 2;
407         float toCenterY = (float) (top + bottom) / 2;
408 
409         synchronized (mLock) {
410             if (mIsImeVisibleArray.get(displayId, false)
411                     && !isPositionInSourceBounds(displayId, toCenterX, toCenterY)
412                     && isTrackingTypingFocusEnabled(displayId)) {
413                 moveWindowMagnifierToPositionInternal(displayId, toCenterX, toCenterY,
414                         STUB_ANIMATION_CALLBACK);
415             }
416         }
417     }
418 
setMagnificationFollowTypingEnabled(boolean enabled)419     void setMagnificationFollowTypingEnabled(boolean enabled) {
420         mMagnificationFollowTypingEnabled = enabled;
421     }
422 
isMagnificationFollowTypingEnabled()423     boolean isMagnificationFollowTypingEnabled() {
424         return mMagnificationFollowTypingEnabled;
425     }
426 
427     /**
428      * Get the ID of the last service that changed the magnification config.
429      *
430      * @param displayId The logical display id.
431      * @return The id
432      */
getIdOfLastServiceToMagnify(int displayId)433     public int getIdOfLastServiceToMagnify(int displayId) {
434         synchronized (mLock) {
435             final WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
436             if (magnifier != null) {
437                 return magnifier.mIdOfLastServiceToControl;
438             }
439         }
440         return INVALID_SERVICE_ID;
441     }
442 
443     /**
444      * Enable or disable tracking typing focus for the specific magnification window.
445      *
446      * The tracking typing focus should be set to enabled with the following conditions:
447      * 1. IME is shown.
448      *
449      * The tracking typing focus should be set to disabled with the following conditions:
450      * 1. A user drags the magnification window by 1 finger.
451      * 2. A user scroll the magnification window by 2 fingers.
452      *
453      * @param displayId The logical display id.
454      * @param trackingTypingFocusEnabled Enabled or disable the function of tracking typing focus.
455      */
setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled)456     void setTrackingTypingFocusEnabled(int displayId, boolean trackingTypingFocusEnabled) {
457         synchronized (mLock) {
458             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
459             if (magnifier == null) {
460                 return;
461             }
462             magnifier.setTrackingTypingFocusEnabled(trackingTypingFocusEnabled);
463         }
464     }
465 
466     /**
467      * Enable tracking typing focus function for all magnifications.
468      */
enableAllTrackingTypingFocus()469     private void enableAllTrackingTypingFocus() {
470         synchronized (mLock) {
471             for (int i = 0; i < mWindowMagnifiers.size(); i++) {
472                 WindowMagnifier magnifier = mWindowMagnifiers.valueAt(i);
473                 magnifier.setTrackingTypingFocusEnabled(true);
474             }
475         }
476     }
477 
pauseTrackingTypingFocusRecord(int displayId)478     private void pauseTrackingTypingFocusRecord(int displayId) {
479         WindowMagnifier magnifier;
480         synchronized (mLock) {
481             magnifier = mWindowMagnifiers.get(displayId);
482             if (magnifier == null) {
483                 return;
484             }
485         }
486         magnifier.pauseTrackingTypingFocusRecord();
487     }
488 
489     /**
490      * Called when the IME window visibility changed.
491      *
492      * @param shown {@code true} means the IME window shows on the screen. Otherwise, it's hidden.
493      */
onImeWindowVisibilityChanged(int displayId, boolean shown)494     void onImeWindowVisibilityChanged(int displayId, boolean shown) {
495         synchronized (mLock) {
496             mIsImeVisibleArray.put(displayId, shown);
497         }
498         if (shown) {
499             enableAllTrackingTypingFocus();
500         } else {
501             pauseTrackingTypingFocusRecord(displayId);
502         }
503     }
504 
isImeVisible(int displayId)505     boolean isImeVisible(int displayId) {
506         synchronized (mLock) {
507             return mIsImeVisibleArray.get(displayId);
508         }
509     }
510 
logTrackingTypingFocus(long duration)511     void logTrackingTypingFocus(long duration) {
512         AccessibilityStatsLogUtils.logMagnificationFollowTypingFocusSession(duration);
513     }
514 
515     @Override
processScroll(int displayId, float distanceX, float distanceY)516     public boolean processScroll(int displayId, float distanceX, float distanceY) {
517         moveWindowMagnification(displayId, -distanceX, -distanceY);
518         setTrackingTypingFocusEnabled(displayId, false);
519         return /* event consumed: */ true;
520     }
521 
522     /**
523      * Scales the magnified region on the specified display if window magnification is initiated.
524      *
525      * @param displayId The logical display id.
526      * @param scale The target scale, must be >= 1
527      */
528     @Override
setScale(int displayId, float scale)529     public void setScale(int displayId, float scale) {
530         synchronized (mLock) {
531             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
532             if (magnifier == null) {
533                 return;
534             }
535             magnifier.setScale(scale);
536             mLastActivatedScale.put(displayId, scale);
537         }
538     }
539 
540     /**
541      * Enables window magnification with specified center and scale on the given display and
542      * animating the transition.
543      *
544      * @param displayId The logical display id.
545      * @param scale The target scale, must be >= 1.
546      * @param centerX The screen-relative X coordinate around which to center,
547      *                or {@link Float#NaN} to leave unchanged.
548      * @param centerY The screen-relative Y coordinate around which to center,
549      *                or {@link Float#NaN} to leave unchanged.
550      * @return {@code true} if the magnification is enabled successfully.
551      */
enableWindowMagnification(int displayId, float scale, float centerX, float centerY)552     public boolean enableWindowMagnification(int displayId, float scale, float centerX,
553             float centerY) {
554         return enableWindowMagnification(displayId, scale, centerX, centerY,
555                 STUB_ANIMATION_CALLBACK, MAGNIFICATION_GESTURE_HANDLER_ID);
556     }
557 
558     /**
559      * Enables window magnification with specified center and scale on the given display and
560      * animating the transition.
561      *
562      * @param displayId The logical display id.
563      * @param scale The target scale, must be >= 1.
564      * @param centerX The screen-relative X coordinate around which to center for magnification,
565      *                or {@link Float#NaN} to leave unchanged.
566      * @param centerY The screen-relative Y coordinate around which to center for magnification,
567      *                or {@link Float#NaN} to leave unchanged.
568      * @param animationCallback Called when the animation result is valid.
569      * @param id The connection ID
570      * @return {@code true} if the magnification is enabled successfully.
571      */
enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, int id)572     public boolean enableWindowMagnification(int displayId, float scale, float centerX,
573             float centerY, @Nullable MagnificationAnimationCallback animationCallback, int id) {
574         return enableWindowMagnification(displayId, scale, centerX, centerY, animationCallback,
575                 WINDOW_POSITION_AT_CENTER, id);
576     }
577 
578     /**
579      * Enables window magnification with specified center and scale on the given display and
580      * animating the transition.
581      *
582      * @param displayId The logical display id.
583      * @param scale The target scale, must be >= 1.
584      * @param centerX The screen-relative X coordinate around which to center for magnification,
585      *                or {@link Float#NaN} to leave unchanged.
586      * @param centerY The screen-relative Y coordinate around which to center for magnification,
587      *                or {@link Float#NaN} to leave unchanged.
588      * @param windowPosition Indicate the offset between window position and (centerX, centerY).
589      * @return {@code true} if the magnification is enabled successfully.
590      */
enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @WindowPosition int windowPosition)591     public boolean enableWindowMagnification(int displayId, float scale, float centerX,
592             float centerY, @WindowPosition int windowPosition) {
593         return enableWindowMagnification(displayId, scale, centerX, centerY,
594                 STUB_ANIMATION_CALLBACK, windowPosition, MAGNIFICATION_GESTURE_HANDLER_ID);
595     }
596 
597     /**
598      * Enables window magnification with specified center and scale on the given display and
599      * animating the transition.
600      *
601      * @param displayId         The logical display id.
602      * @param scale             The target scale, must be >= 1.
603      * @param centerX           The screen-relative X coordinate around which to center for
604      *                          magnification, or {@link Float#NaN} to leave unchanged.
605      * @param centerY           The screen-relative Y coordinate around which to center for
606      *                          magnification, or {@link Float#NaN} to leave unchanged.
607      * @param animationCallback Called when the animation result is valid.
608      * @param windowPosition    Indicate the offset between window position and (centerX, centerY).
609      * @return {@code true} if the magnification is enabled successfully.
610      */
enableWindowMagnification(int displayId, float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, @WindowPosition int windowPosition, int id)611     public boolean enableWindowMagnification(int displayId, float scale, float centerX,
612             float centerY, @Nullable MagnificationAnimationCallback animationCallback,
613             @WindowPosition int windowPosition, int id) {
614         final boolean enabled;
615         boolean previousEnabled;
616         synchronized (mLock) {
617             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
618             if (magnifier == null) {
619                 magnifier = createWindowMagnifier(displayId);
620             }
621             previousEnabled = magnifier.mEnabled;
622             enabled = magnifier.enableWindowMagnificationInternal(scale, centerX, centerY,
623                     animationCallback, windowPosition, id);
624             if (enabled) {
625                 mLastActivatedScale.put(displayId, getScale(displayId));
626             }
627         }
628 
629         if (enabled) {
630             setTrackingTypingFocusEnabled(displayId, true);
631             if (!previousEnabled) {
632                 mCallback.onWindowMagnificationActivationState(displayId, true);
633             }
634         }
635         return enabled;
636     }
637 
638     /**
639      * Disables window magnification on the given display.
640      *
641      * @param displayId The logical display id.
642      * @param clear {@true} Clears the state of window magnification.
643      * @return {@code true} if the magnification is turned to be disabled successfully
644      */
disableWindowMagnification(int displayId, boolean clear)645     public boolean disableWindowMagnification(int displayId, boolean clear) {
646         return disableWindowMagnification(displayId, clear, STUB_ANIMATION_CALLBACK);
647     }
648 
649     /**
650      * Disables window magnification on the specified display and animating the transition.
651      *
652      * @param displayId The logical display id.
653      * @param clear {@true} Clears the state of window magnification.
654      * @param animationCallback Called when the animation result is valid.
655      * @return {@code true} if the magnification is turned to be disabled successfully
656      */
disableWindowMagnification(int displayId, boolean clear, MagnificationAnimationCallback animationCallback)657     public boolean disableWindowMagnification(int displayId, boolean clear,
658             MagnificationAnimationCallback animationCallback) {
659         final boolean disabled;
660         synchronized (mLock) {
661             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
662             if (magnifier == null) {
663                 return false;
664             }
665 
666             disabled = magnifier.disableWindowMagnificationInternal(animationCallback);
667             if (clear) {
668                 mWindowMagnifiers.delete(displayId);
669             }
670         }
671 
672         if (disabled) {
673             mCallback.onWindowMagnificationActivationState(displayId, false);
674         }
675         return disabled;
676     }
677 
678     /**
679      * Notify Fullscreen magnification activation changes.
680      */
onFullscreenMagnificationActivationChanged(int displayId, boolean activated)681     public boolean onFullscreenMagnificationActivationChanged(int displayId, boolean activated) {
682         synchronized (mLock) {
683             if (!waitConnectionWithTimeoutIfNeeded()) {
684                 Slog.w(TAG,
685                         "onFullscreenMagnificationActivationChanged mConnectionWrapper is null. "
686                                 + "mConnectionState=" + connectionStateToString(mConnectionState));
687                 return false;
688             }
689             return mConnectionWrapper
690                     .onFullscreenMagnificationActivationChanged(displayId, activated);
691         }
692     }
693 
694     /**
695      * Calculates the number of fingers in the window.
696      *
697      * @param displayId The logical display id.
698      * @param motionEvent The motion event
699      * @return the number of fingers in the window.
700      */
pointersInWindow(int displayId, MotionEvent motionEvent)701     int pointersInWindow(int displayId, MotionEvent motionEvent) {
702         synchronized (mLock) {
703             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
704             if (magnifier == null) {
705                 return 0;
706             }
707             return magnifier.pointersInWindow(motionEvent);
708         }
709     }
710 
711     @GuardedBy("mLock")
isPositionInSourceBounds(int displayId, float x, float y)712     boolean isPositionInSourceBounds(int displayId, float x, float y) {
713         WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
714         if (magnifier == null) {
715             return false;
716         }
717         return magnifier.isPositionInSourceBounds(x, y);
718     }
719 
720     /**
721      * Indicates whether window magnification is enabled on specified display.
722      *
723      * @param displayId The logical display id.
724      * @return {@code true} if the window magnification is enabled.
725      */
isWindowMagnifierEnabled(int displayId)726     public boolean isWindowMagnifierEnabled(int displayId) {
727         synchronized (mLock) {
728             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
729             if (magnifier == null) {
730                 return false;
731             }
732             return magnifier.isEnabled();
733         }
734     }
735 
736     /**
737      * Retrieves a previously magnification scale from the current
738      * user's settings. Only the value of the default display is persisted.
739      *
740      * @return the previously magnification scale, or the default
741      *         scale if none is available
742      */
getPersistedScale(int displayId)743     float getPersistedScale(int displayId) {
744         return MathUtils.constrain(mScaleProvider.getScale(displayId),
745                 MagnificationConstants.PERSISTED_SCALE_MIN_VALUE,
746                 MagnificationScaleProvider.MAX_SCALE);
747     }
748 
749     /**
750      * Persists the default display magnification scale to the current user's settings
751      * <strong>if scale is >= {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}</strong>.
752      * We assume if the scale is < {@link MagnificationConstants.PERSISTED_SCALE_MIN_VALUE}, there
753      * will be no obvious magnification effect.
754      * Only the value of the default display is persisted in user's settings.
755      */
persistScale(int displayId)756     void persistScale(int displayId) {
757         float scale = getScale(displayId);
758         if (scale < MagnificationConstants.PERSISTED_SCALE_MIN_VALUE) {
759             return;
760         }
761         mScaleProvider.putScale(scale, displayId);
762     }
763 
764     /**
765      * Returns the magnification scale.
766      *
767      * @param displayId The logical display id.
768      * @return the scale
769      */
getScale(int displayId)770     public float getScale(int displayId) {
771         synchronized (mLock) {
772             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
773             if (magnifier == null || !magnifier.mEnabled) {
774                 return 1.0f;
775             }
776             return magnifier.getScale();
777         }
778     }
779 
getLastActivatedScale(int displayId)780     protected float getLastActivatedScale(int displayId) {
781         synchronized (mLock) {
782             if (!mLastActivatedScale.contains(displayId)) {
783                 return -1.0f;
784             }
785             return mLastActivatedScale.get(displayId);
786         }
787     }
788 
789     /**
790      * Moves window magnification on the specified display with the specified offset.
791      *
792      * @param displayId The logical display id.
793      * @param offsetX the amount in pixels to offset the region in the X direction, in current
794      *                screen pixels.
795      * @param offsetY the amount in pixels to offset the region in the Y direction, in current
796      *                screen pixels.
797      */
moveWindowMagnification(int displayId, float offsetX, float offsetY)798     void moveWindowMagnification(int displayId, float offsetX, float offsetY) {
799         synchronized (mLock) {
800             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
801             if (magnifier == null) {
802                 return;
803             }
804             magnifier.move(offsetX, offsetY);
805         }
806     }
807 
808     /**
809      * Requests System UI show magnification mode button UI on the specified display.
810      *
811      * @param displayId The logical display id.
812      * @param magnificationMode the current magnification mode.
813      * @return {@code true} if the event was handled, {@code false} otherwise
814      */
showMagnificationButton(int displayId, int magnificationMode)815     public boolean showMagnificationButton(int displayId, int magnificationMode) {
816         synchronized (mLock) {
817             return mConnectionWrapper != null
818                     && mConnectionWrapper.showMagnificationButton(displayId, magnificationMode);
819         }
820     }
821 
822     /**
823      * Requests System UI remove magnification mode button UI on the specified display.
824      *
825      * @param displayId The logical display id.
826      * @return {@code true} if the event was handled, {@code false} otherwise
827      */
removeMagnificationButton(int displayId)828     public boolean removeMagnificationButton(int displayId) {
829         synchronized (mLock) {
830             return mConnectionWrapper != null
831                     && mConnectionWrapper.removeMagnificationButton(displayId);
832         }
833     }
834 
835     /**
836      * Requests System UI remove magnification settings panel on the specified display.
837      *
838      * @param displayId The logical display id.
839      * @return {@code true} if the event was handled, {@code false} otherwise
840      */
removeMagnificationSettingsPanel(int displayId)841     public boolean removeMagnificationSettingsPanel(int displayId) {
842         synchronized (mLock) {
843             return mConnectionWrapper != null
844                     && mConnectionWrapper.removeMagnificationSettingsPanel(displayId);
845         }
846     }
847 
848     /**
849      * Notify System UI the magnification scale on the specified display for userId is changed.
850      *
851      * @param userId the user id.
852      * @param displayId the logical display id.
853      * @param scale magnification scale.
854      */
onUserMagnificationScaleChanged(int userId, int displayId, float scale)855     public boolean onUserMagnificationScaleChanged(int userId, int displayId, float scale) {
856         synchronized (mLock) {
857             return mConnectionWrapper != null
858                     && mConnectionWrapper.onUserMagnificationScaleChanged(userId, displayId, scale);
859         }
860     }
861 
862     /**
863      * Returns the screen-relative X coordinate of the center of the magnified bounds.
864      *
865      * @param displayId The logical display id
866      * @return the X coordinate. {@link Float#NaN} if the window magnification is not enabled.
867      */
getCenterX(int displayId)868     public float getCenterX(int displayId) {
869         synchronized (mLock) {
870             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
871             if (magnifier == null || !magnifier.mEnabled) {
872                 return Float.NaN;
873             }
874             return magnifier.getCenterX();
875         }
876     }
877 
878     /**
879      * Returns the screen-relative Y coordinate of the center of the magnified bounds.
880      *
881      * @param displayId The logical display id
882      * @return the Y coordinate. {@link Float#NaN} if the window magnification is not enabled.
883      */
getCenterY(int displayId)884     public float getCenterY(int displayId) {
885         synchronized (mLock) {
886             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
887             if (magnifier == null || !magnifier.mEnabled) {
888                 return Float.NaN;
889             }
890             return magnifier.getCenterY();
891         }
892     }
893 
isTrackingTypingFocusEnabled(int displayId)894     boolean isTrackingTypingFocusEnabled(int displayId) {
895         synchronized (mLock) {
896             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
897             if (magnifier == null) {
898                 return false;
899             }
900             return magnifier.isTrackingTypingFocusEnabled();
901         }
902     }
903 
904     /**
905      * Populates magnified bounds on the screen. And the populated magnified bounds would be
906      * empty If window magnifier is not activated.
907      *
908      * @param displayId The logical display id.
909      * @param outRegion the region to populate
910      */
getMagnificationSourceBounds(int displayId, @NonNull Region outRegion)911     public void getMagnificationSourceBounds(int displayId, @NonNull Region outRegion) {
912         synchronized (mLock) {
913             WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
914             if (magnifier == null || !magnifier.mEnabled) {
915                 outRegion.setEmpty();
916             } else {
917                 outRegion.set(magnifier.mSourceBounds);
918             }
919         }
920     }
921 
922     /**
923      * Creates the windowMagnifier based on the specified display and stores it.
924      *
925      * @param displayId logical display id.
926      */
927     @GuardedBy("mLock")
createWindowMagnifier(int displayId)928     private WindowMagnifier createWindowMagnifier(int displayId) {
929         final WindowMagnifier magnifier = new WindowMagnifier(displayId, this);
930         mWindowMagnifiers.put(displayId, magnifier);
931         return magnifier;
932     }
933 
934     /**
935      * Removes the window magnifier with given id.
936      *
937      * @param displayId The logical display id.
938      */
onDisplayRemoved(int displayId)939     public void onDisplayRemoved(int displayId) {
940         disableWindowMagnification(displayId, true);
941     }
942 
943     private class ConnectionCallback extends IMagnificationConnectionCallback.Stub implements
944             IBinder.DeathRecipient {
945         private boolean mExpiredDeathRecipient = false;
946 
947         @RequiresNoPermission
948         @Override
onWindowMagnifierBoundsChanged(int displayId, Rect bounds)949         public void onWindowMagnifierBoundsChanged(int displayId, Rect bounds) {
950             if (mTrace.isA11yTracingEnabledForTypes(
951                     FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
952                 mTrace.logTrace(TAG + "ConnectionCallback.onWindowMagnifierBoundsChanged",
953                         FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
954                         "displayId=" + displayId + ";bounds=" + bounds);
955             }
956             synchronized (mLock) {
957                 WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
958                 if (magnifier == null) {
959                     magnifier = createWindowMagnifier(displayId);
960                 }
961                 if (DBG) {
962                     Slog.i(TAG,
963                             "onWindowMagnifierBoundsChanged -" + displayId + " bounds = " + bounds);
964                 }
965                 magnifier.setMagnifierLocation(bounds);
966             }
967         }
968 
969         @RequiresNoPermission
970         @Override
onChangeMagnificationMode(int displayId, int magnificationMode)971         public void onChangeMagnificationMode(int displayId, int magnificationMode)
972                 throws RemoteException {
973             if (mTrace.isA11yTracingEnabledForTypes(
974                     FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
975                 mTrace.logTrace(TAG + "ConnectionCallback.onChangeMagnificationMode",
976                         FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
977                         "displayId=" + displayId + ";mode=" + magnificationMode);
978             }
979             mCallback.onChangeMagnificationMode(displayId, magnificationMode);
980         }
981 
982         @RequiresNoPermission
983         @Override
onSourceBoundsChanged(int displayId, Rect sourceBounds)984         public void onSourceBoundsChanged(int displayId, Rect sourceBounds) {
985             if (mTrace.isA11yTracingEnabledForTypes(
986                     FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
987                 mTrace.logTrace(TAG + "ConnectionCallback.onSourceBoundsChanged",
988                         FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
989                         "displayId=" + displayId + ";source=" + sourceBounds);
990             }
991             synchronized (mLock) {
992                 WindowMagnifier magnifier = mWindowMagnifiers.get(displayId);
993                 if (magnifier == null) {
994                     magnifier = createWindowMagnifier(displayId);
995                 }
996                 magnifier.onSourceBoundsChanged(sourceBounds);
997             }
998             mCallback.onSourceBoundsChanged(displayId, sourceBounds);
999         }
1000 
1001         @RequiresNoPermission
1002         @Override
onPerformScaleAction(int displayId, float scale, boolean updatePersistence)1003         public void onPerformScaleAction(int displayId, float scale, boolean updatePersistence) {
1004             if (mTrace.isA11yTracingEnabledForTypes(
1005                     FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
1006                 mTrace.logTrace(TAG + "ConnectionCallback.onPerformScaleAction",
1007                         FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
1008                         "displayId=" + displayId + ";scale=" + scale
1009                                 + ";updatePersistence=" + updatePersistence);
1010             }
1011             mCallback.onPerformScaleAction(displayId, scale, updatePersistence);
1012         }
1013 
1014         @RequiresNoPermission
1015         @Override
onAccessibilityActionPerformed(int displayId)1016         public void onAccessibilityActionPerformed(int displayId) {
1017             if (mTrace.isA11yTracingEnabledForTypes(
1018                     FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
1019                 mTrace.logTrace(TAG + "ConnectionCallback.onAccessibilityActionPerformed",
1020                         FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
1021                         "displayId=" + displayId);
1022             }
1023             mCallback.onAccessibilityActionPerformed(displayId);
1024         }
1025 
1026         @RequiresNoPermission
1027         @Override
onMove(int displayId)1028         public void onMove(int displayId) {
1029             if (mTrace.isA11yTracingEnabledForTypes(
1030                     FLAGS_MAGNIFICATION_CONNECTION_CALLBACK)) {
1031                 mTrace.logTrace(TAG + "ConnectionCallback.onMove",
1032                         FLAGS_MAGNIFICATION_CONNECTION_CALLBACK,
1033                         "displayId=" + displayId);
1034             }
1035             setTrackingTypingFocusEnabled(displayId, false);
1036         }
1037 
1038         @Override
binderDied()1039         public void binderDied() {
1040             synchronized (mLock) {
1041                 Slog.w(TAG, "binderDied DeathRecipient :" + mExpiredDeathRecipient);
1042                 if (mExpiredDeathRecipient) {
1043                     return;
1044                 }
1045                 mConnectionWrapper.unlinkToDeath(this);
1046                 mConnectionWrapper = null;
1047                 mConnectionCallback = null;
1048                 setConnectionState(DISCONNECTED);
1049                 resetWindowMagnifiers();
1050             }
1051         }
1052     }
1053 
1054     /**
1055      * A class manipulates window magnification per display and contains the magnification
1056      * information.
1057      * <p>
1058      * This class requires to hold the lock when controlling the magnifier.
1059      * </p>
1060      */
1061     private static class WindowMagnifier {
1062 
1063         private final int mDisplayId;
1064         private float mScale = MagnificationScaleProvider.MIN_SCALE;
1065         private boolean mEnabled;
1066 
1067         private final MagnificationConnectionManager mMagnificationConnectionManager;
1068         // Records the bounds of window magnification.
1069         private final Rect mBounds = new Rect();
1070         // The magnified bounds on the screen.
1071         private final Rect mSourceBounds = new Rect();
1072 
1073         private int mIdOfLastServiceToControl = INVALID_SERVICE_ID;
1074 
1075         private final PointF mMagnificationFrameOffsetRatio = new PointF(0f, 0f);
1076 
1077         private boolean mTrackingTypingFocusEnabled = true;
1078 
1079         private volatile long mTrackingTypingFocusStartTime = 0;
1080         private static final AtomicLongFieldUpdater<WindowMagnifier> SUM_TIME_UPDATER =
1081                 AtomicLongFieldUpdater.newUpdater(WindowMagnifier.class,
1082                         "mTrackingTypingFocusSumTime");
1083         private volatile long mTrackingTypingFocusSumTime = 0;
1084 
WindowMagnifier(int displayId, MagnificationConnectionManager magnificationConnectionManager)1085         WindowMagnifier(int displayId,
1086                 MagnificationConnectionManager magnificationConnectionManager) {
1087             mDisplayId = displayId;
1088             mMagnificationConnectionManager = magnificationConnectionManager;
1089         }
1090 
1091         // TODO(b/312324808): Investigating whether
1092         //  mMagnificationConnectionManager#enableWindowMagnificationInternal requires a sync lock
1093         @SuppressWarnings("GuardedBy")
enableWindowMagnificationInternal(float scale, float centerX, float centerY, @Nullable MagnificationAnimationCallback animationCallback, @WindowPosition int windowPosition, int id)1094         boolean enableWindowMagnificationInternal(float scale, float centerX, float centerY,
1095                 @Nullable MagnificationAnimationCallback animationCallback,
1096                 @WindowPosition int windowPosition, int id) {
1097             // Handle defaults. The scale may be NAN when just updating magnification center.
1098             if (Float.isNaN(scale)) {
1099                 scale = getScale();
1100             }
1101             final float normScale = MagnificationScaleProvider.constrainScale(scale);
1102             setMagnificationFrameOffsetRatioByWindowPosition(windowPosition);
1103             if (mMagnificationConnectionManager.enableWindowMagnificationInternal(mDisplayId,
1104                     normScale, centerX, centerY, mMagnificationFrameOffsetRatio.x,
1105                     mMagnificationFrameOffsetRatio.y, animationCallback)) {
1106                 mScale = normScale;
1107                 mEnabled = true;
1108                 mIdOfLastServiceToControl = id;
1109                 return true;
1110             }
1111             return false;
1112         }
1113 
setMagnificationFrameOffsetRatioByWindowPosition(@indowPosition int windowPosition)1114         void setMagnificationFrameOffsetRatioByWindowPosition(@WindowPosition int windowPosition) {
1115             switch (windowPosition) {
1116                 case WINDOW_POSITION_AT_CENTER: {
1117                     mMagnificationFrameOffsetRatio.set(0f, 0f);
1118                 }
1119                 break;
1120                 case WINDOW_POSITION_AT_TOP_LEFT: {
1121                     mMagnificationFrameOffsetRatio.set(-1f, -1f);
1122                 }
1123                 break;
1124             }
1125         }
1126 
1127         // TODO(b/312324808): Investigating whether
1128         //  mMagnificationConnectionManager#disableWindowMagnificationInternal requires a sync lock
1129         @SuppressWarnings("GuardedBy")
disableWindowMagnificationInternal( @ullable MagnificationAnimationCallback animationResultCallback)1130         boolean disableWindowMagnificationInternal(
1131                 @Nullable MagnificationAnimationCallback animationResultCallback) {
1132             if (!mEnabled) {
1133                 return false;
1134             }
1135             if (mMagnificationConnectionManager.disableWindowMagnificationInternal(
1136                     mDisplayId, animationResultCallback)) {
1137                 mEnabled = false;
1138                 mIdOfLastServiceToControl = INVALID_SERVICE_ID;
1139                 mTrackingTypingFocusEnabled = false;
1140                 pauseTrackingTypingFocusRecord();
1141                 return true;
1142             }
1143             return false;
1144         }
1145 
1146         // ErrorProne says the access of mMagnificationConnectionManager#setScaleInternal should
1147         // be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one as
1148         // 'mLock'. Therefore, we'll put @SuppressWarnings here.
1149         @SuppressWarnings("GuardedBy")
1150         @GuardedBy("mLock")
setScale(float scale)1151         void setScale(float scale) {
1152             if (!mEnabled) {
1153                 return;
1154             }
1155             final float normScale = MagnificationScaleProvider.constrainScale(scale);
1156             if (Float.compare(mScale, normScale) != 0
1157                     && mMagnificationConnectionManager
1158                         .setScaleForWindowMagnificationInternal(mDisplayId, scale)) {
1159                 mScale = normScale;
1160             }
1161         }
1162 
1163         @GuardedBy("mLock")
getScale()1164         float getScale() {
1165             return mScale;
1166         }
1167 
1168         @GuardedBy("mLock")
setMagnifierLocation(Rect rect)1169         void setMagnifierLocation(Rect rect) {
1170             mBounds.set(rect);
1171         }
1172 
1173         /**
1174          * Returns the ID of the last service that changed the magnification config.
1175          */
getIdOfLastServiceToControl()1176         int getIdOfLastServiceToControl() {
1177             return mIdOfLastServiceToControl;
1178         }
1179 
pointersInWindow(MotionEvent motionEvent)1180         int pointersInWindow(MotionEvent motionEvent) {
1181             int count = 0;
1182             final int pointerCount = motionEvent.getPointerCount();
1183             for (int i = 0; i < pointerCount; i++) {
1184                 final float x = motionEvent.getX(i);
1185                 final float y = motionEvent.getY(i);
1186                 if (mBounds.contains((int) x, (int) y)) {
1187                     count++;
1188                 }
1189             }
1190             return count;
1191         }
1192 
isPositionInSourceBounds(float x, float y)1193         boolean isPositionInSourceBounds(float x, float y) {
1194             return mSourceBounds.contains((int) x, (int) y);
1195         }
1196 
setTrackingTypingFocusEnabled(boolean trackingTypingFocusEnabled)1197         void setTrackingTypingFocusEnabled(boolean trackingTypingFocusEnabled) {
1198             if (mMagnificationConnectionManager.isWindowMagnifierEnabled(mDisplayId)
1199                     && mMagnificationConnectionManager.isImeVisible(mDisplayId)
1200                     && trackingTypingFocusEnabled) {
1201                 startTrackingTypingFocusRecord();
1202             }
1203             if (mTrackingTypingFocusEnabled && !trackingTypingFocusEnabled) {
1204                 stopAndLogTrackingTypingFocusRecordIfNeeded();
1205             }
1206             mTrackingTypingFocusEnabled = trackingTypingFocusEnabled;
1207         }
1208 
isTrackingTypingFocusEnabled()1209         boolean isTrackingTypingFocusEnabled() {
1210             return mTrackingTypingFocusEnabled;
1211         }
1212 
startTrackingTypingFocusRecord()1213         void startTrackingTypingFocusRecord() {
1214             if (mTrackingTypingFocusStartTime == 0) {
1215                 mTrackingTypingFocusStartTime = SystemClock.uptimeMillis();
1216                 if (DBG) {
1217                     Slog.d(TAG, "start: mTrackingTypingFocusStartTime = "
1218                             + mTrackingTypingFocusStartTime);
1219                 }
1220             }
1221         }
1222 
pauseTrackingTypingFocusRecord()1223         void pauseTrackingTypingFocusRecord() {
1224             if (mTrackingTypingFocusStartTime != 0) {
1225                 final long elapsed = (SystemClock.uptimeMillis() - mTrackingTypingFocusStartTime);
1226                 // update mTrackingTypingFocusSumTime value in an atomic operation
1227                 SUM_TIME_UPDATER.addAndGet(this, elapsed);
1228                 mTrackingTypingFocusStartTime = 0;
1229                 if (DBG) {
1230                     Slog.d(TAG, "pause: mTrackingTypingFocusSumTime = "
1231                             + mTrackingTypingFocusSumTime + ", elapsed = " + elapsed);
1232                 }
1233             }
1234         }
1235 
stopAndLogTrackingTypingFocusRecordIfNeeded()1236         void stopAndLogTrackingTypingFocusRecordIfNeeded() {
1237             if (mTrackingTypingFocusStartTime != 0 || mTrackingTypingFocusSumTime != 0) {
1238                 final long elapsed = mTrackingTypingFocusStartTime != 0
1239                         ? (SystemClock.uptimeMillis() - mTrackingTypingFocusStartTime) : 0;
1240                 final long duration = mTrackingTypingFocusSumTime + elapsed;
1241                 if (DBG) {
1242                     Slog.d(TAG, "stop and log: session duration = " + duration
1243                             + ", elapsed = " + elapsed);
1244                 }
1245                 mMagnificationConnectionManager.logTrackingTypingFocus(duration);
1246                 mTrackingTypingFocusStartTime = 0;
1247                 mTrackingTypingFocusSumTime = 0;
1248             }
1249         }
1250 
isEnabled()1251         boolean isEnabled() {
1252             return mEnabled;
1253         }
1254 
1255         // ErrorProne says the access of mMagnificationConnectionManager#moveWindowMagnifierInternal
1256         // should be guarded by 'this.mMagnificationConnectionManager.mLock' which is the same one
1257         // as 'mLock'. Therefore, we'll put @SuppressWarnings here.
1258         @SuppressWarnings("GuardedBy")
1259         @GuardedBy("mLock")
move(float offsetX, float offsetY)1260         void move(float offsetX, float offsetY) {
1261             mMagnificationConnectionManager.moveWindowMagnifierInternal(
1262                     mDisplayId, offsetX, offsetY);
1263         }
1264 
1265         @GuardedBy("mLock")
reset()1266         void reset() {
1267             mEnabled = false;
1268             mIdOfLastServiceToControl = INVALID_SERVICE_ID;
1269             mSourceBounds.setEmpty();
1270         }
1271 
1272         @GuardedBy("mLock")
onSourceBoundsChanged(Rect sourceBounds)1273         public void onSourceBoundsChanged(Rect sourceBounds) {
1274             mSourceBounds.set(sourceBounds);
1275         }
1276 
1277         @GuardedBy("mLock")
getCenterX()1278         float getCenterX() {
1279             return mSourceBounds.exactCenterX();
1280         }
1281 
1282         @GuardedBy("mLock")
getCenterY()1283         float getCenterY() {
1284             return mSourceBounds.exactCenterY();
1285         }
1286     }
1287 
1288     @GuardedBy("mLock")
enableWindowMagnificationInternal(int displayId, float scale, float centerX, float centerY, float magnificationFrameOffsetRatioX, float magnificationFrameOffsetRatioY, MagnificationAnimationCallback animationCallback)1289     private boolean enableWindowMagnificationInternal(int displayId, float scale, float centerX,
1290             float centerY, float magnificationFrameOffsetRatioX,
1291             float magnificationFrameOffsetRatioY,
1292             MagnificationAnimationCallback animationCallback) {
1293         if (!waitConnectionWithTimeoutIfNeeded()) {
1294             Slog.w(TAG,
1295                     "enableWindowMagnificationInternal mConnectionWrapper is null. "
1296                             + "mConnectionState=" + connectionStateToString(mConnectionState));
1297             return false;
1298         }
1299         return mConnectionWrapper.enableWindowMagnification(
1300                 displayId, scale, centerX, centerY,
1301                 magnificationFrameOffsetRatioX, magnificationFrameOffsetRatioY,
1302                 animationCallback);
1303     }
1304 
1305     @GuardedBy("mLock")
setScaleForWindowMagnificationInternal(int displayId, float scale)1306     private boolean setScaleForWindowMagnificationInternal(int displayId, float scale) {
1307         return mConnectionWrapper != null
1308                 && mConnectionWrapper.setScaleForWindowMagnification(displayId, scale);
1309     }
1310 
1311     @GuardedBy("mLock")
disableWindowMagnificationInternal(int displayId, MagnificationAnimationCallback animationCallback)1312     private boolean disableWindowMagnificationInternal(int displayId,
1313             MagnificationAnimationCallback animationCallback) {
1314         if (mConnectionWrapper == null) {
1315             Slog.w(TAG, "mConnectionWrapper is null");
1316             return false;
1317         }
1318         return mConnectionWrapper.disableWindowMagnification(
1319                 displayId, animationCallback);
1320     }
1321 
1322     @GuardedBy("mLock")
moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY)1323     private boolean moveWindowMagnifierInternal(int displayId, float offsetX, float offsetY) {
1324         return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifier(
1325                 displayId, offsetX, offsetY);
1326     }
1327 
1328     @GuardedBy("mLock")
moveWindowMagnifierToPositionInternal(int displayId, float positionX, float positionY, MagnificationAnimationCallback animationCallback)1329     private boolean moveWindowMagnifierToPositionInternal(int displayId, float positionX,
1330             float positionY, MagnificationAnimationCallback animationCallback) {
1331         return mConnectionWrapper != null && mConnectionWrapper.moveWindowMagnifierToPosition(
1332                 displayId, positionX, positionY, animationCallback);
1333     }
1334 
waitConnectionWithTimeoutIfNeeded()1335     boolean waitConnectionWithTimeoutIfNeeded() {
1336         // Wait for the connection with a timeout.
1337         final long endMillis = SystemClock.uptimeMillis() + WAIT_CONNECTION_TIMEOUT_MILLIS;
1338         while (mConnectionState == CONNECTING && (SystemClock.uptimeMillis() < endMillis)) {
1339             try {
1340                 mLock.wait(endMillis - SystemClock.uptimeMillis());
1341             } catch (InterruptedException ie) {
1342                 /* ignore */
1343             }
1344         }
1345         return isConnected();
1346     }
1347 }
1348