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