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