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