ScreenMagnifier.java revision add52a975aa78d9e24d3e63a8168c00a9bfb80ec
1/*
2 * Copyright (C) 2012 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
17package com.android.server.accessibility;
18
19import android.animation.Animator;
20import android.animation.Animator.AnimatorListener;
21import android.animation.ObjectAnimator;
22import android.animation.TypeEvaluator;
23import android.animation.ValueAnimator;
24import android.content.Context;
25import android.graphics.Canvas;
26import android.graphics.Color;
27import android.graphics.PixelFormat;
28import android.graphics.PointF;
29import android.graphics.PorterDuff.Mode;
30import android.graphics.Rect;
31import android.graphics.drawable.Drawable;
32import android.hardware.display.DisplayManager;
33import android.hardware.display.DisplayManager.DisplayListener;
34import android.os.AsyncTask;
35import android.os.Handler;
36import android.os.Message;
37import android.os.RemoteException;
38import android.os.ServiceManager;
39import android.provider.Settings;
40import android.util.MathUtils;
41import android.util.Property;
42import android.util.Slog;
43import android.view.Display;
44import android.view.DisplayInfo;
45import android.view.Gravity;
46import android.view.IDisplayContentChangeListener;
47import android.view.IWindowManager;
48import android.view.MotionEvent;
49import android.view.ScaleGestureDetector;
50import android.view.MotionEvent.PointerCoords;
51import android.view.MotionEvent.PointerProperties;
52import android.view.ScaleGestureDetector.OnScaleGestureListener;
53import android.view.Surface;
54import android.view.View;
55import android.view.ViewConfiguration;
56import android.view.ViewGroup;
57import android.view.WindowInfo;
58import android.view.WindowManager;
59import android.view.WindowManagerPolicy;
60import android.view.accessibility.AccessibilityEvent;
61import android.view.animation.DecelerateInterpolator;
62import android.view.animation.Interpolator;
63
64import com.android.internal.R;
65import com.android.internal.os.SomeArgs;
66
67import java.util.ArrayList;
68
69/**
70 * This class handles the screen magnification when accessibility is enabled.
71 * The behavior is as follows:
72 *
73 * 1. Triple tap toggles permanent screen magnification which is magnifying
74 *    the area around the location of the triple tap. One can think of the
75 *    location of the triple tap as the center of the magnified viewport.
76 *    For example, a triple tap when not magnified would magnify the screen
77 *    and leave it in a magnified state. A triple tapping when magnified would
78 *    clear magnification and leave the screen in a not magnified state.
79 *
80 * 2. Triple tap and hold would magnify the screen if not magnified and enable
81 *    viewport dragging mode until the finger goes up. One can think of this
82 *    mode as a way to move the magnified viewport since the area around the
83 *    moving finger will be magnified to fit the screen. For example, if the
84 *    screen was not magnified and the user triple taps and holds the screen
85 *    would magnify and the viewport will follow the user's finger. When the
86 *    finger goes up the screen will clear zoom out. If the same user interaction
87 *    is performed when the screen is magnified, the viewport movement will
88 *    be the same but when the finger goes up the screen will stay magnified.
89 *    In other words, the initial magnified state is sticky.
90 *
91 * 3. Pinching with any number of additional fingers when viewport dragging
92 *    is enabled, i.e. the user triple tapped and holds, would adjust the
93 *    magnification scale which will become the current default magnification
94 *    scale. The next time the user magnifies the same magnification scale
95 *    would be used.
96 *
97 * 4. When in a permanent magnified state the user can use two or more fingers
98 *    to pan the viewport. Note that in this mode the content is panned as
99 *    opposed to the viewport dragging mode in which the viewport is moved.
100 *
101 * 5. When in a permanent magnified state the user can use three or more
102 *    fingers to change the magnification scale which will become the current
103 *    default magnification scale. The next time the user magnifies the same
104 *    magnification scale would be used.
105 *
106 * 6. The magnification scale will be persisted in settings and in the cloud.
107 */
108public final class ScreenMagnifier implements EventStreamTransformation {
109
110    private static final boolean DEBUG_STATE_TRANSITIONS = false;
111    private static final boolean DEBUG_DETECTING = false;
112    private static final boolean DEBUG_TRANSFORMATION = false;
113    private static final boolean DEBUG_PANNING = false;
114    private static final boolean DEBUG_SCALING = false;
115    private static final boolean DEBUG_VIEWPORT_WINDOW = false;
116    private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
117    private static final boolean DEBUG_ROTATION = false;
118    private static final boolean DEBUG_GESTURE_DETECTOR = false;
119    private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
120
121    private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
122
123    private static final int STATE_DELEGATING = 1;
124    private static final int STATE_DETECTING = 2;
125    private static final int STATE_SCALING = 3;
126    private static final int STATE_VIEWPORT_DRAGGING = 4;
127    private static final int STATE_PANNING = 5;
128    private static final int STATE_DECIDE_PAN_OR_SCALE = 6;
129
130    private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
131    private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
132    private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f;
133
134    private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface(
135            ServiceManager.getService("window"));
136    private final WindowManager mWindowManager;
137    private final DisplayProvider mDisplayProvider;
138
139    private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler();
140    private final GestureDetector mGestureDetector;
141    private final StateViewportDraggingHandler mStateViewportDraggingHandler =
142            new StateViewportDraggingHandler();
143
144    private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f);
145
146    private final MagnificationController mMagnificationController;
147    private final DisplayContentObserver mDisplayContentObserver;
148    private final Viewport mViewport;
149
150    private final int mTapTimeSlop = ViewConfiguration.getTapTimeout();
151    private final int mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout();
152    private final int mTapDistanceSlop;
153    private final int mMultiTapDistanceSlop;
154
155    private final int mShortAnimationDuration;
156    private final int mLongAnimationDuration;
157    private final float mWindowAnimationScale;
158
159    private final Context mContext;
160
161    private EventStreamTransformation mNext;
162
163    private int mCurrentState;
164    private boolean mTranslationEnabledBeforePan;
165
166    private PointerCoords[] mTempPointerCoords;
167    private PointerProperties[] mTempPointerProperties;
168
169    public ScreenMagnifier(Context context) {
170        mContext = context;
171        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
172
173        mShortAnimationDuration = context.getResources().getInteger(
174                com.android.internal.R.integer.config_shortAnimTime);
175        mLongAnimationDuration = context.getResources().getInteger(
176                com.android.internal.R.integer.config_longAnimTime);
177        mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
178        mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
179        mWindowAnimationScale = Settings.System.getFloat(context.getContentResolver(),
180                Settings.System.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE);
181
182        mMagnificationController = new MagnificationController(mShortAnimationDuration);
183        mDisplayProvider = new DisplayProvider(context, mWindowManager);
184        mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService,
185                mDisplayProvider, mInterpolator, mShortAnimationDuration);
186        mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport,
187                mMagnificationController, mWindowManagerService, mDisplayProvider,
188                mLongAnimationDuration, mWindowAnimationScale);
189
190        mGestureDetector = new GestureDetector(context);
191
192        transitionToState(STATE_DETECTING);
193    }
194
195    @Override
196    public void onMotionEvent(MotionEvent event, int policyFlags) {
197        switch (mCurrentState) {
198            case STATE_DELEGATING: {
199                handleMotionEventStateDelegating(event, policyFlags);
200            } break;
201            case STATE_DETECTING: {
202                mDetectingStateHandler.onMotionEvent(event, policyFlags);
203            } break;
204            case STATE_VIEWPORT_DRAGGING: {
205                mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
206            } break;
207            case STATE_SCALING:
208            case STATE_PANNING:
209            case STATE_DECIDE_PAN_OR_SCALE: {
210                // Handled by the gesture detector. Since the detector
211                // needs all touch events to work properly we cannot
212                // call it only for these states.
213            } break;
214            default: {
215                throw new IllegalStateException("Unknown state: " + mCurrentState);
216            }
217        }
218        mGestureDetector.onMotionEvent(event);
219    }
220
221    @Override
222    public void onAccessibilityEvent(AccessibilityEvent event) {
223        if (mNext != null) {
224            mNext.onAccessibilityEvent(event);
225        }
226    }
227
228    @Override
229    public void setNext(EventStreamTransformation next) {
230        mNext = next;
231    }
232
233    @Override
234    public void clear() {
235        mCurrentState = STATE_DETECTING;
236        mDetectingStateHandler.clear();
237        mStateViewportDraggingHandler.clear();
238        mGestureDetector.clear();
239        if (mNext != null) {
240            mNext.clear();
241        }
242    }
243
244    @Override
245    public void onDestroy() {
246        mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f,
247                0, 0, true);
248        mViewport.setFrameShown(false, true);
249        mDisplayProvider.destroy();
250        mDisplayContentObserver.destroy();
251    }
252
253    private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
254        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
255            if (mDetectingStateHandler.mDelayedEventQueue == null) {
256                transitionToState(STATE_DETECTING);
257            }
258        }
259        if (mNext != null) {
260            // If the event is within the magnified portion of the screen we have
261            // to change its location to be where the user thinks he is poking the
262            // UI which may have been magnified and panned.
263            final float eventX = event.getX();
264            final float eventY = event.getY();
265            if (mMagnificationController.isMagnifying()
266                    && mViewport.getBounds().contains((int) eventX, (int) eventY)) {
267                final float scale = mMagnificationController.getScale();
268                final float scaledOffsetX = mMagnificationController.getScaledOffsetX();
269                final float scaledOffsetY = mMagnificationController.getScaledOffsetY();
270                final int pointerCount = event.getPointerCount();
271                PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
272                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
273                for (int i = 0; i < pointerCount; i++) {
274                    event.getPointerCoords(i, coords[i]);
275                    coords[i].x = (coords[i].x - scaledOffsetX) / scale;
276                    coords[i].y = (coords[i].y - scaledOffsetY) / scale;
277                    event.getPointerProperties(i, properties[i]);
278                }
279                event = MotionEvent.obtain(event.getDownTime(),
280                        event.getEventTime(), event.getAction(), pointerCount, properties,
281                        coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
282                        event.getFlags());
283            }
284            mNext.onMotionEvent(event, policyFlags);
285        }
286    }
287
288    private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
289        final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
290        if (oldSize < size) {
291            PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
292            mTempPointerCoords = new PointerCoords[size];
293            if (oldTempPointerCoords != null) {
294                System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
295            }
296        }
297        for (int i = oldSize; i < size; i++) {
298            mTempPointerCoords[i] = new PointerCoords();
299        }
300        return mTempPointerCoords;
301    }
302
303    private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
304        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
305        if (oldSize < size) {
306            PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
307            mTempPointerProperties = new PointerProperties[size];
308            if (oldTempPointerProperties != null) {
309                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
310            }
311        }
312        for (int i = oldSize; i < size; i++) {
313            mTempPointerProperties[i] = new PointerProperties();
314        }
315        return mTempPointerProperties;
316    }
317
318    private void transitionToState(int state) {
319        if (DEBUG_STATE_TRANSITIONS) {
320            switch (state) {
321                case STATE_DELEGATING: {
322                    Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
323                } break;
324                case STATE_DETECTING: {
325                    Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
326                } break;
327                case STATE_VIEWPORT_DRAGGING: {
328                    Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
329                } break;
330                case STATE_SCALING: {
331                    Slog.i(LOG_TAG, "mCurrentState: STATE_SCALING");
332                } break;
333                case STATE_PANNING: {
334                    Slog.i(LOG_TAG, "mCurrentState: STATE_PANNING");
335                } break;
336                case STATE_DECIDE_PAN_OR_SCALE: {
337                    Slog.i(LOG_TAG, "mCurrentState: STATE_DECIDE_PAN_OR_SCALE");
338                } break;
339                default: {
340                    throw new IllegalArgumentException("Unknown state: " + state);
341                }
342            }
343        }
344        mCurrentState = state;
345    }
346
347    private final class GestureDetector implements OnScaleGestureListener {
348        private static final float MIN_SCALE = 1.3f;
349        private static final float MAX_SCALE = 5.0f;
350
351        private static final float DETECT_SCALING_THRESHOLD = 0.25f;
352        private static final int DETECT_PANNING_THRESHOLD_DIP = 30;
353
354        private final float mScaledDetectPanningThreshold;
355
356        private final ScaleGestureDetector mScaleGestureDetector;
357
358        private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN);
359        private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN);
360
361        private float mCurrScale = Float.NaN;
362        private float mCurrScaleFactor = 1.0f;
363        private float mPrevScaleFactor = 1.0f;
364        private float mCurrPan;
365        private float mPrevPan;
366
367        private float mScaleFocusX = Float.NaN;
368        private float mScaleFocusY = Float.NaN;
369
370        public GestureDetector(Context context) {
371            final float density = context.getResources().getDisplayMetrics().density;
372            mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
373            mScaleGestureDetector = new ScaleGestureDetector(context, this);
374        }
375
376        public void onMotionEvent(MotionEvent event) {
377            mScaleGestureDetector.onTouchEvent(event);
378            switch (mCurrentState) {
379                case STATE_DETECTING:
380                case STATE_DELEGATING:
381                case STATE_VIEWPORT_DRAGGING: {
382                    return;
383                }
384            }
385            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
386                clear();
387                if (mCurrentState == STATE_SCALING) {
388                    persistScale(mMagnificationController.getScale());
389                }
390                transitionToState(STATE_DETECTING);
391            }
392        }
393
394        @Override
395        public boolean onScale(ScaleGestureDetector detector) {
396            switch (mCurrentState) {
397                case STATE_DETECTING:
398                case STATE_DELEGATING:
399                case STATE_VIEWPORT_DRAGGING: {
400                    return true;
401                }
402                case STATE_DECIDE_PAN_OR_SCALE: {
403                    mCurrScaleFactor = mScaleGestureDetector.getScaleFactor();
404                    final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor);
405                    if (DEBUG_GESTURE_DETECTOR) {
406                        Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta);
407                    }
408                    if (scaleDelta > DETECT_SCALING_THRESHOLD) {
409                        performScale(detector, true);
410                        clear();
411                        transitionToState(STATE_SCALING);
412                        return false;
413                    }
414                    mCurrPan = (float) MathUtils.dist(
415                            mScaleGestureDetector.getFocusX(),
416                            mScaleGestureDetector.getFocusY(),
417                            mInitialFocus.x, mInitialFocus.y);
418                    final float panDelta = mCurrPan + mPrevPan;
419                    if (DEBUG_GESTURE_DETECTOR) {
420                        Slog.i(LOG_TAG, "panDelta: " + panDelta);
421                    }
422                    if (panDelta > mScaledDetectPanningThreshold) {
423                        performPan(detector, true);
424                        clear();
425                        transitionToState(STATE_PANNING);
426                        return false;
427                    }
428                } break;
429                case STATE_SCALING: {
430                    performScale(detector, false);
431                } break;
432                case STATE_PANNING: {
433                    performPan(detector, false);
434                } break;
435            }
436            return false;
437        }
438
439        @Override
440        public boolean onScaleBegin(ScaleGestureDetector detector) {
441            switch (mCurrentState) {
442                case STATE_DECIDE_PAN_OR_SCALE: {
443                    mPrevScaleFactor *= mCurrScaleFactor;
444                    mPrevPan += mCurrPan;
445                    mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
446                    mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
447                } break;
448                case STATE_SCALING: {
449                    mPrevScaleFactor = 1.0f;
450                    mCurrScale = Float.NaN;
451                } break;
452                case STATE_PANNING: {
453                    mPrevPan += mCurrPan;
454                    mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
455                    mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
456                } break;
457            }
458            return true;
459        }
460
461        @Override
462        public void onScaleEnd(ScaleGestureDetector detector) {
463            /* do nothing */
464        }
465
466        public void clear() {
467            mCurrScaleFactor = 1.0f;
468            mPrevScaleFactor = 1.0f;
469            mPrevPan = 0;
470            mCurrPan = 0;
471            mInitialFocus.set(Float.NaN, Float.NaN);
472            mPrevFocus.set(Float.NaN, Float.NaN);
473            mCurrScale = Float.NaN;
474            mScaleFocusX = Float.NaN;
475            mScaleFocusY = Float.NaN;
476        }
477
478        private void performPan(ScaleGestureDetector detector, boolean animate) {
479            if (Float.compare(mPrevFocus.x, Float.NaN) == 0
480                    && Float.compare(mPrevFocus.y, Float.NaN) == 0) {
481                mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
482                return;
483            }
484            final float scale = mMagnificationController.getScale();
485            final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale;
486            final float scrollY = (detector.getFocusY() - mPrevFocus.y) / scale;
487            final float centerX = mMagnificationController.getMagnifiedRegionCenterX()
488                    - scrollX;
489            final float centerY = mMagnificationController.getMagnifiedRegionCenterY()
490                    - scrollY;
491            if (DEBUG_PANNING) {
492                Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
493                        + " scrollY: " + scrollY);
494            }
495            mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, animate);
496            mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
497        }
498
499        private void performScale(ScaleGestureDetector detector, boolean animate) {
500            if (Float.compare(mCurrScale, Float.NaN) == 0) {
501                mCurrScale = mMagnificationController.getScale();
502                return;
503            }
504            final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor();
505            final float newScale = mCurrScale * totalScaleFactor;
506            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE),
507                    MAX_SCALE);
508            if (DEBUG_SCALING) {
509                Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
510            }
511            if (Float.compare(mScaleFocusX, Float.NaN) == 0
512                    && Float.compare(mScaleFocusY, Float.NaN) == 0) {
513                mScaleFocusX = detector.getFocusX();
514                mScaleFocusY = detector.getFocusY();
515            }
516            mMagnificationController.setScale(normalizedNewScale, mScaleFocusX,
517                    mScaleFocusY, animate);
518        }
519    }
520
521    private final class StateViewportDraggingHandler {
522        private boolean mLastMoveOutsideMagnifiedRegion;
523
524        private void onMotionEvent(MotionEvent event, int policyFlags) {
525            final int action = event.getActionMasked();
526            switch (action) {
527                case MotionEvent.ACTION_DOWN: {
528                    throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN");
529                }
530                case MotionEvent.ACTION_POINTER_DOWN: {
531                    clear();
532                    transitionToState(STATE_SCALING);
533                } break;
534                case MotionEvent.ACTION_MOVE: {
535                    if (event.getPointerCount() != 1) {
536                        throw new IllegalStateException("Should have one pointer down.");
537                    }
538                    final float eventX = event.getX();
539                    final float eventY = event.getY();
540                    if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
541                        if (mLastMoveOutsideMagnifiedRegion) {
542                            mLastMoveOutsideMagnifiedRegion = false;
543                            mMagnificationController.setMagnifiedRegionCenter(eventX,
544                                    eventY, true);
545                        } else {
546                            mMagnificationController.setMagnifiedRegionCenter(eventX,
547                                    eventY, false);
548                        }
549                    } else {
550                        mLastMoveOutsideMagnifiedRegion = true;
551                    }
552                } break;
553                case MotionEvent.ACTION_UP: {
554                    if (!mTranslationEnabledBeforePan) {
555                        mMagnificationController.reset(true);
556                        mViewport.setFrameShown(false, true);
557                    }
558                    clear();
559                    transitionToState(STATE_DETECTING);
560                } break;
561                case MotionEvent.ACTION_POINTER_UP: {
562                    throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
563                }
564            }
565        }
566
567        public void clear() {
568            mLastMoveOutsideMagnifiedRegion = false;
569        }
570    }
571
572    private final class DetectingStateHandler {
573
574        private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
575
576        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
577
578        private static final int ACTION_TAP_COUNT = 3;
579
580        private MotionEventInfo mDelayedEventQueue;
581
582        private MotionEvent mLastDownEvent;
583        private MotionEvent mLastTapUpEvent;
584        private int mTapCount;
585
586        private final Handler mHandler = new Handler() {
587            @Override
588            public void handleMessage(Message message) {
589                final int type = message.what;
590                switch (type) {
591                    case MESSAGE_ON_ACTION_TAP_AND_HOLD: {
592                        MotionEvent event = (MotionEvent) message.obj;
593                        final int policyFlags = message.arg1;
594                        onActionTapAndHold(event, policyFlags);
595                    } break;
596                    case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
597                        transitionToState(STATE_DELEGATING);
598                        sendDelayedMotionEvents();
599                        clear();
600                    } break;
601                    default: {
602                        throw new IllegalArgumentException("Unknown message type: " + type);
603                    }
604                }
605            }
606        };
607
608        public void onMotionEvent(MotionEvent event, int policyFlags) {
609            cacheDelayedMotionEvent(event, policyFlags);
610            final int action = event.getActionMasked();
611            switch (action) {
612                case MotionEvent.ACTION_DOWN: {
613                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
614                    if (!mViewport.getBounds().contains((int) event.getX(),
615                            (int) event.getY())) {
616                        transitionToDelegatingStateAndClear();
617                        return;
618                    }
619                    if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
620                            && GestureUtils.isMultiTap(mLastDownEvent, event,
621                                    mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
622                        Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
623                                policyFlags, 0, event);
624                        mHandler.sendMessageDelayed(message,
625                                ViewConfiguration.getLongPressTimeout());
626                    } else if (mTapCount < ACTION_TAP_COUNT) {
627                        Message message = mHandler.obtainMessage(
628                                MESSAGE_TRANSITION_TO_DELEGATING_STATE);
629                        mHandler.sendMessageDelayed(message, mTapTimeSlop + mMultiTapDistanceSlop);
630                    }
631                    clearLastDownEvent();
632                    mLastDownEvent = MotionEvent.obtain(event);
633                } break;
634                case MotionEvent.ACTION_POINTER_DOWN: {
635                    if (mMagnificationController.isMagnifying()) {
636                        transitionToState(STATE_DECIDE_PAN_OR_SCALE);
637                        clear();
638                    } else {
639                        transitionToDelegatingStateAndClear();
640                    }
641                } break;
642                case MotionEvent.ACTION_MOVE: {
643                    if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
644                        final double distance = GestureUtils.computeDistance(mLastDownEvent,
645                                event, 0);
646                        if (Math.abs(distance) > mTapDistanceSlop) {
647                            transitionToDelegatingStateAndClear();
648                        }
649                    }
650                } break;
651                case MotionEvent.ACTION_UP: {
652                    if (mLastDownEvent == null) {
653                        return;
654                    }
655                    mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
656                    if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
657                         transitionToDelegatingStateAndClear();
658                         return;
659                    }
660                    if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
661                            mTapDistanceSlop, 0)) {
662                        transitionToDelegatingStateAndClear();
663                        return;
664                    }
665                    if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent,
666                            event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
667                        transitionToDelegatingStateAndClear();
668                        return;
669                    }
670                    mTapCount++;
671                    if (DEBUG_DETECTING) {
672                        Slog.i(LOG_TAG, "Tap count:" + mTapCount);
673                    }
674                    if (mTapCount == ACTION_TAP_COUNT) {
675                        clear();
676                        onActionTap(event, policyFlags);
677                        return;
678                    }
679                    clearLastTapUpEvent();
680                    mLastTapUpEvent = MotionEvent.obtain(event);
681                } break;
682                case MotionEvent.ACTION_POINTER_UP: {
683                    /* do nothing */
684                } break;
685            }
686        }
687
688        public void clear() {
689            mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
690            mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
691            clearTapDetectionState();
692            clearDelayedMotionEvents();
693        }
694
695        private void clearTapDetectionState() {
696            mTapCount = 0;
697            clearLastTapUpEvent();
698            clearLastDownEvent();
699        }
700
701        private void clearLastTapUpEvent() {
702            if (mLastTapUpEvent != null) {
703                mLastTapUpEvent.recycle();
704                mLastTapUpEvent = null;
705            }
706        }
707
708        private void clearLastDownEvent() {
709            if (mLastDownEvent != null) {
710                mLastDownEvent.recycle();
711                mLastDownEvent = null;
712            }
713        }
714
715        private void cacheDelayedMotionEvent(MotionEvent event, int policyFlags) {
716            MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags);
717            if (mDelayedEventQueue == null) {
718                mDelayedEventQueue = info;
719            } else {
720                MotionEventInfo tail = mDelayedEventQueue;
721                while (tail.mNext != null) {
722                    tail = tail.mNext;
723                }
724                tail.mNext = info;
725            }
726        }
727
728        private void sendDelayedMotionEvents() {
729            while (mDelayedEventQueue != null) {
730                MotionEventInfo info = mDelayedEventQueue;
731                mDelayedEventQueue = info.mNext;
732                ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mPolicyFlags);
733                info.recycle();
734            }
735        }
736
737        private void clearDelayedMotionEvents() {
738            while (mDelayedEventQueue != null) {
739                MotionEventInfo info = mDelayedEventQueue;
740                mDelayedEventQueue = info.mNext;
741                info.recycle();
742            }
743        }
744
745        private void transitionToDelegatingStateAndClear() {
746            transitionToState(STATE_DELEGATING);
747            sendDelayedMotionEvents();
748            clear();
749        }
750
751        private void onActionTap(MotionEvent up, int policyFlags) {
752            if (DEBUG_DETECTING) {
753                Slog.i(LOG_TAG, "onActionTap()");
754            }
755            if (!mMagnificationController.isMagnifying()) {
756                mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
757                        up.getX(), up.getY(), true);
758                mViewport.setFrameShown(true, true);
759            } else {
760                mMagnificationController.reset(true);
761                mViewport.setFrameShown(false, true);
762            }
763        }
764
765        private void onActionTapAndHold(MotionEvent down, int policyFlags) {
766            if (DEBUG_DETECTING) {
767                Slog.i(LOG_TAG, "onActionTapAndHold()");
768            }
769            clear();
770            mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
771            mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
772                    down.getX(), down.getY(), true);
773            mViewport.setFrameShown(true, true);
774            transitionToState(STATE_VIEWPORT_DRAGGING);
775        }
776    }
777
778    private void persistScale(final float scale) {
779        new AsyncTask<Void, Void, Void>() {
780            @Override
781            protected Void doInBackground(Void... params) {
782                Settings.Secure.putFloat(mContext.getContentResolver(),
783                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale);
784                return null;
785            }
786        }.execute();
787    }
788
789    private float getPersistedScale() {
790        return Settings.Secure.getFloat(mContext.getContentResolver(),
791                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
792                DEFAULT_MAGNIFICATION_SCALE);
793    }
794
795    private static final class MotionEventInfo {
796
797        private static final int MAX_POOL_SIZE = 10;
798
799        private static final Object sLock = new Object();
800        private static MotionEventInfo sPool;
801        private static int sPoolSize;
802
803        private MotionEventInfo mNext;
804        private boolean mInPool;
805
806        public MotionEvent mEvent;
807        public int mPolicyFlags;
808
809        public static MotionEventInfo obtain(MotionEvent event, int policyFlags) {
810            synchronized (sLock) {
811                MotionEventInfo info;
812                if (sPoolSize > 0) {
813                    sPoolSize--;
814                    info = sPool;
815                    sPool = info.mNext;
816                    info.mNext = null;
817                    info.mInPool = false;
818                } else {
819                    info = new MotionEventInfo();
820                }
821                info.initialize(event, policyFlags);
822                return info;
823            }
824        }
825
826        private void initialize(MotionEvent event, int policyFlags) {
827            mEvent = MotionEvent.obtain(event);
828            mPolicyFlags = policyFlags;
829        }
830
831        public void recycle() {
832            synchronized (sLock) {
833                if (mInPool) {
834                    throw new IllegalStateException("Already recycled.");
835                }
836                clear();
837                if (sPoolSize < MAX_POOL_SIZE) {
838                    sPoolSize++;
839                    mNext = sPool;
840                    sPool = this;
841                    mInPool = true;
842                }
843            }
844        }
845
846        private void clear() {
847            mEvent.recycle();
848            mEvent = null;
849            mPolicyFlags = 0;
850        }
851    }
852
853    private static final class DisplayContentObserver {
854
855        private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
856        private static final int MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS = 2;
857        private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3;
858        private static final int MESSAGE_ON_WINDOW_TRANSITION = 4;
859        private static final int MESSAGE_ON_ROTATION_CHANGED = 5;
860
861        private final Handler mHandler = new MyHandler();
862
863        private final Rect mTempRect = new Rect();
864
865        private final IDisplayContentChangeListener mDisplayContentChangeListener;
866
867        private final Context mContext;
868        private final Viewport mViewport;
869        private final MagnificationController mMagnificationController;
870        private final IWindowManager mWindowManagerService;
871        private final DisplayProvider mDisplayProvider;
872        private final long mLongAnimationDuration;
873        private final float mWindowAnimationScale;
874
875        public DisplayContentObserver(Context context, Viewport viewport,
876                MagnificationController magnificationController,
877                IWindowManager windowManagerService, DisplayProvider displayProvider,
878                long longAnimationDuration, float windowAnimationScale) {
879            mContext = context;
880            mViewport = viewport;
881            mMagnificationController = magnificationController;
882            mWindowManagerService = windowManagerService;
883            mDisplayProvider = displayProvider;
884            mLongAnimationDuration = longAnimationDuration;
885            mWindowAnimationScale = windowAnimationScale;
886
887            mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() {
888                @Override
889                public void onWindowTransition(int displayId, int transition, WindowInfo info) {
890                    mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION, transition, 0,
891                            WindowInfo.obtain(info)).sendToTarget();
892                }
893
894                @Override
895                public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle,
896                        boolean immediate) {
897                    SomeArgs args = SomeArgs.obtain();
898                    args.argi1 = rectangle.left;
899                    args.argi2 = rectangle.top;
900                    args.argi3 = rectangle.right;
901                    args.argi4 = rectangle.bottom;
902                    mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0,
903                            immediate ? 1 : 0, args).sendToTarget();
904                }
905
906                @Override
907                public void onRotationChanged(int rotation) throws RemoteException {
908                    mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0)
909                            .sendToTarget();
910                }
911            };
912
913            try {
914                mWindowManagerService.addDisplayContentChangeListener(
915                        mDisplayProvider.getDisplay().getDisplayId(),
916                        mDisplayContentChangeListener);
917            } catch (RemoteException re) {
918                /* ignore */
919            }
920        }
921
922        public void destroy() {
923            try {
924                mWindowManagerService.removeDisplayContentChangeListener(
925                        mDisplayProvider.getDisplay().getDisplayId(),
926                        mDisplayContentChangeListener);
927            } catch (RemoteException re) {
928                /* ignore*/
929            }
930        }
931
932        private void handleOnRotationChanged(int rotation) {
933            if (DEBUG_ROTATION) {
934                Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation));
935            }
936            resetMagnificationIfNeeded();
937            mViewport.setFrameShown(false, false);
938            mViewport.rotationChanged();
939            mViewport.recomputeBounds(false);
940            if (mMagnificationController.isMagnifying()) {
941                final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
942                Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
943                mHandler.sendMessageDelayed(message, delay);
944            }
945        }
946
947        private void handleOnWindowTransition(int transition, WindowInfo info) {
948            if (DEBUG_WINDOW_TRANSITIONS) {
949                Slog.i(LOG_TAG, "Window transitioning: "
950                        + windowTransitionToString(transition));
951            }
952            try {
953                final boolean magnifying = mMagnificationController.isMagnifying();
954                if (magnifying) {
955                    switch (transition) {
956                        case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
957                        case WindowManagerPolicy.TRANSIT_TASK_OPEN:
958                        case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
959                        case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
960                        case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
961                        case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
962                            resetMagnificationIfNeeded();
963                        }
964                    }
965                }
966                if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
967                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
968                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
969                    switch (transition) {
970                        case WindowManagerPolicy.TRANSIT_ENTER:
971                        case WindowManagerPolicy.TRANSIT_SHOW:
972                        case WindowManagerPolicy.TRANSIT_EXIT:
973                        case WindowManagerPolicy.TRANSIT_HIDE: {
974                            mViewport.recomputeBounds(mMagnificationController.isMagnifying());
975                        } break;
976                    }
977                } else {
978                    switch (transition) {
979                        case WindowManagerPolicy.TRANSIT_ENTER:
980                        case WindowManagerPolicy.TRANSIT_SHOW: {
981                            if (!magnifying || !screenMagnificationAutoUpdateEnabled(mContext)) {
982                                break;
983                            }
984                            final int type = info.type;
985                            switch (type) {
986                                // TODO: Are these all the windows we want to make
987                                //       visible when they appear on the screen?
988                                //       Do we need to take some of them out?
989                                case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
990                                case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
991                                case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
992                                case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
993                                case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
994                                case WindowManager.LayoutParams.TYPE_PHONE:
995                                case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
996                                case WindowManager.LayoutParams.TYPE_TOAST:
997                                case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
998                                case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
999                                case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
1000                                case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
1001                                case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
1002                                case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
1003                                case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
1004                                    Rect magnifiedRegionBounds = mMagnificationController
1005                                            .getMagnifiedRegionBounds();
1006                                    Rect touchableRegion = info.touchableRegion;
1007                                    if (!magnifiedRegionBounds.intersect(touchableRegion)) {
1008                                        ensureRectangleInMagnifiedRegionBounds(
1009                                                magnifiedRegionBounds, touchableRegion);
1010                                    }
1011                                } break;
1012                            } break;
1013                        }
1014                    }
1015                }
1016            } finally {
1017                if (info != null) {
1018                    info.recycle();
1019                }
1020            }
1021        }
1022
1023        private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) {
1024            if (!mMagnificationController.isMagnifying()) {
1025                return;
1026            }
1027            Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds();
1028            if (magnifiedRegionBounds.contains(rectangle)) {
1029                return;
1030            }
1031            ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle);
1032        }
1033
1034        private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds,
1035                Rect rectangle) {
1036            if (!Rect.intersects(rectangle, mViewport.getBounds())) {
1037                return;
1038            }
1039            final float scrollX;
1040            final float scrollY;
1041            if (rectangle.width() > magnifiedRegionBounds.width()) {
1042                scrollX = rectangle.left - magnifiedRegionBounds.left;
1043            } else if (rectangle.left < magnifiedRegionBounds.left) {
1044                scrollX = rectangle.left - magnifiedRegionBounds.left;
1045            } else if (rectangle.right > magnifiedRegionBounds.right) {
1046                scrollX = rectangle.right - magnifiedRegionBounds.right;
1047            } else {
1048                scrollX = 0;
1049            }
1050            if (rectangle.height() > magnifiedRegionBounds.height()) {
1051                scrollY = rectangle.top - magnifiedRegionBounds.top;
1052            } else if (rectangle.top < magnifiedRegionBounds.top) {
1053                scrollY = rectangle.top - magnifiedRegionBounds.top;
1054            } else if (rectangle.bottom > magnifiedRegionBounds.bottom) {
1055                scrollY = rectangle.bottom - magnifiedRegionBounds.bottom;
1056            } else {
1057                scrollY = 0;
1058            }
1059            final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX()
1060                    + scrollX;
1061            final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY()
1062                    + scrollY;
1063            mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY,
1064                    true);
1065        }
1066
1067        private void resetMagnificationIfNeeded() {
1068            if (mMagnificationController.isMagnifying()
1069                    && screenMagnificationAutoUpdateEnabled(mContext)) {
1070                mMagnificationController.reset(true);
1071                mViewport.setFrameShown(false, true);
1072            }
1073        }
1074
1075        private boolean screenMagnificationAutoUpdateEnabled(Context context) {
1076            return (Settings.Secure.getInt(context.getContentResolver(),
1077                    Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
1078                    DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
1079        }
1080
1081        private String windowTransitionToString(int transition) {
1082            switch (transition) {
1083                case WindowManagerPolicy.TRANSIT_UNSET: {
1084                    return "TRANSIT_UNSET";
1085                }
1086                case WindowManagerPolicy.TRANSIT_NONE: {
1087                    return "TRANSIT_NONE";
1088                }
1089                case WindowManagerPolicy.TRANSIT_ENTER: {
1090                    return "TRANSIT_ENTER";
1091                }
1092                case WindowManagerPolicy.TRANSIT_EXIT: {
1093                    return "TRANSIT_EXIT";
1094                }
1095                case WindowManagerPolicy.TRANSIT_SHOW: {
1096                    return "TRANSIT_SHOW";
1097                }
1098                case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
1099                    return "TRANSIT_EXIT_MASK";
1100                }
1101                case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
1102                    return "TRANSIT_PREVIEW_DONE";
1103                }
1104                case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
1105                    return "TRANSIT_ACTIVITY_OPEN";
1106                }
1107                case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
1108                    return "TRANSIT_ACTIVITY_CLOSE";
1109                }
1110                case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
1111                    return "TRANSIT_TASK_OPEN";
1112                }
1113                case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
1114                    return "TRANSIT_TASK_CLOSE";
1115                }
1116                case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
1117                    return "TRANSIT_TASK_TO_FRONT";
1118                }
1119                case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
1120                    return "TRANSIT_TASK_TO_BACK";
1121                }
1122                case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
1123                    return "TRANSIT_WALLPAPER_CLOSE";
1124                }
1125                case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
1126                    return "TRANSIT_WALLPAPER_OPEN";
1127                }
1128                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
1129                    return "TRANSIT_WALLPAPER_INTRA_OPEN";
1130                }
1131                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
1132                    return "TRANSIT_WALLPAPER_INTRA_CLOSE";
1133                }
1134                default: {
1135                    return "<UNKNOWN>";
1136                }
1137            }
1138        }
1139
1140        private String rotationToString(int rotation) {
1141            switch (rotation) {
1142                case Surface.ROTATION_0: {
1143                    return "ROTATION_0";
1144                }
1145                case Surface.ROTATION_90: {
1146                    return "ROATATION_90";
1147                }
1148                case Surface.ROTATION_180: {
1149                    return "ROATATION_180";
1150                }
1151                case Surface.ROTATION_270: {
1152                    return "ROATATION_270";
1153                }
1154                default: {
1155                    throw new IllegalArgumentException("Invalid rotation: "
1156                        + rotation);
1157                }
1158            }
1159        }
1160
1161        private final class MyHandler extends Handler {
1162            @Override
1163            public void handleMessage(Message message) {
1164                final int action = message.what;
1165                switch (action) {
1166                    case MESSAGE_SHOW_VIEWPORT_FRAME: {
1167                        mViewport.setFrameShown(true, true);
1168                    } break;
1169                    case MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS: {
1170                        final boolean animate = message.arg1 == 1;
1171                        mViewport.recomputeBounds(animate);
1172                    } break;
1173                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
1174                        SomeArgs args = (SomeArgs) message.obj;
1175                        try {
1176                            mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4);
1177                            final boolean immediate = (message.arg1 == 1);
1178                            handleOnRectangleOnScreenRequested(mTempRect, immediate);
1179                        } finally {
1180                            args.recycle();
1181                        }
1182                    } break;
1183                    case MESSAGE_ON_WINDOW_TRANSITION: {
1184                        final int transition = message.arg1;
1185                        WindowInfo info = (WindowInfo) message.obj;
1186                        handleOnWindowTransition(transition, info);
1187                    } break;
1188                    case MESSAGE_ON_ROTATION_CHANGED: {
1189                        final int rotation = message.arg1;
1190                        handleOnRotationChanged(rotation);
1191                    } break;
1192                    default: {
1193                        throw new IllegalArgumentException("Unknown message: " + action);
1194                    }
1195                }
1196            }
1197        }
1198    }
1199
1200    private final class MagnificationController {
1201
1202        private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION =
1203                "accessibilityTransformation";
1204
1205        private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
1206
1207        private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
1208
1209        private final Rect mTempRect = new Rect();
1210
1211        private final ValueAnimator mTransformationAnimator;
1212
1213        public MagnificationController(int animationDuration) {
1214            Property<MagnificationController, MagnificationSpec> property =
1215                    Property.of(MagnificationController.class, MagnificationSpec.class,
1216                    PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION);
1217            TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
1218                private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec();
1219                @Override
1220                public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
1221                        MagnificationSpec toSpec) {
1222                    MagnificationSpec result = mTempTransformationSpec;
1223                    result.mScale = fromSpec.mScale
1224                            + (toSpec.mScale - fromSpec.mScale) * fraction;
1225                    result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX
1226                            + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX)
1227                            * fraction;
1228                    result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY
1229                            + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY)
1230                            * fraction;
1231                    result.mScaledOffsetX = fromSpec.mScaledOffsetX
1232                            + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX)
1233                            * fraction;
1234                    result.mScaledOffsetY = fromSpec.mScaledOffsetY
1235                            + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY)
1236                            * fraction;
1237                    return result;
1238                }
1239            };
1240            mTransformationAnimator = ObjectAnimator.ofObject(this, property,
1241                    evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
1242            mTransformationAnimator.setDuration((long) (animationDuration));
1243            mTransformationAnimator.setInterpolator(mInterpolator);
1244        }
1245
1246        public boolean isMagnifying() {
1247            return mCurrentMagnificationSpec.mScale > 1.0f;
1248        }
1249
1250        public void reset(boolean animate) {
1251            if (mTransformationAnimator.isRunning()) {
1252                mTransformationAnimator.cancel();
1253            }
1254            mCurrentMagnificationSpec.reset();
1255            if (animate) {
1256                animateAccessibilityTranformation(mSentMagnificationSpec,
1257                        mCurrentMagnificationSpec);
1258            } else {
1259                setAccessibilityTransformation(mCurrentMagnificationSpec);
1260            }
1261        }
1262
1263        public Rect getMagnifiedRegionBounds() {
1264            mTempRect.set(mViewport.getBounds());
1265            mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX,
1266                    (int) -mCurrentMagnificationSpec.mScaledOffsetY);
1267            mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale);
1268            return mTempRect;
1269        }
1270
1271        public float getScale() {
1272            return mCurrentMagnificationSpec.mScale;
1273        }
1274
1275        public float getMagnifiedRegionCenterX() {
1276            return mCurrentMagnificationSpec.mMagnifiedRegionCenterX;
1277        }
1278
1279        public float getMagnifiedRegionCenterY() {
1280            return mCurrentMagnificationSpec.mMagnifiedRegionCenterY;
1281        }
1282
1283        public float getScaledOffsetX() {
1284            return mCurrentMagnificationSpec.mScaledOffsetX;
1285        }
1286
1287        public float getScaledOffsetY() {
1288            return mCurrentMagnificationSpec.mScaledOffsetY;
1289        }
1290
1291        public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
1292            MagnificationSpec spec = mCurrentMagnificationSpec;
1293            final float oldScale = spec.mScale;
1294            final float oldCenterX = spec.mMagnifiedRegionCenterX;
1295            final float oldCenterY = spec.mMagnifiedRegionCenterY;
1296            final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale;
1297            final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale;
1298            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
1299            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
1300            final float centerX = normPivotX + offsetX;
1301            final float centerY = normPivotY + offsetY;
1302            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
1303        }
1304
1305        public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
1306            setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY,
1307                    animate);
1308        }
1309
1310        public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
1311                boolean animate) {
1312            if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0
1313                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX,
1314                            centerX) == 0
1315                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY,
1316                            centerY) == 0) {
1317                return;
1318            }
1319            if (mTransformationAnimator.isRunning()) {
1320                mTransformationAnimator.cancel();
1321            }
1322            if (DEBUG_MAGNIFICATION_CONTROLLER) {
1323                Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX
1324                        + " centerY: " + centerY);
1325            }
1326            mCurrentMagnificationSpec.initialize(scale, centerX, centerY);
1327            if (animate) {
1328                animateAccessibilityTranformation(mSentMagnificationSpec,
1329                        mCurrentMagnificationSpec);
1330            } else {
1331                setAccessibilityTransformation(mCurrentMagnificationSpec);
1332            }
1333        }
1334
1335        private void animateAccessibilityTranformation(MagnificationSpec fromSpec,
1336                MagnificationSpec toSpec) {
1337            mTransformationAnimator.setObjectValues(fromSpec, toSpec);
1338            mTransformationAnimator.start();
1339        }
1340
1341        @SuppressWarnings("unused")
1342        // Called from an animator.
1343        public MagnificationSpec getAccessibilityTransformation() {
1344            return mSentMagnificationSpec;
1345        }
1346
1347        public void setAccessibilityTransformation(MagnificationSpec transformation) {
1348            if (DEBUG_TRANSFORMATION) {
1349                Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale
1350                        + " offsetX: " + transformation.mScaledOffsetX
1351                        + " offsetY: " + transformation.mScaledOffsetY);
1352            }
1353            try {
1354                mSentMagnificationSpec.updateFrom(transformation);
1355                mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(),
1356                        transformation.mScale, transformation.mScaledOffsetX,
1357                        transformation.mScaledOffsetY);
1358            } catch (RemoteException re) {
1359                /* ignore */
1360            }
1361        }
1362
1363        private class MagnificationSpec {
1364
1365            private static final float DEFAULT_SCALE = 1.0f;
1366
1367            public float mScale = DEFAULT_SCALE;
1368
1369            public float mMagnifiedRegionCenterX;
1370
1371            public float mMagnifiedRegionCenterY;
1372
1373            public float mScaledOffsetX;
1374
1375            public float mScaledOffsetY;
1376
1377            public void initialize(float scale, float magnifiedRegionCenterX,
1378                    float magnifiedRegionCenterY) {
1379                mScale = scale;
1380
1381                final int viewportWidth = mViewport.getBounds().width();
1382                final int viewportHeight = mViewport.getBounds().height();
1383                final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale;
1384                final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale;
1385                final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX;
1386                final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY;
1387
1388                mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX,
1389                        minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX);
1390                mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY,
1391                        minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY);
1392
1393                mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2);
1394                mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2);
1395            }
1396
1397            public void updateFrom(MagnificationSpec other) {
1398                mScale = other.mScale;
1399                mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX;
1400                mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY;
1401                mScaledOffsetX = other.mScaledOffsetX;
1402                mScaledOffsetY = other.mScaledOffsetY;
1403            }
1404
1405            public void reset() {
1406                mScale = DEFAULT_SCALE;
1407                mMagnifiedRegionCenterX = 0;
1408                mMagnifiedRegionCenterY = 0;
1409                mScaledOffsetX = 0;
1410                mScaledOffsetY = 0;
1411            }
1412        }
1413    }
1414
1415    private static final class Viewport {
1416
1417        private static final String PROPERTY_NAME_ALPHA = "alpha";
1418
1419        private static final String PROPERTY_NAME_BOUNDS = "bounds";
1420
1421        private static final int MIN_ALPHA = 0;
1422
1423        private static final int MAX_ALPHA = 255;
1424
1425        private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
1426
1427        private final Rect mTempRect = new Rect();
1428
1429        private final IWindowManager mWindowManagerService;
1430        private final DisplayProvider mDisplayProvider;
1431
1432        private final ViewportWindow mViewportFrame;
1433
1434        private final ValueAnimator mResizeFrameAnimator;
1435
1436        private final ValueAnimator mShowHideFrameAnimator;
1437
1438        public Viewport(Context context, WindowManager windowManager,
1439                IWindowManager windowManagerService, DisplayProvider displayInfoProvider,
1440                Interpolator animationInterpolator, long animationDuration) {
1441            mWindowManagerService = windowManagerService;
1442            mDisplayProvider = displayInfoProvider;
1443            mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider);
1444
1445            mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
1446                  MIN_ALPHA, MAX_ALPHA);
1447            mShowHideFrameAnimator.setInterpolator(animationInterpolator);
1448            mShowHideFrameAnimator.setDuration(animationDuration);
1449            mShowHideFrameAnimator.addListener(new AnimatorListener() {
1450                @Override
1451                public void onAnimationEnd(Animator animation) {
1452                    if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
1453                        mViewportFrame.hide();
1454                    }
1455                }
1456                @Override
1457                public void onAnimationStart(Animator animation) {
1458                    /* do nothing - stub */
1459                }
1460                @Override
1461                public void onAnimationCancel(Animator animation) {
1462                    /* do nothing - stub */
1463                }
1464                @Override
1465                public void onAnimationRepeat(Animator animation) {
1466                    /* do nothing - stub */
1467                }
1468            });
1469
1470            Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class,
1471                    Rect.class, PROPERTY_NAME_BOUNDS);
1472            TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() {
1473                private final Rect mReusableResultRect = new Rect();
1474                @Override
1475                public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) {
1476                    Rect result = mReusableResultRect;
1477                    result.left = (int) (fromFrame.left
1478                            + (toFrame.left - fromFrame.left) * fraction);
1479                    result.top = (int) (fromFrame.top
1480                            + (toFrame.top - fromFrame.top) * fraction);
1481                    result.right = (int) (fromFrame.right
1482                            + (toFrame.right - fromFrame.right) * fraction);
1483                    result.bottom = (int) (fromFrame.bottom
1484                            + (toFrame.bottom - fromFrame.bottom) * fraction);
1485                    return result;
1486                }
1487            };
1488            mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
1489                    evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
1490            mResizeFrameAnimator.setDuration((long) (animationDuration));
1491            mResizeFrameAnimator.setInterpolator(animationInterpolator);
1492
1493            recomputeBounds(false);
1494        }
1495
1496        public void recomputeBounds(boolean animate) {
1497            Rect frame = mTempRect;
1498            frame.set(0, 0, mDisplayProvider.getDisplayInfo().logicalWidth,
1499                    mDisplayProvider.getDisplayInfo().logicalHeight);
1500            ArrayList<WindowInfo> infos = mTempWindowInfoList;
1501            infos.clear();
1502            try {
1503                mWindowManagerService.getVisibleWindowsForDisplay(
1504                        mDisplayProvider.getDisplay().getDisplayId(), infos);
1505                final int windowCount = infos.size();
1506                for (int i = 0; i < windowCount; i++) {
1507                    WindowInfo info = infos.get(i);
1508                    if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
1509                            || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
1510                            || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
1511                        subtract(frame, info.touchableRegion);
1512                    }
1513                    info.recycle();
1514                }
1515            } catch (RemoteException re) {
1516                /* ignore */
1517            } finally {
1518                infos.clear();
1519            }
1520            resize(frame, animate);
1521        }
1522
1523        public void rotationChanged() {
1524            mViewportFrame.rotationChanged();
1525        }
1526
1527        public Rect getBounds() {
1528            return mViewportFrame.getBounds();
1529        }
1530
1531        public void setFrameShown(boolean shown, boolean animate) {
1532            if (mViewportFrame.isShown() == shown) {
1533                return;
1534            }
1535            if (animate) {
1536                if (mShowHideFrameAnimator.isRunning()) {
1537                    mShowHideFrameAnimator.reverse();
1538                } else {
1539                    if (shown) {
1540                        mViewportFrame.show();
1541                        mShowHideFrameAnimator.start();
1542                    } else {
1543                        mShowHideFrameAnimator.reverse();
1544                    }
1545                }
1546            } else {
1547                mShowHideFrameAnimator.cancel();
1548                if (shown) {
1549                    mViewportFrame.show();
1550                } else {
1551                    mViewportFrame.hide();
1552                }
1553            }
1554        }
1555
1556        private void resize(Rect bounds, boolean animate) {
1557            if (mViewportFrame.getBounds().equals(bounds)) {
1558                return;
1559            }
1560            if (animate) {
1561                if (mResizeFrameAnimator.isRunning()) {
1562                    mResizeFrameAnimator.cancel();
1563                }
1564                mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
1565                mResizeFrameAnimator.start();
1566            } else {
1567                mViewportFrame.setBounds(bounds);
1568            }
1569        }
1570
1571        private boolean subtract(Rect lhs, Rect rhs) {
1572            if (lhs.right < rhs.left || lhs.left  > rhs.right
1573                    || lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
1574                return false;
1575            }
1576            if (lhs.left < rhs.left) {
1577                lhs.right = rhs.left;
1578            }
1579            if (lhs.top < rhs.top) {
1580                lhs.bottom = rhs.top;
1581            }
1582            if (lhs.right > rhs.right) {
1583                lhs.left = rhs.right;
1584            }
1585            if (lhs.bottom > rhs.bottom) {
1586                lhs.top = rhs.bottom;
1587            }
1588            return true;
1589        }
1590
1591        private static final class ViewportWindow {
1592            private static final String WINDOW_TITLE = "Magnification Overlay";
1593
1594            private final WindowManager mWindowManager;
1595            private final DisplayProvider mDisplayProvider;
1596
1597            private final ContentView mWindowContent;
1598            private final WindowManager.LayoutParams mWindowParams;
1599
1600            private final Rect mBounds = new Rect();
1601            private boolean mShown;
1602            private int mAlpha;
1603
1604            public ViewportWindow(Context context, WindowManager windowManager,
1605                    DisplayProvider displayProvider) {
1606                mWindowManager = windowManager;
1607                mDisplayProvider = displayProvider;
1608
1609                ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
1610                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
1611                mWindowContent = new ContentView(context);
1612                mWindowContent.setLayoutParams(contentParams);
1613                mWindowContent.setBackgroundColor(R.color.transparent);
1614
1615                mWindowParams = new WindowManager.LayoutParams(
1616                        WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
1617                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1618                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1619                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1620                mWindowParams.setTitle(WINDOW_TITLE);
1621                mWindowParams.gravity = Gravity.CENTER;
1622                mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth;
1623                mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight;
1624                mWindowParams.format = PixelFormat.TRANSLUCENT;
1625            }
1626
1627            public boolean isShown() {
1628                return mShown;
1629            }
1630
1631            public void show() {
1632                if (mShown) {
1633                    return;
1634                }
1635                mShown = true;
1636                mWindowManager.addView(mWindowContent, mWindowParams);
1637                if (DEBUG_VIEWPORT_WINDOW) {
1638                    Slog.i(LOG_TAG, "ViewportWindow shown.");
1639                }
1640            }
1641
1642            public void hide() {
1643                if (!mShown) {
1644                    return;
1645                }
1646                mShown = false;
1647                mWindowManager.removeView(mWindowContent);
1648                if (DEBUG_VIEWPORT_WINDOW) {
1649                    Slog.i(LOG_TAG, "ViewportWindow hidden.");
1650                }
1651            }
1652
1653            @SuppressWarnings("unused")
1654            // Called reflectively from an animator.
1655            public int getAlpha() {
1656                return mAlpha;
1657            }
1658
1659            @SuppressWarnings("unused")
1660            // Called reflectively from an animator.
1661            public void setAlpha(int alpha) {
1662                if (mAlpha == alpha) {
1663                    return;
1664                }
1665                mAlpha = alpha;
1666                if (mShown) {
1667                    mWindowContent.invalidate();
1668                }
1669                if (DEBUG_VIEWPORT_WINDOW) {
1670                    Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
1671                }
1672            }
1673
1674            public Rect getBounds() {
1675                return mBounds;
1676            }
1677
1678            public void rotationChanged() {
1679                mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth;
1680                mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight;
1681                if (mShown) {
1682                    mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
1683                }
1684            }
1685
1686            public void setBounds(Rect bounds) {
1687                if (mBounds.equals(bounds)) {
1688                    return;
1689                }
1690                mBounds.set(bounds);
1691                if (mShown) {
1692                    mWindowContent.invalidate();
1693                }
1694                if (DEBUG_VIEWPORT_WINDOW) {
1695                    Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
1696                }
1697            }
1698
1699            private final class ContentView extends View {
1700                private final Drawable mHighlightFrame;
1701
1702                public ContentView(Context context) {
1703                    super(context);
1704                    mHighlightFrame = context.getResources().getDrawable(
1705                            R.drawable.magnified_region_frame);
1706                }
1707
1708                @Override
1709                public void onDraw(Canvas canvas) {
1710                    canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
1711                    mHighlightFrame.setBounds(mBounds);
1712                    mHighlightFrame.setAlpha(mAlpha);
1713                    mHighlightFrame.draw(canvas);
1714                }
1715            }
1716        }
1717    }
1718
1719    private static class DisplayProvider implements DisplayListener {
1720        private final WindowManager mWindowManager;
1721        private final DisplayManager mDisplayManager;
1722        private final Display mDefaultDisplay;
1723        private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
1724
1725        public DisplayProvider(Context context, WindowManager windowManager) {
1726            mWindowManager = windowManager;
1727            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1728            mDefaultDisplay = mWindowManager.getDefaultDisplay();
1729            mDisplayManager.registerDisplayListener(this, null);
1730            updateDisplayInfo();
1731        }
1732
1733        public DisplayInfo getDisplayInfo() {
1734            return mDefaultDisplayInfo;
1735        }
1736
1737        public Display getDisplay() {
1738            return mDefaultDisplay;
1739        }
1740
1741        private void updateDisplayInfo() {
1742            if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
1743                Slog.e(LOG_TAG, "Default display is not valid.");
1744            }
1745        }
1746
1747        public void destroy() {
1748            mDisplayManager.unregisterDisplayListener(this);
1749        }
1750
1751        @Override
1752        public void onDisplayAdded(int displayId) {
1753            /* do noting */
1754        }
1755
1756        @Override
1757        public void onDisplayRemoved(int displayId) {
1758            // Having no default display
1759        }
1760
1761        @Override
1762        public void onDisplayChanged(int displayId) {
1763            updateDisplayInfo();
1764        }
1765    }
1766}
1767