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