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