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