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