1765 lines
74 KiB
Java
1765 lines
74 KiB
Java
/*
|
|
* Copyright (C) 2012 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.server.accessibility;
|
|
|
|
import android.animation.Animator;
|
|
import android.animation.Animator.AnimatorListener;
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.TypeEvaluator;
|
|
import android.animation.ValueAnimator;
|
|
import android.content.Context;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.PointF;
|
|
import android.graphics.PorterDuff.Mode;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.hardware.display.DisplayManager;
|
|
import android.hardware.display.DisplayManager.DisplayListener;
|
|
import android.os.AsyncTask;
|
|
import android.os.Handler;
|
|
import android.os.Message;
|
|
import android.os.RemoteException;
|
|
import android.os.ServiceManager;
|
|
import android.provider.Settings;
|
|
import android.util.MathUtils;
|
|
import android.util.Property;
|
|
import android.util.Slog;
|
|
import android.view.Display;
|
|
import android.view.DisplayInfo;
|
|
import android.view.Gravity;
|
|
import android.view.IDisplayContentChangeListener;
|
|
import android.view.IWindowManager;
|
|
import android.view.MotionEvent;
|
|
import android.view.ScaleGestureDetector;
|
|
import android.view.MotionEvent.PointerCoords;
|
|
import android.view.MotionEvent.PointerProperties;
|
|
import android.view.ScaleGestureDetector.OnScaleGestureListener;
|
|
import android.view.Surface;
|
|
import android.view.View;
|
|
import android.view.ViewConfiguration;
|
|
import android.view.ViewGroup;
|
|
import android.view.WindowInfo;
|
|
import android.view.WindowManager;
|
|
import android.view.WindowManagerPolicy;
|
|
import android.view.accessibility.AccessibilityEvent;
|
|
import android.view.animation.DecelerateInterpolator;
|
|
import android.view.animation.Interpolator;
|
|
|
|
import com.android.internal.R;
|
|
import com.android.internal.os.SomeArgs;
|
|
|
|
import java.util.ArrayList;
|
|
|
|
/**
|
|
* This class handles the screen magnification when accessibility is enabled.
|
|
* The behavior is as follows:
|
|
*
|
|
* 1. Triple tap toggles permanent screen magnification which is magnifying
|
|
* the area around the location of the triple tap. One can think of the
|
|
* location of the triple tap as the center of the magnified viewport.
|
|
* For example, a triple tap when not magnified would magnify the screen
|
|
* and leave it in a magnified state. A triple tapping when magnified would
|
|
* clear magnification and leave the screen in a not magnified state.
|
|
*
|
|
* 2. Triple tap and hold would magnify the screen if not magnified and enable
|
|
* viewport dragging mode until the finger goes up. One can think of this
|
|
* mode as a way to move the magnified viewport since the area around the
|
|
* moving finger will be magnified to fit the screen. For example, if the
|
|
* screen was not magnified and the user triple taps and holds the screen
|
|
* would magnify and the viewport will follow the user's finger. When the
|
|
* finger goes up the screen will clear zoom out. If the same user interaction
|
|
* is performed when the screen is magnified, the viewport movement will
|
|
* be the same but when the finger goes up the screen will stay magnified.
|
|
* In other words, the initial magnified state is sticky.
|
|
*
|
|
* 3. Pinching with any number of additional fingers when viewport dragging
|
|
* is enabled, i.e. the user triple tapped and holds, would adjust the
|
|
* magnification scale which will become the current default magnification
|
|
* scale. The next time the user magnifies the same magnification scale
|
|
* would be used.
|
|
*
|
|
* 4. When in a permanent magnified state the user can use two or more fingers
|
|
* to pan the viewport. Note that in this mode the content is panned as
|
|
* opposed to the viewport dragging mode in which the viewport is moved.
|
|
*
|
|
* 5. When in a permanent magnified state the user can use three or more
|
|
* fingers to change the magnification scale which will become the current
|
|
* default magnification scale. The next time the user magnifies the same
|
|
* magnification scale would be used.
|
|
*
|
|
* 6. The magnification scale will be persisted in settings and in the cloud.
|
|
*/
|
|
public final class ScreenMagnifier implements EventStreamTransformation {
|
|
|
|
private static final boolean DEBUG_STATE_TRANSITIONS = false;
|
|
private static final boolean DEBUG_DETECTING = false;
|
|
private static final boolean DEBUG_TRANSFORMATION = false;
|
|
private static final boolean DEBUG_PANNING = false;
|
|
private static final boolean DEBUG_SCALING = false;
|
|
private static final boolean DEBUG_VIEWPORT_WINDOW = false;
|
|
private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
|
|
private static final boolean DEBUG_ROTATION = false;
|
|
private static final boolean DEBUG_GESTURE_DETECTOR = false;
|
|
private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
|
|
|
|
private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
|
|
|
|
private static final int STATE_DELEGATING = 1;
|
|
private static final int STATE_DETECTING = 2;
|
|
private static final int STATE_SCALING = 3;
|
|
private static final int STATE_VIEWPORT_DRAGGING = 4;
|
|
private static final int STATE_PANNING = 5;
|
|
private static final int STATE_DECIDE_PAN_OR_SCALE = 6;
|
|
|
|
private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
|
|
private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
|
|
private static final float DEFAULT_WINDOW_ANIMATION_SCALE = 1.0f;
|
|
|
|
private final IWindowManager mWindowManagerService = IWindowManager.Stub.asInterface(
|
|
ServiceManager.getService("window"));
|
|
private final WindowManager mWindowManager;
|
|
private final DisplayProvider mDisplayProvider;
|
|
|
|
private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler();
|
|
private final GestureDetector mGestureDetector;
|
|
private final StateViewportDraggingHandler mStateViewportDraggingHandler =
|
|
new StateViewportDraggingHandler();
|
|
|
|
private final Interpolator mInterpolator = new DecelerateInterpolator(2.5f);
|
|
|
|
private final MagnificationController mMagnificationController;
|
|
private final DisplayContentObserver mDisplayContentObserver;
|
|
private final Viewport mViewport;
|
|
|
|
private final int mTapTimeSlop = ViewConfiguration.getTapTimeout();
|
|
private final int mMultiTapTimeSlop = ViewConfiguration.getDoubleTapTimeout();
|
|
private final int mTapDistanceSlop;
|
|
private final int mMultiTapDistanceSlop;
|
|
|
|
private final int mShortAnimationDuration;
|
|
private final int mLongAnimationDuration;
|
|
private final float mWindowAnimationScale;
|
|
|
|
private final Context mContext;
|
|
|
|
private EventStreamTransformation mNext;
|
|
|
|
private int mCurrentState;
|
|
private boolean mTranslationEnabledBeforePan;
|
|
|
|
private PointerCoords[] mTempPointerCoords;
|
|
private PointerProperties[] mTempPointerProperties;
|
|
|
|
public ScreenMagnifier(Context context) {
|
|
mContext = context;
|
|
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
|
|
|
|
mShortAnimationDuration = context.getResources().getInteger(
|
|
com.android.internal.R.integer.config_shortAnimTime);
|
|
mLongAnimationDuration = context.getResources().getInteger(
|
|
com.android.internal.R.integer.config_longAnimTime);
|
|
mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
|
mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
|
|
mWindowAnimationScale = Settings.System.getFloat(context.getContentResolver(),
|
|
Settings.System.WINDOW_ANIMATION_SCALE, DEFAULT_WINDOW_ANIMATION_SCALE);
|
|
|
|
mMagnificationController = new MagnificationController(mShortAnimationDuration);
|
|
mDisplayProvider = new DisplayProvider(context, mWindowManager);
|
|
mViewport = new Viewport(mContext, mWindowManager, mWindowManagerService,
|
|
mDisplayProvider, mInterpolator, mShortAnimationDuration);
|
|
mDisplayContentObserver = new DisplayContentObserver(mContext, mViewport,
|
|
mMagnificationController, mWindowManagerService, mDisplayProvider,
|
|
mLongAnimationDuration, mWindowAnimationScale);
|
|
|
|
mGestureDetector = new GestureDetector(context);
|
|
|
|
transitionToState(STATE_DETECTING);
|
|
}
|
|
|
|
@Override
|
|
public void onMotionEvent(MotionEvent event, int policyFlags) {
|
|
switch (mCurrentState) {
|
|
case STATE_DELEGATING: {
|
|
handleMotionEventStateDelegating(event, policyFlags);
|
|
} break;
|
|
case STATE_DETECTING: {
|
|
mDetectingStateHandler.onMotionEvent(event, policyFlags);
|
|
} break;
|
|
case STATE_VIEWPORT_DRAGGING: {
|
|
mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
|
|
} break;
|
|
case STATE_SCALING:
|
|
case STATE_PANNING:
|
|
case STATE_DECIDE_PAN_OR_SCALE: {
|
|
// Handled by the gesture detector. Since the detector
|
|
// needs all touch events to work properly we cannot
|
|
// call it only for these states.
|
|
} break;
|
|
default: {
|
|
throw new IllegalStateException("Unknown state: " + mCurrentState);
|
|
}
|
|
}
|
|
mGestureDetector.onMotionEvent(event);
|
|
}
|
|
|
|
@Override
|
|
public void onAccessibilityEvent(AccessibilityEvent event) {
|
|
if (mNext != null) {
|
|
mNext.onAccessibilityEvent(event);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setNext(EventStreamTransformation next) {
|
|
mNext = next;
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
mCurrentState = STATE_DETECTING;
|
|
mDetectingStateHandler.clear();
|
|
mStateViewportDraggingHandler.clear();
|
|
mGestureDetector.clear();
|
|
|
|
if (mNext != null) {
|
|
mNext.clear();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDestroy() {
|
|
mDisplayProvider.destroy();
|
|
mDisplayContentObserver.destroy();
|
|
}
|
|
|
|
private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
|
|
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
|
if (mDetectingStateHandler.mDelayedEventQueue == null) {
|
|
transitionToState(STATE_DETECTING);
|
|
}
|
|
}
|
|
if (mNext != null) {
|
|
// If the event is within the magnified portion of the screen we have
|
|
// to change its location to be where the user thinks he is poking the
|
|
// UI which may have been magnified and panned.
|
|
final float eventX = event.getX();
|
|
final float eventY = event.getY();
|
|
if (mMagnificationController.isMagnifying()
|
|
&& mViewport.getBounds().contains((int) eventX, (int) eventY)) {
|
|
final float scale = mMagnificationController.getScale();
|
|
final float scaledOffsetX = mMagnificationController.getScaledOffsetX();
|
|
final float scaledOffsetY = mMagnificationController.getScaledOffsetY();
|
|
final int pointerCount = event.getPointerCount();
|
|
PointerCoords[] coords = getTempPointerCoordsWithMinSize(pointerCount);
|
|
PointerProperties[] properties = getTempPointerPropertiesWithMinSize(pointerCount);
|
|
for (int i = 0; i < pointerCount; i++) {
|
|
event.getPointerCoords(i, coords[i]);
|
|
coords[i].x = (coords[i].x - scaledOffsetX) / scale;
|
|
coords[i].y = (coords[i].y - scaledOffsetY) / scale;
|
|
event.getPointerProperties(i, properties[i]);
|
|
}
|
|
event = MotionEvent.obtain(event.getDownTime(),
|
|
event.getEventTime(), event.getAction(), pointerCount, properties,
|
|
coords, 0, 0, 1.0f, 1.0f, event.getDeviceId(), 0, event.getSource(),
|
|
event.getFlags());
|
|
}
|
|
mNext.onMotionEvent(event, policyFlags);
|
|
}
|
|
}
|
|
|
|
private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
|
|
final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
|
|
if (oldSize < size) {
|
|
PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
|
|
mTempPointerCoords = new PointerCoords[size];
|
|
if (oldTempPointerCoords != null) {
|
|
System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
|
|
}
|
|
}
|
|
for (int i = oldSize; i < size; i++) {
|
|
mTempPointerCoords[i] = new PointerCoords();
|
|
}
|
|
return mTempPointerCoords;
|
|
}
|
|
|
|
private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
|
|
final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
|
|
if (oldSize < size) {
|
|
PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
|
|
mTempPointerProperties = new PointerProperties[size];
|
|
if (oldTempPointerProperties != null) {
|
|
System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
|
|
}
|
|
}
|
|
for (int i = oldSize; i < size; i++) {
|
|
mTempPointerProperties[i] = new PointerProperties();
|
|
}
|
|
return mTempPointerProperties;
|
|
}
|
|
|
|
private void transitionToState(int state) {
|
|
if (DEBUG_STATE_TRANSITIONS) {
|
|
switch (state) {
|
|
case STATE_DELEGATING: {
|
|
Slog.i(LOG_TAG, "mCurrentState: STATE_DELEGATING");
|
|
} break;
|
|
case STATE_DETECTING: {
|
|
Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING");
|
|
} break;
|
|
case STATE_VIEWPORT_DRAGGING: {
|
|
Slog.i(LOG_TAG, "mCurrentState: STATE_VIEWPORT_DRAGGING");
|
|
} break;
|
|
case STATE_SCALING: {
|
|
Slog.i(LOG_TAG, "mCurrentState: STATE_SCALING");
|
|
} break;
|
|
case STATE_PANNING: {
|
|
Slog.i(LOG_TAG, "mCurrentState: STATE_PANNING");
|
|
} break;
|
|
case STATE_DECIDE_PAN_OR_SCALE: {
|
|
Slog.i(LOG_TAG, "mCurrentState: STATE_DECIDE_PAN_OR_SCALE");
|
|
} break;
|
|
default: {
|
|
throw new IllegalArgumentException("Unknown state: " + state);
|
|
}
|
|
}
|
|
}
|
|
mCurrentState = state;
|
|
}
|
|
|
|
private final class GestureDetector implements OnScaleGestureListener {
|
|
private static final float MIN_SCALE = 1.3f;
|
|
private static final float MAX_SCALE = 5.0f;
|
|
|
|
private static final float DETECT_SCALING_THRESHOLD = 0.25f;
|
|
private static final int DETECT_PANNING_THRESHOLD_DIP = 30;
|
|
|
|
private final float mScaledDetectPanningThreshold;
|
|
|
|
private final ScaleGestureDetector mScaleGestureDetector;
|
|
|
|
private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN);
|
|
private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN);
|
|
|
|
private float mCurrScale = Float.NaN;
|
|
private float mCurrScaleFactor = 1.0f;
|
|
private float mPrevScaleFactor = 1.0f;
|
|
private float mCurrPan;
|
|
private float mPrevPan;
|
|
|
|
private float mScaleFocusX = Float.NaN;
|
|
private float mScaleFocusY = Float.NaN;
|
|
|
|
public GestureDetector(Context context) {
|
|
final float density = context.getResources().getDisplayMetrics().density;
|
|
mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
|
|
mScaleGestureDetector = new ScaleGestureDetector(context, this);
|
|
}
|
|
|
|
public void onMotionEvent(MotionEvent event) {
|
|
mScaleGestureDetector.onTouchEvent(event);
|
|
switch (mCurrentState) {
|
|
case STATE_DETECTING:
|
|
case STATE_DELEGATING:
|
|
case STATE_VIEWPORT_DRAGGING: {
|
|
return;
|
|
}
|
|
}
|
|
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
|
|
clear();
|
|
if (mCurrentState == STATE_SCALING) {
|
|
persistScale(mMagnificationController.getScale());
|
|
}
|
|
transitionToState(STATE_DETECTING);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean onScale(ScaleGestureDetector detector) {
|
|
switch (mCurrentState) {
|
|
case STATE_DETECTING:
|
|
case STATE_DELEGATING:
|
|
case STATE_VIEWPORT_DRAGGING: {
|
|
return true;
|
|
}
|
|
case STATE_DECIDE_PAN_OR_SCALE: {
|
|
mCurrScaleFactor = mScaleGestureDetector.getScaleFactor();
|
|
final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor);
|
|
if (DEBUG_GESTURE_DETECTOR) {
|
|
Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta);
|
|
}
|
|
if (scaleDelta > DETECT_SCALING_THRESHOLD) {
|
|
performScale(detector, true);
|
|
clear();
|
|
transitionToState(STATE_SCALING);
|
|
return false;
|
|
}
|
|
mCurrPan = (float) MathUtils.dist(
|
|
mScaleGestureDetector.getFocusX(),
|
|
mScaleGestureDetector.getFocusY(),
|
|
mInitialFocus.x, mInitialFocus.y);
|
|
final float panDelta = mCurrPan + mPrevPan;
|
|
if (DEBUG_GESTURE_DETECTOR) {
|
|
Slog.i(LOG_TAG, "panDelta: " + panDelta);
|
|
}
|
|
if (panDelta > mScaledDetectPanningThreshold) {
|
|
performPan(detector, true);
|
|
clear();
|
|
transitionToState(STATE_PANNING);
|
|
return false;
|
|
}
|
|
} break;
|
|
case STATE_SCALING: {
|
|
performScale(detector, false);
|
|
} break;
|
|
case STATE_PANNING: {
|
|
performPan(detector, false);
|
|
} break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public boolean onScaleBegin(ScaleGestureDetector detector) {
|
|
switch (mCurrentState) {
|
|
case STATE_DECIDE_PAN_OR_SCALE: {
|
|
mPrevScaleFactor *= mCurrScaleFactor;
|
|
mPrevPan += mCurrPan;
|
|
mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
|
|
mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
|
|
} break;
|
|
case STATE_SCALING: {
|
|
mPrevScaleFactor = 1.0f;
|
|
mCurrScale = Float.NaN;
|
|
} break;
|
|
case STATE_PANNING: {
|
|
mPrevPan += mCurrPan;
|
|
mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
|
|
mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
|
|
} break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public void onScaleEnd(ScaleGestureDetector detector) {
|
|
/* do nothing */
|
|
}
|
|
|
|
public void clear() {
|
|
mCurrScaleFactor = 1.0f;
|
|
mPrevScaleFactor = 1.0f;
|
|
mPrevPan = 0;
|
|
mCurrPan = 0;
|
|
mInitialFocus.set(Float.NaN, Float.NaN);
|
|
mPrevFocus.set(Float.NaN, Float.NaN);
|
|
mCurrScale = Float.NaN;
|
|
mScaleFocusX = Float.NaN;
|
|
mScaleFocusY = Float.NaN;
|
|
}
|
|
|
|
private void performPan(ScaleGestureDetector detector, boolean animate) {
|
|
if (Float.compare(mPrevFocus.x, Float.NaN) == 0
|
|
&& Float.compare(mPrevFocus.y, Float.NaN) == 0) {
|
|
mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
|
|
return;
|
|
}
|
|
final float scale = mMagnificationController.getScale();
|
|
final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale;
|
|
final float scrollY = (detector.getFocusY() - mPrevFocus.y) / scale;
|
|
final float centerX = mMagnificationController.getMagnifiedRegionCenterX()
|
|
- scrollX;
|
|
final float centerY = mMagnificationController.getMagnifiedRegionCenterY()
|
|
- scrollY;
|
|
if (DEBUG_PANNING) {
|
|
Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
|
|
+ " scrollY: " + scrollY);
|
|
}
|
|
mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, animate);
|
|
mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
|
|
}
|
|
|
|
private void performScale(ScaleGestureDetector detector, boolean animate) {
|
|
if (Float.compare(mCurrScale, Float.NaN) == 0) {
|
|
mCurrScale = mMagnificationController.getScale();
|
|
return;
|
|
}
|
|
final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor();
|
|
final float newScale = mCurrScale * totalScaleFactor;
|
|
final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE),
|
|
MAX_SCALE);
|
|
if (DEBUG_SCALING) {
|
|
Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
|
|
}
|
|
if (Float.compare(mScaleFocusX, Float.NaN) == 0
|
|
&& Float.compare(mScaleFocusY, Float.NaN) == 0) {
|
|
mScaleFocusX = detector.getFocusX();
|
|
mScaleFocusY = detector.getFocusY();
|
|
}
|
|
mMagnificationController.setScale(normalizedNewScale, mScaleFocusX,
|
|
mScaleFocusY, animate);
|
|
}
|
|
}
|
|
|
|
private final class StateViewportDraggingHandler {
|
|
private boolean mLastMoveOutsideMagnifiedRegion;
|
|
|
|
private void onMotionEvent(MotionEvent event, int policyFlags) {
|
|
final int action = event.getActionMasked();
|
|
switch (action) {
|
|
case MotionEvent.ACTION_DOWN: {
|
|
throw new IllegalArgumentException("Unexpected event type: ACTION_DOWN");
|
|
}
|
|
case MotionEvent.ACTION_POINTER_DOWN: {
|
|
clear();
|
|
transitionToState(STATE_SCALING);
|
|
} break;
|
|
case MotionEvent.ACTION_MOVE: {
|
|
if (event.getPointerCount() != 1) {
|
|
throw new IllegalStateException("Should have one pointer down.");
|
|
}
|
|
final float eventX = event.getX();
|
|
final float eventY = event.getY();
|
|
if (mViewport.getBounds().contains((int) eventX, (int) eventY)) {
|
|
if (mLastMoveOutsideMagnifiedRegion) {
|
|
mLastMoveOutsideMagnifiedRegion = false;
|
|
mMagnificationController.setMagnifiedRegionCenter(eventX,
|
|
eventY, true);
|
|
} else {
|
|
mMagnificationController.setMagnifiedRegionCenter(eventX,
|
|
eventY, false);
|
|
}
|
|
} else {
|
|
mLastMoveOutsideMagnifiedRegion = true;
|
|
}
|
|
} break;
|
|
case MotionEvent.ACTION_UP: {
|
|
if (!mTranslationEnabledBeforePan) {
|
|
mMagnificationController.reset(true);
|
|
mViewport.setFrameShown(false, true);
|
|
}
|
|
clear();
|
|
transitionToState(STATE_DETECTING);
|
|
} break;
|
|
case MotionEvent.ACTION_POINTER_UP: {
|
|
throw new IllegalArgumentException("Unexpected event type: ACTION_POINTER_UP");
|
|
}
|
|
}
|
|
}
|
|
|
|
public void clear() {
|
|
mLastMoveOutsideMagnifiedRegion = false;
|
|
}
|
|
}
|
|
|
|
private final class DetectingStateHandler {
|
|
|
|
private static final int MESSAGE_ON_ACTION_TAP_AND_HOLD = 1;
|
|
|
|
private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
|
|
|
|
private static final int ACTION_TAP_COUNT = 3;
|
|
|
|
private MotionEventInfo mDelayedEventQueue;
|
|
|
|
private MotionEvent mLastDownEvent;
|
|
private MotionEvent mLastTapUpEvent;
|
|
private int mTapCount;
|
|
|
|
private final Handler mHandler = new Handler() {
|
|
@Override
|
|
public void handleMessage(Message message) {
|
|
final int type = message.what;
|
|
switch (type) {
|
|
case MESSAGE_ON_ACTION_TAP_AND_HOLD: {
|
|
MotionEvent event = (MotionEvent) message.obj;
|
|
final int policyFlags = message.arg1;
|
|
onActionTapAndHold(event, policyFlags);
|
|
} break;
|
|
case MESSAGE_TRANSITION_TO_DELEGATING_STATE: {
|
|
transitionToState(STATE_DELEGATING);
|
|
sendDelayedMotionEvents();
|
|
clear();
|
|
} break;
|
|
default: {
|
|
throw new IllegalArgumentException("Unknown message type: " + type);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
public void onMotionEvent(MotionEvent event, int policyFlags) {
|
|
cacheDelayedMotionEvent(event, policyFlags);
|
|
final int action = event.getActionMasked();
|
|
switch (action) {
|
|
case MotionEvent.ACTION_DOWN: {
|
|
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
|
|
if (!mViewport.getBounds().contains((int) event.getX(),
|
|
(int) event.getY())) {
|
|
transitionToDelegatingStateAndClear();
|
|
return;
|
|
}
|
|
if (mTapCount == ACTION_TAP_COUNT - 1 && mLastDownEvent != null
|
|
&& GestureUtils.isMultiTap(mLastDownEvent, event,
|
|
mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
|
|
Message message = mHandler.obtainMessage(MESSAGE_ON_ACTION_TAP_AND_HOLD,
|
|
policyFlags, 0, event);
|
|
mHandler.sendMessageDelayed(message,
|
|
ViewConfiguration.getLongPressTimeout());
|
|
} else if (mTapCount < ACTION_TAP_COUNT) {
|
|
Message message = mHandler.obtainMessage(
|
|
MESSAGE_TRANSITION_TO_DELEGATING_STATE);
|
|
mHandler.sendMessageDelayed(message, mTapTimeSlop + mMultiTapDistanceSlop);
|
|
}
|
|
clearLastDownEvent();
|
|
mLastDownEvent = MotionEvent.obtain(event);
|
|
} break;
|
|
case MotionEvent.ACTION_POINTER_DOWN: {
|
|
if (mMagnificationController.isMagnifying()) {
|
|
transitionToState(STATE_DECIDE_PAN_OR_SCALE);
|
|
clear();
|
|
} else {
|
|
transitionToDelegatingStateAndClear();
|
|
}
|
|
} break;
|
|
case MotionEvent.ACTION_MOVE: {
|
|
if (mLastDownEvent != null && mTapCount < ACTION_TAP_COUNT - 1) {
|
|
final double distance = GestureUtils.computeDistance(mLastDownEvent,
|
|
event, 0);
|
|
if (Math.abs(distance) > mTapDistanceSlop) {
|
|
transitionToDelegatingStateAndClear();
|
|
}
|
|
}
|
|
} break;
|
|
case MotionEvent.ACTION_UP: {
|
|
if (mLastDownEvent == null) {
|
|
return;
|
|
}
|
|
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
|
|
if (!mViewport.getBounds().contains((int) event.getX(), (int) event.getY())) {
|
|
transitionToDelegatingStateAndClear();
|
|
return;
|
|
}
|
|
if (!GestureUtils.isTap(mLastDownEvent, event, mTapTimeSlop,
|
|
mTapDistanceSlop, 0)) {
|
|
transitionToDelegatingStateAndClear();
|
|
return;
|
|
}
|
|
if (mLastTapUpEvent != null && !GestureUtils.isMultiTap(mLastTapUpEvent,
|
|
event, mMultiTapTimeSlop, mMultiTapDistanceSlop, 0)) {
|
|
transitionToDelegatingStateAndClear();
|
|
return;
|
|
}
|
|
mTapCount++;
|
|
if (DEBUG_DETECTING) {
|
|
Slog.i(LOG_TAG, "Tap count:" + mTapCount);
|
|
}
|
|
if (mTapCount == ACTION_TAP_COUNT) {
|
|
clear();
|
|
onActionTap(event, policyFlags);
|
|
return;
|
|
}
|
|
clearLastTapUpEvent();
|
|
mLastTapUpEvent = MotionEvent.obtain(event);
|
|
} break;
|
|
case MotionEvent.ACTION_POINTER_UP: {
|
|
/* do nothing */
|
|
} break;
|
|
}
|
|
}
|
|
|
|
public void clear() {
|
|
mHandler.removeMessages(MESSAGE_ON_ACTION_TAP_AND_HOLD);
|
|
mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
|
|
clearTapDetectionState();
|
|
clearDelayedMotionEvents();
|
|
}
|
|
|
|
private void clearTapDetectionState() {
|
|
mTapCount = 0;
|
|
clearLastTapUpEvent();
|
|
clearLastDownEvent();
|
|
}
|
|
|
|
private void clearLastTapUpEvent() {
|
|
if (mLastTapUpEvent != null) {
|
|
mLastTapUpEvent.recycle();
|
|
mLastTapUpEvent = null;
|
|
}
|
|
}
|
|
|
|
private void clearLastDownEvent() {
|
|
if (mLastDownEvent != null) {
|
|
mLastDownEvent.recycle();
|
|
mLastDownEvent = null;
|
|
}
|
|
}
|
|
|
|
private void cacheDelayedMotionEvent(MotionEvent event, int policyFlags) {
|
|
MotionEventInfo info = MotionEventInfo.obtain(event, policyFlags);
|
|
if (mDelayedEventQueue == null) {
|
|
mDelayedEventQueue = info;
|
|
} else {
|
|
MotionEventInfo tail = mDelayedEventQueue;
|
|
while (tail.mNext != null) {
|
|
tail = tail.mNext;
|
|
}
|
|
tail.mNext = info;
|
|
}
|
|
}
|
|
|
|
private void sendDelayedMotionEvents() {
|
|
while (mDelayedEventQueue != null) {
|
|
MotionEventInfo info = mDelayedEventQueue;
|
|
mDelayedEventQueue = info.mNext;
|
|
ScreenMagnifier.this.onMotionEvent(info.mEvent, info.mPolicyFlags);
|
|
info.recycle();
|
|
}
|
|
}
|
|
|
|
private void clearDelayedMotionEvents() {
|
|
while (mDelayedEventQueue != null) {
|
|
MotionEventInfo info = mDelayedEventQueue;
|
|
mDelayedEventQueue = info.mNext;
|
|
info.recycle();
|
|
}
|
|
}
|
|
|
|
private void transitionToDelegatingStateAndClear() {
|
|
transitionToState(STATE_DELEGATING);
|
|
sendDelayedMotionEvents();
|
|
clear();
|
|
}
|
|
|
|
private void onActionTap(MotionEvent up, int policyFlags) {
|
|
if (DEBUG_DETECTING) {
|
|
Slog.i(LOG_TAG, "onActionTap()");
|
|
}
|
|
if (!mMagnificationController.isMagnifying()) {
|
|
mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
|
|
up.getX(), up.getY(), true);
|
|
mViewport.setFrameShown(true, true);
|
|
} else {
|
|
mMagnificationController.reset(true);
|
|
mViewport.setFrameShown(false, true);
|
|
}
|
|
}
|
|
|
|
private void onActionTapAndHold(MotionEvent down, int policyFlags) {
|
|
if (DEBUG_DETECTING) {
|
|
Slog.i(LOG_TAG, "onActionTapAndHold()");
|
|
}
|
|
clear();
|
|
mTranslationEnabledBeforePan = mMagnificationController.isMagnifying();
|
|
mMagnificationController.setScaleAndMagnifiedRegionCenter(getPersistedScale(),
|
|
down.getX(), down.getY(), true);
|
|
mViewport.setFrameShown(true, true);
|
|
transitionToState(STATE_VIEWPORT_DRAGGING);
|
|
}
|
|
}
|
|
|
|
private void persistScale(final float scale) {
|
|
new AsyncTask<Void, Void, Void>() {
|
|
@Override
|
|
protected Void doInBackground(Void... params) {
|
|
Settings.Secure.putFloat(mContext.getContentResolver(),
|
|
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale);
|
|
return null;
|
|
}
|
|
}.execute();
|
|
}
|
|
|
|
private float getPersistedScale() {
|
|
return Settings.Secure.getFloat(mContext.getContentResolver(),
|
|
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
|
|
DEFAULT_MAGNIFICATION_SCALE);
|
|
}
|
|
|
|
private static final class MotionEventInfo {
|
|
|
|
private static final int MAX_POOL_SIZE = 10;
|
|
|
|
private static final Object sLock = new Object();
|
|
private static MotionEventInfo sPool;
|
|
private static int sPoolSize;
|
|
|
|
private MotionEventInfo mNext;
|
|
private boolean mInPool;
|
|
|
|
public MotionEvent mEvent;
|
|
public int mPolicyFlags;
|
|
|
|
public static MotionEventInfo obtain(MotionEvent event, int policyFlags) {
|
|
synchronized (sLock) {
|
|
MotionEventInfo info;
|
|
if (sPoolSize > 0) {
|
|
sPoolSize--;
|
|
info = sPool;
|
|
sPool = info.mNext;
|
|
info.mNext = null;
|
|
info.mInPool = false;
|
|
} else {
|
|
info = new MotionEventInfo();
|
|
}
|
|
info.initialize(event, policyFlags);
|
|
return info;
|
|
}
|
|
}
|
|
|
|
private void initialize(MotionEvent event, int policyFlags) {
|
|
mEvent = MotionEvent.obtain(event);
|
|
mPolicyFlags = policyFlags;
|
|
}
|
|
|
|
public void recycle() {
|
|
synchronized (sLock) {
|
|
if (mInPool) {
|
|
throw new IllegalStateException("Already recycled.");
|
|
}
|
|
clear();
|
|
if (sPoolSize < MAX_POOL_SIZE) {
|
|
sPoolSize++;
|
|
mNext = sPool;
|
|
sPool = this;
|
|
mInPool = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void clear() {
|
|
mEvent.recycle();
|
|
mEvent = null;
|
|
mPolicyFlags = 0;
|
|
}
|
|
}
|
|
|
|
private static final class DisplayContentObserver {
|
|
|
|
private static final int MESSAGE_SHOW_VIEWPORT_FRAME = 1;
|
|
private static final int MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS = 2;
|
|
private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 3;
|
|
private static final int MESSAGE_ON_WINDOW_TRANSITION = 4;
|
|
private static final int MESSAGE_ON_ROTATION_CHANGED = 5;
|
|
|
|
private final Handler mHandler = new MyHandler();
|
|
|
|
private final Rect mTempRect = new Rect();
|
|
|
|
private final IDisplayContentChangeListener mDisplayContentChangeListener;
|
|
|
|
private final Context mContext;
|
|
private final Viewport mViewport;
|
|
private final MagnificationController mMagnificationController;
|
|
private final IWindowManager mWindowManagerService;
|
|
private final DisplayProvider mDisplayProvider;
|
|
private final long mLongAnimationDuration;
|
|
private final float mWindowAnimationScale;
|
|
|
|
public DisplayContentObserver(Context context, Viewport viewport,
|
|
MagnificationController magnificationController,
|
|
IWindowManager windowManagerService, DisplayProvider displayProvider,
|
|
long longAnimationDuration, float windowAnimationScale) {
|
|
mContext = context;
|
|
mViewport = viewport;
|
|
mMagnificationController = magnificationController;
|
|
mWindowManagerService = windowManagerService;
|
|
mDisplayProvider = displayProvider;
|
|
mLongAnimationDuration = longAnimationDuration;
|
|
mWindowAnimationScale = windowAnimationScale;
|
|
|
|
mDisplayContentChangeListener = new IDisplayContentChangeListener.Stub() {
|
|
@Override
|
|
public void onWindowTransition(int displayId, int transition, WindowInfo info) {
|
|
mHandler.obtainMessage(MESSAGE_ON_WINDOW_TRANSITION, transition, 0,
|
|
WindowInfo.obtain(info)).sendToTarget();
|
|
}
|
|
|
|
@Override
|
|
public void onRectangleOnScreenRequested(int dsiplayId, Rect rectangle,
|
|
boolean immediate) {
|
|
SomeArgs args = SomeArgs.obtain();
|
|
args.argi1 = rectangle.left;
|
|
args.argi2 = rectangle.top;
|
|
args.argi3 = rectangle.right;
|
|
args.argi4 = rectangle.bottom;
|
|
mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, 0,
|
|
immediate ? 1 : 0, args).sendToTarget();
|
|
}
|
|
|
|
@Override
|
|
public void onRotationChanged(int rotation) throws RemoteException {
|
|
mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0)
|
|
.sendToTarget();
|
|
}
|
|
};
|
|
|
|
try {
|
|
mWindowManagerService.addDisplayContentChangeListener(
|
|
mDisplayProvider.getDisplay().getDisplayId(),
|
|
mDisplayContentChangeListener);
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
public void destroy() {
|
|
try {
|
|
mWindowManagerService.removeDisplayContentChangeListener(
|
|
mDisplayProvider.getDisplay().getDisplayId(),
|
|
mDisplayContentChangeListener);
|
|
} catch (RemoteException re) {
|
|
/* ignore*/
|
|
}
|
|
}
|
|
|
|
private void handleOnRotationChanged(int rotation) {
|
|
if (DEBUG_ROTATION) {
|
|
Slog.i(LOG_TAG, "Rotation: " + rotationToString(rotation));
|
|
}
|
|
resetMagnificationIfNeeded();
|
|
mViewport.setFrameShown(false, false);
|
|
mViewport.rotationChanged();
|
|
mViewport.recomputeBounds(false);
|
|
if (mMagnificationController.isMagnifying()) {
|
|
final long delay = (long) (2 * mLongAnimationDuration * mWindowAnimationScale);
|
|
Message message = mHandler.obtainMessage(MESSAGE_SHOW_VIEWPORT_FRAME);
|
|
mHandler.sendMessageDelayed(message, delay);
|
|
}
|
|
}
|
|
|
|
private void handleOnWindowTransition(int transition, WindowInfo info) {
|
|
if (DEBUG_WINDOW_TRANSITIONS) {
|
|
Slog.i(LOG_TAG, "Window transitioning: "
|
|
+ windowTransitionToString(transition));
|
|
}
|
|
try {
|
|
final boolean magnifying = mMagnificationController.isMagnifying();
|
|
if (magnifying) {
|
|
switch (transition) {
|
|
case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN:
|
|
case WindowManagerPolicy.TRANSIT_TASK_OPEN:
|
|
case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT:
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN:
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE:
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
|
|
resetMagnificationIfNeeded();
|
|
}
|
|
}
|
|
}
|
|
if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
|
|
|| info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
|
|
|| info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
|
|
switch (transition) {
|
|
case WindowManagerPolicy.TRANSIT_ENTER:
|
|
case WindowManagerPolicy.TRANSIT_SHOW:
|
|
case WindowManagerPolicy.TRANSIT_EXIT:
|
|
case WindowManagerPolicy.TRANSIT_HIDE: {
|
|
mViewport.recomputeBounds(mMagnificationController.isMagnifying());
|
|
} break;
|
|
}
|
|
} else {
|
|
switch (transition) {
|
|
case WindowManagerPolicy.TRANSIT_ENTER:
|
|
case WindowManagerPolicy.TRANSIT_SHOW: {
|
|
if (!magnifying || !screenMagnificationAutoUpdateEnabled(mContext)) {
|
|
break;
|
|
}
|
|
final int type = info.type;
|
|
switch (type) {
|
|
// TODO: Are these all the windows we want to make
|
|
// visible when they appear on the screen?
|
|
// Do we need to take some of them out?
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
|
|
case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
|
|
case WindowManager.LayoutParams.TYPE_PHONE:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
|
|
case WindowManager.LayoutParams.TYPE_TOAST:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
|
|
case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
|
|
case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
|
|
case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
|
|
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
|
|
Rect magnifiedRegionBounds = mMagnificationController
|
|
.getMagnifiedRegionBounds();
|
|
Rect touchableRegion = info.touchableRegion;
|
|
if (!magnifiedRegionBounds.intersect(touchableRegion)) {
|
|
ensureRectangleInMagnifiedRegionBounds(
|
|
magnifiedRegionBounds, touchableRegion);
|
|
}
|
|
} break;
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
if (info != null) {
|
|
info.recycle();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void handleOnRectangleOnScreenRequested(Rect rectangle, boolean immediate) {
|
|
if (!mMagnificationController.isMagnifying()) {
|
|
return;
|
|
}
|
|
Rect magnifiedRegionBounds = mMagnificationController.getMagnifiedRegionBounds();
|
|
if (magnifiedRegionBounds.contains(rectangle)) {
|
|
return;
|
|
}
|
|
ensureRectangleInMagnifiedRegionBounds(magnifiedRegionBounds, rectangle);
|
|
}
|
|
|
|
private void ensureRectangleInMagnifiedRegionBounds(Rect magnifiedRegionBounds,
|
|
Rect rectangle) {
|
|
if (!Rect.intersects(rectangle, mViewport.getBounds())) {
|
|
return;
|
|
}
|
|
final float scrollX;
|
|
final float scrollY;
|
|
if (rectangle.width() > magnifiedRegionBounds.width()) {
|
|
scrollX = rectangle.left - magnifiedRegionBounds.left;
|
|
} else if (rectangle.left < magnifiedRegionBounds.left) {
|
|
scrollX = rectangle.left - magnifiedRegionBounds.left;
|
|
} else if (rectangle.right > magnifiedRegionBounds.right) {
|
|
scrollX = rectangle.right - magnifiedRegionBounds.right;
|
|
} else {
|
|
scrollX = 0;
|
|
}
|
|
if (rectangle.height() > magnifiedRegionBounds.height()) {
|
|
scrollY = rectangle.top - magnifiedRegionBounds.top;
|
|
} else if (rectangle.top < magnifiedRegionBounds.top) {
|
|
scrollY = rectangle.top - magnifiedRegionBounds.top;
|
|
} else if (rectangle.bottom > magnifiedRegionBounds.bottom) {
|
|
scrollY = rectangle.bottom - magnifiedRegionBounds.bottom;
|
|
} else {
|
|
scrollY = 0;
|
|
}
|
|
final float viewportCenterX = mMagnificationController.getMagnifiedRegionCenterX()
|
|
+ scrollX;
|
|
final float viewportCenterY = mMagnificationController.getMagnifiedRegionCenterY()
|
|
+ scrollY;
|
|
mMagnificationController.setMagnifiedRegionCenter(viewportCenterX, viewportCenterY,
|
|
true);
|
|
}
|
|
|
|
private void resetMagnificationIfNeeded() {
|
|
if (mMagnificationController.isMagnifying()
|
|
&& screenMagnificationAutoUpdateEnabled(mContext)) {
|
|
mMagnificationController.reset(true);
|
|
mViewport.setFrameShown(false, true);
|
|
}
|
|
}
|
|
|
|
private boolean screenMagnificationAutoUpdateEnabled(Context context) {
|
|
return (Settings.Secure.getInt(context.getContentResolver(),
|
|
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
|
|
DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
|
|
}
|
|
|
|
private String windowTransitionToString(int transition) {
|
|
switch (transition) {
|
|
case WindowManagerPolicy.TRANSIT_UNSET: {
|
|
return "TRANSIT_UNSET";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_NONE: {
|
|
return "TRANSIT_NONE";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_ENTER: {
|
|
return "TRANSIT_ENTER";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_EXIT: {
|
|
return "TRANSIT_EXIT";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_SHOW: {
|
|
return "TRANSIT_SHOW";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_EXIT_MASK: {
|
|
return "TRANSIT_EXIT_MASK";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_PREVIEW_DONE: {
|
|
return "TRANSIT_PREVIEW_DONE";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN: {
|
|
return "TRANSIT_ACTIVITY_OPEN";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE: {
|
|
return "TRANSIT_ACTIVITY_CLOSE";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_TASK_OPEN: {
|
|
return "TRANSIT_TASK_OPEN";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_TASK_CLOSE: {
|
|
return "TRANSIT_TASK_CLOSE";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_TASK_TO_FRONT: {
|
|
return "TRANSIT_TASK_TO_FRONT";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_TASK_TO_BACK: {
|
|
return "TRANSIT_TASK_TO_BACK";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_CLOSE: {
|
|
return "TRANSIT_WALLPAPER_CLOSE";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_OPEN: {
|
|
return "TRANSIT_WALLPAPER_OPEN";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_OPEN: {
|
|
return "TRANSIT_WALLPAPER_INTRA_OPEN";
|
|
}
|
|
case WindowManagerPolicy.TRANSIT_WALLPAPER_INTRA_CLOSE: {
|
|
return "TRANSIT_WALLPAPER_INTRA_CLOSE";
|
|
}
|
|
default: {
|
|
return "<UNKNOWN>";
|
|
}
|
|
}
|
|
}
|
|
|
|
private String rotationToString(int rotation) {
|
|
switch (rotation) {
|
|
case Surface.ROTATION_0: {
|
|
return "ROTATION_0";
|
|
}
|
|
case Surface.ROTATION_90: {
|
|
return "ROATATION_90";
|
|
}
|
|
case Surface.ROTATION_180: {
|
|
return "ROATATION_180";
|
|
}
|
|
case Surface.ROTATION_270: {
|
|
return "ROATATION_270";
|
|
}
|
|
default: {
|
|
throw new IllegalArgumentException("Invalid rotation: "
|
|
+ rotation);
|
|
}
|
|
}
|
|
}
|
|
|
|
private final class MyHandler extends Handler {
|
|
@Override
|
|
public void handleMessage(Message message) {
|
|
final int action = message.what;
|
|
switch (action) {
|
|
case MESSAGE_SHOW_VIEWPORT_FRAME: {
|
|
mViewport.setFrameShown(true, true);
|
|
} break;
|
|
case MESSAGE_RECOMPUTE_VIEWPORT_BOUNDS: {
|
|
final boolean animate = message.arg1 == 1;
|
|
mViewport.recomputeBounds(animate);
|
|
} break;
|
|
case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
|
|
SomeArgs args = (SomeArgs) message.obj;
|
|
try {
|
|
mTempRect.set(args.argi1, args.argi2, args.argi3, args.argi4);
|
|
final boolean immediate = (message.arg1 == 1);
|
|
handleOnRectangleOnScreenRequested(mTempRect, immediate);
|
|
} finally {
|
|
args.recycle();
|
|
}
|
|
} break;
|
|
case MESSAGE_ON_WINDOW_TRANSITION: {
|
|
final int transition = message.arg1;
|
|
WindowInfo info = (WindowInfo) message.obj;
|
|
handleOnWindowTransition(transition, info);
|
|
} break;
|
|
case MESSAGE_ON_ROTATION_CHANGED: {
|
|
final int rotation = message.arg1;
|
|
handleOnRotationChanged(rotation);
|
|
} break;
|
|
default: {
|
|
throw new IllegalArgumentException("Unknown message: " + action);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private final class MagnificationController {
|
|
|
|
private static final String PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION =
|
|
"accessibilityTransformation";
|
|
|
|
private final MagnificationSpec mSentMagnificationSpec = new MagnificationSpec();
|
|
|
|
private final MagnificationSpec mCurrentMagnificationSpec = new MagnificationSpec();
|
|
|
|
private final Rect mTempRect = new Rect();
|
|
|
|
private final ValueAnimator mTransformationAnimator;
|
|
|
|
public MagnificationController(int animationDuration) {
|
|
Property<MagnificationController, MagnificationSpec> property =
|
|
Property.of(MagnificationController.class, MagnificationSpec.class,
|
|
PROPERTY_NAME_ACCESSIBILITY_TRANSFORMATION);
|
|
TypeEvaluator<MagnificationSpec> evaluator = new TypeEvaluator<MagnificationSpec>() {
|
|
private final MagnificationSpec mTempTransformationSpec = new MagnificationSpec();
|
|
@Override
|
|
public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
|
|
MagnificationSpec toSpec) {
|
|
MagnificationSpec result = mTempTransformationSpec;
|
|
result.mScale = fromSpec.mScale
|
|
+ (toSpec.mScale - fromSpec.mScale) * fraction;
|
|
result.mMagnifiedRegionCenterX = fromSpec.mMagnifiedRegionCenterX
|
|
+ (toSpec.mMagnifiedRegionCenterX - fromSpec.mMagnifiedRegionCenterX)
|
|
* fraction;
|
|
result.mMagnifiedRegionCenterY = fromSpec.mMagnifiedRegionCenterY
|
|
+ (toSpec.mMagnifiedRegionCenterY - fromSpec.mMagnifiedRegionCenterY)
|
|
* fraction;
|
|
result.mScaledOffsetX = fromSpec.mScaledOffsetX
|
|
+ (toSpec.mScaledOffsetX - fromSpec.mScaledOffsetX)
|
|
* fraction;
|
|
result.mScaledOffsetY = fromSpec.mScaledOffsetY
|
|
+ (toSpec.mScaledOffsetY - fromSpec.mScaledOffsetY)
|
|
* fraction;
|
|
return result;
|
|
}
|
|
};
|
|
mTransformationAnimator = ObjectAnimator.ofObject(this, property,
|
|
evaluator, mSentMagnificationSpec, mCurrentMagnificationSpec);
|
|
mTransformationAnimator.setDuration((long) (animationDuration));
|
|
mTransformationAnimator.setInterpolator(mInterpolator);
|
|
}
|
|
|
|
public boolean isMagnifying() {
|
|
return mCurrentMagnificationSpec.mScale > 1.0f;
|
|
}
|
|
|
|
public void reset(boolean animate) {
|
|
if (mTransformationAnimator.isRunning()) {
|
|
mTransformationAnimator.cancel();
|
|
}
|
|
mCurrentMagnificationSpec.reset();
|
|
if (animate) {
|
|
animateAccessibilityTranformation(mSentMagnificationSpec,
|
|
mCurrentMagnificationSpec);
|
|
} else {
|
|
setAccessibilityTransformation(mCurrentMagnificationSpec);
|
|
}
|
|
}
|
|
|
|
public Rect getMagnifiedRegionBounds() {
|
|
mTempRect.set(mViewport.getBounds());
|
|
mTempRect.offset((int) -mCurrentMagnificationSpec.mScaledOffsetX,
|
|
(int) -mCurrentMagnificationSpec.mScaledOffsetY);
|
|
mTempRect.scale(1.0f / mCurrentMagnificationSpec.mScale);
|
|
return mTempRect;
|
|
}
|
|
|
|
public float getScale() {
|
|
return mCurrentMagnificationSpec.mScale;
|
|
}
|
|
|
|
public float getMagnifiedRegionCenterX() {
|
|
return mCurrentMagnificationSpec.mMagnifiedRegionCenterX;
|
|
}
|
|
|
|
public float getMagnifiedRegionCenterY() {
|
|
return mCurrentMagnificationSpec.mMagnifiedRegionCenterY;
|
|
}
|
|
|
|
public float getScaledOffsetX() {
|
|
return mCurrentMagnificationSpec.mScaledOffsetX;
|
|
}
|
|
|
|
public float getScaledOffsetY() {
|
|
return mCurrentMagnificationSpec.mScaledOffsetY;
|
|
}
|
|
|
|
public void setScale(float scale, float pivotX, float pivotY, boolean animate) {
|
|
MagnificationSpec spec = mCurrentMagnificationSpec;
|
|
final float oldScale = spec.mScale;
|
|
final float oldCenterX = spec.mMagnifiedRegionCenterX;
|
|
final float oldCenterY = spec.mMagnifiedRegionCenterY;
|
|
final float normPivotX = (-spec.mScaledOffsetX + pivotX) / oldScale;
|
|
final float normPivotY = (-spec.mScaledOffsetY + pivotY) / oldScale;
|
|
final float offsetX = (oldCenterX - normPivotX) * (oldScale / scale);
|
|
final float offsetY = (oldCenterY - normPivotY) * (oldScale / scale);
|
|
final float centerX = normPivotX + offsetX;
|
|
final float centerY = normPivotY + offsetY;
|
|
setScaleAndMagnifiedRegionCenter(scale, centerX, centerY, animate);
|
|
}
|
|
|
|
public void setMagnifiedRegionCenter(float centerX, float centerY, boolean animate) {
|
|
setScaleAndMagnifiedRegionCenter(mCurrentMagnificationSpec.mScale, centerX, centerY,
|
|
animate);
|
|
}
|
|
|
|
public void setScaleAndMagnifiedRegionCenter(float scale, float centerX, float centerY,
|
|
boolean animate) {
|
|
if (Float.compare(mCurrentMagnificationSpec.mScale, scale) == 0
|
|
&& Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterX,
|
|
centerX) == 0
|
|
&& Float.compare(mCurrentMagnificationSpec.mMagnifiedRegionCenterY,
|
|
centerY) == 0) {
|
|
return;
|
|
}
|
|
if (mTransformationAnimator.isRunning()) {
|
|
mTransformationAnimator.cancel();
|
|
}
|
|
if (DEBUG_MAGNIFICATION_CONTROLLER) {
|
|
Slog.i(LOG_TAG, "scale: " + scale + " centerX: " + centerX
|
|
+ " centerY: " + centerY);
|
|
}
|
|
mCurrentMagnificationSpec.initialize(scale, centerX, centerY);
|
|
if (animate) {
|
|
animateAccessibilityTranformation(mSentMagnificationSpec,
|
|
mCurrentMagnificationSpec);
|
|
} else {
|
|
setAccessibilityTransformation(mCurrentMagnificationSpec);
|
|
}
|
|
}
|
|
|
|
private void animateAccessibilityTranformation(MagnificationSpec fromSpec,
|
|
MagnificationSpec toSpec) {
|
|
mTransformationAnimator.setObjectValues(fromSpec, toSpec);
|
|
mTransformationAnimator.start();
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
// Called from an animator.
|
|
public MagnificationSpec getAccessibilityTransformation() {
|
|
return mSentMagnificationSpec;
|
|
}
|
|
|
|
public void setAccessibilityTransformation(MagnificationSpec transformation) {
|
|
if (DEBUG_TRANSFORMATION) {
|
|
Slog.i(LOG_TAG, "Transformation scale: " + transformation.mScale
|
|
+ " offsetX: " + transformation.mScaledOffsetX
|
|
+ " offsetY: " + transformation.mScaledOffsetY);
|
|
}
|
|
try {
|
|
mSentMagnificationSpec.updateFrom(transformation);
|
|
mWindowManagerService.magnifyDisplay(mDisplayProvider.getDisplay().getDisplayId(),
|
|
transformation.mScale, transformation.mScaledOffsetX,
|
|
transformation.mScaledOffsetY);
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
}
|
|
}
|
|
|
|
private class MagnificationSpec {
|
|
|
|
private static final float DEFAULT_SCALE = 1.0f;
|
|
|
|
public float mScale = DEFAULT_SCALE;
|
|
|
|
public float mMagnifiedRegionCenterX;
|
|
|
|
public float mMagnifiedRegionCenterY;
|
|
|
|
public float mScaledOffsetX;
|
|
|
|
public float mScaledOffsetY;
|
|
|
|
public void initialize(float scale, float magnifiedRegionCenterX,
|
|
float magnifiedRegionCenterY) {
|
|
mScale = scale;
|
|
|
|
final int viewportWidth = mViewport.getBounds().width();
|
|
final int viewportHeight = mViewport.getBounds().height();
|
|
final float minMagnifiedRegionCenterX = (viewportWidth / 2) / scale;
|
|
final float minMagnifiedRegionCenterY = (viewportHeight / 2) / scale;
|
|
final float maxMagnifiedRegionCenterX = viewportWidth - minMagnifiedRegionCenterX;
|
|
final float maxMagnifiedRegionCenterY = viewportHeight - minMagnifiedRegionCenterY;
|
|
|
|
mMagnifiedRegionCenterX = Math.min(Math.max(magnifiedRegionCenterX,
|
|
minMagnifiedRegionCenterX), maxMagnifiedRegionCenterX);
|
|
mMagnifiedRegionCenterY = Math.min(Math.max(magnifiedRegionCenterY,
|
|
minMagnifiedRegionCenterY), maxMagnifiedRegionCenterY);
|
|
|
|
mScaledOffsetX = -(mMagnifiedRegionCenterX * scale - viewportWidth / 2);
|
|
mScaledOffsetY = -(mMagnifiedRegionCenterY * scale - viewportHeight / 2);
|
|
}
|
|
|
|
public void updateFrom(MagnificationSpec other) {
|
|
mScale = other.mScale;
|
|
mMagnifiedRegionCenterX = other.mMagnifiedRegionCenterX;
|
|
mMagnifiedRegionCenterY = other.mMagnifiedRegionCenterY;
|
|
mScaledOffsetX = other.mScaledOffsetX;
|
|
mScaledOffsetY = other.mScaledOffsetY;
|
|
}
|
|
|
|
public void reset() {
|
|
mScale = DEFAULT_SCALE;
|
|
mMagnifiedRegionCenterX = 0;
|
|
mMagnifiedRegionCenterY = 0;
|
|
mScaledOffsetX = 0;
|
|
mScaledOffsetY = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class Viewport {
|
|
|
|
private static final String PROPERTY_NAME_ALPHA = "alpha";
|
|
|
|
private static final String PROPERTY_NAME_BOUNDS = "bounds";
|
|
|
|
private static final int MIN_ALPHA = 0;
|
|
|
|
private static final int MAX_ALPHA = 255;
|
|
|
|
private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
|
|
|
|
private final Rect mTempRect = new Rect();
|
|
|
|
private final IWindowManager mWindowManagerService;
|
|
private final DisplayProvider mDisplayProvider;
|
|
|
|
private final ViewportWindow mViewportFrame;
|
|
|
|
private final ValueAnimator mResizeFrameAnimator;
|
|
|
|
private final ValueAnimator mShowHideFrameAnimator;
|
|
|
|
public Viewport(Context context, WindowManager windowManager,
|
|
IWindowManager windowManagerService, DisplayProvider displayInfoProvider,
|
|
Interpolator animationInterpolator, long animationDuration) {
|
|
mWindowManagerService = windowManagerService;
|
|
mDisplayProvider = displayInfoProvider;
|
|
mViewportFrame = new ViewportWindow(context, windowManager, displayInfoProvider);
|
|
|
|
mShowHideFrameAnimator = ObjectAnimator.ofInt(mViewportFrame, PROPERTY_NAME_ALPHA,
|
|
MIN_ALPHA, MAX_ALPHA);
|
|
mShowHideFrameAnimator.setInterpolator(animationInterpolator);
|
|
mShowHideFrameAnimator.setDuration(animationDuration);
|
|
mShowHideFrameAnimator.addListener(new AnimatorListener() {
|
|
@Override
|
|
public void onAnimationEnd(Animator animation) {
|
|
if (mShowHideFrameAnimator.getAnimatedValue().equals(MIN_ALPHA)) {
|
|
mViewportFrame.hide();
|
|
}
|
|
}
|
|
@Override
|
|
public void onAnimationStart(Animator animation) {
|
|
/* do nothing - stub */
|
|
}
|
|
@Override
|
|
public void onAnimationCancel(Animator animation) {
|
|
/* do nothing - stub */
|
|
}
|
|
@Override
|
|
public void onAnimationRepeat(Animator animation) {
|
|
/* do nothing - stub */
|
|
}
|
|
});
|
|
|
|
Property<ViewportWindow, Rect> property = Property.of(ViewportWindow.class,
|
|
Rect.class, PROPERTY_NAME_BOUNDS);
|
|
TypeEvaluator<Rect> evaluator = new TypeEvaluator<Rect>() {
|
|
private final Rect mReusableResultRect = new Rect();
|
|
@Override
|
|
public Rect evaluate(float fraction, Rect fromFrame, Rect toFrame) {
|
|
Rect result = mReusableResultRect;
|
|
result.left = (int) (fromFrame.left
|
|
+ (toFrame.left - fromFrame.left) * fraction);
|
|
result.top = (int) (fromFrame.top
|
|
+ (toFrame.top - fromFrame.top) * fraction);
|
|
result.right = (int) (fromFrame.right
|
|
+ (toFrame.right - fromFrame.right) * fraction);
|
|
result.bottom = (int) (fromFrame.bottom
|
|
+ (toFrame.bottom - fromFrame.bottom) * fraction);
|
|
return result;
|
|
}
|
|
};
|
|
mResizeFrameAnimator = ObjectAnimator.ofObject(mViewportFrame, property,
|
|
evaluator, mViewportFrame.mBounds, mViewportFrame.mBounds);
|
|
mResizeFrameAnimator.setDuration((long) (animationDuration));
|
|
mResizeFrameAnimator.setInterpolator(animationInterpolator);
|
|
|
|
recomputeBounds(false);
|
|
}
|
|
|
|
public void recomputeBounds(boolean animate) {
|
|
Rect frame = mTempRect;
|
|
frame.set(0, 0, mDisplayProvider.getDisplayInfo().logicalWidth,
|
|
mDisplayProvider.getDisplayInfo().logicalHeight);
|
|
ArrayList<WindowInfo> infos = mTempWindowInfoList;
|
|
infos.clear();
|
|
try {
|
|
mWindowManagerService.getVisibleWindowsForDisplay(
|
|
mDisplayProvider.getDisplay().getDisplayId(), infos);
|
|
final int windowCount = infos.size();
|
|
for (int i = 0; i < windowCount; i++) {
|
|
WindowInfo info = infos.get(i);
|
|
if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
|
|
|| info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
|
|
|| info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
|
|
subtract(frame, info.touchableRegion);
|
|
}
|
|
info.recycle();
|
|
}
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
} finally {
|
|
infos.clear();
|
|
}
|
|
resize(frame, animate);
|
|
}
|
|
|
|
public void rotationChanged() {
|
|
mViewportFrame.rotationChanged();
|
|
}
|
|
|
|
public Rect getBounds() {
|
|
return mViewportFrame.getBounds();
|
|
}
|
|
|
|
public void setFrameShown(boolean shown, boolean animate) {
|
|
if (mViewportFrame.isShown() == shown) {
|
|
return;
|
|
}
|
|
if (animate) {
|
|
if (mShowHideFrameAnimator.isRunning()) {
|
|
mShowHideFrameAnimator.reverse();
|
|
} else {
|
|
if (shown) {
|
|
mViewportFrame.show();
|
|
mShowHideFrameAnimator.start();
|
|
} else {
|
|
mShowHideFrameAnimator.reverse();
|
|
}
|
|
}
|
|
} else {
|
|
mShowHideFrameAnimator.cancel();
|
|
if (shown) {
|
|
mViewportFrame.show();
|
|
} else {
|
|
mViewportFrame.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void resize(Rect bounds, boolean animate) {
|
|
if (mViewportFrame.getBounds().equals(bounds)) {
|
|
return;
|
|
}
|
|
if (animate) {
|
|
if (mResizeFrameAnimator.isRunning()) {
|
|
mResizeFrameAnimator.cancel();
|
|
}
|
|
mResizeFrameAnimator.setObjectValues(mViewportFrame.mBounds, bounds);
|
|
mResizeFrameAnimator.start();
|
|
} else {
|
|
mViewportFrame.setBounds(bounds);
|
|
}
|
|
}
|
|
|
|
private boolean subtract(Rect lhs, Rect rhs) {
|
|
if (lhs.right < rhs.left || lhs.left > rhs.right
|
|
|| lhs.bottom < rhs.top || lhs.top > rhs.bottom) {
|
|
return false;
|
|
}
|
|
if (lhs.left < rhs.left) {
|
|
lhs.right = rhs.left;
|
|
}
|
|
if (lhs.top < rhs.top) {
|
|
lhs.bottom = rhs.top;
|
|
}
|
|
if (lhs.right > rhs.right) {
|
|
lhs.left = rhs.right;
|
|
}
|
|
if (lhs.bottom > rhs.bottom) {
|
|
lhs.top = rhs.bottom;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static final class ViewportWindow {
|
|
private static final String WINDOW_TITLE = "Magnification Overlay";
|
|
|
|
private final WindowManager mWindowManager;
|
|
private final DisplayProvider mDisplayProvider;
|
|
|
|
private final ContentView mWindowContent;
|
|
private final WindowManager.LayoutParams mWindowParams;
|
|
|
|
private final Rect mBounds = new Rect();
|
|
private boolean mShown;
|
|
private int mAlpha;
|
|
|
|
public ViewportWindow(Context context, WindowManager windowManager,
|
|
DisplayProvider displayProvider) {
|
|
mWindowManager = windowManager;
|
|
mDisplayProvider = displayProvider;
|
|
|
|
ViewGroup.LayoutParams contentParams = new ViewGroup.LayoutParams(
|
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
|
|
mWindowContent = new ContentView(context);
|
|
mWindowContent.setLayoutParams(contentParams);
|
|
mWindowContent.setBackgroundColor(R.color.transparent);
|
|
|
|
mWindowParams = new WindowManager.LayoutParams(
|
|
WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY);
|
|
mWindowParams.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
|
|
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
|
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
|
|
mWindowParams.setTitle(WINDOW_TITLE);
|
|
mWindowParams.gravity = Gravity.CENTER;
|
|
mWindowParams.width = displayProvider.getDisplayInfo().logicalWidth;
|
|
mWindowParams.height = displayProvider.getDisplayInfo().logicalHeight;
|
|
mWindowParams.format = PixelFormat.TRANSLUCENT;
|
|
}
|
|
|
|
public boolean isShown() {
|
|
return mShown;
|
|
}
|
|
|
|
public void show() {
|
|
if (mShown) {
|
|
return;
|
|
}
|
|
mShown = true;
|
|
mWindowManager.addView(mWindowContent, mWindowParams);
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportWindow shown.");
|
|
}
|
|
}
|
|
|
|
public void hide() {
|
|
if (!mShown) {
|
|
return;
|
|
}
|
|
mShown = false;
|
|
mWindowManager.removeView(mWindowContent);
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportWindow hidden.");
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
// Called reflectively from an animator.
|
|
public int getAlpha() {
|
|
return mAlpha;
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
// Called reflectively from an animator.
|
|
public void setAlpha(int alpha) {
|
|
if (mAlpha == alpha) {
|
|
return;
|
|
}
|
|
mAlpha = alpha;
|
|
if (mShown) {
|
|
mWindowContent.invalidate();
|
|
}
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportFrame set alpha: " + alpha);
|
|
}
|
|
}
|
|
|
|
public Rect getBounds() {
|
|
return mBounds;
|
|
}
|
|
|
|
public void rotationChanged() {
|
|
mWindowParams.width = mDisplayProvider.getDisplayInfo().logicalWidth;
|
|
mWindowParams.height = mDisplayProvider.getDisplayInfo().logicalHeight;
|
|
if (mShown) {
|
|
mWindowManager.updateViewLayout(mWindowContent, mWindowParams);
|
|
}
|
|
}
|
|
|
|
public void setBounds(Rect bounds) {
|
|
if (mBounds.equals(bounds)) {
|
|
return;
|
|
}
|
|
mBounds.set(bounds);
|
|
if (mShown) {
|
|
mWindowContent.invalidate();
|
|
}
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportFrame set bounds: " + bounds);
|
|
}
|
|
}
|
|
|
|
private final class ContentView extends View {
|
|
private final Drawable mHighlightFrame;
|
|
|
|
public ContentView(Context context) {
|
|
super(context);
|
|
mHighlightFrame = context.getResources().getDrawable(
|
|
R.drawable.magnified_region_frame);
|
|
}
|
|
|
|
@Override
|
|
public void onDraw(Canvas canvas) {
|
|
canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
|
|
mHighlightFrame.setBounds(mBounds);
|
|
mHighlightFrame.setAlpha(mAlpha);
|
|
mHighlightFrame.draw(canvas);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static class DisplayProvider implements DisplayListener {
|
|
private final WindowManager mWindowManager;
|
|
private final DisplayManager mDisplayManager;
|
|
private final Display mDefaultDisplay;
|
|
private final DisplayInfo mDefaultDisplayInfo = new DisplayInfo();
|
|
|
|
public DisplayProvider(Context context, WindowManager windowManager) {
|
|
mWindowManager = windowManager;
|
|
mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
|
|
mDefaultDisplay = mWindowManager.getDefaultDisplay();
|
|
mDisplayManager.registerDisplayListener(this, null);
|
|
updateDisplayInfo();
|
|
}
|
|
|
|
public DisplayInfo getDisplayInfo() {
|
|
return mDefaultDisplayInfo;
|
|
}
|
|
|
|
public Display getDisplay() {
|
|
return mDefaultDisplay;
|
|
}
|
|
|
|
private void updateDisplayInfo() {
|
|
if (!mDefaultDisplay.getDisplayInfo(mDefaultDisplayInfo)) {
|
|
Slog.e(LOG_TAG, "Default display is not valid.");
|
|
}
|
|
}
|
|
|
|
public void destroy() {
|
|
mDisplayManager.unregisterDisplayListener(this);
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayAdded(int displayId) {
|
|
/* do noting */
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayRemoved(int displayId) {
|
|
// Having no default display
|
|
}
|
|
|
|
@Override
|
|
public void onDisplayChanged(int displayId) {
|
|
updateDisplayInfo();
|
|
}
|
|
}
|
|
}
|