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