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