blob: 7886b9ea7fad37d304f7022eee7c7f54d8c2ec59 [file] [log] [blame]
/*
* Copyright (C) 2015 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 com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.SomeArgs;
import com.android.server.LocalServices;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.MathUtils;
import android.util.Property;
import android.util.Slog;
import android.view.MagnificationSpec;
import android.view.View;
import android.view.WindowManagerInternal;
import android.view.animation.DecelerateInterpolator;
import java.util.Locale;
/**
* This class is used to control and query the state of display magnification
* from the accessibility manager and related classes. It is responsible for
* holding the current state of magnification and animation, and it handles
* communication between the accessibility manager and window manager.
*/
class MagnificationController {
private static final String LOG_TAG = "MagnificationController";
private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false;
private static final int DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE = 1;
private static final int INVALID_ID = -1;
private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f;
private static final float MIN_SCALE = 1.0f;
private static final float MAX_SCALE = 5.0f;
/**
* The minimum scaling factor that can be persisted to secure settings.
* This must be > 1.0 to ensure that magnification is actually set to an
* enabled state when the scaling factor is restored from settings.
*/
private static final float MIN_PERSISTED_SCALE = 2.0f;
private final Object mLock;
/**
* The current magnification spec. If an animation is running, this
* reflects the end state.
*/
private final MagnificationSpec mCurrentMagnificationSpec = MagnificationSpec.obtain();
private final Region mMagnificationRegion = Region.obtain();
private final Rect mMagnificationBounds = new Rect();
private final Rect mTempRect = new Rect();
private final Rect mTempRect1 = new Rect();
private final AccessibilityManagerService mAms;
private final ContentResolver mContentResolver;
private final ScreenStateObserver mScreenStateObserver;
private final WindowStateObserver mWindowStateObserver;
private final SpecAnimationBridge mSpecAnimationBridge;
private int mUserId;
private int mIdOfLastServiceToMagnify = INVALID_ID;
// Flag indicating that we are registered with window manager.
private boolean mRegistered;
private boolean mUnregisterPending;
public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) {
mAms = ams;
mContentResolver = context.getContentResolver();
mScreenStateObserver = new ScreenStateObserver(context, this);
mWindowStateObserver = new WindowStateObserver(context, this);
mLock = lock;
mSpecAnimationBridge = new SpecAnimationBridge(context, mLock);
}
/**
* Start tracking the magnification region for services that control magnification and the
* magnification gesture handler.
*
* This tracking imposes a cost on the system, so we avoid tracking this data
* unless it's required.
*/
public void register() {
synchronized (mLock) {
if (!mRegistered) {
mScreenStateObserver.register();
mWindowStateObserver.register();
mSpecAnimationBridge.setEnabled(true);
// Obtain initial state.
mWindowStateObserver.getMagnificationRegion(mMagnificationRegion);
mMagnificationRegion.getBounds(mMagnificationBounds);
mRegistered = true;
}
}
}
/**
* Stop requiring tracking the magnification region. We may remain registered while we
* reset magnification.
*/
public void unregister() {
synchronized (mLock) {
if (!isMagnifying()) {
unregisterInternalLocked();
} else {
mUnregisterPending = true;
resetLocked(true);
}
}
}
/**
* Check if we are registered. Note that we may be planning to unregister at any moment.
*
* @return {@code true} if the controller is registered. {@code false} otherwise.
*/
public boolean isRegisteredLocked() {
return mRegistered;
}
private void unregisterInternalLocked() {
if (mRegistered) {
mSpecAnimationBridge.setEnabled(false);
mScreenStateObserver.unregister();
mWindowStateObserver.unregister();
mMagnificationRegion.setEmpty();
mRegistered = false;
}
mUnregisterPending = false;
}
/**
* @return {@code true} if magnification is active, e.g. the scale
* is > 1, {@code false} otherwise
*/
public boolean isMagnifying() {
return mCurrentMagnificationSpec.scale > 1.0f;
}
/**
* Update our copy of the current magnification region
*
* @param magnified the magnified region
* @param updateSpec {@code true} to update the scale and center based on
* the region bounds, {@code false} to leave them as-is
*/
private void onMagnificationRegionChanged(Region magnified, boolean updateSpec) {
synchronized (mLock) {
if (!mRegistered) {
// Don't update if we've unregistered
return;
}
boolean magnificationChanged = false;
boolean boundsChanged = false;
if (!mMagnificationRegion.equals(magnified)) {
mMagnificationRegion.set(magnified);
mMagnificationRegion.getBounds(mMagnificationBounds);
boundsChanged = true;
}
if (updateSpec) {
final MagnificationSpec sentSpec = mSpecAnimationBridge.mSentMagnificationSpec;
final float scale = sentSpec.scale;
final float offsetX = sentSpec.offsetX;
final float offsetY = sentSpec.offsetY;
// Compute the new center and update spec as needed.
final float centerX = (mMagnificationBounds.width() / 2.0f
+ mMagnificationBounds.left - offsetX) / scale;
final float centerY = (mMagnificationBounds.height() / 2.0f
+ mMagnificationBounds.top - offsetY) / scale;
magnificationChanged = setScaleAndCenterLocked(
scale, centerX, centerY, false, INVALID_ID);
}
// If magnification changed we already notified for the change.
if (boundsChanged && updateSpec && !magnificationChanged) {
onMagnificationChangedLocked();
}
}
}
/**
* Returns whether the magnification region contains the specified
* screen-relative coordinates.
*
* @param x the screen-relative X coordinate to check
* @param y the screen-relative Y coordinate to check
* @return {@code true} if the coordinate is contained within the
* magnified region, or {@code false} otherwise
*/
public boolean magnificationRegionContains(float x, float y) {
synchronized (mLock) {
return mMagnificationRegion.contains((int) x, (int) y);
}
}
/**
* Populates the specified rect with the screen-relative bounds of the
* magnification region. If magnification is not enabled, the returned
* bounds will be empty.
*
* @param outBounds rect to populate with the bounds of the magnified
* region
*/
public void getMagnificationBounds(@NonNull Rect outBounds) {
synchronized (mLock) {
outBounds.set(mMagnificationBounds);
}
}
/**
* Populates the specified region with the screen-relative magnification
* region. If magnification is not enabled, then the returned region
* will be empty.
*
* @param outRegion the region to populate
*/
public void getMagnificationRegion(@NonNull Region outRegion) {
synchronized (mLock) {
outRegion.set(mMagnificationRegion);
}
}
/**
* Returns the magnification scale. If an animation is in progress,
* this reflects the end state of the animation.
*
* @return the scale
*/
public float getScale() {
return mCurrentMagnificationSpec.scale;
}
/**
* Returns the X offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
* @return the X offset
*/
public float getOffsetX() {
return mCurrentMagnificationSpec.offsetX;
}
/**
* Returns the screen-relative X coordinate of the center of the
* magnification viewport.
*
* @return the X coordinate
*/
public float getCenterX() {
synchronized (mLock) {
return (mMagnificationBounds.width() / 2.0f
+ mMagnificationBounds.left - getOffsetX()) / getScale();
}
}
/**
* Returns the Y offset of the magnification viewport. If an animation
* is in progress, this reflects the end state of the animation.
*
* @return the Y offset
*/
public float getOffsetY() {
return mCurrentMagnificationSpec.offsetY;
}
/**
* Returns the screen-relative Y coordinate of the center of the
* magnification viewport.
*
* @return the Y coordinate
*/
public float getCenterY() {
synchronized (mLock) {
return (mMagnificationBounds.height() / 2.0f
+ mMagnificationBounds.top - getOffsetY()) / getScale();
}
}
/**
* Returns the scale currently used by the window manager. If an
* animation is in progress, this reflects the current state of the
* animation.
*
* @return the scale currently used by the window manager
*/
public float getSentScale() {
return mSpecAnimationBridge.mSentMagnificationSpec.scale;
}
/**
* Returns the X offset currently used by the window manager. If an
* animation is in progress, this reflects the current state of the
* animation.
*
* @return the X offset currently used by the window manager
*/
public float getSentOffsetX() {
return mSpecAnimationBridge.mSentMagnificationSpec.offsetX;
}
/**
* Returns the Y offset currently used by the window manager. If an
* animation is in progress, this reflects the current state of the
* animation.
*
* @return the Y offset currently used by the window manager
*/
public float getSentOffsetY() {
return mSpecAnimationBridge.mSentMagnificationSpec.offsetY;
}
/**
* Resets the magnification scale and center, optionally animating the
* transition.
*
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean reset(boolean animate) {
synchronized (mLock) {
return resetLocked(animate);
}
}
private boolean resetLocked(boolean animate) {
if (!mRegistered) {
return false;
}
final MagnificationSpec spec = mCurrentMagnificationSpec;
final boolean changed = !spec.isNop();
if (changed) {
spec.clear();
onMagnificationChangedLocked();
}
mIdOfLastServiceToMagnify = INVALID_ID;
mSpecAnimationBridge.updateSentSpec(spec, animate);
return changed;
}
/**
* Scales the magnified region around the specified pivot point,
* optionally animating the transition. If animation is disabled, the
* transition is immediate.
*
* @param scale the target scale, must be >= 1
* @param pivotX the screen-relative X coordinate around which to scale
* @param pivotY the screen-relative Y coordinate around which to scale
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean setScale(float scale, float pivotX, float pivotY, boolean animate, int id) {
synchronized (mLock) {
if (!mRegistered) {
return false;
}
// Constrain scale immediately for use in the pivot calculations.
scale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
final Rect viewport = mTempRect;
mMagnificationRegion.getBounds(viewport);
final MagnificationSpec spec = mCurrentMagnificationSpec;
final float oldScale = spec.scale;
final float oldCenterX = (viewport.width() / 2.0f - spec.offsetX) / oldScale;
final float oldCenterY = (viewport.height() / 2.0f - spec.offsetY) / oldScale;
final float normPivotX = (pivotX - spec.offsetX) / oldScale;
final float normPivotY = (pivotY - spec.offsetY) / 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;
mIdOfLastServiceToMagnify = id;
return setScaleAndCenterLocked(scale, centerX, centerY, animate, id);
}
}
/**
* Sets the center of the magnified region, optionally animating the
* transition. If animation is disabled, the transition is immediate.
*
* @param centerX the screen-relative X coordinate around which to
* center
* @param centerY the screen-relative Y coordinate around which to
* center
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean setCenter(float centerX, float centerY, boolean animate, int id) {
synchronized (mLock) {
if (!mRegistered) {
return false;
}
return setScaleAndCenterLocked(Float.NaN, centerX, centerY, animate, id);
}
}
/**
* Sets the scale and center of the magnified region, optionally
* animating the transition. If animation is disabled, the transition
* is immediate.
*
* @param scale the target scale, or {@link Float#NaN} to leave unchanged
* @param centerX the screen-relative X coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
* @param centerY the screen-relative Y coordinate around which to
* center and scale, or {@link Float#NaN} to leave unchanged
* @param animate {@code true} to animate the transition, {@code false}
* to transition immediately
* @param id the ID of the service requesting the change
* @return {@code true} if the magnification spec changed, {@code false} if
* the spec did not change
*/
public boolean setScaleAndCenter(
float scale, float centerX, float centerY, boolean animate, int id) {
synchronized (mLock) {
if (!mRegistered) {
return false;
}
return setScaleAndCenterLocked(scale, centerX, centerY, animate, id);
}
}
private boolean setScaleAndCenterLocked(float scale, float centerX, float centerY,
boolean animate, int id) {
final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY);
mSpecAnimationBridge.updateSentSpec(mCurrentMagnificationSpec, animate);
if (isMagnifying() && (id != INVALID_ID)) {
mIdOfLastServiceToMagnify = id;
}
return changed;
}
/**
* Offsets the center of the magnified region.
*
* @param offsetX the amount in pixels to offset the X center
* @param offsetY the amount in pixels to offset the Y center
* @param id the ID of the service requesting the change
*/
public void offsetMagnifiedRegionCenter(float offsetX, float offsetY, int id) {
synchronized (mLock) {
if (!mRegistered) {
return;
}
final MagnificationSpec currSpec = mCurrentMagnificationSpec;
final float nonNormOffsetX = currSpec.offsetX - offsetX;
currSpec.offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
final float nonNormOffsetY = currSpec.offsetY - offsetY;
currSpec.offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
if (id != INVALID_ID) {
mIdOfLastServiceToMagnify = id;
}
mSpecAnimationBridge.updateSentSpec(currSpec, false);
}
}
/**
* Get the ID of the last service that changed the magnification spec.
*
* @return The id
*/
public int getIdOfLastServiceToMagnify() {
return mIdOfLastServiceToMagnify;
}
private void onMagnificationChangedLocked() {
mAms.onMagnificationStateChanged();
mAms.notifyMagnificationChanged(mMagnificationRegion,
getScale(), getCenterX(), getCenterY());
if (mUnregisterPending && !isMagnifying()) {
unregisterInternalLocked();
}
}
/**
* Persists the current magnification scale to the current user's settings.
*/
public void persistScale() {
final float scale = mCurrentMagnificationSpec.scale;
final int userId = mUserId;
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
Settings.Secure.putFloatForUser(mContentResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, scale, userId);
return null;
}
}.execute();
}
/**
* Retrieves a previously persisted magnification scale from the current
* user's settings.
*
* @return the previously persisted magnification scale, or the default
* scale if none is available
*/
public float getPersistedScale() {
return Settings.Secure.getFloatForUser(mContentResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE,
DEFAULT_MAGNIFICATION_SCALE, mUserId);
}
/**
* Updates the current magnification spec.
*
* @param scale the magnification scale
* @param centerX the unscaled, screen-relative X coordinate of the center
* of the viewport, or {@link Float#NaN} to leave unchanged
* @param centerY the unscaled, screen-relative Y coordinate of the center
* of the viewport, or {@link Float#NaN} to leave unchanged
* @return {@code true} if the magnification spec changed or {@code false}
* otherwise
*/
private boolean updateMagnificationSpecLocked(float scale, float centerX, float centerY) {
// Handle defaults.
if (Float.isNaN(centerX)) {
centerX = getCenterX();
}
if (Float.isNaN(centerY)) {
centerY = getCenterY();
}
if (Float.isNaN(scale)) {
scale = getScale();
}
// Ensure requested center is within the magnification region.
if (!magnificationRegionContains(centerX, centerY)) {
return false;
}
// Compute changes.
final MagnificationSpec currSpec = mCurrentMagnificationSpec;
boolean changed = false;
final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE);
if (Float.compare(currSpec.scale, normScale) != 0) {
currSpec.scale = normScale;
changed = true;
}
final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f
+ mMagnificationBounds.left - centerX * scale;
final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0);
if (Float.compare(currSpec.offsetX, offsetX) != 0) {
currSpec.offsetX = offsetX;
changed = true;
}
final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f
+ mMagnificationBounds.top - centerY * scale;
final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0);
if (Float.compare(currSpec.offsetY, offsetY) != 0) {
currSpec.offsetY = offsetY;
changed = true;
}
if (changed) {
onMagnificationChangedLocked();
}
return changed;
}
private float getMinOffsetXLocked() {
final float viewportWidth = mMagnificationBounds.width();
return viewportWidth - viewportWidth * mCurrentMagnificationSpec.scale;
}
private float getMinOffsetYLocked() {
final float viewportHeight = mMagnificationBounds.height();
return viewportHeight - viewportHeight * mCurrentMagnificationSpec.scale;
}
/**
* Sets the currently active user ID.
*
* @param userId the currently active user ID
*/
public void setUserId(int userId) {
if (mUserId != userId) {
mUserId = userId;
synchronized (mLock) {
if (isMagnifying()) {
reset(false);
}
}
}
}
private boolean isScreenMagnificationAutoUpdateEnabled() {
return (Settings.Secure.getInt(mContentResolver,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
DEFAULT_SCREEN_MAGNIFICATION_AUTO_UPDATE) == 1);
}
/**
* Resets magnification if magnification and auto-update are both enabled.
*
* @param animate whether the animate the transition
* @return {@code true} if magnification was reset to the disabled state,
* {@code false} if magnification is still active
*/
boolean resetIfNeeded(boolean animate) {
synchronized (mLock) {
if (isMagnifying() && isScreenMagnificationAutoUpdateEnabled()) {
reset(animate);
return true;
}
return false;
}
}
private void getMagnifiedFrameInContentCoordsLocked(Rect outFrame) {
final float scale = getSentScale();
final float offsetX = getSentOffsetX();
final float offsetY = getSentOffsetY();
getMagnificationBounds(outFrame);
outFrame.offset((int) -offsetX, (int) -offsetY);
outFrame.scale(1.0f / scale);
}
private void requestRectangleOnScreen(int left, int top, int right, int bottom) {
synchronized (mLock) {
final Rect magnifiedFrame = mTempRect;
getMagnificationBounds(magnifiedFrame);
if (!magnifiedFrame.intersects(left, top, right, bottom)) {
return;
}
final Rect magnifFrameInScreenCoords = mTempRect1;
getMagnifiedFrameInContentCoordsLocked(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 = getScale();
offsetMagnifiedRegionCenter(scrollX * scale, scrollY * scale, INVALID_ID);
}
}
/**
* Class responsible for animating spec on the main thread and sending spec
* updates to the window manager.
*/
private static class SpecAnimationBridge {
private static final int ACTION_UPDATE_SPEC = 1;
private final Handler mHandler;
private final WindowManagerInternal mWindowManager;
/**
* The magnification spec that was sent to the window manager. This should
* only be accessed with the lock held.
*/
private final MagnificationSpec mSentMagnificationSpec = MagnificationSpec.obtain();
/**
* The animator that updates the sent spec. This should only be accessed
* and modified on the main (e.g. animation) thread.
*/
private final ValueAnimator mTransformationAnimator;
private final long mMainThreadId;
private final Object mLock;
@GuardedBy("mLock")
private boolean mEnabled = false;
private SpecAnimationBridge(Context context, Object lock) {
mLock = lock;
final Looper mainLooper = context.getMainLooper();
mMainThreadId = mainLooper.getThread().getId();
mHandler = new UpdateHandler(context);
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
final MagnificationSpecProperty property = new MagnificationSpecProperty();
final MagnificationSpecEvaluator evaluator = new MagnificationSpecEvaluator();
final long animationDuration = context.getResources().getInteger(
R.integer.config_longAnimTime);
mTransformationAnimator = ObjectAnimator.ofObject(this, property, evaluator,
mSentMagnificationSpec);
mTransformationAnimator.setDuration(animationDuration);
mTransformationAnimator.setInterpolator(new DecelerateInterpolator(2.5f));
}
/**
* Enabled means the bridge will accept input. When not enabled, the output of the animator
* will be ignored
*/
public void setEnabled(boolean enabled) {
synchronized (mLock) {
if (enabled != mEnabled) {
mEnabled = enabled;
if (!mEnabled) {
mSentMagnificationSpec.clear();
mWindowManager.setMagnificationSpec(mSentMagnificationSpec);
}
}
}
}
public void updateSentSpec(MagnificationSpec spec, boolean animate) {
if (Thread.currentThread().getId() == mMainThreadId) {
// Already on the main thread, don't bother proxying.
updateSentSpecInternal(spec, animate);
} else {
mHandler.obtainMessage(ACTION_UPDATE_SPEC,
animate ? 1 : 0, 0, spec).sendToTarget();
}
}
/**
* Updates the sent spec.
*/
private void updateSentSpecInternal(MagnificationSpec spec, boolean animate) {
if (mTransformationAnimator.isRunning()) {
mTransformationAnimator.cancel();
}
// If the current and sent specs don't match, update the sent spec.
synchronized (mLock) {
final boolean changed = !mSentMagnificationSpec.equals(spec);
if (changed) {
if (animate) {
animateMagnificationSpecLocked(spec);
} else {
setMagnificationSpecLocked(spec);
}
}
}
}
private void animateMagnificationSpecLocked(MagnificationSpec toSpec) {
mTransformationAnimator.setObjectValues(mSentMagnificationSpec, toSpec);
mTransformationAnimator.start();
}
private void setMagnificationSpecLocked(MagnificationSpec spec) {
if (mEnabled) {
if (DEBUG_SET_MAGNIFICATION_SPEC) {
Slog.i(LOG_TAG, "Sending: " + spec);
}
mSentMagnificationSpec.setTo(spec);
mWindowManager.setMagnificationSpec(spec);
}
}
private class UpdateHandler extends Handler {
public UpdateHandler(Context context) {
super(context.getMainLooper());
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ACTION_UPDATE_SPEC:
final boolean animate = msg.arg1 == 1;
final MagnificationSpec spec = (MagnificationSpec) msg.obj;
updateSentSpecInternal(spec, animate);
break;
}
}
}
private static class MagnificationSpecProperty
extends Property<SpecAnimationBridge, MagnificationSpec> {
public MagnificationSpecProperty() {
super(MagnificationSpec.class, "spec");
}
@Override
public MagnificationSpec get(SpecAnimationBridge object) {
synchronized (object.mLock) {
return object.mSentMagnificationSpec;
}
}
@Override
public void set(SpecAnimationBridge object, MagnificationSpec value) {
synchronized (object.mLock) {
object.setMagnificationSpecLocked(value);
}
}
}
private static class MagnificationSpecEvaluator
implements TypeEvaluator<MagnificationSpec> {
private final MagnificationSpec mTempSpec = MagnificationSpec.obtain();
@Override
public MagnificationSpec evaluate(float fraction, MagnificationSpec fromSpec,
MagnificationSpec toSpec) {
final MagnificationSpec result = mTempSpec;
result.scale = fromSpec.scale + (toSpec.scale - fromSpec.scale) * fraction;
result.offsetX = fromSpec.offsetX + (toSpec.offsetX - fromSpec.offsetX) * fraction;
result.offsetY = fromSpec.offsetY + (toSpec.offsetY - fromSpec.offsetY) * fraction;
return result;
}
}
}
private static class ScreenStateObserver extends BroadcastReceiver {
private static final int MESSAGE_ON_SCREEN_STATE_CHANGE = 1;
private final Context mContext;
private final MagnificationController mController;
private final Handler mHandler;
public ScreenStateObserver(Context context, MagnificationController controller) {
mContext = context;
mController = controller;
mHandler = new StateChangeHandler(context);
}
public void register() {
mContext.registerReceiver(this, new IntentFilter(Intent.ACTION_SCREEN_OFF));
}
public void unregister() {
mContext.unregisterReceiver(this);
}
@Override
public void onReceive(Context context, Intent intent) {
mHandler.obtainMessage(MESSAGE_ON_SCREEN_STATE_CHANGE,
intent.getAction()).sendToTarget();
}
private void handleOnScreenStateChange() {
mController.resetIfNeeded(false);
}
private class StateChangeHandler extends Handler {
public StateChangeHandler(Context context) {
super(context.getMainLooper());
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_ON_SCREEN_STATE_CHANGE:
handleOnScreenStateChange();
break;
}
}
}
}
/**
* This class handles the screen magnification when accessibility is enabled.
*/
private static class WindowStateObserver
implements WindowManagerInternal.MagnificationCallbacks {
private static final int MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED = 1;
private static final int MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED = 2;
private static final int MESSAGE_ON_USER_CONTEXT_CHANGED = 3;
private static final int MESSAGE_ON_ROTATION_CHANGED = 4;
private final MagnificationController mController;
private final WindowManagerInternal mWindowManager;
private final Handler mHandler;
private boolean mSpecIsDirty;
public WindowStateObserver(Context context, MagnificationController controller) {
mController = controller;
mWindowManager = LocalServices.getService(WindowManagerInternal.class);
mHandler = new CallbackHandler(context);
}
public void register() {
mWindowManager.setMagnificationCallbacks(this);
}
public void unregister() {
mWindowManager.setMagnificationCallbacks(null);
}
@Override
public void onMagnificationRegionChanged(Region magnificationRegion) {
final SomeArgs args = SomeArgs.obtain();
args.arg1 = Region.obtain(magnificationRegion);
mHandler.obtainMessage(MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget();
}
private void handleOnMagnifiedBoundsChanged(Region magnificationRegion) {
mController.onMagnificationRegionChanged(magnificationRegion, mSpecIsDirty);
mSpecIsDirty = false;
}
@Override
public void onRectangleOnScreenRequested(int left, int top, int right, int bottom) {
final 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();
}
private void handleOnRectangleOnScreenRequested(int left, int top, int right, int bottom) {
mController.requestRectangleOnScreen(left, top, right, bottom);
}
@Override
public void onRotationChanged(int rotation) {
mHandler.obtainMessage(MESSAGE_ON_ROTATION_CHANGED, rotation, 0).sendToTarget();
}
private void handleOnRotationChanged() {
// If there was a rotation and magnification is still enabled,
// we'll need to rewrite the spec to reflect the new screen
// configuration. Conveniently, we'll receive a callback from
// the window manager with updated bounds for the magnified
// region.
mSpecIsDirty = !mController.resetIfNeeded(true);
}
@Override
public void onUserContextChanged() {
mHandler.sendEmptyMessage(MESSAGE_ON_USER_CONTEXT_CHANGED);
}
private void handleOnUserContextChanged() {
mController.resetIfNeeded(true);
}
/**
* This method is used to get the magnification region in the tiny time slice between
* registering the callbacks and handling the message.
* TODO: Elimiante this extra path, perhaps by processing the message immediately
*
* @param outMagnificationRegion
*/
public void getMagnificationRegion(@NonNull Region outMagnificationRegion) {
mWindowManager.getMagnificationRegion(outMagnificationRegion);
}
private class CallbackHandler extends Handler {
public CallbackHandler(Context context) {
super(context.getMainLooper());
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_ON_MAGNIFIED_BOUNDS_CHANGED: {
final SomeArgs args = (SomeArgs) message.obj;
final Region magnifiedBounds = (Region) args.arg1;
handleOnMagnifiedBoundsChanged(magnifiedBounds);
magnifiedBounds.recycle();
} break;
case MESSAGE_ON_RECTANGLE_ON_SCREEN_REQUESTED: {
final SomeArgs args = (SomeArgs) message.obj;
final int left = args.argi1;
final int top = args.argi2;
final int right = args.argi3;
final int bottom = args.argi4;
handleOnRectangleOnScreenRequested(left, top, right, bottom);
args.recycle();
} break;
case MESSAGE_ON_USER_CONTEXT_CHANGED: {
handleOnUserContextChanged();
} break;
case MESSAGE_ON_ROTATION_CHANGED: {
handleOnRotationChanged();
} break;
}
}
}
}
}