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