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