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