1. The accessibility manager service updates its internal state based on which settings are enabled, what accessibility services are installed and what features are requested by the enabled services. It was trying to do the minimal amount of work to react to contextual changes like these which resulted in missed cases and complex code. Now there is a single method that reads the contextual information and single method that reacts to contextual changes. This makes the code much easier to maintain. 2. The accessibility manager service was not updating its internal state when requested features from accessibility services change. It was relying on changing system settings and reacting to the settings change. This is problematic since the internal state is not updated atomically which leads to race condition bugs. For example, if touch exploration is enabled and a service requests it is disabled, the internal state will not be updated but a request for a settings change will be made. Now while the settings change is propagating another request form the same service comes to enable touch exploration but the system incorrectly thinks touch exploration is enabled. At the end the feature is disabled even though it was requested. 3. Fixed a potential NPE if the accessibility input filter's event handler was nullified between processing two event batches. 4. Fixed a bug where, if magnification is enabled, it does not work on the settings screen since the magnified bounds are not pushed from the window manager to the accessibility manager. Change-Id: Idf629a06480e12f0d88372762df6c024fe0d7856
753 lines
31 KiB
Java
753 lines
31 KiB
Java
/*
|
|
* Copyright (C) 2012 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package com.android.server.wm;
|
|
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.ValueAnimator;
|
|
import android.app.Service;
|
|
import android.content.Context;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.Matrix;
|
|
import android.graphics.Paint;
|
|
import android.graphics.Path;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.Point;
|
|
import android.graphics.PorterDuff.Mode;
|
|
import android.graphics.Rect;
|
|
import android.graphics.RectF;
|
|
import android.graphics.Region;
|
|
import android.os.Handler;
|
|
import android.os.Looper;
|
|
import android.os.Message;
|
|
import android.os.RemoteException;
|
|
import android.util.Pools.SimplePool;
|
|
import android.util.Slog;
|
|
import android.util.SparseArray;
|
|
import android.util.TypedValue;
|
|
import android.view.IMagnificationCallbacks;
|
|
import android.view.MagnificationSpec;
|
|
import android.view.Surface;
|
|
import android.view.Surface.OutOfResourcesException;
|
|
import android.view.WindowManager;
|
|
import android.view.WindowManagerPolicy;
|
|
import android.view.animation.DecelerateInterpolator;
|
|
import android.view.animation.Interpolator;
|
|
|
|
import com.android.internal.R;
|
|
import com.android.internal.os.SomeArgs;
|
|
|
|
/**
|
|
* This class is a part of the window manager and encapsulates the
|
|
* functionality related to display magnification.
|
|
*/
|
|
final class DisplayMagnifier {
|
|
private static final String LOG_TAG = DisplayMagnifier.class.getSimpleName();
|
|
|
|
private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
|
|
private static final boolean DEBUG_ROTATION = false;
|
|
private static final boolean DEBUG_LAYERS = false;
|
|
private static final boolean DEBUG_RECTANGLE_REQUESTED = false;
|
|
private static final boolean DEBUG_VIEWPORT_WINDOW = false;
|
|
|
|
private final Rect mTempRect1 = new Rect();
|
|
private final Rect mTempRect2 = new Rect();
|
|
|
|
private final Region mTempRegion1 = new Region();
|
|
private final Region mTempRegion2 = new Region();
|
|
private final Region mTempRegion3 = new Region();
|
|
private final Region mTempRegion4 = new Region();
|
|
|
|
private final Context mContext;
|
|
private final WindowManagerService mWindowManagerService;
|
|
private final MagnifiedViewport mMagnifedViewport;
|
|
private final Handler mHandler;
|
|
|
|
private final IMagnificationCallbacks mCallbacks;
|
|
|
|
private final long mLongAnimationDuration;
|
|
|
|
public DisplayMagnifier(WindowManagerService windowManagerService,
|
|
IMagnificationCallbacks callbacks) {
|
|
mContext = windowManagerService.mContext;
|
|
mWindowManagerService = windowManagerService;
|
|
mCallbacks = callbacks;
|
|
mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
|
|
mMagnifedViewport = new MagnifiedViewport();
|
|
mLongAnimationDuration = mContext.getResources().getInteger(
|
|
com.android.internal.R.integer.config_longAnimTime);
|
|
}
|
|
|
|
public void setMagnificationSpecLocked(MagnificationSpec spec) {
|
|
mMagnifedViewport.updateMagnificationSpecLocked(spec);
|
|
mMagnifedViewport.recomputeBoundsLocked();
|
|
mWindowManagerService.scheduleAnimationLocked();
|
|
}
|
|
|
|
public void onRectangleOnScreenRequestedLocked(Rect rectangle, boolean immediate) {
|
|
if (DEBUG_RECTANGLE_REQUESTED) {
|
|
Slog.i(LOG_TAG, "Rectangle on screen requested: " + rectangle);
|
|
}
|
|
if (!mMagnifedViewport.isMagnifyingLocked()) {
|
|
return;
|
|
}
|
|
Rect magnifiedRegionBounds = mTempRect2;
|
|
mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(magnifiedRegionBounds);
|
|
if (magnifiedRegionBounds.contains(rectangle)) {
|
|
return;
|
|
}
|
|
SomeArgs args = SomeArgs.obtain();
|
|
args.argi1 = rectangle.left;
|
|
args.argi2 = rectangle.top;
|
|
args.argi3 = rectangle.right;
|
|
args.argi4 = rectangle.bottom;
|
|
mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED,
|
|
args).sendToTarget();
|
|
}
|
|
|
|
public void onWindowLayersChangedLocked() {
|
|
if (DEBUG_LAYERS) {
|
|
Slog.i(LOG_TAG, "Layers changed.");
|
|
}
|
|
mMagnifedViewport.recomputeBoundsLocked();
|
|
mWindowManagerService.scheduleAnimationLocked();
|
|
}
|
|
|
|
public void onRotationChangedLocked(DisplayContent displayContent, int rotation) {
|
|
if (DEBUG_ROTATION) {
|
|
Slog.i(LOG_TAG, "Rotaton: " + Surface.rotationToString(rotation)
|
|
+ " displayId: " + displayContent.getDisplayId());
|
|
}
|
|
mMagnifedViewport.onRotationChangedLocked();
|
|
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
|
|
}
|
|
|
|
public void onAppWindowTransitionLocked(WindowState windowState, int transition) {
|
|
if (DEBUG_WINDOW_TRANSITIONS) {
|
|
Slog.i(LOG_TAG, "Window transition: "
|
|
+ AppTransition.appTransitionToString(transition)
|
|
+ " displayId: " + windowState.getDisplayId());
|
|
}
|
|
final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
|
|
if (magnifying) {
|
|
switch (transition) {
|
|
case AppTransition.TRANSIT_ACTIVITY_OPEN:
|
|
case AppTransition.TRANSIT_TASK_OPEN:
|
|
case AppTransition.TRANSIT_TASK_TO_FRONT:
|
|
case AppTransition.TRANSIT_WALLPAPER_OPEN:
|
|
case AppTransition.TRANSIT_WALLPAPER_CLOSE:
|
|
case AppTransition.TRANSIT_WALLPAPER_INTRA_OPEN: {
|
|
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_USER_CONTEXT_CHANGED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onWindowTransitionLocked(WindowState windowState, int transition) {
|
|
if (DEBUG_WINDOW_TRANSITIONS) {
|
|
Slog.i(LOG_TAG, "Window transition: "
|
|
+ AppTransition.appTransitionToString(transition)
|
|
+ " displayId: " + windowState.getDisplayId());
|
|
}
|
|
final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
|
|
final int type = windowState.mAttrs.type;
|
|
switch (transition) {
|
|
case WindowManagerPolicy.TRANSIT_ENTER:
|
|
case WindowManagerPolicy.TRANSIT_SHOW: {
|
|
if (!magnifying) {
|
|
break;
|
|
}
|
|
switch (type) {
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
|
|
case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG:
|
|
case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
|
|
case WindowManager.LayoutParams.TYPE_PHONE:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
|
|
case WindowManager.LayoutParams.TYPE_TOAST:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
|
|
case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
|
|
case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
|
|
case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
|
|
case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
|
|
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
|
|
case WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY: {
|
|
Rect magnifiedRegionBounds = mTempRect2;
|
|
mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
|
|
magnifiedRegionBounds);
|
|
Rect touchableRegionBounds = mTempRect1;
|
|
windowState.getTouchableRegion(mTempRegion1);
|
|
mTempRegion1.getBounds(touchableRegionBounds);
|
|
if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
|
|
try {
|
|
mCallbacks.onRectangleOnScreenRequested(
|
|
touchableRegionBounds.left,
|
|
touchableRegionBounds.top,
|
|
touchableRegionBounds.right,
|
|
touchableRegionBounds.bottom);
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
}
|
|
}
|
|
} break;
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
|
|
MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
|
|
if (spec != null && !spec.isNop()) {
|
|
WindowManagerPolicy policy = mWindowManagerService.mPolicy;
|
|
final int windowType = windowState.mAttrs.type;
|
|
if (!policy.isTopLevelWindow(windowType) && windowState.mAttachedWindow != null
|
|
&& !policy.canMagnifyWindow(windowType)) {
|
|
return null;
|
|
}
|
|
if (!policy.canMagnifyWindow(windowState.mAttrs.type)) {
|
|
return null;
|
|
}
|
|
}
|
|
return spec;
|
|
}
|
|
|
|
public void destroyLocked() {
|
|
mMagnifedViewport.destroyWindow();
|
|
}
|
|
|
|
/** NOTE: This has to be called within a surface transaction. */
|
|
public void drawMagnifiedRegionBorderIfNeededLocked() {
|
|
mMagnifedViewport.drawWindowIfNeededLocked();
|
|
}
|
|
|
|
private final class MagnifiedViewport {
|
|
|
|
private static final int DEFAUTLT_BORDER_WIDTH_DIP = 5;
|
|
|
|
private final SparseArray<WindowStateInfo> mTempWindowStateInfos =
|
|
new SparseArray<WindowStateInfo>();
|
|
|
|
private final float[] mTempFloats = new float[9];
|
|
|
|
private final RectF mTempRectF = new RectF();
|
|
|
|
private final Point mTempPoint = new Point();
|
|
|
|
private final Matrix mTempMatrix = new Matrix();
|
|
|
|
private final Region mMagnifiedBounds = new Region();
|
|
private final Region mOldMagnifiedBounds = new Region();
|
|
|
|
private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
|
|
|
|
private final WindowManager mWindowManager;
|
|
|
|
private final int mBorderWidth;
|
|
private final int mHalfBorderWidth;
|
|
|
|
private final ViewportWindow mWindow;
|
|
|
|
private boolean mFullRedrawNeeded;
|
|
|
|
public MagnifiedViewport() {
|
|
mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
|
|
mBorderWidth = (int) TypedValue.applyDimension(
|
|
TypedValue.COMPLEX_UNIT_DIP, DEFAUTLT_BORDER_WIDTH_DIP,
|
|
mContext.getResources().getDisplayMetrics());
|
|
mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2;
|
|
mWindow = new ViewportWindow(mContext);
|
|
recomputeBoundsLocked();
|
|
}
|
|
|
|
public void updateMagnificationSpecLocked(MagnificationSpec spec) {
|
|
if (spec != null) {
|
|
mMagnificationSpec.initialize(spec.scale, spec.offsetX, spec.offsetY);
|
|
} else {
|
|
mMagnificationSpec.clear();
|
|
}
|
|
// If this message is pending we are in a rotation animation and do not want
|
|
// to show the border. We will do so when the pending message is handled.
|
|
if (!mHandler.hasMessages(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)) {
|
|
setMagnifiedRegionBorderShownLocked(isMagnifyingLocked(), true);
|
|
}
|
|
}
|
|
|
|
public void recomputeBoundsLocked() {
|
|
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
|
|
final int screenWidth = mTempPoint.x;
|
|
final int screenHeight = mTempPoint.y;
|
|
|
|
Region magnifiedBounds = mMagnifiedBounds;
|
|
magnifiedBounds.set(0, 0, 0, 0);
|
|
|
|
Region availableBounds = mTempRegion1;
|
|
availableBounds.set(0, 0, screenWidth, screenHeight);
|
|
|
|
Region nonMagnifiedBounds = mTempRegion4;
|
|
nonMagnifiedBounds.set(0, 0, 0, 0);
|
|
|
|
SparseArray<WindowStateInfo> visibleWindows = mTempWindowStateInfos;
|
|
visibleWindows.clear();
|
|
getWindowsOnScreenLocked(visibleWindows);
|
|
|
|
final int visibleWindowCount = visibleWindows.size();
|
|
for (int i = visibleWindowCount - 1; i >= 0; i--) {
|
|
WindowStateInfo info = visibleWindows.valueAt(i);
|
|
if (info.mWindowState.mAttrs.type == WindowManager
|
|
.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
|
|
continue;
|
|
}
|
|
|
|
Region windowBounds = mTempRegion2;
|
|
Matrix matrix = mTempMatrix;
|
|
populateTransformationMatrix(info.mWindowState, matrix);
|
|
RectF windowFrame = mTempRectF;
|
|
|
|
if (mWindowManagerService.mPolicy.canMagnifyWindow(info.mWindowState.mAttrs.type)) {
|
|
windowFrame.set(info.mWindowState.mFrame);
|
|
windowFrame.offset(-windowFrame.left, -windowFrame.top);
|
|
matrix.mapRect(windowFrame);
|
|
windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
|
|
(int) windowFrame.right, (int) windowFrame.bottom);
|
|
magnifiedBounds.op(windowBounds, Region.Op.UNION);
|
|
magnifiedBounds.op(availableBounds, Region.Op.INTERSECT);
|
|
} else {
|
|
windowFrame.set(info.mTouchableRegion);
|
|
windowFrame.offset(-info.mWindowState.mFrame.left,
|
|
-info.mWindowState.mFrame.top);
|
|
matrix.mapRect(windowFrame);
|
|
windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
|
|
(int) windowFrame.right, (int) windowFrame.bottom);
|
|
nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
|
|
windowBounds.op(magnifiedBounds, Region.Op.DIFFERENCE);
|
|
availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
|
|
}
|
|
|
|
Region accountedBounds = mTempRegion2;
|
|
accountedBounds.set(magnifiedBounds);
|
|
accountedBounds.op(nonMagnifiedBounds, Region.Op.UNION);
|
|
accountedBounds.op(0, 0, screenWidth, screenHeight, Region.Op.INTERSECT);
|
|
|
|
if (accountedBounds.isRect()) {
|
|
Rect accountedFrame = mTempRect1;
|
|
accountedBounds.getBounds(accountedFrame);
|
|
if (accountedFrame.width() == screenWidth
|
|
&& accountedFrame.height() == screenHeight) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int i = visibleWindowCount - 1; i >= 0; i--) {
|
|
WindowStateInfo info = visibleWindows.valueAt(i);
|
|
info.recycle();
|
|
visibleWindows.removeAt(i);
|
|
}
|
|
|
|
magnifiedBounds.op(mHalfBorderWidth, mHalfBorderWidth,
|
|
screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth,
|
|
Region.Op.INTERSECT);
|
|
|
|
if (!mOldMagnifiedBounds.equals(magnifiedBounds)) {
|
|
Region bounds = Region.obtain();
|
|
bounds.set(magnifiedBounds);
|
|
mHandler.obtainMessage(MyHandler.MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED,
|
|
bounds).sendToTarget();
|
|
|
|
mWindow.setBounds(magnifiedBounds);
|
|
Rect dirtyRect = mTempRect1;
|
|
if (mFullRedrawNeeded) {
|
|
mFullRedrawNeeded = false;
|
|
dirtyRect.set(mHalfBorderWidth, mHalfBorderWidth,
|
|
screenWidth - mHalfBorderWidth, screenHeight - mHalfBorderWidth);
|
|
mWindow.invalidate(dirtyRect);
|
|
} else {
|
|
Region dirtyRegion = mTempRegion3;
|
|
dirtyRegion.set(magnifiedBounds);
|
|
dirtyRegion.op(mOldMagnifiedBounds, Region.Op.UNION);
|
|
dirtyRegion.op(nonMagnifiedBounds, Region.Op.INTERSECT);
|
|
dirtyRegion.getBounds(dirtyRect);
|
|
mWindow.invalidate(dirtyRect);
|
|
}
|
|
|
|
mOldMagnifiedBounds.set(magnifiedBounds);
|
|
}
|
|
}
|
|
|
|
private void populateTransformationMatrix(WindowState windowState, Matrix outMatrix) {
|
|
mTempFloats[Matrix.MSCALE_X] = windowState.mWinAnimator.mDsDx;
|
|
mTempFloats[Matrix.MSKEW_Y] = windowState.mWinAnimator.mDtDx;
|
|
mTempFloats[Matrix.MSKEW_X] = windowState.mWinAnimator.mDsDy;
|
|
mTempFloats[Matrix.MSCALE_Y] = windowState.mWinAnimator.mDtDy;
|
|
mTempFloats[Matrix.MTRANS_X] = windowState.mShownFrame.left;
|
|
mTempFloats[Matrix.MTRANS_Y] = windowState.mShownFrame.top;
|
|
mTempFloats[Matrix.MPERSP_0] = 0;
|
|
mTempFloats[Matrix.MPERSP_1] = 0;
|
|
mTempFloats[Matrix.MPERSP_2] = 1;
|
|
outMatrix.setValues(mTempFloats);
|
|
}
|
|
|
|
private void getWindowsOnScreenLocked(SparseArray<WindowStateInfo> outWindowStates) {
|
|
DisplayContent displayContent = mWindowManagerService.getDefaultDisplayContentLocked();
|
|
WindowList windowList = displayContent.getWindowList();
|
|
final int windowCount = windowList.size();
|
|
for (int i = 0; i < windowCount; i++) {
|
|
WindowState windowState = windowList.get(i);
|
|
if ((windowState.isOnScreen() || windowState.mAttrs.type == WindowManager
|
|
.LayoutParams.TYPE_UNIVERSE_BACKGROUND)
|
|
&& !windowState.mWinAnimator.mEnterAnimationPending) {
|
|
outWindowStates.put(windowState.mLayer, WindowStateInfo.obtain(windowState));
|
|
}
|
|
}
|
|
}
|
|
|
|
public void onRotationChangedLocked() {
|
|
// If we are magnifying, hide the magnified border window immediately so
|
|
// the user does not see strange artifacts during rotation. The screenshot
|
|
// used for rotation has already the border. After the rotation is complete
|
|
// we will show the border.
|
|
if (isMagnifyingLocked()) {
|
|
setMagnifiedRegionBorderShownLocked(false, false);
|
|
final long delay = (long) (mLongAnimationDuration
|
|
* mWindowManagerService.mWindowAnimationScale);
|
|
Message message = mHandler.obtainMessage(
|
|
MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
|
|
mHandler.sendMessageDelayed(message, delay);
|
|
}
|
|
recomputeBoundsLocked();
|
|
mWindow.updateSize();
|
|
}
|
|
|
|
public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
|
|
if (shown) {
|
|
mFullRedrawNeeded = true;
|
|
mOldMagnifiedBounds.set(0, 0, 0, 0);
|
|
}
|
|
mWindow.setShown(shown, animate);
|
|
}
|
|
|
|
public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
|
|
MagnificationSpec spec = mMagnificationSpec;
|
|
mMagnifiedBounds.getBounds(rect);
|
|
rect.offset((int) -spec.offsetX, (int) -spec.offsetY);
|
|
rect.scale(1.0f / spec.scale);
|
|
}
|
|
|
|
public boolean isMagnifyingLocked() {
|
|
return mMagnificationSpec.scale > 1.0f;
|
|
}
|
|
|
|
public MagnificationSpec getMagnificationSpecLocked() {
|
|
return mMagnificationSpec;
|
|
}
|
|
|
|
/** NOTE: This has to be called within a surface transaction. */
|
|
public void drawWindowIfNeededLocked() {
|
|
recomputeBoundsLocked();
|
|
mWindow.drawIfNeeded();
|
|
}
|
|
|
|
public void destroyWindow() {
|
|
mWindow.releaseSurface();
|
|
}
|
|
|
|
private final class ViewportWindow {
|
|
private static final String SURFACE_TITLE = "Magnification Overlay";
|
|
|
|
private static final String PROPERTY_NAME_ALPHA = "alpha";
|
|
|
|
private static final int MIN_ALPHA = 0;
|
|
private static final int MAX_ALPHA = 255;
|
|
|
|
private final Region mBounds = new Region();
|
|
private final Rect mDirtyRect = new Rect();
|
|
private final Paint mPaint = new Paint();
|
|
|
|
private final ValueAnimator mShowHideFrameAnimator;
|
|
private final Surface mSurface;
|
|
|
|
private boolean mShown;
|
|
private int mAlpha;
|
|
|
|
private boolean mInvalidated;
|
|
|
|
public ViewportWindow(Context context) {
|
|
Surface surface = null;
|
|
try {
|
|
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
|
|
surface = new Surface(mWindowManagerService.mFxSession, SURFACE_TITLE,
|
|
mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, Surface.HIDDEN);
|
|
} catch (OutOfResourcesException oore) {
|
|
/* ignore */
|
|
}
|
|
mSurface = surface;
|
|
mSurface.setLayerStack(mWindowManager.getDefaultDisplay().getLayerStack());
|
|
mSurface.setLayer(mWindowManagerService.mPolicy.windowTypeToLayerLw(
|
|
WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY)
|
|
* WindowManagerService.TYPE_LAYER_MULTIPLIER);
|
|
mSurface.setPosition(0, 0);
|
|
|
|
TypedValue typedValue = new TypedValue();
|
|
context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
|
|
typedValue, true);
|
|
final int borderColor = context.getResources().getColor(typedValue.resourceId);
|
|
|
|
mPaint.setStyle(Paint.Style.STROKE);
|
|
mPaint.setStrokeWidth(mBorderWidth);
|
|
mPaint.setColor(borderColor);
|
|
|
|
Interpolator interpolator = new DecelerateInterpolator(2.5f);
|
|
final long longAnimationDuration = context.getResources().getInteger(
|
|
com.android.internal.R.integer.config_longAnimTime);
|
|
|
|
mShowHideFrameAnimator = ObjectAnimator.ofInt(this, PROPERTY_NAME_ALPHA,
|
|
MIN_ALPHA, MAX_ALPHA);
|
|
mShowHideFrameAnimator.setInterpolator(interpolator);
|
|
mShowHideFrameAnimator.setDuration(longAnimationDuration);
|
|
mInvalidated = true;
|
|
}
|
|
|
|
public void setShown(boolean shown, boolean animate) {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
if (mShown == shown) {
|
|
return;
|
|
}
|
|
mShown = shown;
|
|
if (animate) {
|
|
if (mShowHideFrameAnimator.isRunning()) {
|
|
mShowHideFrameAnimator.reverse();
|
|
} else {
|
|
if (shown) {
|
|
mShowHideFrameAnimator.start();
|
|
} else {
|
|
mShowHideFrameAnimator.reverse();
|
|
}
|
|
}
|
|
} else {
|
|
mShowHideFrameAnimator.cancel();
|
|
if (shown) {
|
|
setAlpha(MAX_ALPHA);
|
|
} else {
|
|
setAlpha(MIN_ALPHA);
|
|
}
|
|
}
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
|
|
}
|
|
}
|
|
}
|
|
|
|
@SuppressWarnings("unused")
|
|
// Called reflectively from an animator.
|
|
public int getAlpha() {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
return mAlpha;
|
|
}
|
|
}
|
|
|
|
public void setAlpha(int alpha) {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
if (mAlpha == alpha) {
|
|
return;
|
|
}
|
|
mAlpha = alpha;
|
|
invalidate(null);
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportWindow set alpha: " + alpha);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setBounds(Region bounds) {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
if (mBounds.equals(bounds)) {
|
|
return;
|
|
}
|
|
mBounds.set(bounds);
|
|
invalidate(mDirtyRect);
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "ViewportWindow set bounds: " + bounds);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void updateSize() {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
|
|
mSurface.setSize(mTempPoint.x, mTempPoint.y);
|
|
invalidate(mDirtyRect);
|
|
}
|
|
}
|
|
|
|
public void invalidate(Rect dirtyRect) {
|
|
if (dirtyRect != null) {
|
|
mDirtyRect.set(dirtyRect);
|
|
} else {
|
|
mDirtyRect.setEmpty();
|
|
}
|
|
mInvalidated = true;
|
|
mWindowManagerService.scheduleAnimationLocked();
|
|
}
|
|
|
|
/** NOTE: This has to be called within a surface transaction. */
|
|
public void drawIfNeeded() {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
if (!mInvalidated) {
|
|
return;
|
|
}
|
|
mInvalidated = false;
|
|
Canvas canvas = null;
|
|
try {
|
|
// Empty dirty rectangle means unspecified.
|
|
if (mDirtyRect.isEmpty()) {
|
|
mBounds.getBounds(mDirtyRect);
|
|
}
|
|
mDirtyRect.inset(- mHalfBorderWidth, - mHalfBorderWidth);
|
|
canvas = mSurface.lockCanvas(mDirtyRect);
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "Dirty rect: " + mDirtyRect);
|
|
}
|
|
} catch (IllegalArgumentException iae) {
|
|
/* ignore */
|
|
} catch (OutOfResourcesException oore) {
|
|
/* ignore */
|
|
}
|
|
if (canvas == null) {
|
|
return;
|
|
}
|
|
if (DEBUG_VIEWPORT_WINDOW) {
|
|
Slog.i(LOG_TAG, "Bounds: " + mBounds);
|
|
}
|
|
canvas.drawColor(Color.TRANSPARENT, Mode.CLEAR);
|
|
mPaint.setAlpha(mAlpha);
|
|
Path path = mBounds.getBoundaryPath();
|
|
canvas.drawPath(path, mPaint);
|
|
|
|
mSurface.unlockCanvasAndPost(canvas);
|
|
|
|
if (mAlpha > 0) {
|
|
mSurface.show();
|
|
} else {
|
|
mSurface.hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void releaseSurface() {
|
|
mSurface.release();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static final class WindowStateInfo {
|
|
private static final int MAX_POOL_SIZE = 30;
|
|
|
|
private static final SimplePool<WindowStateInfo> sPool =
|
|
new SimplePool<WindowStateInfo>(MAX_POOL_SIZE);
|
|
|
|
private static final Region mTempRegion = new Region();
|
|
|
|
public WindowState mWindowState;
|
|
public final Rect mTouchableRegion = new Rect();
|
|
|
|
public static WindowStateInfo obtain(WindowState windowState) {
|
|
WindowStateInfo info = sPool.acquire();
|
|
if (info == null) {
|
|
info = new WindowStateInfo();
|
|
}
|
|
info.mWindowState = windowState;
|
|
windowState.getTouchableRegion(mTempRegion);
|
|
mTempRegion.getBounds(info.mTouchableRegion);
|
|
return info;
|
|
}
|
|
|
|
public void recycle() {
|
|
mWindowState = null;
|
|
mTouchableRegion.setEmpty();
|
|
sPool.release(this);
|
|
}
|
|
}
|
|
|
|
private class MyHandler extends Handler {
|
|
public static final int MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED = 1;
|
|
public static final int MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 2;
|
|
public static final int MESSAGE_NOTIFY_USER_CONTEXT_CHANGED = 3;
|
|
public static final int MESSAGE_NOTIFY_ROTATION_CHANGED = 4;
|
|
public static final int MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED = 5;
|
|
|
|
public MyHandler(Looper looper) {
|
|
super(looper);
|
|
}
|
|
|
|
@Override
|
|
public void handleMessage(Message message) {
|
|
switch (message.what) {
|
|
case MESSAGE_NOTIFY_MAGNIFIED_BOUNDS_CHANGED: {
|
|
Region bounds = (Region) message.obj;
|
|
try {
|
|
mCallbacks.onMagnifedBoundsChanged(bounds);
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
} finally {
|
|
bounds.recycle();
|
|
}
|
|
} break;
|
|
case MESSAGE_NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
|
|
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;
|
|
try {
|
|
mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
} finally {
|
|
args.recycle();
|
|
}
|
|
} break;
|
|
case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
|
|
try {
|
|
mCallbacks.onUserContextChanged();
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
}
|
|
} break;
|
|
case MESSAGE_NOTIFY_ROTATION_CHANGED: {
|
|
final int rotation = message.arg1;
|
|
try {
|
|
mCallbacks.onRotationChanged(rotation);
|
|
} catch (RemoteException re) {
|
|
/* ignore */
|
|
}
|
|
} break;
|
|
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
|
|
synchronized (mWindowManagerService.mWindowMap) {
|
|
if (mMagnifedViewport.isMagnifyingLocked()) {
|
|
mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
|
|
mWindowManagerService.scheduleAnimationLocked();
|
|
}
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
}
|