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