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