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