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