ScreenMagnifier.java revision 55468c64bc4f3c4b16bf144f66907d75bb656b0a
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 = Math.min(Math.max(mMagnificationController.getScale(),
380                        MIN_SCALE), MAX_SCALE);
381                if (scale != getPersistedScale()) {
382                    persistScale(scale);
383                }
384                if (mPreviousState == STATE_VIEWPORT_DRAGGING) {
385                    transitionToState(STATE_VIEWPORT_DRAGGING);
386                } else {
387                    transitionToState(STATE_DETECTING);
388                }
389            }
390        }
391
392        @Override
393        public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX,
394                float distanceY) {
395            if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
396                return true;
397            }
398            final float scale = mMagnificationController.getScale();
399            final float scrollX = distanceX / scale;
400            final float scrollY = distanceY / scale;
401            final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX;
402            final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY;
403            if (DEBUG_PANNING) {
404                Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
405                        + " scrollY: " + scrollY);
406            }
407            mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false);
408            return true;
409        }
410
411        @Override
412        public boolean onScale(ScaleGestureDetector detector) {
413            if (!mScaling) {
414                if (mInitialScaleFactor < 0) {
415                    mInitialScaleFactor = detector.getScaleFactor();
416                } else {
417                    final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
418                    if (Math.abs(deltaScale) > SCALING_THRESHOLD) {
419                        mScaling = true;
420                        return true;
421                    }
422                }
423                return false;
424            }
425            final float newScale = mMagnificationController.getScale()
426                    * detector.getScaleFactor();
427            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
428            if (DEBUG_SCALING) {
429                Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
430            }
431            mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
432                    detector.getFocusY(), false);
433            return true;
434        }
435
436        @Override
437        public boolean onScaleBegin(ScaleGestureDetector detector) {
438            return (mCurrentState == STATE_MAGNIFIED_INTERACTION);
439        }
440
441        @Override
442        public void onScaleEnd(ScaleGestureDetector detector) {
443            clear();
444        }
445
446        private void clear() {
447            mInitialScaleFactor = -1;
448            mScaling = false;
449        }
450    }
451
452    private final class StateViewportDraggingHandler {
453        private boolean mLastMoveOutsideMagnifiedRegion;
454
455        private void onMotionEvent(MotionEvent event, int policyFlags) {
456            final int action = event.getActionMasked();
457            switch (action) {
458                case MotionEvent.ACTION_DOWN: {
459                    throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN");
460                }
461                case MotionEvent.ACTION_POINTER_DOWN: {
462                    clear();
463                    transitionToState(STATE_MAGNIFIED_INTERACTION);
464                } break;
465                case MotionEvent.ACTION_MOVE: {
466                    if (event.getPointerCount() != 1) {
467                        throw new IllegalStateException("Should have one pointer down.");
468                    }
469                    final float eventX = event.getX();
470                    final float eventY = event.getY();
471                    if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
472                        if (mLastMoveOutsideMagnifiedRegion) {
473                            mLastMoveOutsideMagnifiedRegion = false;
474                            mMagnificationController.setMagnifiedRegionCenter(eventX,
475                                    eventY, true);
476                        } else {
477                            mMagnificationController.setMagnifiedRegionCenter(eventX,
478                                    eventY, false);
479                        }
480                    } else {
481                        mLastMoveOutsideMagnifiedRegion = true;
482                    }
483                } break;
484                case MotionEvent.ACTION_UP: {
485                    if (!mTranslationEnabledBeforePan) {
486                        mMagnificationController.reset(true);
487                        mViewport.setFrameShown(false, true);
488                    }
489                    clear();
490                    transitionToState(STATE_DETECTING);
491                } break;
492                case MotionEvent.ACTION_POINTER_UP: {
493                    throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
494                }
495            }
496        }
497
498        public void clear() {
499            mLastMoveOutsideMagnifiedRegion = false;
500        }
501    }
502
503    private final class DetectingStateHandler {
504
505        private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
506
507        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
508
509        private static final int ACTION_TAP_COUNT = 3;
510
511        private MotionEventInfo mDelayedEventQueue;
512
513        private MotionEvent mLastDownEvent;
514        private MotionEvent mLastTapUpEvent;
515        private int mTapCount;
516
517        private final Handler mHandler = new Handler() {
518            @Override
519            public void handleMessage(Message message) {
520                final int type = message.what;
521                switch (type) {
522                    case MESSAGE_ON_ACTION_TAP_AND_HOLD: {
523                        MotionEvent event = (MotionEvent) message.obj;
524                        final int policyFlags = message.arg1;
525                        onActionTapAndHold(event, policyFlags);
526                    } break;
527                    case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
528                        transitionToState(STATE_DELEGATING);
529                        sendDelayedMotionEvents();
530                        clear();
531                    } break;
532                    default: {
533                        throw new IllegalArgumentException("Unknown message type: " + type);
534                    }
535                }
536            }
537        };
538
539        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
540            cacheDelayedMotionEvent(event, rawEvent, policyFlags);
541            final int action = event.getActionMasked();
542            switch (action) {
543                case MotionEvent.ACTION_DOWN: {
544                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
545                    if (!mViewport.getBounds().contains((int) event.getX(),
546                            (int) event.getY())) {
547                        transitionToDelegatingStateAndClear();
548                        return;
549                    }
550                    if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
551                            && GestureUtils.isMultiTap(mLastDownEvent, event,
552                                    mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
553                        Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
554                                policyFlags, 0, event);
555                        mHandler.sendMessageDelayed(message,
556                                ViewConfiguration.getLongPressTimeout());
557                    } else if (mTapCount < ACTION_TAP_COUNT) {
558                        Message message = mHandler.obtainMessage(
559                                MESSAGE_TRANSITION_TO_DELEGATING_STATE);
560                        mHandler.sendMessageDelayed(message, mMultiTapTimeSlop);
561                    }
562                    clearLastDownEvent();
563                    mLastDownEvent = MotionEvent.obtain(event);
564                } break;
565                case MotionEvent.ACTION_POINTER_DOWN: {
566                    if (mMagnificationController.isMagnifying()) {
567                        transitionToState(STATE_MAGNIFIED_INTERACTION);
568                        clear();
569                    } else {
570                        transitionToDelegatingStateAndClear();
571                    }
572                } break;
573                case MotionEvent.ACTION_MOVE: {
574                    if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
575                        final double distance = GestureUtils.computeDistance(mLastDownEvent,
576                                event, 0);
577                        if (Math.abs(distance) > mTapDistanceSlop) {
578                            transitionToDelegatingStateAndClear();
579                        }
580                    }
581                } break;
582                case MotionEvent.ACTION_UP: {
583                    if (mLastDownEvent == null) {
584                        return;
585                    }
586                    mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
587                    if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
588                         transitionToDelegatingStateAndClear();
589                         return;
590                    }
591                    if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
592                            mTapDistanceSlop, 0)) {
593                        transitionToDelegatingStateAndClear();
594                        return;
595                    }
596                    if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent,
597                            event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
598                        transitionToDelegatingStateAndClear();
599                        return;
600                    }
601                    mTapCount++;
602                    if (DEBUG_DETECTING) {
603                        Slog.i(LOG_TAG, "Tap count:" + mTapCount);
604                    }
605                    if (mTapCount == ACTION_TAP_COUNT) {
606                        clear();
607                        onActionTap(event, policyFlags);
608                        return;
609                    }
610                    clearLastTapUpEvent();
611                    mLastTapUpEvent = MotionEvent.obtain(event);
612                } break;
613                case MotionEvent.ACTION_POINTER_UP: {
614                    /* do nothing */
615                } break;
616            }
617        }
618
619        public void clear() {
620            mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
621            mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
622            clearTapDetectionState();
623            clearDelayedMotionEvents();
624        }
625
626        private void clearTapDetectionState() {
627            mTapCount = 0;
628            clearLastTapUpEvent();
629            clearLastDownEvent();
630        }
631
632        private void clearLastTapUpEvent() {
633            if (mLastTapUpEvent != null) {
634                mLastTapUpEvent.recycle();
635                mLastTapUpEvent = null;
636            }
637        }
638
639        private void clearLastDownEvent() {
640            if (mLastDownEvent != null) {
641                mLastDownEvent.recycle();
642                mLastDownEvent = null;
643            }
644        }
645
646        private void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent,
647                int policyFlags) {
648            MotionEventInfo info = MotionEventInfo.obtain(event, rawEvent,
649                    policyFlags);
650            if (mDelayedEventQueue == null) {
651                mDelayedEventQueue = info;
652            } else {
653                MotionEventInfo tail = mDelayedEventQueue;
654                while (tail.mNext != null) {
655                    tail = tail.mNext;
656                }
657                tail.mNext = info;
658            }
659        }
660
661        private void sendDelayedMotionEvents() {
662            while (mDelayedEventQueue != null) {
663                MotionEventInfo info = mDelayedEventQueue;
664                mDelayedEventQueue = info.mNext;
665                ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mRawEvent,
666                        info.mPolicyFlags);
667                info.recycle();
668            }
669        }
670
671        private void clearDelayedMotionEvents() {
672            while (mDelayedEventQueue != null) {
673                MotionEventInfo info = mDelayedEventQueue;
674                mDelayedEventQueue = info.mNext;
675                info.recycle();
676            }
677        }
678
679        private void transitionToDelegatingStateAndClear() {
680            transitionToState(STATE_DELEGATING);
681            sendDelayedMotionEvents();
682            clear();
683        }
684
685        private void onActionTap(MotionEvent up, int policyFlags) {
686            if (DEBUG_DETECTING) {
687                Slog.i(LOG_TAG, "onActionTap()");
688            }
689            if (!mMagnificationController.isMagnifying()) {
690                mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
691                        up.getX(), up.getY(), true);
692                mViewport.setFrameShown(true, true);
693            } else {
694                mMagnificationController.reset(true);
695                mViewport.setFrameShown(false, true);
696            }
697        }
698
699        private void onActionTapAndHold(MotionEvent down, int policyFlags) {
700            if (DEBUG_DETECTING) {
701                Slog.i(LOG_TAG, "onActionTapAndHold()");
702            }
703            clear();
704            mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
705            mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
706                    down.getX(), down.getY(), true);
707            mViewport.setFrameShown(true, true);
708            transitionToState(STATE_VIEWPORT_DRAGGING);
709        }
710    }
711
712    private void persistScale(final float scale) {
713        new AsyncTask<Void, Void, Void>() {
714            @Override
715            protected Void doInBackground(Void... params) {
716                Settings.Secure.putFloat(mContext.getContentResolver(),
717                        Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale);
718                return null;
719            }
720        }.execute();
721    }
722
723    private float getPersistedScale() {
724        return Settings.Secure.getFloat(mContext.getContentResolver(),
725                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
726                DEFAULT_MAGNIFICATION_SCALE);
727    }
728
729    private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) {
730        return (Settings.Secure.getInt(context.getContentResolver(),
731                Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
732                DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
733    }
734
735    private static final class MotionEventInfo {
736
737        private static final int MAX_POOL_SIZE = 10;
738
739        private static final Object sLock = new Object();
740        private static MotionEventInfo sPool;
741        private static int sPoolSize;
742
743        private MotionEventInfo mNext;
744        private boolean mInPool;
745
746        public MotionEvent mEvent;
747        public MotionEvent mRawEvent;
748        public int mPolicyFlags;
749
750        public static MotionEventInfo obtain(MotionEvent event, MotionEvent rawEvent,
751                int policyFlags) {
752            synchronized (sLock) {
753                MotionEventInfo info;
754                if (sPoolSize > 0) {
755                    sPoolSize--;
756                    info = sPool;
757                    sPool = info.mNext;
758                    info.mNext = null;
759                    info.mInPool = false;
760                } else {
761                    info = new MotionEventInfo();
762                }
763                info.initialize(event, rawEvent, policyFlags);
764                return info;
765            }
766        }
767
768        private void initialize(MotionEvent event, MotionEvent rawEvent,
769                int policyFlags) {
770            mEvent = MotionEvent.obtain(event);
771            mRawEvent = MotionEvent.obtain(rawEvent);
772            mPolicyFlags = policyFlags;
773        }
774
775        public void recycle() {
776            synchronized (sLock) {
777                if (mInPool) {
778                    throw new IllegalStateException("Already recycled.");
779                }
780                clear();
781                if (sPoolSize < MAX_POOL_SIZE) {
782                    sPoolSize++;
783                    mNext = sPool;
784                    sPool = this;
785                    mInPool = true;
786                }
787            }
788        }
789
790        private void clear() {
791            mEvent.recycle();
792            mEvent = null;
793            mRawEvent.recycle();
794            mRawEvent = null;
795            mPolicyFlags = 0;
796        }
797    }
798
799    private static final class ScreenStateObserver extends BroadcastReceiver {
800
801        private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
802
803        private final Handler mHandler = new Handler() {
804            @Override
805            public void handleMessage(Message message) {
806                switch (message.what) {
807                    case MESSAGE_ON_SCREEN_STATE_CHANGE: {
808                        String action = (String) message.obj;
809                        handleOnScreenStateChange(action);
810                    } break;
811                }
812            }
813        };
814
815        private final Context mContext;
816        private final Viewport mViewport;
817        private final MagnificationController mMagnificationController;
818
819        public ScreenStateObserver(Context context, Viewport viewport,
820                MagnificationController magnificationController) {
821            mContext = context;
822            mViewport = viewport;
823            mMagnificationController = magnificationController;
824            mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
825        }
826
827        public void destroy() {
828            mContext.unregisterReceiver(this);
829        }
830
831        @Override
832        public void onReceive(Context context, Intent intent) {
833            mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
834                    intent.getAction()).sendToTarget();
835        }
836
837        private void handleOnScreenStateChange(String action) {
838            if (action.equals(Intent.ACTION_SCREEN_OFF)
839                    && mMagnificationController.isMagnifying()
840                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
841                mMagnificationController.reset(false);
842                mViewport.setFrameShown(false, false);
843            }
844        }
845    }
846
847    private static final class DisplayContentObserver {
848
849        private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
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        private static final int MESSAGE_ON_WINDOW_LAYERS_CHANGED = 6;
854
855        private final Handler mHandler = new MyHandler();
856
857        private final Rect mTempRect = new Rect();
858
859        private final IDisplayContentChangeListener mDisplayContentChangeListener;
860
861        private final Context mContext;
862        private final Viewport mViewport;
863        private final MagnificationController mMagnificationController;
864        private final IWindowManager mWindowManagerService;
865        private final DisplayProvider mDisplayProvider;
866        private final long mLongAnimationDuration;
867        private final float mWindowAnimationScale;
868
869        public DisplayContentObserver(Context context, Viewport viewport,
870                MagnificationController magnificationController,
871                IWindowManager windowManagerService, DisplayProvider displayProvider,
872                long longAnimationDuration, float windowAnimationScale) {
873            mContext = context;
874            mViewport = viewport;
875            mMagnificationController = magnificationController;
876            mWindowManagerService = windowManagerService;
877            mDisplayProvider = displayProvider;
878            mLongAnimationDuration = longAnimationDuration;
879            mWindowAnimationScale = windowAnimationScale;
880
881            mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() {
882                @Override
883                public void onWindowTransition(int displayId, int transition, WindowInfo info) {
884                    mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION,
885                            transition, 0, WindowInfo.obtain(info)).sendToTarget();
886                }
887
888                @Override
889                public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle,
890                        boolean immediate) {
891                    SomeArgs args = SomeArgs.obtain();
892                    args.argi1 = rectangle.left;
893                    args.argi2 = rectangle.top;
894                    args.argi3 = rectangle.right;
895                    args.argi4 = rectangle.bottom;
896                    mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0,
897                            immediate ? 1 : 0, args).sendToTarget();
898                }
899
900                @Override
901                public void onRotationChanged(int rotation) throws RemoteException {
902                    mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0)
903                            .sendToTarget();
904                }
905
906                @Override
907                public void onWindowLayersChanged(int displayId) throws RemoteException {
908                    mHandler.sendEmptyMessage(MESSAGE_ON_WINDOW_LAYERS_CHANGED);
909                }
910            };
911
912            try {
913                mWindowManagerService.addDisplayContentChangeListener(
914                        mDisplayProvider.getDisplay().getDisplayId(),
915                        mDisplayContentChangeListener);
916            } catch (RemoteException re) {
917                /* ignore */
918            }
919        }
920
921        public void destroy() {
922            try {
923                mWindowManagerService.removeDisplayContentChangeListener(
924                        mDisplayProvider.getDisplay().getDisplayId(),
925                        mDisplayContentChangeListener);
926            } catch (RemoteException re) {
927                /* ignore*/
928            }
929        }
930
931        private void handleOnRotationChanged(int rotation) {
932            if (DEBUG_ROTATION) {
933                Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation));
934            }
935            resetMagnificationIfNeeded();
936            mViewport.setFrameShown(false, false);
937            mViewport.rotationChanged();
938            mViewport.recomputeBounds(false);
939            if (mMagnificationController.isMagnifying()) {
940                final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
941                Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
942                mHandler.sendMessageDelayed(message, delay);
943            }
944        }
945
946        private void handleOnWindowTransition(int transition, WindowInfo info) {
947            if (DEBUG_WINDOW_TRANSITIONS) {
948                Slog.i(LOG_TAG, "Window transitioning: "
949                        + windowTransitionToString(transition));
950            }
951            try {
952                final boolean magnifying = mMagnificationController.isMagnifying();
953                if (magnifying) {
954                    switch (transition) {
955                        case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
956                        case WindowManagerPolicy.TRANSIT_TASK_OPEN:
957                        case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
958                        case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
959                        case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
960                        case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
961                            resetMagnificationIfNeeded();
962                        }
963                    }
964                }
965                if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
966                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
967                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
968                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD
969                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
970                    switch (transition) {
971                        case WindowManagerPolicy.TRANSIT_ENTER:
972                        case WindowManagerPolicy.TRANSIT_SHOW:
973                        case WindowManagerPolicy.TRANSIT_EXIT:
974                        case WindowManagerPolicy.TRANSIT_HIDE: {
975                            mViewport.recomputeBounds(mMagnificationController.isMagnifying());
976                        } break;
977                    }
978                } else {
979                    switch (transition) {
980                        case WindowManagerPolicy.TRANSIT_ENTER:
981                        case WindowManagerPolicy.TRANSIT_SHOW: {
982                            if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) {
983                                break;
984                            }
985                            final int type = info.type;
986                            switch (type) {
987                                // TODO: Are these all the windows we want to make
988                                //       visible when they appear on the screen?
989                                //       Do we need to take some of them out?
990                                case WindowManager.LayoutParams.TYPE_APPLICATION:
991                                case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
992                                case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
993                                case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
994                                case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
995                                case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
996                                case WindowManager.LayoutParams.TYPE_PHONE:
997                                case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
998                                case WindowManager.LayoutParams.TYPE_TOAST:
999                                case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
1000                                case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
1001                                case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
1002                                case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
1003                                case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
1004                                case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
1005                                case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
1006                                case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
1007                                    Rect magnifiedRegionBounds = mMagnificationController
1008                                            .getMagnifiedRegionBounds();
1009                                    Rect touchableRegion = info.touchableRegion;
1010                                    if (!magnifiedRegionBounds.intersect(touchableRegion)) {
1011                                        ensureRectangleInMagnifiedRegionBounds(
1012                                                magnifiedRegionBounds, touchableRegion);
1013                                    }
1014                                } break;
1015                            } break;
1016                        }
1017                    }
1018                }
1019            } finally {
1020                if (info != null) {
1021                    info.recycle();
1022                }
1023            }
1024        }
1025
1026        private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) {
1027            if (!mMagnificationController.isMagnifying()) {
1028                return;
1029            }
1030            Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds();
1031            if (magnifiedRegionBounds.contains(rectangle)) {
1032                return;
1033            }
1034            ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle);
1035        }
1036
1037        private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds,
1038                Rect rectangle) {
1039            if (!Rect.intersects(rectangle, mViewport.getBounds())) {
1040                return;
1041            }
1042            final float scrollX;
1043            final float scrollY;
1044            if (rectangle.width() > magnifiedRegionBounds.width()) {
1045                scrollX = rectangle.left - magnifiedRegionBounds.left;
1046            } else if (rectangle.left < magnifiedRegionBounds.left) {
1047                scrollX = rectangle.left - magnifiedRegionBounds.left;
1048            } else if (rectangle.right > magnifiedRegionBounds.right) {
1049                scrollX = rectangle.right - magnifiedRegionBounds.right;
1050            } else {
1051                scrollX = 0;
1052            }
1053            if (rectangle.height() > magnifiedRegionBounds.height()) {
1054                scrollY = rectangle.top - magnifiedRegionBounds.top;
1055            } else if (rectangle.top < magnifiedRegionBounds.top) {
1056                scrollY = rectangle.top - magnifiedRegionBounds.top;
1057            } else if (rectangle.bottom > magnifiedRegionBounds.bottom) {
1058                scrollY = rectangle.bottom - magnifiedRegionBounds.bottom;
1059            } else {
1060                scrollY = 0;
1061            }
1062            final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX()
1063                    + scrollX;
1064            final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY()
1065                    + scrollY;
1066            mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY,
1067                    true);
1068        }
1069
1070        private void resetMagnificationIfNeeded() {
1071            if (mMagnificationController.isMagnifying()
1072                    && isScreenMagnificationAutoUpdateEnabled(mContext)) {
1073                mMagnificationController.reset(true);
1074                mViewport.setFrameShown(false, true);
1075            }
1076        }
1077
1078        private String windowTransitionToString(int transition) {
1079            switch (transition) {
1080                case WindowManagerPolicy.TRANSIT_UNSET: {
1081                    return "TRANSIT_UNSET";
1082                }
1083                case WindowManagerPolicy.TRANSIT_NONE: {
1084                    return "TRANSIT_NONE";
1085                }
1086                case WindowManagerPolicy.TRANSIT_ENTER: {
1087                    return "TRANSIT_ENTER";
1088                }
1089                case WindowManagerPolicy.TRANSIT_EXIT: {
1090                    return "TRANSIT_EXIT";
1091                }
1092                case WindowManagerPolicy.TRANSIT_SHOW: {
1093                    return "TRANSIT_SHOW";
1094                }
1095                case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
1096                    return "TRANSIT_EXIT_MASK";
1097                }
1098                case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
1099                    return "TRANSIT_PREVIEW_DONE";
1100                }
1101                case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
1102                    return "TRANSIT_ACTIVITY_OPEN";
1103                }
1104                case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
1105                    return "TRANSIT_ACTIVITY_CLOSE";
1106                }
1107                case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
1108                    return "TRANSIT_TASK_OPEN";
1109                }
1110                case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
1111                    return "TRANSIT_TASK_CLOSE";
1112                }
1113                case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
1114                    return "TRANSIT_TASK_TO_FRONT";
1115                }
1116                case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
1117                    return "TRANSIT_TASK_TO_BACK";
1118                }
1119                case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
1120                    return "TRANSIT_WALLPAPER_CLOSE";
1121                }
1122                case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
1123                    return "TRANSIT_WALLPAPER_OPEN";
1124                }
1125                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
1126                    return "TRANSIT_WALLPAPER_INTRA_OPEN";
1127                }
1128                case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
1129                    return "TRANSIT_WALLPAPER_INTRA_CLOSE";
1130                }
1131                default: {
1132                    return "<UNKNOWN>";
1133                }
1134            }
1135        }
1136
1137        private String rotationToString(int rotation) {
1138            switch (rotation) {
1139                case Surface.ROTATION_0: {
1140                    return "ROTATION_0";
1141                }
1142                case Surface.ROTATION_90: {
1143                    return "ROATATION_90";
1144                }
1145                case Surface.ROTATION_180: {
1146                    return "ROATATION_180";
1147                }
1148                case Surface.ROTATION_270: {
1149                    return "ROATATION_270";
1150                }
1151                default: {
1152                    throw new IllegalArgumentException("Invalid rotation: "
1153                        + rotation);
1154                }
1155            }
1156        }
1157
1158        private final class MyHandler extends Handler {
1159            @Override
1160            public void handleMessage(Message message) {
1161                final int action = message.what;
1162                switch (action) {
1163                    case MESSAGE_SHOW_VIEWPORT_FRAME: {
1164                        mViewport.setFrameShown(true, true);
1165                    } break;
1166                    case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
1167                        SomeArgs args = (SomeArgs) message.obj;
1168                        try {
1169                            mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4);
1170                            final boolean immediate = (message.arg1 == 1);
1171                            handleOnRectangleOnScreenRequested(mTempRect, immediate);
1172                        } finally {
1173                            args.recycle();
1174                        }
1175                    } break;
1176                    case MESSAGE_ON_WINDOW_TRANSITION: {
1177                        final int transition = message.arg1;
1178                        WindowInfo info = (WindowInfo) message.obj;
1179                        handleOnWindowTransition(transition, info);
1180                    } break;
1181                    case MESSAGE_ON_ROTATION_CHANGED: {
1182                        final int rotation = message.arg1;
1183                        handleOnRotationChanged(rotation);
1184                    } break;
1185                    case MESSAGE_ON_WINDOW_LAYERS_CHANGED: {
1186                        mViewport.recomputeBounds(mMagnificationController.isMagnifying());
1187                    } break;
1188                    default: {
1189                        throw new IllegalArgumentException("Unknown message: " + action);
1190                    }
1191                }
1192            }
1193        }
1194    }
1195
1196    private final class MagnificationController {
1197
1198        private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION =
1199                "accessibilityTransformation";
1200
1201        private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
1202
1203        private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
1204
1205        private final Rect mTempRect = new Rect();
1206
1207        private final ValueAnimator mTransformationAnimator;
1208
1209        public MagnificationController(int animationDuration) {
1210            Property<MagnificationController, MagnificationSpec> property =
1211                    Property.of(MagnificationController.class, MagnificationSpec.class,
1212                    PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION);
1213            TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
1214                private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec();
1215                @Override
1216                public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
1217                        MagnificationSpec toSpec) {
1218                    MagnificationSpec result = mTempTransformationSpec;
1219                    result.mScale = fromSpec.mScale
1220                            + (toSpec.mScale - fromSpec.mScale) * fraction;
1221                    result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX
1222                            + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX)
1223                            * fraction;
1224                    result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY
1225                            + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY)
1226                            * fraction;
1227                    result.mScaledOffsetX = fromSpec.mScaledOffsetX
1228                            + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX)
1229                            * fraction;
1230                    result.mScaledOffsetY = fromSpec.mScaledOffsetY
1231                            + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY)
1232                            * fraction;
1233                    return result;
1234                }
1235            };
1236            mTransformationAnimator = ObjectAnimator.ofObject(this, property,
1237                    evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
1238            mTransformationAnimator.setDuration((long) (animationDuration));
1239            mTransformationAnimator.setInterpolator(mInterpolator);
1240        }
1241
1242        public boolean isMagnifying() {
1243            return mCurrentMagnificationSpec.mScale > 1.0f;
1244        }
1245
1246        public void reset(boolean animate) {
1247            if (mTransformationAnimator.isRunning()) {
1248                mTransformationAnimator.cancel();
1249            }
1250            mCurrentMagnificationSpec.reset();
1251            if (animate) {
1252                animateAccessibilityTranformation(mSentMagnificationSpec,
1253                        mCurrentMagnificationSpec);
1254            } else {
1255                setAccessibilityTransformation(mCurrentMagnificationSpec);
1256            }
1257        }
1258
1259        public Rect getMagnifiedRegionBounds() {
1260            mTempRect.set(mViewport.getBounds());
1261            mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX,
1262                    (int) -mCurrentMagnificationSpec.mScaledOffsetY);
1263            mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale);
1264            return mTempRect;
1265        }
1266
1267        public float getScale() {
1268            return mCurrentMagnificationSpec.mScale;
1269        }
1270
1271        public float getMagnifiedRegionCenterX() {
1272            return mCurrentMagnificationSpec.mMagnifiedRegionCenterX;
1273        }
1274
1275        public float getMagnifiedRegionCenterY() {
1276            return mCurrentMagnificationSpec.mMagnifiedRegionCenterY;
1277        }
1278
1279        public float getScaledOffsetX() {
1280            return mCurrentMagnificationSpec.mScaledOffsetX;
1281        }
1282
1283        public float getScaledOffsetY() {
1284            return mCurrentMagnificationSpec.mScaledOffsetY;
1285        }
1286
1287        public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
1288            MagnificationSpec spec = mCurrentMagnificationSpec;
1289            final float oldScale = spec.mScale;
1290            final float oldCenterX = spec.mMagnifiedRegionCenterX;
1291            final float oldCenterY = spec.mMagnifiedRegionCenterY;
1292            final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale;
1293            final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale;
1294            final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
1295            final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
1296            final float centerX = normPivotX + offsetX;
1297            final float centerY = normPivotY + offsetY;
1298            setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
1299        }
1300
1301        public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
1302            setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY,
1303                    animate);
1304        }
1305
1306        public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
1307                boolean animate) {
1308            if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0
1309                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX,
1310                            centerX) == 0
1311                    && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY,
1312                            centerY) == 0) {
1313                return;
1314            }
1315            if (mTransformationAnimator.isRunning()) {
1316                mTransformationAnimator.cancel();
1317            }
1318            if (DEBUG_MAGNIFICATION_CONTROLLER) {
1319                Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX
1320                        + " centerY: " + centerY);
1321            }
1322            mCurrentMagnificationSpec.initialize(scale, centerX, centerY);
1323            if (animate) {
1324                animateAccessibilityTranformation(mSentMagnificationSpec,
1325                        mCurrentMagnificationSpec);
1326            } else {
1327                setAccessibilityTransformation(mCurrentMagnificationSpec);
1328            }
1329        }
1330
1331        private void animateAccessibilityTranformation(MagnificationSpec fromSpec,
1332                MagnificationSpec toSpec) {
1333            mTransformationAnimator.setObjectValues(fromSpec, toSpec);
1334            mTransformationAnimator.start();
1335        }
1336
1337        @SuppressWarnings("unused")
1338        // Called from an animator.
1339        public MagnificationSpec getAccessibilityTransformation() {
1340            return mSentMagnificationSpec;
1341        }
1342
1343        public void setAccessibilityTransformation(MagnificationSpec transformation) {
1344            if (DEBUG_TRANSFORMATION) {
1345                Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale
1346                        + " offsetX: " + transformation.mScaledOffsetX
1347                        + " offsetY: " + transformation.mScaledOffsetY);
1348            }
1349            try {
1350                mSentMagnificationSpec.updateFrom(transformation);
1351                mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(),
1352                        transformation.mScale, transformation.mScaledOffsetX,
1353                        transformation.mScaledOffsetY);
1354            } catch (RemoteException re) {
1355                /* ignore */
1356            }
1357        }
1358
1359        private class MagnificationSpec {
1360
1361            private static final float DEFAULT_SCALE = 1.0f;
1362
1363            public float mScale = DEFAULT_SCALE;
1364
1365            public float mMagnifiedRegionCenterX;
1366
1367            public float mMagnifiedRegionCenterY;
1368
1369            public float mScaledOffsetX;
1370
1371            public float mScaledOffsetY;
1372
1373            public void initialize(float scale, float magnifiedRegionCenterX,
1374                    float magnifiedRegionCenterY) {
1375                mScale = scale;
1376
1377                final int viewportWidth = mViewport.getBounds().width();
1378                final int viewportHeight = mViewport.getBounds().height();
1379                final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale;
1380                final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale;
1381                final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX;
1382                final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY;
1383
1384                mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX,
1385                        minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX);
1386                mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY,
1387                        minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY);
1388
1389                mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2);
1390                mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2);
1391            }
1392
1393            public void updateFrom(MagnificationSpec other) {
1394                mScale = other.mScale;
1395                mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX;
1396                mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY;
1397                mScaledOffsetX = other.mScaledOffsetX;
1398                mScaledOffsetY = other.mScaledOffsetY;
1399            }
1400
1401            public void reset() {
1402                mScale = DEFAULT_SCALE;
1403                mMagnifiedRegionCenterX = 0;
1404                mMagnifiedRegionCenterY = 0;
1405                mScaledOffsetX = 0;
1406                mScaledOffsetY = 0;
1407            }
1408        }
1409    }
1410
1411    private static final class Viewport {
1412
1413        private static final String PROPERTY_NAME_ALPHA = "alpha";
1414
1415        private static final String PROPERTY_NAME_BOUNDS = "bounds";
1416
1417        private static final int MIN_ALPHA = 0;
1418
1419        private static final int MAX_ALPHA = 255;
1420
1421        private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
1422
1423        private final Rect mTempRect1 = new Rect();
1424        private final Rect mTempRect2 = new Rect();
1425        private final Rect mTempRect3 = new Rect();
1426
1427        private final IWindowManager mWindowManagerService;
1428        private final DisplayProvider mDisplayProvider;
1429
1430        private final ViewportWindow mViewportFrame;
1431
1432        private final ValueAnimator mResizeFrameAnimator;
1433
1434        private final ValueAnimator mShowHideFrameAnimator;
1435
1436        public Viewport(Context context, WindowManager windowManager,
1437                IWindowManager windowManagerService, DisplayProvider displayInfoProvider,
1438                Interpolator animationInterpolator, long animationDuration) {
1439            mWindowManagerService = windowManagerService;
1440            mDisplayProvider = displayInfoProvider;
1441            mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider);
1442
1443            mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
1444                  MIN_ALPHA, MAX_ALPHA);
1445            mShowHideFrameAnimator.setInterpolator(animationInterpolator);
1446            mShowHideFrameAnimator.setDuration(animationDuration);
1447            mShowHideFrameAnimator.addListener(new AnimatorListener() {
1448                @Override
1449                public void onAnimationEnd(Animator animation) {
1450                    if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
1451                        mViewportFrame.hide();
1452                    }
1453                }
1454                @Override
1455                public void onAnimationStart(Animator animation) {
1456                    /* do nothing - stub */
1457                }
1458                @Override
1459                public void onAnimationCancel(Animator animation) {
1460                    /* do nothing - stub */
1461                }
1462                @Override
1463                public void onAnimationRepeat(Animator animation) {
1464                    /* do nothing - stub */
1465                }
1466            });
1467
1468            Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class,
1469                    Rect.class, PROPERTY_NAME_BOUNDS);
1470            TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() {
1471                private final Rect mReusableResultRect = new Rect();
1472                @Override
1473                public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) {
1474                    Rect result = mReusableResultRect;
1475                    result.left = (int) (fromFrame.left
1476                            + (toFrame.left - fromFrame.left) * fraction);
1477                    result.top = (int) (fromFrame.top
1478                            + (toFrame.top - fromFrame.top) * fraction);
1479                    result.right = (int) (fromFrame.right
1480                            + (toFrame.right - fromFrame.right) * fraction);
1481                    result.bottom = (int) (fromFrame.bottom
1482                            + (toFrame.bottom - fromFrame.bottom) * fraction);
1483                    return result;
1484                }
1485            };
1486            mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
1487                    evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
1488            mResizeFrameAnimator.setDuration((long) (animationDuration));
1489            mResizeFrameAnimator.setInterpolator(animationInterpolator);
1490
1491            recomputeBounds(false);
1492        }
1493
1494        private final Comparator<WindowInfo> mWindowInfoInverseComparator =
1495                new Comparator<WindowInfo>() {
1496            @Override
1497            public int compare(WindowInfo lhs, WindowInfo rhs) {
1498                if (lhs.layer != rhs.layer) {
1499                    return rhs.layer - lhs.layer;
1500                }
1501                if (lhs.touchableRegion.top != rhs.touchableRegion.top) {
1502                    return rhs.touchableRegion.top - lhs.touchableRegion.top;
1503                }
1504                if (lhs.touchableRegion.left != rhs.touchableRegion.left) {
1505                    return rhs.touchableRegion.left - lhs.touchableRegion.left;
1506                }
1507                if (lhs.touchableRegion.right != rhs.touchableRegion.right) {
1508                    return rhs.touchableRegion.right - lhs.touchableRegion.right;
1509                }
1510                if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) {
1511                    return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom;
1512                }
1513                return 0;
1514            }
1515        };
1516
1517        public void recomputeBounds(boolean animate) {
1518            Rect magnifiedFrame = mTempRect1;
1519            magnifiedFrame.set(0, 0, 0, 0);
1520
1521            DisplayInfo displayInfo = mDisplayProvider.getDisplayInfo();
1522
1523            Rect availableFrame = mTempRect2;
1524            availableFrame.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight);
1525
1526            ArrayList<WindowInfo> infos = mTempWindowInfoList;
1527            infos.clear();
1528            int windowCount = 0;
1529            try {
1530                mWindowManagerService.getVisibleWindowsForDisplay(
1531                        mDisplayProvider.getDisplay().getDisplayId(), infos);
1532                Collections.sort(infos, mWindowInfoInverseComparator);
1533                windowCount = infos.size();
1534                for (int i = 0; i < windowCount; i++) {
1535                    WindowInfo info = infos.get(i);
1536                    if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
1537                        continue;
1538                    }
1539                    Rect windowFrame = mTempRect3;
1540                    windowFrame.set(info.touchableRegion);
1541                    if (isWindowMagnified(info.type)) {
1542                        magnifiedFrame.union(windowFrame);
1543                        magnifiedFrame.intersect(availableFrame);
1544                    } else {
1545                        subtract(windowFrame, magnifiedFrame);
1546                        subtract(availableFrame, windowFrame);
1547                    }
1548                    if (availableFrame.equals(magnifiedFrame)) {
1549                        break;
1550                    }
1551                }
1552            } catch (RemoteException re) {
1553                /* ignore */
1554            } finally {
1555                for (int i = windowCount - 1; i >= 0; i--) {
1556                    infos.remove(i).recycle();
1557                }
1558            }
1559
1560            final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth;
1561            final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight;
1562            magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
1563
1564            resize(magnifiedFrame, animate);
1565        }
1566
1567        private boolean isWindowMagnified(int type) {
1568            return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
1569                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD
1570                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1571        }
1572
1573        public void rotationChanged() {
1574            mViewportFrame.rotationChanged();
1575        }
1576
1577        public Rect getBounds() {
1578            return mViewportFrame.getBounds();
1579        }
1580
1581        public void setFrameShown(boolean shown, boolean animate) {
1582            if (mViewportFrame.isShown() == shown) {
1583                return;
1584            }
1585            if (animate) {
1586                if (mShowHideFrameAnimator.isRunning()) {
1587                    mShowHideFrameAnimator.reverse();
1588                } else {
1589                    if (shown) {
1590                        mViewportFrame.show();
1591                        mShowHideFrameAnimator.start();
1592                    } else {
1593                        mShowHideFrameAnimator.reverse();
1594                    }
1595                }
1596            } else {
1597                mShowHideFrameAnimator.cancel();
1598                if (shown) {
1599                    mViewportFrame.show();
1600                } else {
1601                    mViewportFrame.hide();
1602                }
1603            }
1604        }
1605
1606        private void resize(Rect bounds, boolean animate) {
1607            if (mViewportFrame.getBounds().equals(bounds)) {
1608                return;
1609            }
1610            if (animate) {
1611                if (mResizeFrameAnimator.isRunning()) {
1612                    mResizeFrameAnimator.cancel();
1613                }
1614                mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
1615                mResizeFrameAnimator.start();
1616            } else {
1617                mViewportFrame.setBounds(bounds);
1618            }
1619        }
1620
1621        private boolean subtract(Rect lhs, Rect rhs) {
1622            if (lhs.right < rhs.left || lhs.left  > rhs.right
1623                    || lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
1624                return false;
1625            }
1626            if (lhs.left < rhs.left) {
1627                lhs.right = rhs.left;
1628            }
1629            if (lhs.top < rhs.top) {
1630                lhs.bottom = rhs.top;
1631            }
1632            if (lhs.right > rhs.right) {
1633                lhs.left = rhs.right;
1634            }
1635            if (lhs.bottom > rhs.bottom) {
1636                lhs.top = rhs.bottom;
1637            }
1638            return true;
1639        }
1640
1641        private static final class ViewportWindow {
1642            private static final String WINDOW_TITLE = "Magnification Overlay";
1643
1644            private final WindowManager mWindowManager;
1645            private final DisplayProvider mDisplayProvider;
1646
1647            private final ContentView mWindowContent;
1648            private final WindowManager.LayoutParams mWindowParams;
1649
1650            private final Rect mBounds = new Rect();
1651            private boolean mShown;
1652            private int mAlpha;
1653
1654            public ViewportWindow(Context context, WindowManager windowManager,
1655                    DisplayProvider displayProvider) {
1656                mWindowManager = windowManager;
1657                mDisplayProvider = displayProvider;
1658
1659                ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
1660                        ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
1661                mWindowContent = new ContentView(context);
1662                mWindowContent.setLayoutParams(contentParams);
1663                mWindowContent.setBackgroundColor(R.color.transparent);
1664
1665                mWindowParams = new WindowManager.LayoutParams(
1666                        WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
1667                mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
1668                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1669                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
1670                mWindowParams.setTitle(WINDOW_TITLE);
1671                mWindowParams.gravity = Gravity.CENTER;
1672                mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth;
1673                mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight;
1674                mWindowParams.format = PixelFormat.TRANSLUCENT;
1675            }
1676
1677            public boolean isShown() {
1678                return mShown;
1679            }
1680
1681            public void show() {
1682                if (mShown) {
1683                    return;
1684                }
1685                mShown = true;
1686                mWindowManager.addView(mWindowContent, mWindowParams);
1687                if (DEBUG_VIEWPORT_WINDOW) {
1688                    Slog.i(LOG_TAG, "ViewportWindow shown.");
1689                }
1690            }
1691
1692            public void hide() {
1693                if (!mShown) {
1694                    return;
1695                }
1696                mShown = false;
1697                mWindowManager.removeView(mWindowContent);
1698                if (DEBUG_VIEWPORT_WINDOW) {
1699                    Slog.i(LOG_TAG, "ViewportWindow hidden.");
1700                }
1701            }
1702
1703            @SuppressWarnings("unused")
1704            // Called reflectively from an animator.
1705            public int getAlpha() {
1706                return mAlpha;
1707            }
1708
1709            @SuppressWarnings("unused")
1710            // Called reflectively from an animator.
1711            public void setAlpha(int alpha) {
1712                if (mAlpha == alpha) {
1713                    return;
1714                }
1715                mAlpha = alpha;
1716                if (mShown) {
1717                    mWindowContent.invalidate();
1718                }
1719                if (DEBUG_VIEWPORT_WINDOW) {
1720                    Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
1721                }
1722            }
1723
1724            public Rect getBounds() {
1725                return mBounds;
1726            }
1727
1728            public void rotationChanged() {
1729                mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth;
1730                mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight;
1731                if (mShown) {
1732                    mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
1733                }
1734            }
1735
1736            public void setBounds(Rect bounds) {
1737                if (mBounds.equals(bounds)) {
1738                    return;
1739                }
1740                mBounds.set(bounds);
1741                if (mShown) {
1742                    mWindowContent.invalidate();
1743                }
1744                if (DEBUG_VIEWPORT_WINDOW) {
1745                    Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
1746                }
1747            }
1748
1749            private final class ContentView extends View {
1750                private final Drawable mHighlightFrame;
1751
1752                public ContentView(Context context) {
1753                    super(context);
1754                    mHighlightFrame = context.getResources().getDrawable(
1755                            R.drawable.magnified_region_frame);
1756                }
1757
1758                @Override
1759                public void onDraw(Canvas canvas) {
1760                    canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
1761                    mHighlightFrame.setBounds(mBounds);
1762                    mHighlightFrame.setAlpha(mAlpha);
1763                    mHighlightFrame.draw(canvas);
1764                }
1765            }
1766        }
1767    }
1768
1769    private static class DisplayProvider implements DisplayListener {
1770        private final WindowManager mWindowManager;
1771        private final DisplayManager mDisplayManager;
1772        private final Display mDefaultDisplay;
1773        private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
1774
1775        public DisplayProvider(Context context, WindowManager windowManager) {
1776            mWindowManager = windowManager;
1777            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
1778            mDefaultDisplay = mWindowManager.getDefaultDisplay();
1779            mDisplayManager.registerDisplayListener(this, null);
1780            updateDisplayInfo();
1781        }
1782
1783        public DisplayInfo getDisplayInfo() {
1784            return mDefaultDisplayInfo;
1785        }
1786
1787        public Display getDisplay() {
1788            return mDefaultDisplay;
1789        }
1790
1791        private void updateDisplayInfo() {
1792            if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
1793                Slog.e(LOG_TAG, "Default display is not valid.");
1794            }
1795        }
1796
1797        public void destroy() {
1798            mDisplayManager.unregisterDisplayListener(this);
1799        }
1800
1801        @Override
1802        public void onDisplayAdded(int displayId) {
1803            /* do noting */
1804        }
1805
1806        @Override
1807        public void onDisplayRemoved(int displayId) {
1808            // Having no default display
1809        }
1810
1811        @Override
1812        public void onDisplayChanged(int displayId) {
1813            updateDisplayInfo();
1814        }
1815    }
1816}
1817