| /* |
| * 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 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.Message; |
| import android.provider.Settings; |
| import android.text.TextUtils; |
| import android.util.MathUtils; |
| import android.util.Slog; |
| import android.view.MagnificationSpec; |
| import android.view.View; |
| import android.view.WindowManagerInternal; |
| import android.view.animation.DecelerateInterpolator; |
| |
| import com.android.internal.R; |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.os.SomeArgs; |
| import com.android.server.LocalServices; |
| |
| 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. |
| * |
| * Magnification is limited to the range [MIN_SCALE, MAX_SCALE], and can only occur inside the |
| * magnification region. If a value is out of bounds, it will be adjusted to guarantee these |
| * constraints. |
| */ |
| class MagnificationController implements Handler.Callback { |
| private static final boolean DEBUG = false; |
| private static final String LOG_TAG = "MagnificationController"; |
| |
| public static final float MIN_SCALE = 1.0f; |
| public static final float MAX_SCALE = 5.0f; |
| |
| private static final boolean DEBUG_SET_MAGNIFICATION_SPEC = false; |
| |
| private static final int INVALID_ID = -1; |
| |
| private static final float DEFAULT_MAGNIFICATION_SCALE = 2.0f; |
| |
| // Messages |
| private static final int MSG_SEND_SPEC_TO_ANIMATION = 1; |
| private static final int MSG_SCREEN_TURNED_OFF = 2; |
| private static final int MSG_ON_MAGNIFIED_BOUNDS_CHANGED = 3; |
| private static final int MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED = 4; |
| private static final int MSG_ON_USER_CONTEXT_CHANGED = 5; |
| |
| 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 SettingsBridge mSettingsBridge; |
| |
| private final ScreenStateObserver mScreenStateObserver; |
| |
| private final SpecAnimationBridge mSpecAnimationBridge; |
| |
| private final WindowManagerInternal.MagnificationCallbacks mWMCallbacks = |
| new WindowManagerInternal.MagnificationCallbacks () { |
| @Override |
| public void onMagnificationRegionChanged(Region region) { |
| final SomeArgs args = SomeArgs.obtain(); |
| args.arg1 = Region.obtain(region); |
| mHandler.obtainMessage(MSG_ON_MAGNIFIED_BOUNDS_CHANGED, args).sendToTarget(); |
| } |
| |
| @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(MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED, args) |
| .sendToTarget(); |
| } |
| |
| @Override |
| public void onRotationChanged(int rotation) { |
| // Treat as context change and reset |
| mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED); |
| } |
| |
| @Override |
| public void onUserContextChanged() { |
| mHandler.sendEmptyMessage(MSG_ON_USER_CONTEXT_CHANGED); |
| } |
| }; |
| |
| private int mUserId; |
| |
| private final long mMainThreadId; |
| |
| private Handler mHandler; |
| |
| private int mIdOfLastServiceToMagnify = INVALID_ID; |
| |
| private final WindowManagerInternal mWindowManager; |
| |
| // Flag indicating that we are registered with window manager. |
| @VisibleForTesting boolean mRegistered; |
| |
| private boolean mUnregisterPending; |
| |
| public MagnificationController(Context context, AccessibilityManagerService ams, Object lock) { |
| this(context, ams, lock, null, LocalServices.getService(WindowManagerInternal.class), |
| new ValueAnimator(), new SettingsBridge(context.getContentResolver())); |
| mHandler = new Handler(context.getMainLooper(), this); |
| } |
| |
| public MagnificationController( |
| Context context, |
| AccessibilityManagerService ams, |
| Object lock, |
| Handler handler, |
| WindowManagerInternal windowManagerInternal, |
| ValueAnimator valueAnimator, |
| SettingsBridge settingsBridge) { |
| mHandler = handler; |
| mWindowManager = windowManagerInternal; |
| mMainThreadId = context.getMainLooper().getThread().getId(); |
| mAms = ams; |
| mScreenStateObserver = new ScreenStateObserver(context, this); |
| mLock = lock; |
| mSpecAnimationBridge = new SpecAnimationBridge( |
| context, mLock, mWindowManager, valueAnimator); |
| mSettingsBridge = settingsBridge; |
| } |
| |
| /** |
| * 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(); |
| mWindowManager.setMagnificationCallbacks(mWMCallbacks); |
| mSpecAnimationBridge.setEnabled(true); |
| // Obtain initial state. |
| mWindowManager.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(); |
| mWindowManager.setMagnificationCallbacks(null); |
| 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 |
| */ |
| private void onMagnificationRegionChanged(Region magnified) { |
| synchronized (mLock) { |
| if (!mRegistered) { |
| // Don't update if we've unregistered |
| return; |
| } |
| if (!mMagnificationRegion.equals(magnified)) { |
| mMagnificationRegion.set(magnified); |
| mMagnificationRegion.getBounds(mMagnificationBounds); |
| // It's possible that our magnification spec is invalid with the new bounds. |
| // Adjust the current spec's offsets if necessary. |
| if (updateCurrentSpecWithOffsetsLocked( |
| mCurrentMagnificationSpec.offsetX, mCurrentMagnificationSpec.offsetY)) { |
| sendSpecToAnimation(mCurrentMagnificationSpec, false); |
| } |
| 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 |
| */ |
| private 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 |
| */ |
| private 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 |
| */ |
| private 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; |
| sendSpecToAnimation(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) { |
| if (DEBUG) { |
| Slog.i(LOG_TAG, |
| "setScaleAndCenterLocked(scale = " + scale + ", centerX = " + centerX |
| + ", centerY = " + centerY + ", animate = " + animate + ", id = " + id |
| + ")"); |
| } |
| final boolean changed = updateMagnificationSpecLocked(scale, centerX, centerY); |
| sendSpecToAnimation(mCurrentMagnificationSpec, animate); |
| if (isMagnifying() && (id != INVALID_ID)) { |
| mIdOfLastServiceToMagnify = id; |
| } |
| return changed; |
| } |
| |
| /** |
| * Offsets the magnified region. Note that the offsetX and offsetY values actually move in the |
| * opposite direction as the offsets passed in here. |
| * |
| * @param offsetX the amount in pixels to offset the region in the X direction, in current |
| * screen pixels. |
| * @param offsetY the amount in pixels to offset the region in the Y direction, in current |
| * screen pixels. |
| * @param id the ID of the service requesting the change |
| */ |
| public void offsetMagnifiedRegion(float offsetX, float offsetY, int id) { |
| synchronized (mLock) { |
| if (!mRegistered) { |
| return; |
| } |
| |
| final float nonNormOffsetX = mCurrentMagnificationSpec.offsetX - offsetX; |
| final float nonNormOffsetY = mCurrentMagnificationSpec.offsetY - offsetY; |
| if (updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY)) { |
| onMagnificationChangedLocked(); |
| } |
| if (id != INVALID_ID) { |
| mIdOfLastServiceToMagnify = id; |
| } |
| sendSpecToAnimation(mCurrentMagnificationSpec, 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.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) { |
| mSettingsBridge.putMagnificationScale(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 mSettingsBridge.getMagnificationScale(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(); |
| } |
| |
| // Compute changes. |
| boolean changed = false; |
| |
| final float normScale = MathUtils.constrain(scale, MIN_SCALE, MAX_SCALE); |
| if (Float.compare(mCurrentMagnificationSpec.scale, normScale) != 0) { |
| mCurrentMagnificationSpec.scale = normScale; |
| changed = true; |
| } |
| |
| final float nonNormOffsetX = mMagnificationBounds.width() / 2.0f |
| + mMagnificationBounds.left - centerX * normScale; |
| final float nonNormOffsetY = mMagnificationBounds.height() / 2.0f |
| + mMagnificationBounds.top - centerY * normScale; |
| changed |= updateCurrentSpecWithOffsetsLocked(nonNormOffsetX, nonNormOffsetY); |
| |
| if (changed) { |
| onMagnificationChangedLocked(); |
| } |
| |
| return changed; |
| } |
| |
| private boolean updateCurrentSpecWithOffsetsLocked(float nonNormOffsetX, float nonNormOffsetY) { |
| if (DEBUG) { |
| Slog.i(LOG_TAG, |
| "updateCurrentSpecWithOffsetsLocked(nonNormOffsetX = " + nonNormOffsetX |
| + ", nonNormOffsetY = " + nonNormOffsetY + ")"); |
| } |
| boolean changed = false; |
| final float offsetX = MathUtils.constrain(nonNormOffsetX, getMinOffsetXLocked(), 0); |
| if (Float.compare(mCurrentMagnificationSpec.offsetX, offsetX) != 0) { |
| mCurrentMagnificationSpec.offsetX = offsetX; |
| changed = true; |
| } |
| final float offsetY = MathUtils.constrain(nonNormOffsetY, getMinOffsetYLocked(), 0); |
| if (Float.compare(mCurrentMagnificationSpec.offsetY, offsetY) != 0) { |
| mCurrentMagnificationSpec.offsetY = offsetY; |
| changed = true; |
| } |
| 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); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Resets magnification if magnification and auto-update are both enabled. |
| * |
| * @param animate whether the animate the transition |
| * @return whether was {@link #isMagnifying magnifying} |
| */ |
| boolean resetIfNeeded(boolean animate) { |
| synchronized (mLock) { |
| if (isMagnifying()) { |
| reset(animate); |
| return true; |
| } |
| return false; |
| } |
| } |
| |
| void setForceShowMagnifiableBounds(boolean show) { |
| if (mRegistered) { |
| mWindowManager.setForceShowMagnifiableBounds(show); |
| } |
| } |
| |
| 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(); |
| offsetMagnifiedRegion(scrollX * scale, scrollY * scale, INVALID_ID); |
| } |
| } |
| |
| private void sendSpecToAnimation(MagnificationSpec spec, boolean animate) { |
| if (DEBUG) { |
| Slog.i(LOG_TAG, "sendSpecToAnimation(spec = " + spec + ", animate = " + animate + ")"); |
| } |
| if (Thread.currentThread().getId() == mMainThreadId) { |
| mSpecAnimationBridge.updateSentSpecMainThread(spec, animate); |
| } else { |
| mHandler.obtainMessage(MSG_SEND_SPEC_TO_ANIMATION, |
| animate ? 1 : 0, 0, spec).sendToTarget(); |
| } |
| } |
| |
| private void onScreenTurnedOff() { |
| mHandler.sendEmptyMessage(MSG_SCREEN_TURNED_OFF); |
| } |
| |
| public boolean handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_SEND_SPEC_TO_ANIMATION: |
| final boolean animate = msg.arg1 == 1; |
| final MagnificationSpec spec = (MagnificationSpec) msg.obj; |
| mSpecAnimationBridge.updateSentSpecMainThread(spec, animate); |
| break; |
| case MSG_SCREEN_TURNED_OFF: |
| resetIfNeeded(false); |
| break; |
| case MSG_ON_MAGNIFIED_BOUNDS_CHANGED: { |
| final SomeArgs args = (SomeArgs) msg.obj; |
| final Region magnifiedBounds = (Region) args.arg1; |
| onMagnificationRegionChanged(magnifiedBounds); |
| magnifiedBounds.recycle(); |
| args.recycle(); |
| } break; |
| case MSG_ON_RECTANGLE_ON_SCREEN_REQUESTED: { |
| final SomeArgs args = (SomeArgs) msg.obj; |
| final int left = args.argi1; |
| final int top = args.argi2; |
| final int right = args.argi3; |
| final int bottom = args.argi4; |
| requestRectangleOnScreen(left, top, right, bottom); |
| args.recycle(); |
| } break; |
| case MSG_ON_USER_CONTEXT_CHANGED: |
| resetIfNeeded(true); |
| break; |
| } |
| return true; |
| } |
| |
| @Override |
| public String toString() { |
| return "MagnificationController{" + |
| "mCurrentMagnificationSpec=" + mCurrentMagnificationSpec + |
| ", mMagnificationRegion=" + mMagnificationRegion + |
| ", mMagnificationBounds=" + mMagnificationBounds + |
| ", mUserId=" + mUserId + |
| ", mIdOfLastServiceToMagnify=" + mIdOfLastServiceToMagnify + |
| ", mRegistered=" + mRegistered + |
| ", mUnregisterPending=" + mUnregisterPending + |
| '}'; |
| } |
| |
| /** |
| * Class responsible for animating spec on the main thread and sending spec |
| * updates to the window manager. |
| */ |
| private static class SpecAnimationBridge implements ValueAnimator.AnimatorUpdateListener { |
| 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(); |
| |
| private final MagnificationSpec mStartMagnificationSpec = MagnificationSpec.obtain(); |
| |
| private final MagnificationSpec mEndMagnificationSpec = MagnificationSpec.obtain(); |
| |
| private final MagnificationSpec mTmpMagnificationSpec = MagnificationSpec.obtain(); |
| |
| /** |
| * The animator should only be accessed and modified on the main (e.g. animation) thread. |
| */ |
| private final ValueAnimator mValueAnimator; |
| |
| private final Object mLock; |
| |
| @GuardedBy("mLock") |
| private boolean mEnabled = false; |
| |
| private SpecAnimationBridge(Context context, Object lock, WindowManagerInternal wm, |
| ValueAnimator animator) { |
| mLock = lock; |
| mWindowManager = wm; |
| final long animationDuration = context.getResources().getInteger( |
| R.integer.config_longAnimTime); |
| mValueAnimator = animator; |
| mValueAnimator.setDuration(animationDuration); |
| mValueAnimator.setInterpolator(new DecelerateInterpolator(2.5f)); |
| mValueAnimator.setFloatValues(0.0f, 1.0f); |
| mValueAnimator.addUpdateListener(this); |
| } |
| |
| /** |
| * 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 updateSentSpecMainThread(MagnificationSpec spec, boolean animate) { |
| if (mValueAnimator.isRunning()) { |
| mValueAnimator.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 setMagnificationSpecLocked(MagnificationSpec spec) { |
| if (mEnabled) { |
| if (DEBUG_SET_MAGNIFICATION_SPEC) { |
| Slog.i(LOG_TAG, "Sending: " + spec); |
| } |
| |
| mSentMagnificationSpec.setTo(spec); |
| mWindowManager.setMagnificationSpec(spec); |
| } |
| } |
| |
| private void animateMagnificationSpecLocked(MagnificationSpec toSpec) { |
| mEndMagnificationSpec.setTo(toSpec); |
| mStartMagnificationSpec.setTo(mSentMagnificationSpec); |
| mValueAnimator.start(); |
| } |
| |
| @Override |
| public void onAnimationUpdate(ValueAnimator animation) { |
| synchronized (mLock) { |
| if (mEnabled) { |
| float fract = animation.getAnimatedFraction(); |
| mTmpMagnificationSpec.scale = mStartMagnificationSpec.scale + |
| (mEndMagnificationSpec.scale - mStartMagnificationSpec.scale) * fract; |
| mTmpMagnificationSpec.offsetX = mStartMagnificationSpec.offsetX + |
| (mEndMagnificationSpec.offsetX - mStartMagnificationSpec.offsetX) |
| * fract; |
| mTmpMagnificationSpec.offsetY = mStartMagnificationSpec.offsetY + |
| (mEndMagnificationSpec.offsetY - mStartMagnificationSpec.offsetY) |
| * fract; |
| synchronized (mLock) { |
| setMagnificationSpecLocked(mTmpMagnificationSpec); |
| } |
| } |
| } |
| } |
| } |
| |
| private static class ScreenStateObserver extends BroadcastReceiver { |
| private final Context mContext; |
| private final MagnificationController mController; |
| |
| public ScreenStateObserver(Context context, MagnificationController controller) { |
| mContext = context; |
| mController = controller; |
| } |
| |
| 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) { |
| mController.onScreenTurnedOff(); |
| } |
| } |
| |
| // Extra class to get settings so tests can mock it |
| public static class SettingsBridge { |
| private final ContentResolver mContentResolver; |
| |
| public SettingsBridge(ContentResolver contentResolver) { |
| mContentResolver = contentResolver; |
| } |
| |
| public void putMagnificationScale(float value, int userId) { |
| Settings.Secure.putFloatForUser(mContentResolver, |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, value, userId); |
| } |
| |
| public float getMagnificationScale(int userId) { |
| return Settings.Secure.getFloatForUser(mContentResolver, |
| Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_SCALE, |
| DEFAULT_MAGNIFICATION_SCALE, userId); |
| } |
| } |
| } |