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