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