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