ScreenMagnifier.java revision 9b4125e435b6bc8f8bd2f6e569d9b0b296ab16ef
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.PointF;
32import android.graphics.PorterDuff.Mode;
33import android.graphics.Rect;
34import android.graphics.drawable.Drawable;
35import android.hardware.display.DisplayManager;
36import android.hardware.display.DisplayManager.DisplayListener;
37import android.os.AsyncTask;
38import android.os.Handler;
39import android.os.Message;
40import android.os.RemoteException;
41import android.os.ServiceManager;
42import android.provider.Settings;
43import android.util.MathUtils;
44import android.util.Property;
45import android.util.Slog;
46import android.view.Display;
47import android.view.DisplayInfo;
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.SimpleOnScaleGestureListener;
55import android.view.Surface;
56import android.view.View;
57import android.view.ViewConfiguration;
58import android.view.ViewGroup;
59import android.view.WindowInfo;
60import android.view.WindowManager;
61import android.view.WindowManagerPolicy;
62import android.view.accessibility.AccessibilityEvent;
63import android.view.animation.DecelerateInterpolator;
64import android.view.animation.Interpolator;
65
66import com.android.internal.R;
67import com.android.internal.os.SomeArgs;
68
69import java.util.ArrayList;
70import java.util.Collections;
71import java.util.Comparator;
72
73/**
74 * This class handles the screen magnification when accessibility is enabled.
75 * The behavior is as follows:
76 *
77 * 1. Triple tap toggles permanent screen magnification which is magnifying
78 *    the area around the location of the triple tap. One can think of the
79 *    location of the triple tap as the center of the magnified viewport.
80 *    For example, a triple tap when not magnified would magnify the screen
81 *    and leave it in a magnified state. A triple tapping when magnified would
82 *    clear magnification and leave the screen in a not magnified state.
83 *
84 * 2. Triple tap and hold would magnify the screen if not magnified and enable
85 *    viewport dragging mode until the finger goes up. One can think of this
86 *    mode as a way to move the magnified viewport since the area around the
87 *    moving finger will be magnified to fit the screen. For example, if the
88 *    screen was not magnified and the user triple taps and holds the screen
89 *    would magnify and the viewport will follow the user's finger. When the
90 *    finger goes up the screen will clear zoom out. If the same user interaction
91 *    is performed when the screen is magnified, the viewport movement will
92 *    be the same but when the finger goes up the screen will stay magnified.
93 *    In other words, the initial magnified state is sticky.
94 *
95 * 3. Pinching with any number of additional fingers when viewport dragging
96 *    is enabled, i.e. the user triple tapped and holds, would adjust the
97 *    magnification scale which will become the current default magnification
98 *    scale. The next time the user magnifies the same magnification scale
99 *    would be used.
100 *
101 * 4. When in a permanent magnified state the user can use two or more fingers
102 *    to pan the viewport. Note that in this mode the content is panned as
103 *    opposed to the viewport dragging mode in which the viewport is moved.
104 *
105 * 5. When in a permanent magnified state the user can use three or more
106 *    fingers to change the magnification scale which will become the current
107 *    default magnification scale. The next time the user magnifies the same
108 *    magnification scale would be used.
109 *
110 * 6. The magnification scale will be persisted in settings and in the cloud.
111 */
112public final class ScreenMagnifier implements EventStreamTransformation {
113
114    private static final boolean DEBUG_STATE_TRANSITIONS = false;
115    private static final boolean DEBUG_DETECTING = false;
116    private static final boolean DEBUG_TRANSFORMATION = false;
117    private static final boolean DEBUG_PANNING = false;
118    private static final boolean DEBUG_SCALING = false;
119    private static final boolean DEBUG_VIEWPORT_WINDOW = false;
120    private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
121    private static final boolean DEBUG_ROTATION = false;
122    private static final boolean DEBUG_GESTURE_DETECTOR = 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 GestureDetector mGestureDetector;
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        mGestureDetector = new GestureDetector(context);
200
201        transitionToState(STATE_DETECTING);
202    }
203
204    @Override
205    public void onMotionEvent(MotionEvent event, int policyFlags) {
206        mGestureDetector.onMotionEvent(event);
207        switch (mCurrentState) {
208            case STATE_DELEGATING: {
209                handleMotionEventStateDelegating(event, policyFlags);
210            } break;
211            case STATE_DETECTING: {
212                mDetectingStateHandler.onMotionEvent(event, policyFlags);
213            } break;
214            case STATE_VIEWPORT_DRAGGING: {
215                mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
216            } break;
217            case STATE_MAGNIFIED_INTERACTION: {
218                // Handled by the gesture detector. Since the detector
219                // needs all touch events to work properly we cannot
220                // call it only for this state.
221            } break;
222            default: {
223                throw new IllegalStateException("Unknown state: " + mCurrentState);
224            }
225        }
226    }
227
228    @Override
229    public void onAccessibilityEvent(AccessibilityEvent event) {
230        if (mNext != null) {
231            mNext.onAccessibilityEvent(event);
232        }
233    }
234
235    @Override
236    public void setNext(EventStreamTransformation next) {
237        mNext = next;
238    }
239
240    @Override
241    public void clear() {
242        mCurrentState = STATE_DETECTING;
243        mDetectingStateHandler.clear();
244        mStateViewportDraggingHandler.clear();
245        mGestureDetector.clear();
246        if (mNext != null) {
247            mNext.clear();
248        }
249    }
250
251    @Override
252    public void onDestroy() {
253        mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f,
254                0, 0, true);
255        mViewport.setFrameShown(false, true);
256        mDisplayProvider.destroy();
257        mDisplayContentObserver.destroy();
258        mScreenStateObserver.destroy();
259    }
260
261    private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
262        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
263            if (mDetectingStateHandler.mDelayedEventQueue == null) {
264                transitionToState(STATE_DETECTING);
265            }
266        }
267        if (mNext != null) {
268            // If the event is within the magnified portion of the screen we have
269            // to change its location to be where the user thinks he is poking the
270            // UI which may have been magnified and panned.
271            final float eventX = event.getX();
272            final float eventY = event.getY();
273            if (mMagnificationController.isMagnifying()
274                    && mViewport.getBounds().contains((int) eventX, (int) eventY)) {
275                final float scale = mMagnificationController.getScale();
276                final float scaledOffsetX = mMagnificationController.getScaledOffsetX();
277                final float scaledOffsetY = mMagnificationController.getScaledOffsetY();
278                final int pointerCount = event.getPointerCount();
279                PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
280                PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
281                for (int i = 0; i < pointerCount; i++) {
282                    event.getPointerCoords(i, coords[i]);
283                    coords[i].x = (coords[i].x - scaledOffsetX) / scale;
284                    coords[i].y = (coords[i].y - scaledOffsetY) / scale;
285                    event.getPointerProperties(i, properties[i]);
286                }
287                event = MotionEvent.obtain(event.getDownTime(),
288                        event.getEventTime(), event.getAction(), pointerCount, properties,
289                        coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
290                        event.getFlags());
291            }
292            mNext.onMotionEvent(event, policyFlags);
293        }
294    }
295
296    private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
297        final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
298        if (oldSize < size) {
299            PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
300            mTempPointerCoords = new PointerCoords[size];
301            if (oldTempPointerCoords != null) {
302                System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
303            }
304        }
305        for (int i = oldSize; i < size; i++) {
306            mTempPointerCoords[i] = new PointerCoords();
307        }
308        return mTempPointerCoords;
309    }
310
311    private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
312        final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
313        if (oldSize < size) {
314            PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
315            mTempPointerProperties = new PointerProperties[size];
316            if (oldTempPointerProperties != null) {
317                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
318            }
319        }
320        for (int i = oldSize; i < size; i++) {
321            mTempPointerProperties[i] = new PointerProperties();
322        }
323        return mTempPointerProperties;
324    }
325
326    private void transitionToState(int state) {
327        if (DEBUG_STATE_TRANSITIONS) {
328            switch (state) {
329                case STATE_DELEGATING: {
330                    Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
331                } break;
332                case STATE_DETECTING: {
333                    Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
334                } break;
335                case STATE_VIEWPORT_DRAGGING: {
336                    Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
337                } break;
338                case STATE_MAGNIFIED_INTERACTION: {
339                    Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
340                } break;
341                default: {
342                    throw new IllegalArgumentException("Unknown state: " + state);
343                }
344            }
345        }
346        mPreviousState = mCurrentState;
347        mCurrentState = state;
348    }
349
350    private final class GestureDetector implements OnScaleGestureListener {
351        private static final float MIN_SCALE = 1.3f;
352        private static final float MAX_SCALE = 5.0f;
353
354        private static final float DETECT_SCALING_THRESHOLD = 0.30f;
355        private static final int DETECT_PANNING_THRESHOLD_DIP = 30;
356
357        private final float mScaledDetectPanningThreshold;
358
359        private final ScaleGestureDetector mScaleGestureDetector;
360
361        private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN);
362        private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN);
363
364        private float mCurrScale = Float.NaN;
365        private float mCurrScaleFactor = 1.0f;
366        private float mPrevScaleFactor = 1.0f;
367        private float mCurrPan;
368        private float mPrevPan;
369
370        private float mScaleFocusX = Float.NaN;
371        private float mScaleFocusY = Float.NaN;
372
373        private boolean mScaling;
374        private boolean mPanning;
375
376        public GestureDetector(Context context) {
377            final float density = context.getResources().getDisplayMetrics().density;
378            mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
379            mScaleGestureDetector = new ScaleGestureDetector(this);
380        }
381
382        public void onMotionEvent(MotionEvent event) {
383            mScaleGestureDetector.onTouchEvent(event);
384            switch (mCurrentState) {
385                case STATE_DETECTING:
386                case STATE_DELEGATING:
387                case STATE_VIEWPORT_DRAGGING: {
388                    return;
389                }
390            }
391            if (event.getActionMasked() == MotionEvent.ACTION_UP) {
392                clear();
393                final float scale = mMagnificationController.getScale();
394                if (scale != getPersistedScale()) {
395                    persistScale(scale);
396                }
397                if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
398                    transitionToState(STATE_VIEWPORT_DRAGGING);
399                } else {
400                    transitionToState(STATE_DETECTING);
401                }
402            }
403        }
404
405        @Override
406        public boolean onScale(ScaleGestureDetector detector) {
407            switch (mCurrentState) {
408                case STATE_DETECTING:
409                case STATE_DELEGATING:
410                case STATE_VIEWPORT_DRAGGING: {
411                    return true;
412                }
413                case STATE_MAGNIFIED_INTERACTION: {
414                    mCurrScaleFactor = mScaleGestureDetector.getScaleFactor();
415                    final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor);
416                    if (DEBUG_GESTURE_DETECTOR) {
417                        Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta);
418                    }
419                    if (!mScaling && scaleDelta > DETECT_SCALING_THRESHOLD) {
420                        mScaling = true;
421                        clearContextualState();
422                        return true;
423                    }
424                    if (mScaling) {
425                        performScale(detector);
426                    }
427                    mCurrPan = (float) MathUtils.dist(
428                            mScaleGestureDetector.getFocusX(),
429                            mScaleGestureDetector.getFocusY(),
430                            mInitialFocus.x, mInitialFocus.y);
431                    final float panDelta = mCurrPan + mPrevPan;
432                    if (DEBUG_GESTURE_DETECTOR) {
433                        Slog.i(LOG_TAG, "panDelta: " + panDelta);
434                    }
435                    if (!mPanning && panDelta > mScaledDetectPanningThreshold) {
436                        mPanning = true;
437                        clearContextualState();
438                        return true;
439                    }
440                    if (mPanning) {
441                        performPan(detector);
442                    }
443                } break;
444            }
445            return false;
446        }
447
448        @Override
449        public boolean onScaleBegin(ScaleGestureDetector detector) {
450            mPrevScaleFactor *= mCurrScaleFactor;
451            mCurrScale = Float.NaN;
452            mPrevPan += mCurrPan;
453            mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
454            mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
455            return true;
456        }
457
458        @Override
459        public void onScaleEnd(ScaleGestureDetector detector) {
460            clearContextualState();
461        }
462
463        public void clear() {
464            clearContextualState();
465            mScaling = false;
466            mPanning = false;
467        }
468
469        private void clearContextualState() {
470            mCurrScaleFactor = 1.0f;
471            mPrevScaleFactor = 1.0f;
472            mPrevPan = 0;
473            mCurrPan = 0;
474            mInitialFocus.set(Float.NaN, Float.NaN);
475            mPrevFocus.set(Float.NaN, Float.NaN);
476            mCurrScale = Float.NaN;
477            mScaleFocusX = Float.NaN;
478            mScaleFocusY = Float.NaN;
479        }
480
481        private void performPan(ScaleGestureDetector detector) {
482            if (Float.compare(mPrevFocus.x, Float.NaN) == 0
483                    && Float.compare(mPrevFocus.y, Float.NaN) == 0) {
484                mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
485                return;
486            }
487            final float scale = mMagnificationController.getScale();
488            final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale;
489            final float scrollY = (detector.getFocusY() - mPrevFocus.y) / scale;
490            final float centerX = mMagnificationController.getMagnifiedRegionCenterX()
491                    - scrollX;
492            final float centerY = mMagnificationController.getMagnifiedRegionCenterY()
493                    - scrollY;
494            if (DEBUG_PANNING) {
495                Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
496                        + " scrollY: " + scrollY);
497            }
498            mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false);
499            mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
500        }
501
502        private void performScale(ScaleGestureDetector detector) {
503            if (Float.compare(mCurrScale, Float.NaN) == 0) {
504                mCurrScale = mMagnificationController.getScale();
505                return;
506            }
507            final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor();
508            final float newScale = mCurrScale * totalScaleFactor;
509            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE),
510                    MAX_SCALE);
511            if (DEBUG_SCALING) {
512                Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
513            }
514            if (Float.compare(mScaleFocusX, Float.NaN) == 0
515                    && Float.compare(mScaleFocusY, Float.NaN) == 0) {
516                mScaleFocusX = detector.getFocusX();
517                mScaleFocusY = detector.getFocusY();
518            }
519            mMagnificationController.setScale(normalizedNewScale, mScaleFocusX,
520                    mScaleFocusY, false);
521        }
522    }
523
524    private final class StateViewportDraggingHandler {
525        private boolean mLastMoveOutsideMagnifiedRegion;
526
527        private void onMotionEvent(MotionEvent event, int policyFlags) {
528            final int action = event.getActionMasked();
529            switch (action) {
530                case MotionEvent.ACTION_DOWN: {
531                    throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN");
532                }
533                case MotionEvent.ACTION_POINTER_DOWN: {
534                    clear();
535                    transitionToState(STATE_MAGNIFIED_INTERACTION);
536                } break;
537                case MotionEvent.ACTION_MOVE: {
538                    if (event.getPointerCount() != 1) {
539                        throw new IllegalStateException("Should have one pointer down.");
540                    }
541                    final float eventX = event.getX();
542                    final float eventY = event.getY();
543                    if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
544                        if (mLastMoveOutsideMagnifiedRegion) {
545                            mLastMoveOutsideMagnifiedRegion = false;
546                            mMagnificationController.setMagnifiedRegionCenter(eventX,
547                                    eventY, true);
548                        } else {
549                            mMagnificationController.setMagnifiedRegionCenter(eventX,
550                                    eventY, false);
551                        }
552                    } else {
553                        mLastMoveOutsideMagnifiedRegion = true;
554                    }
555                } break;
556                case MotionEvent.ACTION_UP: {
557                    if (!mTranslationEnabledBeforePan) {
558                        mMagnificationController.reset(true);
559                        mViewport.setFrameShown(false, true);
560                    }
561                    clear();
562                    transitionToState(STATE_DETECTING);
563                } break;
564                case MotionEvent.ACTION_POINTER_UP: {
565                    throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
566                }
567            }
568        }
569
570        public void clear() {
571            mLastMoveOutsideMagnifiedRegion = false;
572        }
573    }
574
575    private final class DetectingStateHandler {
576
577        private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
578
579        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
580
581        private static final int ACTION_TAP_COUNT = 3;
582
583        private MotionEventInfo mDelayedEventQueue;
584
585        private MotionEvent mLastDownEvent;
586        private MotionEvent mLastTapUpEvent;
587        private int mTapCount;
588
589        private final Handler mHandler = new Handler() {
590            @Override
591            public void handleMessage(Message message) {
592                final int type = message.what;
593                switch (type) {
594                    case MESSAGE_ON_ACTION_TAP_AND_HOLD: {
595                        MotionEvent event = (MotionEvent) message.obj;
596                        final int policyFlags = message.arg1;
597                        onActionTapAndHold(event, policyFlags);
598                    } break;
599                    case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
600                        transitionToState(STATE_DELEGATING);
601                        sendDelayedMotionEvents();
602                        clear();
603                    } break;
604                    default: {
605                        throw new IllegalArgumentException("Unknown message type: " + type);
606                    }
607                }
608            }
609        };
610
611        public void onMotionEvent(MotionEvent event, int policyFlags) {
612            cacheDelayedMotionEvent(event, policyFlags);
613            final int action = event.getActionMasked();
614            switch (action) {
615                case MotionEvent.ACTION_DOWN: {
616                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
617                    if (!mViewport.getBounds().contains((int) event.getX(),
618                            (int) event.getY())) {
619                        transitionToDelegatingStateAndClear();
620                        return;
621                    }
622                    if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
623                            && GestureUtils.isMultiTap(mLastDownEvent, event,
624                                    mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
625                        Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
626                                policyFlags, 0, event);
627                        mHandler.sendMessageDelayed(message,
628                                ViewConfiguration.getLongPressTimeout());
629                    } else if (mTapCount < ACTION_TAP_COUNT) {
630                        Message message = mHandler.obtainMessage(
631                                MESSAGE_TRANSITION_TO_DELEGATING_STATE);
632                        mHandler.sendMessageDelayed(message, mMultiTapTimeSlop);
633                    }
634                    clearLastDownEvent();
635                    mLastDownEvent = MotionEvent.obtain(event);
636                } break;
637                case MotionEvent.ACTION_POINTER_DOWN: {
638                    if (mMagnificationController.isMagnifying()) {
639                        transitionToState(STATE_MAGNIFIED_INTERACTION);
640                        clear();
641                    } else {
642                        transitionToDelegatingStateAndClear();
643                    }
644                } break;
645                case MotionEvent.ACTION_MOVE: {
646                    if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
647                        final double distance = GestureUtils.computeDistance(mLastDownEvent,
648                                event, 0);
649                        if (Math.abs(distance) > mTapDistanceSlop) {
650                            transitionToDelegatingStateAndClear();
651                        }
652                    }
653                } break;
654                case MotionEvent.ACTION_UP: {
655                    if (mLastDownEvent == null) {
656                        return;
657                    }
658                    mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
659                    if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
660                         transitionToDelegatingStateAndClear();
661                         return;
662                    }
663                    if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
664                            mTapDistanceSlop, 0)) {
665                        transitionToDelegatingStateAndClear();
666                        return;
667                    }
668                    if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent,
669                            event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
670                        transitionToDelegatingStateAndClear();
671                        return;
672                    }
673                    mTapCount++;
674                    if (DEBUG_DETECTING) {
675                        Slog.i(LOG_TAG, "Tap count:" + mTapCount);
676                    }
677                    if (mTapCount == ACTION_TAP_COUNT) {
678                        clear();
679                        onActionTap(event, policyFlags);
680                        return;
681                    }
682                    clearLastTapUpEvent();
683                    mLastTapUpEvent = MotionEvent.obtain(event);
684                } break;
685                case MotionEvent.ACTION_POINTER_UP: {
686                    /* do nothing */
687                } break;
688            }
689        }
690
691        public void clear() {
692            mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
693            mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
694            clearTapDetectionState();
695            clearDelayedMotionEvents();
696        }
697
698        private void clearTapDetectionState() {
699            mTapCount = 0;
700            clearLastTapUpEvent();
701            clearLastDownEvent();
702        }
703
704        private void clearLastTapUpEvent() {
705            if (mLastTapUpEvent != null) {
706                mLastTapUpEvent.recycle();
707                mLastTapUpEvent = null;
708            }
709        }
710
711        private void clearLastDownEvent() {
712            if (mLastDownEvent != null) {
713                mLastDownEvent.recycle();
714                mLastDownEvent = null;
715            }
716        }
717
718        private void cacheDelayedMotionEvent(MotionEvent event, int policyFlags) {
719            MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags);
720            if (mDelayedEventQueue == null) {
721                mDelayedEventQueue = info;
722            } else {
723                MotionEventInfo tail = mDelayedEventQueue;
724                while (tail.mNext != null) {
725                    tail = tail.mNext;
726                }
727                tail.mNext = info;
728            }
729        }
730
731        private void sendDelayedMotionEvents() {
732            while (mDelayedEventQueue != null) {
733                MotionEventInfo info = mDelayedEventQueue;
734                mDelayedEventQueue = info.mNext;
735                ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mPolicyFlags);
736                info.recycle();
737            }
738        }
739
740        private void clearDelayedMotionEvents() {
741            while (mDelayedEventQueue != null) {
742                MotionEventInfo info = mDelayedEventQueue;
743                mDelayedEventQueue = info.mNext;
744                info.recycle();
745            }
746        }
747
748        private void transitionToDelegatingStateAndClear() {
749            transitionToState(STATE_DELEGATING);
750            sendDelayedMotionEvents();
751            clear();
752        }
753
754        private void onActionTap(MotionEvent up, int policyFlags) {
755            if (DEBUG_DETECTING) {
756                Slog.i(LOG_TAG, "onActionTap()");
757            }
758            if (!mMagnificationController.isMagnifying()) {
759                mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
760                        up.getX(), up.getY(), true);
761                mViewport.setFrameShown(true, true);
762            } else {
763                mMagnificationController.reset(true);
764                mViewport.setFrameShown(false, true);
765            }
766        }
767
768        private void onActionTapAndHold(MotionEvent down, int policyFlags) {
769            if (DEBUG_DETECTING) {
770                Slog.i(LOG_TAG, "onActionTapAndHold()");
771            }
772            clear();
773            mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
774            mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
775                    down.getX(), down.getY(), true);
776            mViewport.setFrameShown(true, true);
777            transitionToState(STATE_VIEWPORT_DRAGGING);
778        }
779    }
780
781    private void persistScale(final float scale) {
782        new AsyncTask<Void, Void, Void>() {
783            @Override
784            protected Void doInBackground(Void... params) {
785                Settings.Secure.putFloat(mContext.getContentResolver(),
786                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale);
787                return null;
788            }
789        }.execute();
790    }
791
792    private float getPersistedScale() {
793        return Settings.Secure.getFloat(mContext.getContentResolver(),
794                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
795                DEFAULT_MAGNIFICATION_SCALE);
796    }
797
798    private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
799        return (Settings.Secure.getInt(context.getContentResolver(),
800                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
801                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
802    }
803
804    private static final class MotionEventInfo {
805
806        private static final int MAX_POOL_SIZE = 10;
807
808        private static final Object sLock = new Object();
809        private static MotionEventInfo sPool;
810        private static int sPoolSize;
811
812        private MotionEventInfo mNext;
813        private boolean mInPool;
814
815        public MotionEvent mEvent;
816        public int mPolicyFlags;
817
818        public static MotionEventInfo obtain(MotionEvent event, int policyFlags) {
819            synchronized (sLock) {
820                MotionEventInfo info;
821                if (sPoolSize > 0) {
822                    sPoolSize--;
823                    info = sPool;
824                    sPool = info.mNext;
825                    info.mNext = null;
826                    info.mInPool = false;
827                } else {
828                    info = new MotionEventInfo();
829                }
830                info.initialize(event, policyFlags);
831                return info;
832            }
833        }
834
835        private void initialize(MotionEvent event, int policyFlags) {
836            mEvent = MotionEvent.obtain(event);
837            mPolicyFlags = policyFlags;
838        }
839
840        public void recycle() {
841            synchronized (sLock) {
842                if (mInPool) {
843                    throw new IllegalStateException("Already recycled.");
844                }
845                clear();
846                if (sPoolSize < MAX_POOL_SIZE) {
847                    sPoolSize++;
848                    mNext = sPool;
849                    sPool = this;
850                    mInPool = true;
851                }
852            }
853        }
854
855        private void clear() {
856            mEvent.recycle();
857            mEvent = null;
858            mPolicyFlags = 0;
859        }
860    }
861
862    private static final class ScreenStateObserver extends BroadcastReceiver {
863
864        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
865
866        private final Handler mHandler = new Handler() {
867            @Override
868            public void handleMessage(Message message) {
869                switch (message.what) {
870                    case MESSAGE_ON_SCREEN_STATE_CHANGE: {
871                        String action = (String) message.obj;
872                        handleOnScreenStateChange(action);
873                    } break;
874                }
875            }
876        };
877
878        private final Context mContext;
879        private final Viewport mViewport;
880        private final MagnificationController mMagnificationController;
881
882        public ScreenStateObserver(Context context, Viewport viewport,
883                MagnificationController magnificationController) {
884            mContext = context;
885            mViewport = viewport;
886            mMagnificationController = magnificationController;
887            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
888        }
889
890        public void destroy() {
891            mContext.unregisterReceiver(this);
892        }
893
894        @Override
895        public void onReceive(Context context, Intent intent) {
896            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
897                    intent.getAction()).sendToTarget();
898        }
899
900        private void handleOnScreenStateChange(String action) {
901            if (action.equals(Intent.ACTION_SCREEN_OFF)
902                    && mMagnificationController.isMagnifying()
903                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
904                mMagnificationController.reset(false);
905                mViewport.setFrameShown(false, false);
906            }
907        }
908    }
909
910    private static final class DisplayContentObserver {
911
912        private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
913        private static final int MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS = 2;
914        private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3;
915        private static final int MESSAGE_ON_WINDOW_TRANSITION = 4;
916        private static final int MESSAGE_ON_ROTATION_CHANGED = 5;
917
918        private final Handler mHandler = new MyHandler();
919
920        private final Rect mTempRect = new Rect();
921
922        private final IDisplayContentChangeListener mDisplayContentChangeListener;
923
924        private final Context mContext;
925        private final Viewport mViewport;
926        private final MagnificationController mMagnificationController;
927        private final IWindowManager mWindowManagerService;
928        private final DisplayProvider mDisplayProvider;
929        private final long mLongAnimationDuration;
930        private final float mWindowAnimationScale;
931
932        public DisplayContentObserver(Context context, Viewport viewport,
933                MagnificationController magnificationController,
934                IWindowManager windowManagerService, DisplayProvider displayProvider,
935                long longAnimationDuration, float windowAnimationScale) {
936            mContext = context;
937            mViewport = viewport;
938            mMagnificationController = magnificationController;
939            mWindowManagerService = windowManagerService;
940            mDisplayProvider = displayProvider;
941            mLongAnimationDuration = longAnimationDuration;
942            mWindowAnimationScale = windowAnimationScale;
943
944            mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() {
945                @Override
946                public void onWindowTransition(int displayId, int transition, WindowInfo info) {
947                    mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION, transition, 0,
948                            WindowInfo.obtain(info)).sendToTarget();
949                }
950
951                @Override
952                public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle,
953                        boolean immediate) {
954                    SomeArgs args = SomeArgs.obtain();
955                    args.argi1 = rectangle.left;
956                    args.argi2 = rectangle.top;
957                    args.argi3 = rectangle.right;
958                    args.argi4 = rectangle.bottom;
959                    mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0,
960                            immediate ? 1 : 0, args).sendToTarget();
961                }
962
963                @Override
964                public void onRotationChanged(int rotation) throws RemoteException {
965                    mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0)
966                            .sendToTarget();
967                }
968            };
969
970            try {
971                mWindowManagerService.addDisplayContentChangeListener(
972                        mDisplayProvider.getDisplay().getDisplayId(),
973                        mDisplayContentChangeListener);
974            } catch (RemoteException re) {
975                /* ignore */
976            }
977        }
978
979        public void destroy() {
980            try {
981                mWindowManagerService.removeDisplayContentChangeListener(
982                        mDisplayProvider.getDisplay().getDisplayId(),
983                        mDisplayContentChangeListener);
984            } catch (RemoteException re) {
985                /* ignore*/
986            }
987        }
988
989        private void handleOnRotationChanged(int rotation) {
990            if (DEBUG_ROTATION) {
991                Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation));
992            }
993            resetMagnificationIfNeeded();
994            mViewport.setFrameShown(false, false);
995            mViewport.rotationChanged();
996            mViewport.recomputeBounds(false);
997            if (mMagnificationController.isMagnifying()) {
998                final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
999                Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
1000                mHandler.sendMessageDelayed(message, delay);
1001            }
1002        }
1003
1004        private void handleOnWindowTransition(int transition, WindowInfo info) {
1005            if (DEBUG_WINDOW_TRANSITIONS) {
1006                Slog.i(LOG_TAG, "Window transitioning: "
1007                        + windowTransitionToString(transition));
1008            }
1009            try {
1010                final boolean magnifying = mMagnificationController.isMagnifying();
1011                if (magnifying) {
1012                    switch (transition) {
1013                        case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
1014                        case WindowManagerPolicy.TRANSIT_TASK_OPEN:
1015                        case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
1016                        case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
1017                        case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
1018                        case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
1019                            resetMagnificationIfNeeded();
1020                        }
1021                    }
1022                }
1023                if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
1024                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
1025                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
1026                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD
1027                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
1028                    switch (transition) {
1029                        case WindowManagerPolicy.TRANSIT_ENTER:
1030                        case WindowManagerPolicy.TRANSIT_SHOW:
1031                        case WindowManagerPolicy.TRANSIT_EXIT:
1032                        case WindowManagerPolicy.TRANSIT_HIDE: {
1033                            mViewport.recomputeBounds(mMagnificationController.isMagnifying());
1034                        } break;
1035                    }
1036                } else {
1037                    switch (transition) {
1038                        case WindowManagerPolicy.TRANSIT_ENTER:
1039                        case WindowManagerPolicy.TRANSIT_SHOW: {
1040                            if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) {
1041                                break;
1042                            }
1043                            final int type = info.type;
1044                            switch (type) {
1045                                // TODO: Are these all the windows we want to make
1046                                //       visible when they appear on the screen?
1047                                //       Do we need to take some of them out?
1048                                case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
1049                                case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
1050                                case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
1051                                case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
1052                                case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
1053                                case WindowManager.LayoutParams.TYPE_PHONE:
1054                                case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
1055                                case WindowManager.LayoutParams.TYPE_TOAST:
1056                                case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
1057                                case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
1058                                case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
1059                                case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
1060                                case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
1061                                case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
1062                                case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
1063                                    Rect magnifiedRegionBounds = mMagnificationController
1064                                            .getMagnifiedRegionBounds();
1065                                    Rect touchableRegion = info.touchableRegion;
1066                                    if (!magnifiedRegionBounds.intersect(touchableRegion)) {
1067                                        ensureRectangleInMagnifiedRegionBounds(
1068                                                magnifiedRegionBounds, touchableRegion);
1069                                    }
1070                                } break;
1071                            } break;
1072                        }
1073                    }
1074                }
1075            } finally {
1076                if (info != null) {
1077                    info.recycle();
1078                }
1079            }
1080        }
1081
1082        private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) {
1083            if (!mMagnificationController.isMagnifying()) {
1084                return;
1085            }
1086            Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds();
1087            if (magnifiedRegionBounds.contains(rectangle)) {
1088                return;
1089            }
1090            ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle);
1091        }
1092
1093        private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds,
1094                Rect rectangle) {
1095            if (!Rect.intersects(rectangle, mViewport.getBounds())) {
1096                return;
1097            }
1098            final float scrollX;
1099            final float scrollY;
1100            if (rectangle.width() > magnifiedRegionBounds.width()) {
1101                scrollX = rectangle.left - magnifiedRegionBounds.left;
1102            } else if (rectangle.left < magnifiedRegionBounds.left) {
1103                scrollX = rectangle.left - magnifiedRegionBounds.left;
1104            } else if (rectangle.right > magnifiedRegionBounds.right) {
1105                scrollX = rectangle.right - magnifiedRegionBounds.right;
1106            } else {
1107                scrollX = 0;
1108            }
1109            if (rectangle.height() > magnifiedRegionBounds.height()) {
1110                scrollY = rectangle.top - magnifiedRegionBounds.top;
1111            } else if (rectangle.top < magnifiedRegionBounds.top) {
1112                scrollY = rectangle.top - magnifiedRegionBounds.top;
1113            } else if (rectangle.bottom > magnifiedRegionBounds.bottom) {
1114                scrollY = rectangle.bottom - magnifiedRegionBounds.bottom;
1115            } else {
1116                scrollY = 0;
1117            }
1118            final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX()
1119                    + scrollX;
1120            final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY()
1121                    + scrollY;
1122            mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY,
1123                    true);
1124        }
1125
1126        private void resetMagnificationIfNeeded() {
1127            if (mMagnificationController.isMagnifying()
1128                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
1129                mMagnificationController.reset(true);
1130                mViewport.setFrameShown(false, true);
1131            }
1132        }
1133
1134        private String windowTransitionToString(int transition) {
1135            switch (transition) {
1136                case WindowManagerPolicy.TRANSIT_UNSET: {
1137                    return "TRANSIT_UNSET";
1138                }
1139                case WindowManagerPolicy.TRANSIT_NONE: {
1140                    return "TRANSIT_NONE";
1141                }
1142                case WindowManagerPolicy.TRANSIT_ENTER: {
1143                    return "TRANSIT_ENTER";
1144                }
1145                case WindowManagerPolicy.TRANSIT_EXIT: {
1146                    return "TRANSIT_EXIT";
1147                }
1148                case WindowManagerPolicy.TRANSIT_SHOW: {
1149                    return "TRANSIT_SHOW";
1150                }
1151                case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
1152                    return "TRANSIT_EXIT_MASK";
1153                }
1154                case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
1155                    return "TRANSIT_PREVIEW_DONE";
1156                }
1157                case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
1158                    return "TRANSIT_ACTIVITY_OPEN";
1159                }
1160                case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
1161                    return "TRANSIT_ACTIVITY_CLOSE";
1162                }
1163                case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
1164                    return "TRANSIT_TASK_OPEN";
1165                }
1166                case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
1167                    return "TRANSIT_TASK_CLOSE";
1168                }
1169                case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
1170                    return "TRANSIT_TASK_TO_FRONT";
1171                }
1172                case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
1173                    return "TRANSIT_TASK_TO_BACK";
1174                }
1175                case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
1176                    return "TRANSIT_WALLPAPER_CLOSE";
1177                }
1178                case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
1179                    return "TRANSIT_WALLPAPER_OPEN";
1180                }
1181                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
1182                    return "TRANSIT_WALLPAPER_INTRA_OPEN";
1183                }
1184                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
1185                    return "TRANSIT_WALLPAPER_INTRA_CLOSE";
1186                }
1187                default: {
1188                    return "<UNKNOWN>";
1189                }
1190            }
1191        }
1192
1193        private String rotationToString(int rotation) {
1194            switch (rotation) {
1195                case Surface.ROTATION_0: {
1196                    return "ROTATION_0";
1197                }
1198                case Surface.ROTATION_90: {
1199                    return "ROATATION_90";
1200                }
1201                case Surface.ROTATION_180: {
1202                    return "ROATATION_180";
1203                }
1204                case Surface.ROTATION_270: {
1205                    return "ROATATION_270";
1206                }
1207                default: {
1208                    throw new IllegalArgumentException("Invalid rotation: "
1209                        + rotation);
1210                }
1211            }
1212        }
1213
1214        private final class MyHandler extends Handler {
1215            @Override
1216            public void handleMessage(Message message) {
1217                final int action = message.what;
1218                switch (action) {
1219                    case MESSAGE_SHOW_VIEWPORT_FRAME: {
1220                        mViewport.setFrameShown(true, true);
1221                    } break;
1222                    case MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS: {
1223                        final boolean animate = message.arg1 == 1;
1224                        mViewport.recomputeBounds(animate);
1225                    } break;
1226                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
1227                        SomeArgs args = (SomeArgs) message.obj;
1228                        try {
1229                            mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4);
1230                            final boolean immediate = (message.arg1 == 1);
1231                            handleOnRectangleOnScreenRequested(mTempRect, immediate);
1232                        } finally {
1233                            args.recycle();
1234                        }
1235                    } break;
1236                    case MESSAGE_ON_WINDOW_TRANSITION: {
1237                        final int transition = message.arg1;
1238                        WindowInfo info = (WindowInfo) message.obj;
1239                        handleOnWindowTransition(transition, info);
1240                    } break;
1241                    case MESSAGE_ON_ROTATION_CHANGED: {
1242                        final int rotation = message.arg1;
1243                        handleOnRotationChanged(rotation);
1244                    } break;
1245                    default: {
1246                        throw new IllegalArgumentException("Unknown message: " + action);
1247                    }
1248                }
1249            }
1250        }
1251    }
1252
1253    private final class MagnificationController {
1254
1255        private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION =
1256                "accessibilityTransformation";
1257
1258        private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
1259
1260        private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
1261
1262        private final Rect mTempRect = new Rect();
1263
1264        private final ValueAnimator mTransformationAnimator;
1265
1266        public MagnificationController(int animationDuration) {
1267            Property<MagnificationController, MagnificationSpec> property =
1268                    Property.of(MagnificationController.class, MagnificationSpec.class,
1269                    PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION);
1270            TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
1271                private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec();
1272                @Override
1273                public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
1274                        MagnificationSpec toSpec) {
1275                    MagnificationSpec result = mTempTransformationSpec;
1276                    result.mScale = fromSpec.mScale
1277                            + (toSpec.mScale - fromSpec.mScale) * fraction;
1278                    result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX
1279                            + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX)
1280                            * fraction;
1281                    result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY
1282                            + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY)
1283                            * fraction;
1284                    result.mScaledOffsetX = fromSpec.mScaledOffsetX
1285                            + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX)
1286                            * fraction;
1287                    result.mScaledOffsetY = fromSpec.mScaledOffsetY
1288                            + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY)
1289                            * fraction;
1290                    return result;
1291                }
1292            };
1293            mTransformationAnimator = ObjectAnimator.ofObject(this, property,
1294                    evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
1295            mTransformationAnimator.setDuration((long) (animationDuration));
1296            mTransformationAnimator.setInterpolator(mInterpolator);
1297        }
1298
1299        public boolean isMagnifying() {
1300            return mCurrentMagnificationSpec.mScale > 1.0f;
1301        }
1302
1303        public void reset(boolean animate) {
1304            if (mTransformationAnimator.isRunning()) {
1305                mTransformationAnimator.cancel();
1306            }
1307            mCurrentMagnificationSpec.reset();
1308            if (animate) {
1309                animateAccessibilityTranformation(mSentMagnificationSpec,
1310                        mCurrentMagnificationSpec);
1311            } else {
1312                setAccessibilityTransformation(mCurrentMagnificationSpec);
1313            }
1314        }
1315
1316        public Rect getMagnifiedRegionBounds() {
1317            mTempRect.set(mViewport.getBounds());
1318            mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX,
1319                    (int) -mCurrentMagnificationSpec.mScaledOffsetY);
1320            mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale);
1321            return mTempRect;
1322        }
1323
1324        public float getScale() {
1325            return mCurrentMagnificationSpec.mScale;
1326        }
1327
1328        public float getMagnifiedRegionCenterX() {
1329            return mCurrentMagnificationSpec.mMagnifiedRegionCenterX;
1330        }
1331
1332        public float getMagnifiedRegionCenterY() {
1333            return mCurrentMagnificationSpec.mMagnifiedRegionCenterY;
1334        }
1335
1336        public float getScaledOffsetX() {
1337            return mCurrentMagnificationSpec.mScaledOffsetX;
1338        }
1339
1340        public float getScaledOffsetY() {
1341            return mCurrentMagnificationSpec.mScaledOffsetY;
1342        }
1343
1344        public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
1345            MagnificationSpec spec = mCurrentMagnificationSpec;
1346            final float oldScale = spec.mScale;
1347            final float oldCenterX = spec.mMagnifiedRegionCenterX;
1348            final float oldCenterY = spec.mMagnifiedRegionCenterY;
1349            final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale;
1350            final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale;
1351            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
1352            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
1353            final float centerX = normPivotX + offsetX;
1354            final float centerY = normPivotY + offsetY;
1355            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
1356        }
1357
1358        public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
1359            setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY,
1360                    animate);
1361        }
1362
1363        public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
1364                boolean animate) {
1365            if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0
1366                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX,
1367                            centerX) == 0
1368                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY,
1369                            centerY) == 0) {
1370                return;
1371            }
1372            if (mTransformationAnimator.isRunning()) {
1373                mTransformationAnimator.cancel();
1374            }
1375            if (DEBUG_MAGNIFICATION_CONTROLLER) {
1376                Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX
1377                        + " centerY: " + centerY);
1378            }
1379            mCurrentMagnificationSpec.initialize(scale, centerX, centerY);
1380            if (animate) {
1381                animateAccessibilityTranformation(mSentMagnificationSpec,
1382                        mCurrentMagnificationSpec);
1383            } else {
1384                setAccessibilityTransformation(mCurrentMagnificationSpec);
1385            }
1386        }
1387
1388        private void animateAccessibilityTranformation(MagnificationSpec fromSpec,
1389                MagnificationSpec toSpec) {
1390            mTransformationAnimator.setObjectValues(fromSpec, toSpec);
1391            mTransformationAnimator.start();
1392        }
1393
1394        @SuppressWarnings("unused")
1395        // Called from an animator.
1396        public MagnificationSpec getAccessibilityTransformation() {
1397            return mSentMagnificationSpec;
1398        }
1399
1400        public void setAccessibilityTransformation(MagnificationSpec transformation) {
1401            if (DEBUG_TRANSFORMATION) {
1402                Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale
1403                        + " offsetX: " + transformation.mScaledOffsetX
1404                        + " offsetY: " + transformation.mScaledOffsetY);
1405            }
1406            try {
1407                mSentMagnificationSpec.updateFrom(transformation);
1408                mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(),
1409                        transformation.mScale, transformation.mScaledOffsetX,
1410                        transformation.mScaledOffsetY);
1411            } catch (RemoteException re) {
1412                /* ignore */
1413            }
1414        }
1415
1416        private class MagnificationSpec {
1417
1418            private static final float DEFAULT_SCALE = 1.0f;
1419
1420            public float mScale = DEFAULT_SCALE;
1421
1422            public float mMagnifiedRegionCenterX;
1423
1424            public float mMagnifiedRegionCenterY;
1425
1426            public float mScaledOffsetX;
1427
1428            public float mScaledOffsetY;
1429
1430            public void initialize(float scale, float magnifiedRegionCenterX,
1431                    float magnifiedRegionCenterY) {
1432                mScale = scale;
1433
1434                final int viewportWidth = mViewport.getBounds().width();
1435                final int viewportHeight = mViewport.getBounds().height();
1436                final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale;
1437                final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale;
1438                final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX;
1439                final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY;
1440
1441                mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX,
1442                        minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX);
1443                mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY,
1444                        minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY);
1445
1446                mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2);
1447                mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2);
1448            }
1449
1450            public void updateFrom(MagnificationSpec other) {
1451                mScale = other.mScale;
1452                mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX;
1453                mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY;
1454                mScaledOffsetX = other.mScaledOffsetX;
1455                mScaledOffsetY = other.mScaledOffsetY;
1456            }
1457
1458            public void reset() {
1459                mScale = DEFAULT_SCALE;
1460                mMagnifiedRegionCenterX = 0;
1461                mMagnifiedRegionCenterY = 0;
1462                mScaledOffsetX = 0;
1463                mScaledOffsetY = 0;
1464            }
1465        }
1466    }
1467
1468    private static final class Viewport {
1469
1470        private static final String PROPERTY_NAME_ALPHA = "alpha";
1471
1472        private static final String PROPERTY_NAME_BOUNDS = "bounds";
1473
1474        private static final int MIN_ALPHA = 0;
1475
1476        private static final int MAX_ALPHA = 255;
1477
1478        private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
1479
1480        private final Rect mTempRect1 = new Rect();
1481        private final Rect mTempRect2 = new Rect();
1482        private final Rect mTempRect3 = new Rect();
1483
1484        private final IWindowManager mWindowManagerService;
1485        private final DisplayProvider mDisplayProvider;
1486
1487        private final ViewportWindow mViewportFrame;
1488
1489        private final ValueAnimator mResizeFrameAnimator;
1490
1491        private final ValueAnimator mShowHideFrameAnimator;
1492
1493        public Viewport(Context context, WindowManager windowManager,
1494                IWindowManager windowManagerService, DisplayProvider displayInfoProvider,
1495                Interpolator animationInterpolator, long animationDuration) {
1496            mWindowManagerService = windowManagerService;
1497            mDisplayProvider = displayInfoProvider;
1498            mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider);
1499
1500            mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
1501                  MIN_ALPHA, MAX_ALPHA);
1502            mShowHideFrameAnimator.setInterpolator(animationInterpolator);
1503            mShowHideFrameAnimator.setDuration(animationDuration);
1504            mShowHideFrameAnimator.addListener(new AnimatorListener() {
1505                @Override
1506                public void onAnimationEnd(Animator animation) {
1507                    if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
1508                        mViewportFrame.hide();
1509                    }
1510                }
1511                @Override
1512                public void onAnimationStart(Animator animation) {
1513                    /* do nothing - stub */
1514                }
1515                @Override
1516                public void onAnimationCancel(Animator animation) {
1517                    /* do nothing - stub */
1518                }
1519                @Override
1520                public void onAnimationRepeat(Animator animation) {
1521                    /* do nothing - stub */
1522                }
1523            });
1524
1525            Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class,
1526                    Rect.class, PROPERTY_NAME_BOUNDS);
1527            TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() {
1528                private final Rect mReusableResultRect = new Rect();
1529                @Override
1530                public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) {
1531                    Rect result = mReusableResultRect;
1532                    result.left = (int) (fromFrame.left
1533                            + (toFrame.left - fromFrame.left) * fraction);
1534                    result.top = (int) (fromFrame.top
1535                            + (toFrame.top - fromFrame.top) * fraction);
1536                    result.right = (int) (fromFrame.right
1537                            + (toFrame.right - fromFrame.right) * fraction);
1538                    result.bottom = (int) (fromFrame.bottom
1539                            + (toFrame.bottom - fromFrame.bottom) * fraction);
1540                    return result;
1541                }
1542            };
1543            mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
1544                    evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
1545            mResizeFrameAnimator.setDuration((long) (animationDuration));
1546            mResizeFrameAnimator.setInterpolator(animationInterpolator);
1547
1548            recomputeBounds(false);
1549        }
1550
1551        private final Comparator<WindowInfo> mWindowInfoInverseComparator =
1552                new Comparator<WindowInfo>() {
1553            @Override
1554            public int compare(WindowInfo lhs, WindowInfo rhs) {
1555                if (lhs.layer != rhs.layer) {
1556                    return rhs.layer - lhs.layer;
1557                }
1558                if (lhs.touchableRegion.top != rhs.touchableRegion.top) {
1559                    return rhs.touchableRegion.top - lhs.touchableRegion.top;
1560                }
1561                if (lhs.touchableRegion.left != rhs.touchableRegion.left) {
1562                    return rhs.touchableRegion.left - lhs.touchableRegion.left;
1563                }
1564                if (lhs.touchableRegion.right != rhs.touchableRegion.right) {
1565                    return rhs.touchableRegion.right - lhs.touchableRegion.right;
1566                }
1567                if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) {
1568                    return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom;
1569                }
1570                return 0;
1571            }
1572        };
1573
1574        public void recomputeBounds(boolean animate) {
1575            Rect magnifiedFrame = mTempRect1;
1576            magnifiedFrame.set(0, 0, 0, 0);
1577
1578            Rect notMagnifiedFrame = mTempRect2;
1579            notMagnifiedFrame.set(0, 0, 0, 0);
1580
1581            ArrayList<WindowInfo> infos = mTempWindowInfoList;
1582            infos.clear();
1583            int windowCount = 0;
1584            try {
1585                mWindowManagerService.getVisibleWindowsForDisplay(
1586                        mDisplayProvider.getDisplay().getDisplayId(), infos);
1587                Collections.sort(infos, mWindowInfoInverseComparator);
1588                windowCount = infos.size();
1589                for (int i = 0; i < windowCount; i++) {
1590                    WindowInfo info = infos.get(i);
1591                    if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
1592                        continue;
1593                    }
1594                    if (isWindowMagnified(info.type)) {
1595                        Rect clippedFrame = mTempRect3;
1596                        clippedFrame.set(info.touchableRegion);
1597                        subtract(clippedFrame, notMagnifiedFrame);
1598                        magnifiedFrame.union(clippedFrame);
1599                    } else {
1600                        Rect clippedFrame = mTempRect3;
1601                        clippedFrame.set(info.touchableRegion);
1602                        subtract(clippedFrame, magnifiedFrame);
1603                        notMagnifiedFrame.union(clippedFrame);
1604                    }
1605                    if (magnifiedFrame.bottom >= notMagnifiedFrame.top) {
1606                        break;
1607                    }
1608                }
1609            } catch (RemoteException re) {
1610                /* ignore */
1611            } finally {
1612                for (int i = windowCount - 1; i >= 0; i--) {
1613                    infos.remove(i).recycle();
1614                }
1615            }
1616
1617            final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth;
1618            final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight;
1619            magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
1620
1621            resize(magnifiedFrame, animate);
1622        }
1623
1624        private boolean isWindowMagnified(int type) {
1625            return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
1626                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD
1627                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1628        }
1629
1630        public void rotationChanged() {
1631            mViewportFrame.rotationChanged();
1632        }
1633
1634        public Rect getBounds() {
1635            return mViewportFrame.getBounds();
1636        }
1637
1638        public void setFrameShown(boolean shown, boolean animate) {
1639            if (mViewportFrame.isShown() == shown) {
1640                return;
1641            }
1642            if (animate) {
1643                if (mShowHideFrameAnimator.isRunning()) {
1644                    mShowHideFrameAnimator.reverse();
1645                } else {
1646                    if (shown) {
1647                        mViewportFrame.show();
1648                        mShowHideFrameAnimator.start();
1649                    } else {
1650                        mShowHideFrameAnimator.reverse();
1651                    }
1652                }
1653            } else {
1654                mShowHideFrameAnimator.cancel();
1655                if (shown) {
1656                    mViewportFrame.show();
1657                } else {
1658                    mViewportFrame.hide();
1659                }
1660            }
1661        }
1662
1663        private void resize(Rect bounds, boolean animate) {
1664            if (mViewportFrame.getBounds().equals(bounds)) {
1665                return;
1666            }
1667            if (animate) {
1668                if (mResizeFrameAnimator.isRunning()) {
1669                    mResizeFrameAnimator.cancel();
1670                }
1671                mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
1672                mResizeFrameAnimator.start();
1673            } else {
1674                mViewportFrame.setBounds(bounds);
1675            }
1676        }
1677
1678        private boolean subtract(Rect lhs, Rect rhs) {
1679            if (lhs.right < rhs.left || lhs.left  > rhs.right
1680                    || lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
1681                return false;
1682            }
1683            if (lhs.left < rhs.left) {
1684                lhs.right = rhs.left;
1685            }
1686            if (lhs.top < rhs.top) {
1687                lhs.bottom = rhs.top;
1688            }
1689            if (lhs.right > rhs.right) {
1690                lhs.left = rhs.right;
1691            }
1692            if (lhs.bottom > rhs.bottom) {
1693                lhs.top = rhs.bottom;
1694            }
1695            return true;
1696        }
1697
1698        private static final class ViewportWindow {
1699            private static final String WINDOW_TITLE = "Magnification Overlay";
1700
1701            private final WindowManager mWindowManager;
1702            private final DisplayProvider mDisplayProvider;
1703
1704            private final ContentView mWindowContent;
1705            private final WindowManager.LayoutParams mWindowParams;
1706
1707            private final Rect mBounds = new Rect();
1708            private boolean mShown;
1709            private int mAlpha;
1710
1711            public ViewportWindow(Context context, WindowManager windowManager,
1712                    DisplayProvider displayProvider) {
1713                mWindowManager = windowManager;
1714                mDisplayProvider = displayProvider;
1715
1716                ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
1717                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
1718                mWindowContent = new ContentView(context);
1719                mWindowContent.setLayoutParams(contentParams);
1720                mWindowContent.setBackgroundColor(R.color.transparent);
1721
1722                mWindowParams = new WindowManager.LayoutParams(
1723                        WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
1724                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1725                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1726                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1727                mWindowParams.setTitle(WINDOW_TITLE);
1728                mWindowParams.gravity = Gravity.CENTER;
1729                mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth;
1730                mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight;
1731                mWindowParams.format = PixelFormat.TRANSLUCENT;
1732            }
1733
1734            public boolean isShown() {
1735                return mShown;
1736            }
1737
1738            public void show() {
1739                if (mShown) {
1740                    return;
1741                }
1742                mShown = true;
1743                mWindowManager.addView(mWindowContent, mWindowParams);
1744                if (DEBUG_VIEWPORT_WINDOW) {
1745                    Slog.i(LOG_TAG, "ViewportWindow shown.");
1746                }
1747            }
1748
1749            public void hide() {
1750                if (!mShown) {
1751                    return;
1752                }
1753                mShown = false;
1754                mWindowManager.removeView(mWindowContent);
1755                if (DEBUG_VIEWPORT_WINDOW) {
1756                    Slog.i(LOG_TAG, "ViewportWindow hidden.");
1757                }
1758            }
1759
1760            @SuppressWarnings("unused")
1761            // Called reflectively from an animator.
1762            public int getAlpha() {
1763                return mAlpha;
1764            }
1765
1766            @SuppressWarnings("unused")
1767            // Called reflectively from an animator.
1768            public void setAlpha(int alpha) {
1769                if (mAlpha == alpha) {
1770                    return;
1771                }
1772                mAlpha = alpha;
1773                if (mShown) {
1774                    mWindowContent.invalidate();
1775                }
1776                if (DEBUG_VIEWPORT_WINDOW) {
1777                    Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
1778                }
1779            }
1780
1781            public Rect getBounds() {
1782                return mBounds;
1783            }
1784
1785            public void rotationChanged() {
1786                mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth;
1787                mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight;
1788                if (mShown) {
1789                    mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
1790                }
1791            }
1792
1793            public void setBounds(Rect bounds) {
1794                if (mBounds.equals(bounds)) {
1795                    return;
1796                }
1797                mBounds.set(bounds);
1798                if (mShown) {
1799                    mWindowContent.invalidate();
1800                }
1801                if (DEBUG_VIEWPORT_WINDOW) {
1802                    Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
1803                }
1804            }
1805
1806            private final class ContentView extends View {
1807                private final Drawable mHighlightFrame;
1808
1809                public ContentView(Context context) {
1810                    super(context);
1811                    mHighlightFrame = context.getResources().getDrawable(
1812                            R.drawable.magnified_region_frame);
1813                }
1814
1815                @Override
1816                public void onDraw(Canvas canvas) {
1817                    canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
1818                    mHighlightFrame.setBounds(mBounds);
1819                    mHighlightFrame.setAlpha(mAlpha);
1820                    mHighlightFrame.draw(canvas);
1821                }
1822            }
1823        }
1824    }
1825
1826    private static class DisplayProvider implements DisplayListener {
1827        private final WindowManager mWindowManager;
1828        private final DisplayManager mDisplayManager;
1829        private final Display mDefaultDisplay;
1830        private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
1831
1832        public DisplayProvider(Context context, WindowManager windowManager) {
1833            mWindowManager = windowManager;
1834            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1835            mDefaultDisplay = mWindowManager.getDefaultDisplay();
1836            mDisplayManager.registerDisplayListener(this, null);
1837            updateDisplayInfo();
1838        }
1839
1840        public DisplayInfo getDisplayInfo() {
1841            return mDefaultDisplayInfo;
1842        }
1843
1844        public Display getDisplay() {
1845            return mDefaultDisplay;
1846        }
1847
1848        private void updateDisplayInfo() {
1849            if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
1850                Slog.e(LOG_TAG, "Default display is not valid.");
1851            }
1852        }
1853
1854        public void destroy() {
1855            mDisplayManager.unregisterDisplayListener(this);
1856        }
1857
1858        @Override
1859        public void onDisplayAdded(int displayId) {
1860            /* do noting */
1861        }
1862
1863        @Override
1864        public void onDisplayRemoved(int displayId) {
1865            // Having no default display
1866        }
1867
1868        @Override
1869        public void onDisplayChanged(int displayId) {
1870            updateDisplayInfo();
1871        }
1872    }
1873
1874    /**
1875     * The listener for receiving notifications when gestures occur.
1876     * If you want to listen for all the different gestures then implement
1877     * this interface. If you only want to listen for a subset it might
1878     * be easier to extend {@link SimpleOnScaleGestureListener}.
1879     *
1880     * An application will receive events in the following order:
1881     * <ul>
1882     *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
1883     *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
1884     *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
1885     * </ul>
1886     */
1887    interface OnScaleGestureListener {
1888        /**
1889         * Responds to scaling events for a gesture in progress.
1890         * Reported by pointer motion.
1891         *
1892         * @param detector The detector reporting the event - use this to
1893         *          retrieve extended info about event state.
1894         * @return Whether or not the detector should consider this event
1895         *          as handled. If an event was not handled, the detector
1896         *          will continue to accumulate movement until an event is
1897         *          handled. This can be useful if an application, for example,
1898         *          only wants to update scaling factors if the change is
1899         *          greater than 0.01.
1900         */
1901        public boolean onScale(ScaleGestureDetector detector);
1902
1903        /**
1904         * Responds to the beginning of a scaling gesture. Reported by
1905         * new pointers going down.
1906         *
1907         * @param detector The detector reporting the event - use this to
1908         *          retrieve extended info about event state.
1909         * @return Whether or not the detector should continue recognizing
1910         *          this gesture. For example, if a gesture is beginning
1911         *          with a focal point outside of a region where it makes
1912         *          sense, onScaleBegin() may return false to ignore the
1913         *          rest of the gesture.
1914         */
1915        public boolean onScaleBegin(ScaleGestureDetector detector);
1916
1917        /**
1918         * Responds to the end of a scale gesture. Reported by existing
1919         * pointers going up.
1920         *
1921         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
1922         * and {@link ScaleGestureDetector#getFocusY()} will return the location
1923         * of the pointer remaining on the screen.
1924         *
1925         * @param detector The detector reporting the event - use this to
1926         *          retrieve extended info about event state.
1927         */
1928        public void onScaleEnd(ScaleGestureDetector detector);
1929    }
1930
1931    class ScaleGestureDetector {
1932
1933        private final MinCircleFinder mMinCircleFinder = new MinCircleFinder();
1934
1935        private final OnScaleGestureListener mListener;
1936
1937        private float mFocusX;
1938        private float mFocusY;
1939
1940        private float mCurrSpan;
1941        private float mPrevSpan;
1942        private float mCurrSpanX;
1943        private float mCurrSpanY;
1944        private float mPrevSpanX;
1945        private float mPrevSpanY;
1946        private long mCurrTime;
1947        private long mPrevTime;
1948        private boolean mInProgress;
1949
1950        public ScaleGestureDetector(OnScaleGestureListener listener) {
1951            mListener = listener;
1952        }
1953
1954        /**
1955         * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
1956         * when appropriate.
1957         *
1958         * <p>Applications should pass a complete and consistent event stream to this method.
1959         * A complete and consistent event stream involves all MotionEvents from the initial
1960         * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
1961         *
1962         * @param event The event to process
1963         * @return true if the event was processed and the detector wants to receive the
1964         *         rest of the MotionEvents in this event stream.
1965         */
1966        public boolean onTouchEvent(MotionEvent event) {
1967            boolean streamEnded = false;
1968            boolean contextChanged = false;
1969            int excludedPtrIdx = -1;
1970            final int action = event.getActionMasked();
1971            switch (action) {
1972                case MotionEvent.ACTION_DOWN:
1973                case MotionEvent.ACTION_POINTER_DOWN: {
1974                    contextChanged = true;
1975                } break;
1976                case MotionEvent.ACTION_POINTER_UP: {
1977                    contextChanged = true;
1978                    excludedPtrIdx = event.getActionIndex();
1979                } break;
1980                case MotionEvent.ACTION_UP:
1981                case MotionEvent.ACTION_CANCEL: {
1982                    streamEnded = true;
1983                } break;
1984            }
1985
1986            if (mInProgress && (contextChanged || streamEnded)) {
1987                mListener.onScaleEnd(this);
1988                mInProgress = false;
1989                mPrevSpan = 0;
1990                mPrevSpanX = 0;
1991                mPrevSpanY = 0;
1992                return true;
1993            }
1994
1995            final long currTime = mCurrTime;
1996
1997            mFocusX = 0;
1998            mFocusY = 0;
1999            mCurrSpan = 0;
2000            mCurrSpanX = 0;
2001            mCurrSpanY = 0;
2002            mCurrTime = 0;
2003            mPrevTime = 0;
2004
2005            if (!streamEnded) {
2006                MinCircleFinder.Circle circle =
2007                        mMinCircleFinder.computeMinCircleAroundPointers(event);
2008                mFocusX = circle.centerX;
2009                mFocusY = circle.centerY;
2010
2011                double sumSlope = 0;
2012                final int pointerCount = event.getPointerCount();
2013                for (int i = 0; i < pointerCount; i++) {
2014                    if (i == excludedPtrIdx) {
2015                        continue;
2016                    }
2017                    float x = event.getX(i) - mFocusX;
2018                    float y = event.getY(i) - mFocusY;
2019                    if (x == 0) {
2020                        x += 0.1f;
2021                    }
2022                    sumSlope += y / x;
2023                }
2024                final double avgSlope = sumSlope
2025                        / ((excludedPtrIdx < 0) ? pointerCount : pointerCount - 1);
2026
2027                double angle = Math.atan(avgSlope);
2028                mCurrSpan = 2 * circle.radius;
2029                mCurrSpanX = (float) Math.abs((Math.cos(angle) * mCurrSpan));
2030                mCurrSpanY = (float) Math.abs((Math.sin(angle) * mCurrSpan));
2031            }
2032
2033            if (contextChanged || mPrevSpan == 0 || mPrevSpanX == 0 || mPrevSpanY == 0) {
2034                mPrevSpan = mCurrSpan;
2035                mPrevSpanX = mCurrSpanX;
2036                mPrevSpanY = mCurrSpanY;
2037            }
2038
2039            if (!mInProgress && mCurrSpan != 0 && !streamEnded) {
2040                mInProgress = mListener.onScaleBegin(this);
2041            }
2042
2043            if (mInProgress) {
2044                mPrevTime = (currTime != 0) ? currTime : event.getEventTime();
2045                mCurrTime = event.getEventTime();
2046                if (mCurrSpan == 0) {
2047                    mListener.onScaleEnd(this);
2048                    mInProgress = false;
2049                } else {
2050                    if (mListener.onScale(this)) {
2051                        mPrevSpanX = mCurrSpanX;
2052                        mPrevSpanY = mCurrSpanY;
2053                        mPrevSpan = mCurrSpan;
2054                    }
2055                }
2056            }
2057
2058            return true;
2059        }
2060
2061        /**
2062         * Returns {@code true} if a scale gesture is in progress.
2063         */
2064        public boolean isInProgress() {
2065            return mInProgress;
2066        }
2067
2068        /**
2069         * Get the X coordinate of the current gesture's focal point.
2070         * If a gesture is in progress, the focal point is between
2071         * each of the pointers forming the gesture.
2072         *
2073         * If {@link #isInProgress()} would return false, the result of this
2074         * function is undefined.
2075         *
2076         * @return X coordinate of the focal point in pixels.
2077         */
2078        public float getFocusX() {
2079            return mFocusX;
2080        }
2081
2082        /**
2083         * Get the Y coordinate of the current gesture's focal point.
2084         * If a gesture is in progress, the focal point is between
2085         * each of the pointers forming the gesture.
2086         *
2087         * If {@link #isInProgress()} would return false, the result of this
2088         * function is undefined.
2089         *
2090         * @return Y coordinate of the focal point in pixels.
2091         */
2092        public float getFocusY() {
2093            return mFocusY;
2094        }
2095
2096        /**
2097         * Return the average distance between each of the pointers forming the
2098         * gesture in progress through the focal point.
2099         *
2100         * @return Distance between pointers in pixels.
2101         */
2102        public float getCurrentSpan() {
2103            return mCurrSpan;
2104        }
2105
2106        /**
2107         * Return the average X distance between each of the pointers forming the
2108         * gesture in progress through the focal point.
2109         *
2110         * @return Distance between pointers in pixels.
2111         */
2112        public float getCurrentSpanX() {
2113            return mCurrSpanX;
2114        }
2115
2116        /**
2117         * Return the average Y distance between each of the pointers forming the
2118         * gesture in progress through the focal point.
2119         *
2120         * @return Distance between pointers in pixels.
2121         */
2122        public float getCurrentSpanY() {
2123            return mCurrSpanY;
2124        }
2125
2126        /**
2127         * Return the previous average distance between each of the pointers forming the
2128         * gesture in progress through the focal point.
2129         *
2130         * @return Previous distance between pointers in pixels.
2131         */
2132        public float getPreviousSpan() {
2133            return mPrevSpan;
2134        }
2135
2136        /**
2137         * Return the previous average X distance between each of the pointers forming the
2138         * gesture in progress through the focal point.
2139         *
2140         * @return Previous distance between pointers in pixels.
2141         */
2142        public float getPreviousSpanX() {
2143            return mPrevSpanX;
2144        }
2145
2146        /**
2147         * Return the previous average Y distance between each of the pointers forming the
2148         * gesture in progress through the focal point.
2149         *
2150         * @return Previous distance between pointers in pixels.
2151         */
2152        public float getPreviousSpanY() {
2153            return mPrevSpanY;
2154        }
2155
2156        /**
2157         * Return the scaling factor from the previous scale event to the current
2158         * event. This value is defined as
2159         * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
2160         *
2161         * @return The current scaling factor.
2162         */
2163        public float getScaleFactor() {
2164            return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
2165        }
2166
2167        /**
2168         * Return the time difference in milliseconds between the previous
2169         * accepted scaling event and the current scaling event.
2170         *
2171         * @return Time difference since the last scaling event in milliseconds.
2172         */
2173        public long getTimeDelta() {
2174            return mCurrTime - mPrevTime;
2175        }
2176
2177        /**
2178         * Return the event time of the current event being processed.
2179         *
2180         * @return Current event time in milliseconds.
2181         */
2182        public long getEventTime() {
2183            return mCurrTime;
2184        }
2185    }
2186
2187    private static final class MinCircleFinder {
2188        private final ArrayList<PointHolder> mPoints = new ArrayList<PointHolder>();
2189        private final ArrayList<PointHolder> sBoundary = new ArrayList<PointHolder>();
2190        private final Circle mMinCircle = new Circle();
2191
2192        /**
2193         * Finds the minimal circle that contains all pointers of a motion event.
2194         *
2195         * @param event A motion event.
2196         * @return The minimal circle.
2197         */
2198        public Circle computeMinCircleAroundPointers(MotionEvent event) {
2199            ArrayList<PointHolder> points = mPoints;
2200            points.clear();
2201            final int pointerCount = event.getPointerCount();
2202            for (int i = 0; i < pointerCount; i++) {
2203                PointHolder point = PointHolder.obtain(event.getX(i), event.getY(i));
2204                points.add(point);
2205            }
2206            ArrayList<PointHolder> boundary = sBoundary;
2207            boundary.clear();
2208            computeMinCircleAroundPointsRecursive(points, boundary, mMinCircle);
2209            for (int i = points.size() - 1; i >= 0; i--) {
2210                points.remove(i).recycle();
2211            }
2212            boundary.clear();
2213            return mMinCircle;
2214        }
2215
2216        private static void computeMinCircleAroundPointsRecursive(ArrayList<PointHolder> points,
2217                ArrayList<PointHolder> boundary, Circle outCircle) {
2218            if (points.isEmpty()) {
2219                if (boundary.size() == 0) {
2220                    outCircle.initialize();
2221                } else if (boundary.size() == 1) {
2222                    outCircle.initialize(boundary.get(0).mData, boundary.get(0).mData);
2223                } else if (boundary.size() == 2) {
2224                    outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData);
2225                } else if (boundary.size() == 3) {
2226                    outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData,
2227                            boundary.get(2).mData);
2228                }
2229                return;
2230            }
2231            PointHolder point = points.remove(points.size() - 1);
2232            computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
2233            if (!outCircle.contains(point.mData)) {
2234                boundary.add(point);
2235                computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
2236                boundary.remove(point);
2237            }
2238            points.add(point);
2239        }
2240
2241        private static final class PointHolder {
2242            private static final int MAX_POOL_SIZE = 20;
2243            private static PointHolder sPool;
2244            private static int sPoolSize;
2245
2246            private PointHolder mNext;
2247            private boolean mIsInPool;
2248
2249            private final PointF mData = new PointF();
2250
2251            public static PointHolder obtain(float x, float y) {
2252                PointHolder holder;
2253                if (sPoolSize > 0) {
2254                    sPoolSize--;
2255                    holder = sPool;
2256                    sPool = sPool.mNext;
2257                    holder.mNext = null;
2258                    holder.mIsInPool = false;
2259                } else {
2260                    holder = new PointHolder();
2261                }
2262                holder.mData.set(x, y);
2263                return holder;
2264            }
2265
2266            public void recycle() {
2267                if (mIsInPool) {
2268                    throw new IllegalStateException("Already recycled.");
2269                }
2270                clear();
2271                if (sPoolSize < MAX_POOL_SIZE) {
2272                    sPoolSize++;
2273                    mNext = sPool;
2274                    sPool = this;
2275                    mIsInPool = true;
2276                }
2277            }
2278
2279            private void clear() {
2280                mData.set(0, 0);
2281            }
2282        }
2283
2284        public static final class Circle {
2285            public float centerX;
2286            public float centerY;
2287            public float radius;
2288
2289            private void initialize() {
2290                centerX = 0;
2291                centerY = 0;
2292                radius = 0;
2293            }
2294
2295            private void initialize(PointF first, PointF second, PointF third) {
2296                if (!hasLineWithInfiniteSlope(first, second, third)) {
2297                    initializeInternal(first, second, third);
2298                } else if (!hasLineWithInfiniteSlope(first, third, second)) {
2299                    initializeInternal(first, third, second);
2300                } else if (!hasLineWithInfiniteSlope(second, first, third)) {
2301                    initializeInternal(second, first, third);
2302                } else if (!hasLineWithInfiniteSlope(second, third, first)) {
2303                    initializeInternal(second, third, first);
2304                } else if (!hasLineWithInfiniteSlope(third, first, second)) {
2305                    initializeInternal(third, first, second);
2306                } else if (!hasLineWithInfiniteSlope(third, second, first)) {
2307                    initializeInternal(third, second, first);
2308                } else {
2309                    initialize();
2310                }
2311            }
2312
2313            private void initialize(PointF first, PointF second) {
2314                radius = (float) (Math.hypot(second.x - first.x, second.y - first.y) / 2);
2315                centerX = (float) (second.x + first.x) / 2;
2316                centerY = (float) (second.y + first.y) / 2;
2317            }
2318
2319            public boolean contains(PointF point) {
2320                return (int) (Math.hypot(point.x - centerX, point.y - centerY)) <= radius;
2321            }
2322
2323            private void initializeInternal(PointF first, PointF second, PointF third) {
2324                final float x1 = first.x;
2325                final float y1 = first.y;
2326                final float x2 = second.x;
2327                final float y2 = second.y;
2328                final float x3 = third.x;
2329                final float y3 = third.y;
2330
2331                final float sl1 = (y2 - y1) / (x2 - x1);
2332                final float sl2 = (y3 - y2) / (x3 - x2);
2333
2334                centerX = (int) ((sl1 * sl2 * (y1 - y3) + sl2 * (x1 + x2) - sl1 * (x2 + x3))
2335                        / (2 * (sl2 - sl1)));
2336                centerY = (int) (-1 / sl1 * (centerX - (x1 + x2) / 2) + (y1 + y2) / 2);
2337                radius = (int) Math.hypot(x1 - centerX, y1 - centerY);
2338            }
2339
2340            private boolean hasLineWithInfiniteSlope(PointF first, PointF second, PointF third) {
2341                return (second.x - first.x == 0 || third.x - second.x == 0
2342                        || second.y - first.y == 0 || third.y - second.y == 0);
2343            }
2344
2345            @Override
2346            public String toString() {
2347                return "cetner: [" + centerX + ", " + centerY + "] radius: " + radius;
2348            }
2349        }
2350    }
2351}
2352