/* * 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() { @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 { 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 { 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; } } } } }