ScreenMagnifierpublic final class ScreenMagnifier extends Object implements WindowManagerInternal.MagnificationCallbacks, EventStreamTransformationThis 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 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 two 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. |
Fields Summary |
---|
private static final String | LOG_TAG | private static final boolean | DEBUG_STATE_TRANSITIONS | private static final boolean | DEBUG_DETECTING | private static final boolean | DEBUG_SET_MAGNIFICATION_SPEC | private static final boolean | DEBUG_PANNING | private static final boolean | DEBUG_SCALING | private static final boolean | DEBUG_MAGNIFICATION_CONTROLLER | private static final int | STATE_DELEGATING | private static final int | STATE_DETECTING | private static final int | STATE_VIEWPORT_DRAGGING | private static final int | STATE_MAGNIFIED_INTERACTION | private static final float | DEFAULT_MAGNIFICATION_SCALE | private static final int | MULTI_TAP_TIME_SLOP_ADJUSTMENT | private static final int | MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED | private static final int | MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED | private static final int | MESSAGE_ON_USER_CONTEXT_CHANGED | private static final int | MESSAGE_ON_ROTATION_CHANGED | private static final int | DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE | private static final int | MY_PID | private final android.graphics.Rect | mTempRect | private final android.graphics.Rect | mTempRect1 | private final android.content.Context | mContext | private final android.view.WindowManagerInternal | mWindowManager | private final MagnificationController | mMagnificationController | private final ScreenStateObserver | mScreenStateObserver | private final DetectingStateHandler | mDetectingStateHandler | private final MagnifiedContentInteractonStateHandler | mMagnifiedContentInteractonStateHandler | private final StateViewportDraggingHandler | mStateViewportDraggingHandler | private final AccessibilityManagerService | mAms | private final int | mTapTimeSlop | private final int | mMultiTapTimeSlop | private final int | mTapDistanceSlop | private final int | mMultiTapDistanceSlop | private final long | mLongAnimationDuration | private final android.graphics.Region | mMagnifiedBounds | private EventStreamTransformation | mNext | private int | mCurrentState | private int | mPreviousState | private boolean | mTranslationEnabledBeforePan | private android.view.MotionEvent.PointerCoords[] | mTempPointerCoords | private android.view.MotionEvent.PointerProperties[] | mTempPointerProperties | private long | mDelegatingStateDownTime | private boolean | mUpdateMagnificationSpecOnNextBoundsChange | private final android.os.Handler | mHandler |
Constructors Summary |
---|
public ScreenMagnifier(android.content.Context context, int displayId, AccessibilityManagerService service)
mContext = context;
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
mAms = service;
mLongAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
mTapDistanceSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mMultiTapDistanceSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
mDetectingStateHandler = new DetectingStateHandler();
mStateViewportDraggingHandler = new StateViewportDraggingHandler();
mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
context);
mMagnificationController = new MagnificationController(mLongAnimationDuration);
mScreenStateObserver = new ScreenStateObserver(context, mMagnificationController);
mWindowManager.setMagnificationCallbacks(this);
transitionToState(STATE_DETECTING);
|
Methods Summary |
---|
public void | clear()
mCurrentState = STATE_DETECTING;
mDetectingStateHandler.clear();
mStateViewportDraggingHandler.clear();
mMagnifiedContentInteractonStateHandler.clear();
if (mNext != null) {
mNext.clear();
}
| private void | getMagnifiedFrameInContentCoords(android.graphics.Rect rect)
MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
mMagnifiedBounds.getBounds(rect);
rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
rect.scale(1.0f / spec.scale);
| private float | getPersistedScale()
return Settings.Secure.getFloat(mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
DEFAULT_MAGNIFICATION_SCALE);
| private android.view.MotionEvent.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 android.view.MotionEvent.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 | handleMotionEventStateDelegating(android.view.MotionEvent event, android.view.MotionEvent rawEvent, int policyFlags)
switch (event.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
mDelegatingStateDownTime = event.getDownTime();
} break;
case MotionEvent.ACTION_UP: {
if (mDetectingStateHandler.mDelayedEventQueue == null) {
transitionToState(STATE_DETECTING);
}
} break;
}
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()
&& mMagnifiedBounds.contains((int) eventX, (int) eventY)) {
final float scale = mMagnificationController.getScale();
final float scaledOffsetX = mMagnificationController.getOffsetX();
final float scaledOffsetY = mMagnificationController.getOffsetY();
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());
}
// We cache some events to see if the user wants to trigger magnification.
// If no magnification is triggered we inject these events with adjusted
// time and down time to prevent subsequent transformations being confused
// by stale events. After the cached events, which always have a down, are
// injected we need to also update the down time of all subsequent non cached
// events. All delegated events cached and non-cached are delivered here.
event.setDownTime(mDelegatingStateDownTime);
mNext.onMotionEvent(event, rawEvent, policyFlags);
}
| private void | handleOnMagnifiedBoundsChanged(android.graphics.Region bounds)
// If there was a rotation we have to update the center of the magnified
// region since the old offset X/Y may be out of its acceptable range for
// the new display width and height.
if (mUpdateMagnificationSpecOnNextBoundsChange) {
mUpdateMagnificationSpecOnNextBoundsChange = false;
MagnificationSpec spec = mMagnificationController.getMagnificationSpec();
Rect magnifiedFrame = mTempRect;
mMagnifiedBounds.getBounds(magnifiedFrame);
final float scale = spec.scale;
final float centerX = (-spec.offsetX + magnifiedFrame.width() / 2) / scale;
final float centerY = (-spec.offsetY + magnifiedFrame.height() / 2) / scale;
mMagnificationController.setScaleAndMagnifiedRegionCenter(scale, centerX,
centerY, false);
}
mMagnifiedBounds.set(bounds);
mAms.onMagnificationStateChanged();
| private void | handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom)
Rect magnifiedFrame = mTempRect;
mMagnifiedBounds.getBounds(magnifiedFrame);
if (!magnifiedFrame.intersects(left, top, right, bottom)) {
return;
}
Rect magnifFrameInScreenCoords = mTempRect1;
getMagnifiedFrameInContentCoords(magnifFrameInScreenCoords);
final float scrollX;
final float scrollY;
if (right - left > magnifFrameInScreenCoords.width()) {
final int direction = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault());
if (direction == View.LAYOUT_DIRECTION_LTR) {
scrollX = left - magnifFrameInScreenCoords.left;
} else {
scrollX = right - magnifFrameInScreenCoords.right;
}
} else if (left < magnifFrameInScreenCoords.left) {
scrollX = left - magnifFrameInScreenCoords.left;
} else if (right > magnifFrameInScreenCoords.right) {
scrollX = right - magnifFrameInScreenCoords.right;
} else {
scrollX = 0;
}
if (bottom - top > magnifFrameInScreenCoords.height()) {
scrollY = top - magnifFrameInScreenCoords.top;
} else if (top < magnifFrameInScreenCoords.top) {
scrollY = top - magnifFrameInScreenCoords.top;
} else if (bottom > magnifFrameInScreenCoords.bottom) {
scrollY = bottom - magnifFrameInScreenCoords.bottom;
} else {
scrollY = 0;
}
final float scale = mMagnificationController.getScale();
mMagnificationController.offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale);
| private void | handleOnRotationChanged(int rotation)
resetMagnificationIfNeeded();
if (mMagnificationController.isMagnifying()) {
mUpdateMagnificationSpecOnNextBoundsChange = true;
}
| private void | handleOnUserContextChanged()
resetMagnificationIfNeeded();
| private static boolean | isScreenMagnificationAutoUpdateEnabled(android.content.Context context)
return (Settings.Secure.getInt(context.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
| public void | onAccessibilityEvent(android.view.accessibility.AccessibilityEvent event)
if (mNext != null) {
mNext.onAccessibilityEvent(event);
}
| public void | onDestroy()
mScreenStateObserver.destroy();
mWindowManager.setMagnificationCallbacks(null);
| public void | onMagnifedBoundsChanged(android.graphics.Region bounds)
Region newBounds = Region.obtain(bounds);
mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, newBounds).sendToTarget();
if (MY_PID != Binder.getCallingPid()) {
bounds.recycle();
}
| public void | onMotionEvent(android.view.MotionEvent event, android.view.MotionEvent rawEvent, int policyFlags)
mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
switch (mCurrentState) {
case STATE_DELEGATING: {
handleMotionEventStateDelegating(event, rawEvent, policyFlags);
} break;
case STATE_DETECTING: {
mDetectingStateHandler.onMotionEvent(event, rawEvent, policyFlags);
} break;
case STATE_VIEWPORT_DRAGGING: {
mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
} break;
case STATE_MAGNIFIED_INTERACTION: {
// mMagnifiedContentInteractonStateHandler handles events only
// if this is the current state since it uses ScaleGestureDetecotr
// and a GestureDetector which need well formed event stream.
} break;
default: {
throw new IllegalStateException("Unknown state: " + mCurrentState);
}
}
| public void | onRectangleOnScreenRequested(int left, int top, int right, int bottom)
SomeArgs args = SomeArgs.obtain();
args.argi1 = left;
args.argi2 = top;
args.argi3 = right;
args.argi4 = bottom;
mHandler.obtainMessage(MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED, args).sendToTarget();
| public void | onRotationChanged(int rotation)
mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
| public void | onUserContextChanged()
mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
| private void | persistScale(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 void | resetMagnificationIfNeeded()
if (mMagnificationController.isMagnifying()
&& isScreenMagnificationAutoUpdateEnabled(mContext)) {
mMagnificationController.reset(true);
}
| public void | setNext(EventStreamTransformation next)
mNext = next;
| 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_MAGNIFIED_INTERACTION: {
Slog.i(LOG_TAG, "mCurrentState: STATE_MAGNIFIED_INTERACTION");
} break;
default: {
throw new IllegalArgumentException("Unknown state: " + state);
}
}
}
mPreviousState = mCurrentState;
mCurrentState = state;
|
|