ScreenMagnifier.java revision 9b4125e435b6bc8f8bd2f6e569d9b0b296ab16ef
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.BroadcastReceiver; 25import android.content.Context; 26import android.content.Intent; 27import android.content.IntentFilter; 28import android.graphics.Canvas; 29import android.graphics.Color; 30import android.graphics.PixelFormat; 31import android.graphics.PointF; 32import android.graphics.PorterDuff.Mode; 33import android.graphics.Rect; 34import android.graphics.drawable.Drawable; 35import android.hardware.display.DisplayManager; 36import android.hardware.display.DisplayManager.DisplayListener; 37import android.os.AsyncTask; 38import android.os.Handler; 39import android.os.Message; 40import android.os.RemoteException; 41import android.os.ServiceManager; 42import android.provider.Settings; 43import android.util.MathUtils; 44import android.util.Property; 45import android.util.Slog; 46import android.view.Display; 47import android.view.DisplayInfo; 48import android.view.Gravity; 49import android.view.IDisplayContentChangeListener; 50import android.view.IWindowManager; 51import android.view.MotionEvent; 52import android.view.MotionEvent.PointerCoords; 53import android.view.MotionEvent.PointerProperties; 54import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener; 55import android.view.Surface; 56import android.view.View; 57import android.view.ViewConfiguration; 58import android.view.ViewGroup; 59import android.view.WindowInfo; 60import android.view.WindowManager; 61import android.view.WindowManagerPolicy; 62import android.view.accessibility.AccessibilityEvent; 63import android.view.animation.DecelerateInterpolator; 64import android.view.animation.Interpolator; 65 66import com.android.internal.R; 67import com.android.internal.os.SomeArgs; 68 69import java.util.ArrayList; 70import java.util.Collections; 71import java.util.Comparator; 72 73/** 74 * This class handles the screen magnification when accessibility is enabled. 75 * The behavior is as follows: 76 * 77 * 1. Triple tap toggles permanent screen magnification which is magnifying 78 * the area around the location of the triple tap. One can think of the 79 * location of the triple tap as the center of the magnified viewport. 80 * For example, a triple tap when not magnified would magnify the screen 81 * and leave it in a magnified state. A triple tapping when magnified would 82 * clear magnification and leave the screen in a not magnified state. 83 * 84 * 2. Triple tap and hold would magnify the screen if not magnified and enable 85 * viewport dragging mode until the finger goes up. One can think of this 86 * mode as a way to move the magnified viewport since the area around the 87 * moving finger will be magnified to fit the screen. For example, if the 88 * screen was not magnified and the user triple taps and holds the screen 89 * would magnify and the viewport will follow the user's finger. When the 90 * finger goes up the screen will clear zoom out. If the same user interaction 91 * is performed when the screen is magnified, the viewport movement will 92 * be the same but when the finger goes up the screen will stay magnified. 93 * In other words, the initial magnified state is sticky. 94 * 95 * 3. Pinching with any number of additional fingers when viewport dragging 96 * is enabled, i.e. the user triple tapped and holds, would adjust the 97 * magnification scale which will become the current default magnification 98 * scale. The next time the user magnifies the same magnification scale 99 * would be used. 100 * 101 * 4. When in a permanent magnified state the user can use two or more fingers 102 * to pan the viewport. Note that in this mode the content is panned as 103 * opposed to the viewport dragging mode in which the viewport is moved. 104 * 105 * 5. When in a permanent magnified state the user can use three or more 106 * fingers to change the magnification scale which will become the current 107 * default magnification scale. The next time the user magnifies the same 108 * magnification scale would be used. 109 * 110 * 6. The magnification scale will be persisted in settings and in the cloud. 111 */ 112public final class ScreenMagnifier implements EventStreamTransformation { 113 114 private static final boolean DEBUG_STATE_TRANSITIONS = false; 115 private static final boolean DEBUG_DETECTING = false; 116 private static final boolean DEBUG_TRANSFORMATION = false; 117 private static final boolean DEBUG_PANNING = false; 118 private static final boolean DEBUG_SCALING = false; 119 private static final boolean DEBUG_VIEWPORT_WINDOW = false; 120 private static final boolean DEBUG_WINDOW_TRANSITIONS = false; 121 private static final boolean DEBUG_ROTATION = false; 122 private static final boolean DEBUG_GESTURE_DETECTOR = false; 123 private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false; 124 125 private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName(); 126 127 private static final int STATE_DELEGATING = 1; 128 private static final int STATE_DETECTING = 2; 129 private static final int STATE_VIEWPORT_DRAGGING = 3; 130 private static final int STATE_MAGNIFIED_INTERACTION = 4; 131 132 private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; 133 private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1; 134 private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f; 135 136 private static final int MULTI_TAP_TIME_SLOP_ADJUSTMENT = 50; 137 138 private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface( 139 ServiceManager.getService("window")); 140 private final WindowManager mWindowManager; 141 private final DisplayProvider mDisplayProvider; 142 143 private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler(); 144 private final GestureDetector mGestureDetector; 145 private final StateViewportDraggingHandler mStateViewportDraggingHandler = 146 new StateViewportDraggingHandler(); 147 148 private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f); 149 150 private final MagnificationController mMagnificationController; 151 private final DisplayContentObserver mDisplayContentObserver; 152 private final ScreenStateObserver mScreenStateObserver; 153 private final Viewport mViewport; 154 155 private final int mTapTimeSlop = ViewConfiguration.getTapTimeout(); 156 private final int mMultiTapTimeSlop = 157 ViewConfiguration.getDoubleTapTimeout() - MULTI_TAP_TIME_SLOP_ADJUSTMENT; 158 private final int mTapDistanceSlop; 159 private final int mMultiTapDistanceSlop; 160 161 private final int mShortAnimationDuration; 162 private final int mLongAnimationDuration; 163 private final float mWindowAnimationScale; 164 165 private final Context mContext; 166 167 private EventStreamTransformation mNext; 168 169 private int mCurrentState; 170 private int mPreviousState; 171 private boolean mTranslationEnabledBeforePan; 172 173 private PointerCoords[] mTempPointerCoords; 174 private PointerProperties[] mTempPointerProperties; 175 176 public ScreenMagnifier(Context context) { 177 mContext = context; 178 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 179 180 mShortAnimationDuration = context.getResources().getInteger( 181 com.android.internal.R.integer.config_shortAnimTime); 182 mLongAnimationDuration = context.getResources().getInteger( 183 com.android.internal.R.integer.config_longAnimTime); 184 mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 185 mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop(); 186 mWindowAnimationScale = Settings.System.getFloat(context.getContentResolver(), 187 Settings.System.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE); 188 189 mMagnificationController = new MagnificationController(mShortAnimationDuration); 190 mDisplayProvider = new DisplayProvider(context, mWindowManager); 191 mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService, 192 mDisplayProvider, mInterpolator, mShortAnimationDuration); 193 mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport, 194 mMagnificationController, mWindowManagerService, mDisplayProvider, 195 mLongAnimationDuration, mWindowAnimationScale); 196 mScreenStateObserver = new ScreenStateObserver(mContext, mViewport, 197 mMagnificationController); 198 199 mGestureDetector = new GestureDetector(context); 200 201 transitionToState(STATE_DETECTING); 202 } 203 204 @Override 205 public void onMotionEvent(MotionEvent event, int policyFlags) { 206 mGestureDetector.onMotionEvent(event); 207 switch (mCurrentState) { 208 case STATE_DELEGATING: { 209 handleMotionEventStateDelegating(event, policyFlags); 210 } break; 211 case STATE_DETECTING: { 212 mDetectingStateHandler.onMotionEvent(event, policyFlags); 213 } break; 214 case STATE_VIEWPORT_DRAGGING: { 215 mStateViewportDraggingHandler.onMotionEvent(event, policyFlags); 216 } break; 217 case STATE_MAGNIFIED_INTERACTION: { 218 // Handled by the gesture detector. Since the detector 219 // needs all touch events to work properly we cannot 220 // call it only for this state. 221 } break; 222 default: { 223 throw new IllegalStateException("Unknown state: " + mCurrentState); 224 } 225 } 226 } 227 228 @Override 229 public void onAccessibilityEvent(AccessibilityEvent event) { 230 if (mNext != null) { 231 mNext.onAccessibilityEvent(event); 232 } 233 } 234 235 @Override 236 public void setNext(EventStreamTransformation next) { 237 mNext = next; 238 } 239 240 @Override 241 public void clear() { 242 mCurrentState = STATE_DETECTING; 243 mDetectingStateHandler.clear(); 244 mStateViewportDraggingHandler.clear(); 245 mGestureDetector.clear(); 246 if (mNext != null) { 247 mNext.clear(); 248 } 249 } 250 251 @Override 252 public void onDestroy() { 253 mMagnificationController.setScaleAndMagnifiedRegionCenter(1.0f, 254 0, 0, true); 255 mViewport.setFrameShown(false, true); 256 mDisplayProvider.destroy(); 257 mDisplayContentObserver.destroy(); 258 mScreenStateObserver.destroy(); 259 } 260 261 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) { 262 if (event.getActionMasked() == MotionEvent.ACTION_UP) { 263 if (mDetectingStateHandler.mDelayedEventQueue == null) { 264 transitionToState(STATE_DETECTING); 265 } 266 } 267 if (mNext != null) { 268 // If the event is within the magnified portion of the screen we have 269 // to change its location to be where the user thinks he is poking the 270 // UI which may have been magnified and panned. 271 final float eventX = event.getX(); 272 final float eventY = event.getY(); 273 if (mMagnificationController.isMagnifying() 274 && mViewport.getBounds().contains((int) eventX, (int) eventY)) { 275 final float scale = mMagnificationController.getScale(); 276 final float scaledOffsetX = mMagnificationController.getScaledOffsetX(); 277 final float scaledOffsetY = mMagnificationController.getScaledOffsetY(); 278 final int pointerCount = event.getPointerCount(); 279 PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount); 280 PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount); 281 for (int i = 0; i < pointerCount; i++) { 282 event.getPointerCoords(i, coords[i]); 283 coords[i].x = (coords[i].x - scaledOffsetX) / scale; 284 coords[i].y = (coords[i].y - scaledOffsetY) / scale; 285 event.getPointerProperties(i, properties[i]); 286 } 287 event = MotionEvent.obtain(event.getDownTime(), 288 event.getEventTime(), event.getAction(), pointerCount, properties, 289 coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(), 290 event.getFlags()); 291 } 292 mNext.onMotionEvent(event, policyFlags); 293 } 294 } 295 296 private PointerCoords[] getTempPointerCoordsWithMinSize(int size) { 297 final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0; 298 if (oldSize < size) { 299 PointerCoords[] oldTempPointerCoords = mTempPointerCoords; 300 mTempPointerCoords = new PointerCoords[size]; 301 if (oldTempPointerCoords != null) { 302 System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize); 303 } 304 } 305 for (int i = oldSize; i < size; i++) { 306 mTempPointerCoords[i] = new PointerCoords(); 307 } 308 return mTempPointerCoords; 309 } 310 311 private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) { 312 final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0; 313 if (oldSize < size) { 314 PointerProperties[] oldTempPointerProperties = mTempPointerProperties; 315 mTempPointerProperties = new PointerProperties[size]; 316 if (oldTempPointerProperties != null) { 317 System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize); 318 } 319 } 320 for (int i = oldSize; i < size; i++) { 321 mTempPointerProperties[i] = new PointerProperties(); 322 } 323 return mTempPointerProperties; 324 } 325 326 private void transitionToState(int state) { 327 if (DEBUG_STATE_TRANSITIONS) { 328 switch (state) { 329 case STATE_DELEGATING: { 330 Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING"); 331 } break; 332 case STATE_DETECTING: { 333 Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING"); 334 } break; 335 case STATE_VIEWPORT_DRAGGING: { 336 Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING"); 337 } break; 338 case STATE_MAGNIFIED_INTERACTION: { 339 Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION"); 340 } break; 341 default: { 342 throw new IllegalArgumentException("Unknown state: " + state); 343 } 344 } 345 } 346 mPreviousState = mCurrentState; 347 mCurrentState = state; 348 } 349 350 private final class GestureDetector implements OnScaleGestureListener { 351 private static final float MIN_SCALE = 1.3f; 352 private static final float MAX_SCALE = 5.0f; 353 354 private static final float DETECT_SCALING_THRESHOLD = 0.30f; 355 private static final int DETECT_PANNING_THRESHOLD_DIP = 30; 356 357 private final float mScaledDetectPanningThreshold; 358 359 private final ScaleGestureDetector mScaleGestureDetector; 360 361 private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN); 362 private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN); 363 364 private float mCurrScale = Float.NaN; 365 private float mCurrScaleFactor = 1.0f; 366 private float mPrevScaleFactor = 1.0f; 367 private float mCurrPan; 368 private float mPrevPan; 369 370 private float mScaleFocusX = Float.NaN; 371 private float mScaleFocusY = Float.NaN; 372 373 private boolean mScaling; 374 private boolean mPanning; 375 376 public GestureDetector(Context context) { 377 final float density = context.getResources().getDisplayMetrics().density; 378 mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density; 379 mScaleGestureDetector = new ScaleGestureDetector(this); 380 } 381 382 public void onMotionEvent(MotionEvent event) { 383 mScaleGestureDetector.onTouchEvent(event); 384 switch (mCurrentState) { 385 case STATE_DETECTING: 386 case STATE_DELEGATING: 387 case STATE_VIEWPORT_DRAGGING: { 388 return; 389 } 390 } 391 if (event.getActionMasked() == MotionEvent.ACTION_UP) { 392 clear(); 393 final float scale = mMagnificationController.getScale(); 394 if (scale != getPersistedScale()) { 395 persistScale(scale); 396 } 397 if (mPreviousState == STATE_VIEWPORT_DRAGGING) { 398 transitionToState(STATE_VIEWPORT_DRAGGING); 399 } else { 400 transitionToState(STATE_DETECTING); 401 } 402 } 403 } 404 405 @Override 406 public boolean onScale(ScaleGestureDetector detector) { 407 switch (mCurrentState) { 408 case STATE_DETECTING: 409 case STATE_DELEGATING: 410 case STATE_VIEWPORT_DRAGGING: { 411 return true; 412 } 413 case STATE_MAGNIFIED_INTERACTION: { 414 mCurrScaleFactor = mScaleGestureDetector.getScaleFactor(); 415 final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor); 416 if (DEBUG_GESTURE_DETECTOR) { 417 Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta); 418 } 419 if (!mScaling && scaleDelta > DETECT_SCALING_THRESHOLD) { 420 mScaling = true; 421 clearContextualState(); 422 return true; 423 } 424 if (mScaling) { 425 performScale(detector); 426 } 427 mCurrPan = (float) MathUtils.dist( 428 mScaleGestureDetector.getFocusX(), 429 mScaleGestureDetector.getFocusY(), 430 mInitialFocus.x, mInitialFocus.y); 431 final float panDelta = mCurrPan + mPrevPan; 432 if (DEBUG_GESTURE_DETECTOR) { 433 Slog.i(LOG_TAG, "panDelta: " + panDelta); 434 } 435 if (!mPanning && panDelta > mScaledDetectPanningThreshold) { 436 mPanning = true; 437 clearContextualState(); 438 return true; 439 } 440 if (mPanning) { 441 performPan(detector); 442 } 443 } break; 444 } 445 return false; 446 } 447 448 @Override 449 public boolean onScaleBegin(ScaleGestureDetector detector) { 450 mPrevScaleFactor *= mCurrScaleFactor; 451 mCurrScale = Float.NaN; 452 mPrevPan += mCurrPan; 453 mPrevFocus.x = mInitialFocus.x = detector.getFocusX(); 454 mPrevFocus.y = mInitialFocus.y = detector.getFocusY(); 455 return true; 456 } 457 458 @Override 459 public void onScaleEnd(ScaleGestureDetector detector) { 460 clearContextualState(); 461 } 462 463 public void clear() { 464 clearContextualState(); 465 mScaling = false; 466 mPanning = false; 467 } 468 469 private void clearContextualState() { 470 mCurrScaleFactor = 1.0f; 471 mPrevScaleFactor = 1.0f; 472 mPrevPan = 0; 473 mCurrPan = 0; 474 mInitialFocus.set(Float.NaN, Float.NaN); 475 mPrevFocus.set(Float.NaN, Float.NaN); 476 mCurrScale = Float.NaN; 477 mScaleFocusX = Float.NaN; 478 mScaleFocusY = Float.NaN; 479 } 480 481 private void performPan(ScaleGestureDetector detector) { 482 if (Float.compare(mPrevFocus.x, Float.NaN) == 0 483 && Float.compare(mPrevFocus.y, Float.NaN) == 0) { 484 mPrevFocus.set(detector.getFocusX(), detector.getFocusY()); 485 return; 486 } 487 final float scale = mMagnificationController.getScale(); 488 final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale; 489 final float scrollY = (detector.getFocusY() - mPrevFocus.y) / scale; 490 final float centerX = mMagnificationController.getMagnifiedRegionCenterX() 491 - scrollX; 492 final float centerY = mMagnificationController.getMagnifiedRegionCenterY() 493 - scrollY; 494 if (DEBUG_PANNING) { 495 Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX 496 + " scrollY: " + scrollY); 497 } 498 mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false); 499 mPrevFocus.set(detector.getFocusX(), detector.getFocusY()); 500 } 501 502 private void performScale(ScaleGestureDetector detector) { 503 if (Float.compare(mCurrScale, Float.NaN) == 0) { 504 mCurrScale = mMagnificationController.getScale(); 505 return; 506 } 507 final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor(); 508 final float newScale = mCurrScale * totalScaleFactor; 509 final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), 510 MAX_SCALE); 511 if (DEBUG_SCALING) { 512 Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale); 513 } 514 if (Float.compare(mScaleFocusX, Float.NaN) == 0 515 && Float.compare(mScaleFocusY, Float.NaN) == 0) { 516 mScaleFocusX = detector.getFocusX(); 517 mScaleFocusY = detector.getFocusY(); 518 } 519 mMagnificationController.setScale(normalizedNewScale, mScaleFocusX, 520 mScaleFocusY, false); 521 } 522 } 523 524 private final class StateViewportDraggingHandler { 525 private boolean mLastMoveOutsideMagnifiedRegion; 526 527 private void onMotionEvent(MotionEvent event, int policyFlags) { 528 final int action = event.getActionMasked(); 529 switch (action) { 530 case MotionEvent.ACTION_DOWN: { 531 throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN"); 532 } 533 case MotionEvent.ACTION_POINTER_DOWN: { 534 clear(); 535 transitionToState(STATE_MAGNIFIED_INTERACTION); 536 } break; 537 case MotionEvent.ACTION_MOVE: { 538 if (event.getPointerCount() != 1) { 539 throw new IllegalStateException("Should have one pointer down."); 540 } 541 final float eventX = event.getX(); 542 final float eventY = event.getY(); 543 if (mViewport.getBounds().contains((int) eventX, (int) eventY)) { 544 if (mLastMoveOutsideMagnifiedRegion) { 545 mLastMoveOutsideMagnifiedRegion = false; 546 mMagnificationController.setMagnifiedRegionCenter(eventX, 547 eventY, true); 548 } else { 549 mMagnificationController.setMagnifiedRegionCenter(eventX, 550 eventY, false); 551 } 552 } else { 553 mLastMoveOutsideMagnifiedRegion = true; 554 } 555 } break; 556 case MotionEvent.ACTION_UP: { 557 if (!mTranslationEnabledBeforePan) { 558 mMagnificationController.reset(true); 559 mViewport.setFrameShown(false, true); 560 } 561 clear(); 562 transitionToState(STATE_DETECTING); 563 } break; 564 case MotionEvent.ACTION_POINTER_UP: { 565 throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP"); 566 } 567 } 568 } 569 570 public void clear() { 571 mLastMoveOutsideMagnifiedRegion = false; 572 } 573 } 574 575 private final class DetectingStateHandler { 576 577 private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1; 578 579 private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2; 580 581 private static final int ACTION_TAP_COUNT = 3; 582 583 private MotionEventInfo mDelayedEventQueue; 584 585 private MotionEvent mLastDownEvent; 586 private MotionEvent mLastTapUpEvent; 587 private int mTapCount; 588 589 private final Handler mHandler = new Handler() { 590 @Override 591 public void handleMessage(Message message) { 592 final int type = message.what; 593 switch (type) { 594 case MESSAGE_ON_ACTION_TAP_AND_HOLD: { 595 MotionEvent event = (MotionEvent) message.obj; 596 final int policyFlags = message.arg1; 597 onActionTapAndHold(event, policyFlags); 598 } break; 599 case MESSAGE_TRANSITION_TO_DELEGATING_STATE: { 600 transitionToState(STATE_DELEGATING); 601 sendDelayedMotionEvents(); 602 clear(); 603 } break; 604 default: { 605 throw new IllegalArgumentException("Unknown message type: " + type); 606 } 607 } 608 } 609 }; 610 611 public void onMotionEvent(MotionEvent event, int policyFlags) { 612 cacheDelayedMotionEvent(event, policyFlags); 613 final int action = event.getActionMasked(); 614 switch (action) { 615 case MotionEvent.ACTION_DOWN: { 616 mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); 617 if (!mViewport.getBounds().contains((int) event.getX(), 618 (int) event.getY())) { 619 transitionToDelegatingStateAndClear(); 620 return; 621 } 622 if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null 623 && GestureUtils.isMultiTap(mLastDownEvent, event, 624 mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { 625 Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD, 626 policyFlags, 0, event); 627 mHandler.sendMessageDelayed(message, 628 ViewConfiguration.getLongPressTimeout()); 629 } else if (mTapCount < ACTION_TAP_COUNT) { 630 Message message = mHandler.obtainMessage( 631 MESSAGE_TRANSITION_TO_DELEGATING_STATE); 632 mHandler.sendMessageDelayed(message, mMultiTapTimeSlop); 633 } 634 clearLastDownEvent(); 635 mLastDownEvent = MotionEvent.obtain(event); 636 } break; 637 case MotionEvent.ACTION_POINTER_DOWN: { 638 if (mMagnificationController.isMagnifying()) { 639 transitionToState(STATE_MAGNIFIED_INTERACTION); 640 clear(); 641 } else { 642 transitionToDelegatingStateAndClear(); 643 } 644 } break; 645 case MotionEvent.ACTION_MOVE: { 646 if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) { 647 final double distance = GestureUtils.computeDistance(mLastDownEvent, 648 event, 0); 649 if (Math.abs(distance) > mTapDistanceSlop) { 650 transitionToDelegatingStateAndClear(); 651 } 652 } 653 } break; 654 case MotionEvent.ACTION_UP: { 655 if (mLastDownEvent == null) { 656 return; 657 } 658 mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); 659 if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) { 660 transitionToDelegatingStateAndClear(); 661 return; 662 } 663 if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop, 664 mTapDistanceSlop, 0)) { 665 transitionToDelegatingStateAndClear(); 666 return; 667 } 668 if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent, 669 event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) { 670 transitionToDelegatingStateAndClear(); 671 return; 672 } 673 mTapCount++; 674 if (DEBUG_DETECTING) { 675 Slog.i(LOG_TAG, "Tap count:" + mTapCount); 676 } 677 if (mTapCount == ACTION_TAP_COUNT) { 678 clear(); 679 onActionTap(event, policyFlags); 680 return; 681 } 682 clearLastTapUpEvent(); 683 mLastTapUpEvent = MotionEvent.obtain(event); 684 } break; 685 case MotionEvent.ACTION_POINTER_UP: { 686 /* do nothing */ 687 } break; 688 } 689 } 690 691 public void clear() { 692 mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD); 693 mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); 694 clearTapDetectionState(); 695 clearDelayedMotionEvents(); 696 } 697 698 private void clearTapDetectionState() { 699 mTapCount = 0; 700 clearLastTapUpEvent(); 701 clearLastDownEvent(); 702 } 703 704 private void clearLastTapUpEvent() { 705 if (mLastTapUpEvent != null) { 706 mLastTapUpEvent.recycle(); 707 mLastTapUpEvent = null; 708 } 709 } 710 711 private void clearLastDownEvent() { 712 if (mLastDownEvent != null) { 713 mLastDownEvent.recycle(); 714 mLastDownEvent = null; 715 } 716 } 717 718 private void cacheDelayedMotionEvent(MotionEvent event, int policyFlags) { 719 MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags); 720 if (mDelayedEventQueue == null) { 721 mDelayedEventQueue = info; 722 } else { 723 MotionEventInfo tail = mDelayedEventQueue; 724 while (tail.mNext != null) { 725 tail = tail.mNext; 726 } 727 tail.mNext = info; 728 } 729 } 730 731 private void sendDelayedMotionEvents() { 732 while (mDelayedEventQueue != null) { 733 MotionEventInfo info = mDelayedEventQueue; 734 mDelayedEventQueue = info.mNext; 735 ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mPolicyFlags); 736 info.recycle(); 737 } 738 } 739 740 private void clearDelayedMotionEvents() { 741 while (mDelayedEventQueue != null) { 742 MotionEventInfo info = mDelayedEventQueue; 743 mDelayedEventQueue = info.mNext; 744 info.recycle(); 745 } 746 } 747 748 private void transitionToDelegatingStateAndClear() { 749 transitionToState(STATE_DELEGATING); 750 sendDelayedMotionEvents(); 751 clear(); 752 } 753 754 private void onActionTap(MotionEvent up, int policyFlags) { 755 if (DEBUG_DETECTING) { 756 Slog.i(LOG_TAG, "onActionTap()"); 757 } 758 if (!mMagnificationController.isMagnifying()) { 759 mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), 760 up.getX(), up.getY(), true); 761 mViewport.setFrameShown(true, true); 762 } else { 763 mMagnificationController.reset(true); 764 mViewport.setFrameShown(false, true); 765 } 766 } 767 768 private void onActionTapAndHold(MotionEvent down, int policyFlags) { 769 if (DEBUG_DETECTING) { 770 Slog.i(LOG_TAG, "onActionTapAndHold()"); 771 } 772 clear(); 773 mTranslationEnabledBeforePan = mMagnificationController.isMagnifying(); 774 mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(), 775 down.getX(), down.getY(), true); 776 mViewport.setFrameShown(true, true); 777 transitionToState(STATE_VIEWPORT_DRAGGING); 778 } 779 } 780 781 private void persistScale(final float scale) { 782 new AsyncTask<Void, Void, Void>() { 783 @Override 784 protected Void doInBackground(Void... params) { 785 Settings.Secure.putFloat(mContext.getContentResolver(), 786 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale); 787 return null; 788 } 789 }.execute(); 790 } 791 792 private float getPersistedScale() { 793 return Settings.Secure.getFloat(mContext.getContentResolver(), 794 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, 795 DEFAULT_MAGNIFICATION_SCALE); 796 } 797 798 private static boolean isScreenMagnificationAutoUpdateEnabled(Context context) { 799 return (Settings.Secure.getInt(context.getContentResolver(), 800 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE, 801 DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1); 802 } 803 804 private static final class MotionEventInfo { 805 806 private static final int MAX_POOL_SIZE = 10; 807 808 private static final Object sLock = new Object(); 809 private static MotionEventInfo sPool; 810 private static int sPoolSize; 811 812 private MotionEventInfo mNext; 813 private boolean mInPool; 814 815 public MotionEvent mEvent; 816 public int mPolicyFlags; 817 818 public static MotionEventInfo obtain(MotionEvent event, int policyFlags) { 819 synchronized (sLock) { 820 MotionEventInfo info; 821 if (sPoolSize > 0) { 822 sPoolSize--; 823 info = sPool; 824 sPool = info.mNext; 825 info.mNext = null; 826 info.mInPool = false; 827 } else { 828 info = new MotionEventInfo(); 829 } 830 info.initialize(event, policyFlags); 831 return info; 832 } 833 } 834 835 private void initialize(MotionEvent event, int policyFlags) { 836 mEvent = MotionEvent.obtain(event); 837 mPolicyFlags = policyFlags; 838 } 839 840 public void recycle() { 841 synchronized (sLock) { 842 if (mInPool) { 843 throw new IllegalStateException("Already recycled."); 844 } 845 clear(); 846 if (sPoolSize < MAX_POOL_SIZE) { 847 sPoolSize++; 848 mNext = sPool; 849 sPool = this; 850 mInPool = true; 851 } 852 } 853 } 854 855 private void clear() { 856 mEvent.recycle(); 857 mEvent = null; 858 mPolicyFlags = 0; 859 } 860 } 861 862 private static final class ScreenStateObserver extends BroadcastReceiver { 863 864 private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1; 865 866 private final Handler mHandler = new Handler() { 867 @Override 868 public void handleMessage(Message message) { 869 switch (message.what) { 870 case MESSAGE_ON_SCREEN_STATE_CHANGE: { 871 String action = (String) message.obj; 872 handleOnScreenStateChange(action); 873 } break; 874 } 875 } 876 }; 877 878 private final Context mContext; 879 private final Viewport mViewport; 880 private final MagnificationController mMagnificationController; 881 882 public ScreenStateObserver(Context context, Viewport viewport, 883 MagnificationController magnificationController) { 884 mContext = context; 885 mViewport = viewport; 886 mMagnificationController = magnificationController; 887 mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF)); 888 } 889 890 public void destroy() { 891 mContext.unregisterReceiver(this); 892 } 893 894 @Override 895 public void onReceive(Context context, Intent intent) { 896 mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE, 897 intent.getAction()).sendToTarget(); 898 } 899 900 private void handleOnScreenStateChange(String action) { 901 if (action.equals(Intent.ACTION_SCREEN_OFF) 902 && mMagnificationController.isMagnifying() 903 && isScreenMagnificationAutoUpdateEnabled(mContext)) { 904 mMagnificationController.reset(false); 905 mViewport.setFrameShown(false, false); 906 } 907 } 908 } 909 910 private static final class DisplayContentObserver { 911 912 private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1; 913 private static final int MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS = 2; 914 private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3; 915 private static final int MESSAGE_ON_WINDOW_TRANSITION = 4; 916 private static final int MESSAGE_ON_ROTATION_CHANGED = 5; 917 918 private final Handler mHandler = new MyHandler(); 919 920 private final Rect mTempRect = new Rect(); 921 922 private final IDisplayContentChangeListener mDisplayContentChangeListener; 923 924 private final Context mContext; 925 private final Viewport mViewport; 926 private final MagnificationController mMagnificationController; 927 private final IWindowManager mWindowManagerService; 928 private final DisplayProvider mDisplayProvider; 929 private final long mLongAnimationDuration; 930 private final float mWindowAnimationScale; 931 932 public DisplayContentObserver(Context context, Viewport viewport, 933 MagnificationController magnificationController, 934 IWindowManager windowManagerService, DisplayProvider displayProvider, 935 long longAnimationDuration, float windowAnimationScale) { 936 mContext = context; 937 mViewport = viewport; 938 mMagnificationController = magnificationController; 939 mWindowManagerService = windowManagerService; 940 mDisplayProvider = displayProvider; 941 mLongAnimationDuration = longAnimationDuration; 942 mWindowAnimationScale = windowAnimationScale; 943 944 mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() { 945 @Override 946 public void onWindowTransition(int displayId, int transition, WindowInfo info) { 947 mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION, transition, 0, 948 WindowInfo.obtain(info)).sendToTarget(); 949 } 950 951 @Override 952 public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle, 953 boolean immediate) { 954 SomeArgs args = SomeArgs.obtain(); 955 args.argi1 = rectangle.left; 956 args.argi2 = rectangle.top; 957 args.argi3 = rectangle.right; 958 args.argi4 = rectangle.bottom; 959 mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0, 960 immediate ? 1 : 0, args).sendToTarget(); 961 } 962 963 @Override 964 public void onRotationChanged(int rotation) throws RemoteException { 965 mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0) 966 .sendToTarget(); 967 } 968 }; 969 970 try { 971 mWindowManagerService.addDisplayContentChangeListener( 972 mDisplayProvider.getDisplay().getDisplayId(), 973 mDisplayContentChangeListener); 974 } catch (RemoteException re) { 975 /* ignore */ 976 } 977 } 978 979 public void destroy() { 980 try { 981 mWindowManagerService.removeDisplayContentChangeListener( 982 mDisplayProvider.getDisplay().getDisplayId(), 983 mDisplayContentChangeListener); 984 } catch (RemoteException re) { 985 /* ignore*/ 986 } 987 } 988 989 private void handleOnRotationChanged(int rotation) { 990 if (DEBUG_ROTATION) { 991 Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation)); 992 } 993 resetMagnificationIfNeeded(); 994 mViewport.setFrameShown(false, false); 995 mViewport.rotationChanged(); 996 mViewport.recomputeBounds(false); 997 if (mMagnificationController.isMagnifying()) { 998 final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale); 999 Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME); 1000 mHandler.sendMessageDelayed(message, delay); 1001 } 1002 } 1003 1004 private void handleOnWindowTransition(int transition, WindowInfo info) { 1005 if (DEBUG_WINDOW_TRANSITIONS) { 1006 Slog.i(LOG_TAG, "Window transitioning: " 1007 + windowTransitionToString(transition)); 1008 } 1009 try { 1010 final boolean magnifying = mMagnificationController.isMagnifying(); 1011 if (magnifying) { 1012 switch (transition) { 1013 case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: 1014 case WindowManagerPolicy.TRANSIT_TASK_OPEN: 1015 case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: 1016 case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: 1017 case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: 1018 case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { 1019 resetMagnificationIfNeeded(); 1020 } 1021 } 1022 } 1023 if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR 1024 || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD 1025 || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG 1026 || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD 1027 || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) { 1028 switch (transition) { 1029 case WindowManagerPolicy.TRANSIT_ENTER: 1030 case WindowManagerPolicy.TRANSIT_SHOW: 1031 case WindowManagerPolicy.TRANSIT_EXIT: 1032 case WindowManagerPolicy.TRANSIT_HIDE: { 1033 mViewport.recomputeBounds(mMagnificationController.isMagnifying()); 1034 } break; 1035 } 1036 } else { 1037 switch (transition) { 1038 case WindowManagerPolicy.TRANSIT_ENTER: 1039 case WindowManagerPolicy.TRANSIT_SHOW: { 1040 if (!magnifying || !isScreenMagnificationAutoUpdateEnabled(mContext)) { 1041 break; 1042 } 1043 final int type = info.type; 1044 switch (type) { 1045 // TODO: Are these all the windows we want to make 1046 // visible when they appear on the screen? 1047 // Do we need to take some of them out? 1048 case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL: 1049 case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA: 1050 case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL: 1051 case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: 1052 case WindowManager.LayoutParams.TYPE_SEARCH_BAR: 1053 case WindowManager.LayoutParams.TYPE_PHONE: 1054 case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT: 1055 case WindowManager.LayoutParams.TYPE_TOAST: 1056 case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY: 1057 case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE: 1058 case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG: 1059 case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG: 1060 case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR: 1061 case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY: 1062 case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: { 1063 Rect magnifiedRegionBounds = mMagnificationController 1064 .getMagnifiedRegionBounds(); 1065 Rect touchableRegion = info.touchableRegion; 1066 if (!magnifiedRegionBounds.intersect(touchableRegion)) { 1067 ensureRectangleInMagnifiedRegionBounds( 1068 magnifiedRegionBounds, touchableRegion); 1069 } 1070 } break; 1071 } break; 1072 } 1073 } 1074 } 1075 } finally { 1076 if (info != null) { 1077 info.recycle(); 1078 } 1079 } 1080 } 1081 1082 private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) { 1083 if (!mMagnificationController.isMagnifying()) { 1084 return; 1085 } 1086 Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds(); 1087 if (magnifiedRegionBounds.contains(rectangle)) { 1088 return; 1089 } 1090 ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle); 1091 } 1092 1093 private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds, 1094 Rect rectangle) { 1095 if (!Rect.intersects(rectangle, mViewport.getBounds())) { 1096 return; 1097 } 1098 final float scrollX; 1099 final float scrollY; 1100 if (rectangle.width() > magnifiedRegionBounds.width()) { 1101 scrollX = rectangle.left - magnifiedRegionBounds.left; 1102 } else if (rectangle.left < magnifiedRegionBounds.left) { 1103 scrollX = rectangle.left - magnifiedRegionBounds.left; 1104 } else if (rectangle.right > magnifiedRegionBounds.right) { 1105 scrollX = rectangle.right - magnifiedRegionBounds.right; 1106 } else { 1107 scrollX = 0; 1108 } 1109 if (rectangle.height() > magnifiedRegionBounds.height()) { 1110 scrollY = rectangle.top - magnifiedRegionBounds.top; 1111 } else if (rectangle.top < magnifiedRegionBounds.top) { 1112 scrollY = rectangle.top - magnifiedRegionBounds.top; 1113 } else if (rectangle.bottom > magnifiedRegionBounds.bottom) { 1114 scrollY = rectangle.bottom - magnifiedRegionBounds.bottom; 1115 } else { 1116 scrollY = 0; 1117 } 1118 final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX() 1119 + scrollX; 1120 final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY() 1121 + scrollY; 1122 mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY, 1123 true); 1124 } 1125 1126 private void resetMagnificationIfNeeded() { 1127 if (mMagnificationController.isMagnifying() 1128 && isScreenMagnificationAutoUpdateEnabled(mContext)) { 1129 mMagnificationController.reset(true); 1130 mViewport.setFrameShown(false, true); 1131 } 1132 } 1133 1134 private String windowTransitionToString(int transition) { 1135 switch (transition) { 1136 case WindowManagerPolicy.TRANSIT_UNSET: { 1137 return "TRANSIT_UNSET"; 1138 } 1139 case WindowManagerPolicy.TRANSIT_NONE: { 1140 return "TRANSIT_NONE"; 1141 } 1142 case WindowManagerPolicy.TRANSIT_ENTER: { 1143 return "TRANSIT_ENTER"; 1144 } 1145 case WindowManagerPolicy.TRANSIT_EXIT: { 1146 return "TRANSIT_EXIT"; 1147 } 1148 case WindowManagerPolicy.TRANSIT_SHOW: { 1149 return "TRANSIT_SHOW"; 1150 } 1151 case WindowManagerPolicy.TRANSIT_EXIT_MASK: { 1152 return "TRANSIT_EXIT_MASK"; 1153 } 1154 case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: { 1155 return "TRANSIT_PREVIEW_DONE"; 1156 } 1157 case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: { 1158 return "TRANSIT_ACTIVITY_OPEN"; 1159 } 1160 case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: { 1161 return "TRANSIT_ACTIVITY_CLOSE"; 1162 } 1163 case WindowManagerPolicy.TRANSIT_TASK_OPEN: { 1164 return "TRANSIT_TASK_OPEN"; 1165 } 1166 case WindowManagerPolicy.TRANSIT_TASK_CLOSE: { 1167 return "TRANSIT_TASK_CLOSE"; 1168 } 1169 case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: { 1170 return "TRANSIT_TASK_TO_FRONT"; 1171 } 1172 case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: { 1173 return "TRANSIT_TASK_TO_BACK"; 1174 } 1175 case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: { 1176 return "TRANSIT_WALLPAPER_CLOSE"; 1177 } 1178 case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: { 1179 return "TRANSIT_WALLPAPER_OPEN"; 1180 } 1181 case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: { 1182 return "TRANSIT_WALLPAPER_INTRA_OPEN"; 1183 } 1184 case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: { 1185 return "TRANSIT_WALLPAPER_INTRA_CLOSE"; 1186 } 1187 default: { 1188 return "<UNKNOWN>"; 1189 } 1190 } 1191 } 1192 1193 private String rotationToString(int rotation) { 1194 switch (rotation) { 1195 case Surface.ROTATION_0: { 1196 return "ROTATION_0"; 1197 } 1198 case Surface.ROTATION_90: { 1199 return "ROATATION_90"; 1200 } 1201 case Surface.ROTATION_180: { 1202 return "ROATATION_180"; 1203 } 1204 case Surface.ROTATION_270: { 1205 return "ROATATION_270"; 1206 } 1207 default: { 1208 throw new IllegalArgumentException("Invalid rotation: " 1209 + rotation); 1210 } 1211 } 1212 } 1213 1214 private final class MyHandler extends Handler { 1215 @Override 1216 public void handleMessage(Message message) { 1217 final int action = message.what; 1218 switch (action) { 1219 case MESSAGE_SHOW_VIEWPORT_FRAME: { 1220 mViewport.setFrameShown(true, true); 1221 } break; 1222 case MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS: { 1223 final boolean animate = message.arg1 == 1; 1224 mViewport.recomputeBounds(animate); 1225 } break; 1226 case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: { 1227 SomeArgs args = (SomeArgs) message.obj; 1228 try { 1229 mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4); 1230 final boolean immediate = (message.arg1 == 1); 1231 handleOnRectangleOnScreenRequested(mTempRect, immediate); 1232 } finally { 1233 args.recycle(); 1234 } 1235 } break; 1236 case MESSAGE_ON_WINDOW_TRANSITION: { 1237 final int transition = message.arg1; 1238 WindowInfo info = (WindowInfo) message.obj; 1239 handleOnWindowTransition(transition, info); 1240 } break; 1241 case MESSAGE_ON_ROTATION_CHANGED: { 1242 final int rotation = message.arg1; 1243 handleOnRotationChanged(rotation); 1244 } break; 1245 default: { 1246 throw new IllegalArgumentException("Unknown message: " + action); 1247 } 1248 } 1249 } 1250 } 1251 } 1252 1253 private final class MagnificationController { 1254 1255 private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION = 1256 "accessibilityTransformation"; 1257 1258 private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec(); 1259 1260 private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec(); 1261 1262 private final Rect mTempRect = new Rect(); 1263 1264 private final ValueAnimator mTransformationAnimator; 1265 1266 public MagnificationController(int animationDuration) { 1267 Property<MagnificationController, MagnificationSpec> property = 1268 Property.of(MagnificationController.class, MagnificationSpec.class, 1269 PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION); 1270 TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() { 1271 private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec(); 1272 @Override 1273 public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec, 1274 MagnificationSpec toSpec) { 1275 MagnificationSpec result = mTempTransformationSpec; 1276 result.mScale = fromSpec.mScale 1277 + (toSpec.mScale - fromSpec.mScale) * fraction; 1278 result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX 1279 + (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX) 1280 * fraction; 1281 result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY 1282 + (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY) 1283 * fraction; 1284 result.mScaledOffsetX = fromSpec.mScaledOffsetX 1285 + (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX) 1286 * fraction; 1287 result.mScaledOffsetY = fromSpec.mScaledOffsetY 1288 + (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY) 1289 * fraction; 1290 return result; 1291 } 1292 }; 1293 mTransformationAnimator = ObjectAnimator.ofObject(this, property, 1294 evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec); 1295 mTransformationAnimator.setDuration((long) (animationDuration)); 1296 mTransformationAnimator.setInterpolator(mInterpolator); 1297 } 1298 1299 public boolean isMagnifying() { 1300 return mCurrentMagnificationSpec.mScale > 1.0f; 1301 } 1302 1303 public void reset(boolean animate) { 1304 if (mTransformationAnimator.isRunning()) { 1305 mTransformationAnimator.cancel(); 1306 } 1307 mCurrentMagnificationSpec.reset(); 1308 if (animate) { 1309 animateAccessibilityTranformation(mSentMagnificationSpec, 1310 mCurrentMagnificationSpec); 1311 } else { 1312 setAccessibilityTransformation(mCurrentMagnificationSpec); 1313 } 1314 } 1315 1316 public Rect getMagnifiedRegionBounds() { 1317 mTempRect.set(mViewport.getBounds()); 1318 mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX, 1319 (int) -mCurrentMagnificationSpec.mScaledOffsetY); 1320 mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale); 1321 return mTempRect; 1322 } 1323 1324 public float getScale() { 1325 return mCurrentMagnificationSpec.mScale; 1326 } 1327 1328 public float getMagnifiedRegionCenterX() { 1329 return mCurrentMagnificationSpec.mMagnifiedRegionCenterX; 1330 } 1331 1332 public float getMagnifiedRegionCenterY() { 1333 return mCurrentMagnificationSpec.mMagnifiedRegionCenterY; 1334 } 1335 1336 public float getScaledOffsetX() { 1337 return mCurrentMagnificationSpec.mScaledOffsetX; 1338 } 1339 1340 public float getScaledOffsetY() { 1341 return mCurrentMagnificationSpec.mScaledOffsetY; 1342 } 1343 1344 public void setScale(float scale, float pivotX, float pivotY, boolean animate) { 1345 MagnificationSpec spec = mCurrentMagnificationSpec; 1346 final float oldScale = spec.mScale; 1347 final float oldCenterX = spec.mMagnifiedRegionCenterX; 1348 final float oldCenterY = spec.mMagnifiedRegionCenterY; 1349 final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale; 1350 final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale; 1351 final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale); 1352 final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale); 1353 final float centerX = normPivotX + offsetX; 1354 final float centerY = normPivotY + offsetY; 1355 setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate); 1356 } 1357 1358 public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) { 1359 setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY, 1360 animate); 1361 } 1362 1363 public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY, 1364 boolean animate) { 1365 if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0 1366 && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX, 1367 centerX) == 0 1368 && Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY, 1369 centerY) == 0) { 1370 return; 1371 } 1372 if (mTransformationAnimator.isRunning()) { 1373 mTransformationAnimator.cancel(); 1374 } 1375 if (DEBUG_MAGNIFICATION_CONTROLLER) { 1376 Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX 1377 + " centerY: " + centerY); 1378 } 1379 mCurrentMagnificationSpec.initialize(scale, centerX, centerY); 1380 if (animate) { 1381 animateAccessibilityTranformation(mSentMagnificationSpec, 1382 mCurrentMagnificationSpec); 1383 } else { 1384 setAccessibilityTransformation(mCurrentMagnificationSpec); 1385 } 1386 } 1387 1388 private void animateAccessibilityTranformation(MagnificationSpec fromSpec, 1389 MagnificationSpec toSpec) { 1390 mTransformationAnimator.setObjectValues(fromSpec, toSpec); 1391 mTransformationAnimator.start(); 1392 } 1393 1394 @SuppressWarnings("unused") 1395 // Called from an animator. 1396 public MagnificationSpec getAccessibilityTransformation() { 1397 return mSentMagnificationSpec; 1398 } 1399 1400 public void setAccessibilityTransformation(MagnificationSpec transformation) { 1401 if (DEBUG_TRANSFORMATION) { 1402 Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale 1403 + " offsetX: " + transformation.mScaledOffsetX 1404 + " offsetY: " + transformation.mScaledOffsetY); 1405 } 1406 try { 1407 mSentMagnificationSpec.updateFrom(transformation); 1408 mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(), 1409 transformation.mScale, transformation.mScaledOffsetX, 1410 transformation.mScaledOffsetY); 1411 } catch (RemoteException re) { 1412 /* ignore */ 1413 } 1414 } 1415 1416 private class MagnificationSpec { 1417 1418 private static final float DEFAULT_SCALE = 1.0f; 1419 1420 public float mScale = DEFAULT_SCALE; 1421 1422 public float mMagnifiedRegionCenterX; 1423 1424 public float mMagnifiedRegionCenterY; 1425 1426 public float mScaledOffsetX; 1427 1428 public float mScaledOffsetY; 1429 1430 public void initialize(float scale, float magnifiedRegionCenterX, 1431 float magnifiedRegionCenterY) { 1432 mScale = scale; 1433 1434 final int viewportWidth = mViewport.getBounds().width(); 1435 final int viewportHeight = mViewport.getBounds().height(); 1436 final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale; 1437 final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale; 1438 final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX; 1439 final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY; 1440 1441 mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX, 1442 minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX); 1443 mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY, 1444 minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY); 1445 1446 mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2); 1447 mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2); 1448 } 1449 1450 public void updateFrom(MagnificationSpec other) { 1451 mScale = other.mScale; 1452 mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX; 1453 mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY; 1454 mScaledOffsetX = other.mScaledOffsetX; 1455 mScaledOffsetY = other.mScaledOffsetY; 1456 } 1457 1458 public void reset() { 1459 mScale = DEFAULT_SCALE; 1460 mMagnifiedRegionCenterX = 0; 1461 mMagnifiedRegionCenterY = 0; 1462 mScaledOffsetX = 0; 1463 mScaledOffsetY = 0; 1464 } 1465 } 1466 } 1467 1468 private static final class Viewport { 1469 1470 private static final String PROPERTY_NAME_ALPHA = "alpha"; 1471 1472 private static final String PROPERTY_NAME_BOUNDS = "bounds"; 1473 1474 private static final int MIN_ALPHA = 0; 1475 1476 private static final int MAX_ALPHA = 255; 1477 1478 private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>(); 1479 1480 private final Rect mTempRect1 = new Rect(); 1481 private final Rect mTempRect2 = new Rect(); 1482 private final Rect mTempRect3 = new Rect(); 1483 1484 private final IWindowManager mWindowManagerService; 1485 private final DisplayProvider mDisplayProvider; 1486 1487 private final ViewportWindow mViewportFrame; 1488 1489 private final ValueAnimator mResizeFrameAnimator; 1490 1491 private final ValueAnimator mShowHideFrameAnimator; 1492 1493 public Viewport(Context context, WindowManager windowManager, 1494 IWindowManager windowManagerService, DisplayProvider displayInfoProvider, 1495 Interpolator animationInterpolator, long animationDuration) { 1496 mWindowManagerService = windowManagerService; 1497 mDisplayProvider = displayInfoProvider; 1498 mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider); 1499 1500 mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA, 1501 MIN_ALPHA, MAX_ALPHA); 1502 mShowHideFrameAnimator.setInterpolator(animationInterpolator); 1503 mShowHideFrameAnimator.setDuration(animationDuration); 1504 mShowHideFrameAnimator.addListener(new AnimatorListener() { 1505 @Override 1506 public void onAnimationEnd(Animator animation) { 1507 if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) { 1508 mViewportFrame.hide(); 1509 } 1510 } 1511 @Override 1512 public void onAnimationStart(Animator animation) { 1513 /* do nothing - stub */ 1514 } 1515 @Override 1516 public void onAnimationCancel(Animator animation) { 1517 /* do nothing - stub */ 1518 } 1519 @Override 1520 public void onAnimationRepeat(Animator animation) { 1521 /* do nothing - stub */ 1522 } 1523 }); 1524 1525 Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class, 1526 Rect.class, PROPERTY_NAME_BOUNDS); 1527 TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() { 1528 private final Rect mReusableResultRect = new Rect(); 1529 @Override 1530 public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) { 1531 Rect result = mReusableResultRect; 1532 result.left = (int) (fromFrame.left 1533 + (toFrame.left - fromFrame.left) * fraction); 1534 result.top = (int) (fromFrame.top 1535 + (toFrame.top - fromFrame.top) * fraction); 1536 result.right = (int) (fromFrame.right 1537 + (toFrame.right - fromFrame.right) * fraction); 1538 result.bottom = (int) (fromFrame.bottom 1539 + (toFrame.bottom - fromFrame.bottom) * fraction); 1540 return result; 1541 } 1542 }; 1543 mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property, 1544 evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds); 1545 mResizeFrameAnimator.setDuration((long) (animationDuration)); 1546 mResizeFrameAnimator.setInterpolator(animationInterpolator); 1547 1548 recomputeBounds(false); 1549 } 1550 1551 private final Comparator<WindowInfo> mWindowInfoInverseComparator = 1552 new Comparator<WindowInfo>() { 1553 @Override 1554 public int compare(WindowInfo lhs, WindowInfo rhs) { 1555 if (lhs.layer != rhs.layer) { 1556 return rhs.layer - lhs.layer; 1557 } 1558 if (lhs.touchableRegion.top != rhs.touchableRegion.top) { 1559 return rhs.touchableRegion.top - lhs.touchableRegion.top; 1560 } 1561 if (lhs.touchableRegion.left != rhs.touchableRegion.left) { 1562 return rhs.touchableRegion.left - lhs.touchableRegion.left; 1563 } 1564 if (lhs.touchableRegion.right != rhs.touchableRegion.right) { 1565 return rhs.touchableRegion.right - lhs.touchableRegion.right; 1566 } 1567 if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) { 1568 return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom; 1569 } 1570 return 0; 1571 } 1572 }; 1573 1574 public void recomputeBounds(boolean animate) { 1575 Rect magnifiedFrame = mTempRect1; 1576 magnifiedFrame.set(0, 0, 0, 0); 1577 1578 Rect notMagnifiedFrame = mTempRect2; 1579 notMagnifiedFrame.set(0, 0, 0, 0); 1580 1581 ArrayList<WindowInfo> infos = mTempWindowInfoList; 1582 infos.clear(); 1583 int windowCount = 0; 1584 try { 1585 mWindowManagerService.getVisibleWindowsForDisplay( 1586 mDisplayProvider.getDisplay().getDisplayId(), infos); 1587 Collections.sort(infos, mWindowInfoInverseComparator); 1588 windowCount = infos.size(); 1589 for (int i = 0; i < windowCount; i++) { 1590 WindowInfo info = infos.get(i); 1591 if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) { 1592 continue; 1593 } 1594 if (isWindowMagnified(info.type)) { 1595 Rect clippedFrame = mTempRect3; 1596 clippedFrame.set(info.touchableRegion); 1597 subtract(clippedFrame, notMagnifiedFrame); 1598 magnifiedFrame.union(clippedFrame); 1599 } else { 1600 Rect clippedFrame = mTempRect3; 1601 clippedFrame.set(info.touchableRegion); 1602 subtract(clippedFrame, magnifiedFrame); 1603 notMagnifiedFrame.union(clippedFrame); 1604 } 1605 if (magnifiedFrame.bottom >= notMagnifiedFrame.top) { 1606 break; 1607 } 1608 } 1609 } catch (RemoteException re) { 1610 /* ignore */ 1611 } finally { 1612 for (int i = windowCount - 1; i >= 0; i--) { 1613 infos.remove(i).recycle(); 1614 } 1615 } 1616 1617 final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth; 1618 final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight; 1619 magnifiedFrame.intersect(0, 0, displayWidth, displayHeight); 1620 1621 resize(magnifiedFrame, animate); 1622 } 1623 1624 private boolean isWindowMagnified(int type) { 1625 return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR 1626 && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD 1627 && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 1628 } 1629 1630 public void rotationChanged() { 1631 mViewportFrame.rotationChanged(); 1632 } 1633 1634 public Rect getBounds() { 1635 return mViewportFrame.getBounds(); 1636 } 1637 1638 public void setFrameShown(boolean shown, boolean animate) { 1639 if (mViewportFrame.isShown() == shown) { 1640 return; 1641 } 1642 if (animate) { 1643 if (mShowHideFrameAnimator.isRunning()) { 1644 mShowHideFrameAnimator.reverse(); 1645 } else { 1646 if (shown) { 1647 mViewportFrame.show(); 1648 mShowHideFrameAnimator.start(); 1649 } else { 1650 mShowHideFrameAnimator.reverse(); 1651 } 1652 } 1653 } else { 1654 mShowHideFrameAnimator.cancel(); 1655 if (shown) { 1656 mViewportFrame.show(); 1657 } else { 1658 mViewportFrame.hide(); 1659 } 1660 } 1661 } 1662 1663 private void resize(Rect bounds, boolean animate) { 1664 if (mViewportFrame.getBounds().equals(bounds)) { 1665 return; 1666 } 1667 if (animate) { 1668 if (mResizeFrameAnimator.isRunning()) { 1669 mResizeFrameAnimator.cancel(); 1670 } 1671 mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds); 1672 mResizeFrameAnimator.start(); 1673 } else { 1674 mViewportFrame.setBounds(bounds); 1675 } 1676 } 1677 1678 private boolean subtract(Rect lhs, Rect rhs) { 1679 if (lhs.right < rhs.left || lhs.left > rhs.right 1680 || lhs.bottom < rhs.top || lhs.top > rhs.bottom) { 1681 return false; 1682 } 1683 if (lhs.left < rhs.left) { 1684 lhs.right = rhs.left; 1685 } 1686 if (lhs.top < rhs.top) { 1687 lhs.bottom = rhs.top; 1688 } 1689 if (lhs.right > rhs.right) { 1690 lhs.left = rhs.right; 1691 } 1692 if (lhs.bottom > rhs.bottom) { 1693 lhs.top = rhs.bottom; 1694 } 1695 return true; 1696 } 1697 1698 private static final class ViewportWindow { 1699 private static final String WINDOW_TITLE = "Magnification Overlay"; 1700 1701 private final WindowManager mWindowManager; 1702 private final DisplayProvider mDisplayProvider; 1703 1704 private final ContentView mWindowContent; 1705 private final WindowManager.LayoutParams mWindowParams; 1706 1707 private final Rect mBounds = new Rect(); 1708 private boolean mShown; 1709 private int mAlpha; 1710 1711 public ViewportWindow(Context context, WindowManager windowManager, 1712 DisplayProvider displayProvider) { 1713 mWindowManager = windowManager; 1714 mDisplayProvider = displayProvider; 1715 1716 ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams( 1717 ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 1718 mWindowContent = new ContentView(context); 1719 mWindowContent.setLayoutParams(contentParams); 1720 mWindowContent.setBackgroundColor(R.color.transparent); 1721 1722 mWindowParams = new WindowManager.LayoutParams( 1723 WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY); 1724 mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 1725 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 1726 | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; 1727 mWindowParams.setTitle(WINDOW_TITLE); 1728 mWindowParams.gravity = Gravity.CENTER; 1729 mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth; 1730 mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight; 1731 mWindowParams.format = PixelFormat.TRANSLUCENT; 1732 } 1733 1734 public boolean isShown() { 1735 return mShown; 1736 } 1737 1738 public void show() { 1739 if (mShown) { 1740 return; 1741 } 1742 mShown = true; 1743 mWindowManager.addView(mWindowContent, mWindowParams); 1744 if (DEBUG_VIEWPORT_WINDOW) { 1745 Slog.i(LOG_TAG, "ViewportWindow shown."); 1746 } 1747 } 1748 1749 public void hide() { 1750 if (!mShown) { 1751 return; 1752 } 1753 mShown = false; 1754 mWindowManager.removeView(mWindowContent); 1755 if (DEBUG_VIEWPORT_WINDOW) { 1756 Slog.i(LOG_TAG, "ViewportWindow hidden."); 1757 } 1758 } 1759 1760 @SuppressWarnings("unused") 1761 // Called reflectively from an animator. 1762 public int getAlpha() { 1763 return mAlpha; 1764 } 1765 1766 @SuppressWarnings("unused") 1767 // Called reflectively from an animator. 1768 public void setAlpha(int alpha) { 1769 if (mAlpha == alpha) { 1770 return; 1771 } 1772 mAlpha = alpha; 1773 if (mShown) { 1774 mWindowContent.invalidate(); 1775 } 1776 if (DEBUG_VIEWPORT_WINDOW) { 1777 Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha); 1778 } 1779 } 1780 1781 public Rect getBounds() { 1782 return mBounds; 1783 } 1784 1785 public void rotationChanged() { 1786 mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth; 1787 mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight; 1788 if (mShown) { 1789 mWindowManager.updateViewLayout(mWindowContent, mWindowParams); 1790 } 1791 } 1792 1793 public void setBounds(Rect bounds) { 1794 if (mBounds.equals(bounds)) { 1795 return; 1796 } 1797 mBounds.set(bounds); 1798 if (mShown) { 1799 mWindowContent.invalidate(); 1800 } 1801 if (DEBUG_VIEWPORT_WINDOW) { 1802 Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds); 1803 } 1804 } 1805 1806 private final class ContentView extends View { 1807 private final Drawable mHighlightFrame; 1808 1809 public ContentView(Context context) { 1810 super(context); 1811 mHighlightFrame = context.getResources().getDrawable( 1812 R.drawable.magnified_region_frame); 1813 } 1814 1815 @Override 1816 public void onDraw(Canvas canvas) { 1817 canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR); 1818 mHighlightFrame.setBounds(mBounds); 1819 mHighlightFrame.setAlpha(mAlpha); 1820 mHighlightFrame.draw(canvas); 1821 } 1822 } 1823 } 1824 } 1825 1826 private static class DisplayProvider implements DisplayListener { 1827 private final WindowManager mWindowManager; 1828 private final DisplayManager mDisplayManager; 1829 private final Display mDefaultDisplay; 1830 private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo(); 1831 1832 public DisplayProvider(Context context, WindowManager windowManager) { 1833 mWindowManager = windowManager; 1834 mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); 1835 mDefaultDisplay = mWindowManager.getDefaultDisplay(); 1836 mDisplayManager.registerDisplayListener(this, null); 1837 updateDisplayInfo(); 1838 } 1839 1840 public DisplayInfo getDisplayInfo() { 1841 return mDefaultDisplayInfo; 1842 } 1843 1844 public Display getDisplay() { 1845 return mDefaultDisplay; 1846 } 1847 1848 private void updateDisplayInfo() { 1849 if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) { 1850 Slog.e(LOG_TAG, "Default display is not valid."); 1851 } 1852 } 1853 1854 public void destroy() { 1855 mDisplayManager.unregisterDisplayListener(this); 1856 } 1857 1858 @Override 1859 public void onDisplayAdded(int displayId) { 1860 /* do noting */ 1861 } 1862 1863 @Override 1864 public void onDisplayRemoved(int displayId) { 1865 // Having no default display 1866 } 1867 1868 @Override 1869 public void onDisplayChanged(int displayId) { 1870 updateDisplayInfo(); 1871 } 1872 } 1873 1874 /** 1875 * The listener for receiving notifications when gestures occur. 1876 * If you want to listen for all the different gestures then implement 1877 * this interface. If you only want to listen for a subset it might 1878 * be easier to extend {@link SimpleOnScaleGestureListener}. 1879 * 1880 * An application will receive events in the following order: 1881 * <ul> 1882 * <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} 1883 * <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} 1884 * <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)} 1885 * </ul> 1886 */ 1887 interface OnScaleGestureListener { 1888 /** 1889 * Responds to scaling events for a gesture in progress. 1890 * Reported by pointer motion. 1891 * 1892 * @param detector The detector reporting the event - use this to 1893 * retrieve extended info about event state. 1894 * @return Whether or not the detector should consider this event 1895 * as handled. If an event was not handled, the detector 1896 * will continue to accumulate movement until an event is 1897 * handled. This can be useful if an application, for example, 1898 * only wants to update scaling factors if the change is 1899 * greater than 0.01. 1900 */ 1901 public boolean onScale(ScaleGestureDetector detector); 1902 1903 /** 1904 * Responds to the beginning of a scaling gesture. Reported by 1905 * new pointers going down. 1906 * 1907 * @param detector The detector reporting the event - use this to 1908 * retrieve extended info about event state. 1909 * @return Whether or not the detector should continue recognizing 1910 * this gesture. For example, if a gesture is beginning 1911 * with a focal point outside of a region where it makes 1912 * sense, onScaleBegin() may return false to ignore the 1913 * rest of the gesture. 1914 */ 1915 public boolean onScaleBegin(ScaleGestureDetector detector); 1916 1917 /** 1918 * Responds to the end of a scale gesture. Reported by existing 1919 * pointers going up. 1920 * 1921 * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()} 1922 * and {@link ScaleGestureDetector#getFocusY()} will return the location 1923 * of the pointer remaining on the screen. 1924 * 1925 * @param detector The detector reporting the event - use this to 1926 * retrieve extended info about event state. 1927 */ 1928 public void onScaleEnd(ScaleGestureDetector detector); 1929 } 1930 1931 class ScaleGestureDetector { 1932 1933 private final MinCircleFinder mMinCircleFinder = new MinCircleFinder(); 1934 1935 private final OnScaleGestureListener mListener; 1936 1937 private float mFocusX; 1938 private float mFocusY; 1939 1940 private float mCurrSpan; 1941 private float mPrevSpan; 1942 private float mCurrSpanX; 1943 private float mCurrSpanY; 1944 private float mPrevSpanX; 1945 private float mPrevSpanY; 1946 private long mCurrTime; 1947 private long mPrevTime; 1948 private boolean mInProgress; 1949 1950 public ScaleGestureDetector(OnScaleGestureListener listener) { 1951 mListener = listener; 1952 } 1953 1954 /** 1955 * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener} 1956 * when appropriate. 1957 * 1958 * <p>Applications should pass a complete and consistent event stream to this method. 1959 * A complete and consistent event stream involves all MotionEvents from the initial 1960 * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p> 1961 * 1962 * @param event The event to process 1963 * @return true if the event was processed and the detector wants to receive the 1964 * rest of the MotionEvents in this event stream. 1965 */ 1966 public boolean onTouchEvent(MotionEvent event) { 1967 boolean streamEnded = false; 1968 boolean contextChanged = false; 1969 int excludedPtrIdx = -1; 1970 final int action = event.getActionMasked(); 1971 switch (action) { 1972 case MotionEvent.ACTION_DOWN: 1973 case MotionEvent.ACTION_POINTER_DOWN: { 1974 contextChanged = true; 1975 } break; 1976 case MotionEvent.ACTION_POINTER_UP: { 1977 contextChanged = true; 1978 excludedPtrIdx = event.getActionIndex(); 1979 } break; 1980 case MotionEvent.ACTION_UP: 1981 case MotionEvent.ACTION_CANCEL: { 1982 streamEnded = true; 1983 } break; 1984 } 1985 1986 if (mInProgress && (contextChanged || streamEnded)) { 1987 mListener.onScaleEnd(this); 1988 mInProgress = false; 1989 mPrevSpan = 0; 1990 mPrevSpanX = 0; 1991 mPrevSpanY = 0; 1992 return true; 1993 } 1994 1995 final long currTime = mCurrTime; 1996 1997 mFocusX = 0; 1998 mFocusY = 0; 1999 mCurrSpan = 0; 2000 mCurrSpanX = 0; 2001 mCurrSpanY = 0; 2002 mCurrTime = 0; 2003 mPrevTime = 0; 2004 2005 if (!streamEnded) { 2006 MinCircleFinder.Circle circle = 2007 mMinCircleFinder.computeMinCircleAroundPointers(event); 2008 mFocusX = circle.centerX; 2009 mFocusY = circle.centerY; 2010 2011 double sumSlope = 0; 2012 final int pointerCount = event.getPointerCount(); 2013 for (int i = 0; i < pointerCount; i++) { 2014 if (i == excludedPtrIdx) { 2015 continue; 2016 } 2017 float x = event.getX(i) - mFocusX; 2018 float y = event.getY(i) - mFocusY; 2019 if (x == 0) { 2020 x += 0.1f; 2021 } 2022 sumSlope += y / x; 2023 } 2024 final double avgSlope = sumSlope 2025 / ((excludedPtrIdx < 0) ? pointerCount : pointerCount - 1); 2026 2027 double angle = Math.atan(avgSlope); 2028 mCurrSpan = 2 * circle.radius; 2029 mCurrSpanX = (float) Math.abs((Math.cos(angle) * mCurrSpan)); 2030 mCurrSpanY = (float) Math.abs((Math.sin(angle) * mCurrSpan)); 2031 } 2032 2033 if (contextChanged || mPrevSpan == 0 || mPrevSpanX == 0 || mPrevSpanY == 0) { 2034 mPrevSpan = mCurrSpan; 2035 mPrevSpanX = mCurrSpanX; 2036 mPrevSpanY = mCurrSpanY; 2037 } 2038 2039 if (!mInProgress && mCurrSpan != 0 && !streamEnded) { 2040 mInProgress = mListener.onScaleBegin(this); 2041 } 2042 2043 if (mInProgress) { 2044 mPrevTime = (currTime != 0) ? currTime : event.getEventTime(); 2045 mCurrTime = event.getEventTime(); 2046 if (mCurrSpan == 0) { 2047 mListener.onScaleEnd(this); 2048 mInProgress = false; 2049 } else { 2050 if (mListener.onScale(this)) { 2051 mPrevSpanX = mCurrSpanX; 2052 mPrevSpanY = mCurrSpanY; 2053 mPrevSpan = mCurrSpan; 2054 } 2055 } 2056 } 2057 2058 return true; 2059 } 2060 2061 /** 2062 * Returns {@code true} if a scale gesture is in progress. 2063 */ 2064 public boolean isInProgress() { 2065 return mInProgress; 2066 } 2067 2068 /** 2069 * Get the X coordinate of the current gesture's focal point. 2070 * If a gesture is in progress, the focal point is between 2071 * each of the pointers forming the gesture. 2072 * 2073 * If {@link #isInProgress()} would return false, the result of this 2074 * function is undefined. 2075 * 2076 * @return X coordinate of the focal point in pixels. 2077 */ 2078 public float getFocusX() { 2079 return mFocusX; 2080 } 2081 2082 /** 2083 * Get the Y coordinate of the current gesture's focal point. 2084 * If a gesture is in progress, the focal point is between 2085 * each of the pointers forming the gesture. 2086 * 2087 * If {@link #isInProgress()} would return false, the result of this 2088 * function is undefined. 2089 * 2090 * @return Y coordinate of the focal point in pixels. 2091 */ 2092 public float getFocusY() { 2093 return mFocusY; 2094 } 2095 2096 /** 2097 * Return the average distance between each of the pointers forming the 2098 * gesture in progress through the focal point. 2099 * 2100 * @return Distance between pointers in pixels. 2101 */ 2102 public float getCurrentSpan() { 2103 return mCurrSpan; 2104 } 2105 2106 /** 2107 * Return the average X distance between each of the pointers forming the 2108 * gesture in progress through the focal point. 2109 * 2110 * @return Distance between pointers in pixels. 2111 */ 2112 public float getCurrentSpanX() { 2113 return mCurrSpanX; 2114 } 2115 2116 /** 2117 * Return the average Y distance between each of the pointers forming the 2118 * gesture in progress through the focal point. 2119 * 2120 * @return Distance between pointers in pixels. 2121 */ 2122 public float getCurrentSpanY() { 2123 return mCurrSpanY; 2124 } 2125 2126 /** 2127 * Return the previous average distance between each of the pointers forming the 2128 * gesture in progress through the focal point. 2129 * 2130 * @return Previous distance between pointers in pixels. 2131 */ 2132 public float getPreviousSpan() { 2133 return mPrevSpan; 2134 } 2135 2136 /** 2137 * Return the previous average X distance between each of the pointers forming the 2138 * gesture in progress through the focal point. 2139 * 2140 * @return Previous distance between pointers in pixels. 2141 */ 2142 public float getPreviousSpanX() { 2143 return mPrevSpanX; 2144 } 2145 2146 /** 2147 * Return the previous average Y distance between each of the pointers forming the 2148 * gesture in progress through the focal point. 2149 * 2150 * @return Previous distance between pointers in pixels. 2151 */ 2152 public float getPreviousSpanY() { 2153 return mPrevSpanY; 2154 } 2155 2156 /** 2157 * Return the scaling factor from the previous scale event to the current 2158 * event. This value is defined as 2159 * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}). 2160 * 2161 * @return The current scaling factor. 2162 */ 2163 public float getScaleFactor() { 2164 return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1; 2165 } 2166 2167 /** 2168 * Return the time difference in milliseconds between the previous 2169 * accepted scaling event and the current scaling event. 2170 * 2171 * @return Time difference since the last scaling event in milliseconds. 2172 */ 2173 public long getTimeDelta() { 2174 return mCurrTime - mPrevTime; 2175 } 2176 2177 /** 2178 * Return the event time of the current event being processed. 2179 * 2180 * @return Current event time in milliseconds. 2181 */ 2182 public long getEventTime() { 2183 return mCurrTime; 2184 } 2185 } 2186 2187 private static final class MinCircleFinder { 2188 private final ArrayList<PointHolder> mPoints = new ArrayList<PointHolder>(); 2189 private final ArrayList<PointHolder> sBoundary = new ArrayList<PointHolder>(); 2190 private final Circle mMinCircle = new Circle(); 2191 2192 /** 2193 * Finds the minimal circle that contains all pointers of a motion event. 2194 * 2195 * @param event A motion event. 2196 * @return The minimal circle. 2197 */ 2198 public Circle computeMinCircleAroundPointers(MotionEvent event) { 2199 ArrayList<PointHolder> points = mPoints; 2200 points.clear(); 2201 final int pointerCount = event.getPointerCount(); 2202 for (int i = 0; i < pointerCount; i++) { 2203 PointHolder point = PointHolder.obtain(event.getX(i), event.getY(i)); 2204 points.add(point); 2205 } 2206 ArrayList<PointHolder> boundary = sBoundary; 2207 boundary.clear(); 2208 computeMinCircleAroundPointsRecursive(points, boundary, mMinCircle); 2209 for (int i = points.size() - 1; i >= 0; i--) { 2210 points.remove(i).recycle(); 2211 } 2212 boundary.clear(); 2213 return mMinCircle; 2214 } 2215 2216 private static void computeMinCircleAroundPointsRecursive(ArrayList<PointHolder> points, 2217 ArrayList<PointHolder> boundary, Circle outCircle) { 2218 if (points.isEmpty()) { 2219 if (boundary.size() == 0) { 2220 outCircle.initialize(); 2221 } else if (boundary.size() == 1) { 2222 outCircle.initialize(boundary.get(0).mData, boundary.get(0).mData); 2223 } else if (boundary.size() == 2) { 2224 outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData); 2225 } else if (boundary.size() == 3) { 2226 outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData, 2227 boundary.get(2).mData); 2228 } 2229 return; 2230 } 2231 PointHolder point = points.remove(points.size() - 1); 2232 computeMinCircleAroundPointsRecursive(points, boundary, outCircle); 2233 if (!outCircle.contains(point.mData)) { 2234 boundary.add(point); 2235 computeMinCircleAroundPointsRecursive(points, boundary, outCircle); 2236 boundary.remove(point); 2237 } 2238 points.add(point); 2239 } 2240 2241 private static final class PointHolder { 2242 private static final int MAX_POOL_SIZE = 20; 2243 private static PointHolder sPool; 2244 private static int sPoolSize; 2245 2246 private PointHolder mNext; 2247 private boolean mIsInPool; 2248 2249 private final PointF mData = new PointF(); 2250 2251 public static PointHolder obtain(float x, float y) { 2252 PointHolder holder; 2253 if (sPoolSize > 0) { 2254 sPoolSize--; 2255 holder = sPool; 2256 sPool = sPool.mNext; 2257 holder.mNext = null; 2258 holder.mIsInPool = false; 2259 } else { 2260 holder = new PointHolder(); 2261 } 2262 holder.mData.set(x, y); 2263 return holder; 2264 } 2265 2266 public void recycle() { 2267 if (mIsInPool) { 2268 throw new IllegalStateException("Already recycled."); 2269 } 2270 clear(); 2271 if (sPoolSize < MAX_POOL_SIZE) { 2272 sPoolSize++; 2273 mNext = sPool; 2274 sPool = this; 2275 mIsInPool = true; 2276 } 2277 } 2278 2279 private void clear() { 2280 mData.set(0, 0); 2281 } 2282 } 2283 2284 public static final class Circle { 2285 public float centerX; 2286 public float centerY; 2287 public float radius; 2288 2289 private void initialize() { 2290 centerX = 0; 2291 centerY = 0; 2292 radius = 0; 2293 } 2294 2295 private void initialize(PointF first, PointF second, PointF third) { 2296 if (!hasLineWithInfiniteSlope(first, second, third)) { 2297 initializeInternal(first, second, third); 2298 } else if (!hasLineWithInfiniteSlope(first, third, second)) { 2299 initializeInternal(first, third, second); 2300 } else if (!hasLineWithInfiniteSlope(second, first, third)) { 2301 initializeInternal(second, first, third); 2302 } else if (!hasLineWithInfiniteSlope(second, third, first)) { 2303 initializeInternal(second, third, first); 2304 } else if (!hasLineWithInfiniteSlope(third, first, second)) { 2305 initializeInternal(third, first, second); 2306 } else if (!hasLineWithInfiniteSlope(third, second, first)) { 2307 initializeInternal(third, second, first); 2308 } else { 2309 initialize(); 2310 } 2311 } 2312 2313 private void initialize(PointF first, PointF second) { 2314 radius = (float) (Math.hypot(second.x - first.x, second.y - first.y) / 2); 2315 centerX = (float) (second.x + first.x) / 2; 2316 centerY = (float) (second.y + first.y) / 2; 2317 } 2318 2319 public boolean contains(PointF point) { 2320 return (int) (Math.hypot(point.x - centerX, point.y - centerY)) <= radius; 2321 } 2322 2323 private void initializeInternal(PointF first, PointF second, PointF third) { 2324 final float x1 = first.x; 2325 final float y1 = first.y; 2326 final float x2 = second.x; 2327 final float y2 = second.y; 2328 final float x3 = third.x; 2329 final float y3 = third.y; 2330 2331 final float sl1 = (y2 - y1) / (x2 - x1); 2332 final float sl2 = (y3 - y2) / (x3 - x2); 2333 2334 centerX = (int) ((sl1 * sl2 * (y1 - y3) + sl2 * (x1 + x2) - sl1 * (x2 + x3)) 2335 / (2 * (sl2 - sl1))); 2336 centerY = (int) (-1 / sl1 * (centerX - (x1 + x2) / 2) + (y1 + y2) / 2); 2337 radius = (int) Math.hypot(x1 - centerX, y1 - centerY); 2338 } 2339 2340 private boolean hasLineWithInfiniteSlope(PointF first, PointF second, PointF third) { 2341 return (second.x - first.x == 0 || third.x - second.x == 0 2342 || second.y - first.y == 0 || third.y - second.y == 0); 2343 } 2344 2345 @Override 2346 public String toString() { 2347 return "cetner: [" + centerX + ", " + centerY + "] radius: " + radius; 2348 } 2349 } 2350 } 2351} 2352