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