blob: b2c1f5ba862d79abbc70f26ccb2bea69eb4a6927 [file] [log] [blame]
/*
* Copyright (C) 2014 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 static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.utils.RegionUtils.forEachRect;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
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.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.ArraySet;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TypedValue;
import android.view.Display;
import android.view.InsetsSource;
import android.view.MagnificationSpec;
import android.view.Surface;
import android.view.Surface.OutOfResourcesException;
import android.view.SurfaceControl;
import android.view.ViewConfiguration;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import com.android.internal.R;
import com.android.internal.os.SomeArgs;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* This class contains the accessibility related logic of the window manager.
*/
final class AccessibilityController {
private final WindowManagerService mService;
private static final Rect EMPTY_RECT = new Rect();
private static final float[] sTempFloats = new float[9];
public AccessibilityController(WindowManagerService service) {
mService = service;
}
private SparseArray<DisplayMagnifier> mDisplayMagnifiers = new SparseArray<>();
private SparseArray<WindowsForAccessibilityObserver> mWindowsForAccessibilityObserver =
new SparseArray<>();
// Set to true if initializing window population complete.
private boolean mAllObserversInitialized = true;
public boolean setMagnificationCallbacksLocked(int displayId,
MagnificationCallbacks callbacks) {
boolean result = false;
if (callbacks != null) {
if (mDisplayMagnifiers.get(displayId) != null) {
throw new IllegalStateException("Magnification callbacks already set!");
}
final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
if (dc != null) {
final Display display = dc.getDisplay();
if (display != null && display.getType() != Display.TYPE_OVERLAY) {
mDisplayMagnifiers.put(displayId, new DisplayMagnifier(
mService, dc, display, callbacks));
result = true;
}
}
} else {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier == null) {
throw new IllegalStateException("Magnification callbacks already cleared!");
}
displayMagnifier.destroyLocked();
mDisplayMagnifiers.remove(displayId);
result = true;
}
return result;
}
/**
* Sets a callback for observing which windows are touchable for the purposes
* of accessibility on specified display.
*
* @param displayId The logical display id.
* @param callback The callback.
* @return {@code false} if display id is not valid or an embedded display.
*/
public boolean setWindowsForAccessibilityCallbackLocked(int displayId,
WindowsForAccessibilityCallback callback) {
final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
if (dc == null) {
return false;
}
if (callback != null) {
if (isEmbeddedDisplay(dc)) {
// If this display is an embedded one, its window observer should have been set from
// window manager after setting its parent window. But if its window observer is
// empty, that means this mapping didn't be set, and needs to do this again.
// This happened when accessibility window observer is disabled and enabled again.
if (mWindowsForAccessibilityObserver.get(displayId) == null) {
handleWindowObserverOfEmbeddedDisplayLocked(displayId, dc.getParentWindow());
}
return false;
} else if (mWindowsForAccessibilityObserver.get(displayId) != null) {
throw new IllegalStateException(
"Windows for accessibility callback of display "
+ displayId + " already set!");
}
final WindowsForAccessibilityObserver observer =
new WindowsForAccessibilityObserver(mService, displayId, callback);
mWindowsForAccessibilityObserver.put(displayId, observer);
mAllObserversInitialized &= observer.mInitialized;
} else {
if (isEmbeddedDisplay(dc)) {
// If this display is an embedded one, its window observer should be removed along
// with the window observer of its parent display removed because the window
// observer of the embedded display and its parent display is the same, and would
// be removed together when stopping the window tracking of its parent display. So
// here don't need to do removing window observer of the embedded display again.
return true;
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver == null) {
throw new IllegalStateException(
"Windows for accessibility callback of display " + displayId
+ " already cleared!");
}
removeObserverOfEmbeddedDisplay(windowsForA11yObserver);
mWindowsForAccessibilityObserver.remove(displayId);
}
return true;
}
public void performComputeChangedWindowsNotLocked(int displayId, boolean forceSend) {
WindowsForAccessibilityObserver observer = null;
synchronized (mService) {
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
observer = windowsForA11yObserver;
}
}
if (observer != null) {
observer.performComputeChangedWindowsNotLocked(forceSend);
}
}
public void setMagnificationSpecLocked(int displayId, MagnificationSpec spec) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.setMagnificationSpecLocked(spec);
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
}
}
public void getMagnificationRegionLocked(int displayId, Region outMagnificationRegion) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.getMagnificationRegionLocked(outMagnificationRegion);
}
}
public void onRectangleOnScreenRequestedLocked(int displayId, Rect rectangle) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.onRectangleOnScreenRequestedLocked(rectangle);
}
// Not relevant for the window observer.
}
public void onWindowLayersChangedLocked(int displayId) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.onWindowLayersChangedLocked();
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onRotationChangedLocked(DisplayContent displayContent) {
final int displayId = displayContent.getDisplayId();
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.onRotationChangedLocked(displayContent);
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onAppWindowTransitionLocked(int displayId, int transition) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.onAppWindowTransitionLocked(displayId, transition);
}
// Not relevant for the window observer.
}
public void onWindowTransitionLocked(WindowState windowState, int transition) {
final int displayId = windowState.getDisplayId();
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.onWindowTransitionLocked(windowState, transition);
}
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
}
}
public void onWindowFocusChangedNotLocked(int displayId) {
// Not relevant for the display magnifier.
WindowsForAccessibilityObserver observer = null;
synchronized (mService) {
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayId);
if (windowsForA11yObserver != null) {
observer = windowsForA11yObserver;
}
}
if (observer != null) {
observer.performComputeChangedWindowsNotLocked(false);
}
// Since we abandon initializing observers if no window has focus, make sure all observers
// are initialized.
sendCallbackToUninitializedObserversIfNeeded();
}
private void sendCallbackToUninitializedObserversIfNeeded() {
List<WindowsForAccessibilityObserver> unInitializedObservers;
synchronized (mService.mGlobalLock) {
if (mAllObserversInitialized) {
return;
}
if (mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus == null) {
return;
}
unInitializedObservers = new ArrayList<>();
for (int i = mWindowsForAccessibilityObserver.size() - 1; i >= 0; --i) {
final WindowsForAccessibilityObserver observer =
mWindowsForAccessibilityObserver.valueAt(i);
if (!observer.mInitialized) {
unInitializedObservers.add(observer);
}
}
// Reset the flag to record the new added observer.
mAllObserversInitialized = true;
}
boolean areAllObserversInitialized = true;
for (int i = unInitializedObservers.size() - 1; i >= 0; --i) {
final WindowsForAccessibilityObserver observer = unInitializedObservers.get(i);
observer.performComputeChangedWindowsNotLocked(true);
areAllObserversInitialized &= observer.mInitialized;
}
synchronized (mService.mGlobalLock) {
mAllObserversInitialized &= areAllObserversInitialized;
}
}
/**
* Called when the location or the size of the window is changed. Moving the window to
* another display is also taken into consideration.
* @param displayIds the display ids of displays when the situation happens.
*/
public void onSomeWindowResizedOrMovedLocked(int... displayIds) {
// Not relevant for the display magnifier.
for (int i = 0; i < displayIds.length; i++) {
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(displayIds[i]);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.scheduleComputeChangedWindowsLocked();
}
}
}
public void drawMagnifiedRegionBorderIfNeededLocked(int displayId,
SurfaceControl.Transaction t) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.drawMagnifiedRegionBorderIfNeededLocked(t);
}
// Not relevant for the window observer.
}
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
final int displayId = windowState.getDisplayId();
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
return displayMagnifier.getMagnificationSpecForWindowLocked(windowState);
}
return null;
}
public boolean hasCallbacksLocked() {
return (mDisplayMagnifiers.size() > 0
|| mWindowsForAccessibilityObserver.size() > 0);
}
public void setForceShowMagnifiableBoundsLocked(int displayId, boolean show) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
if (displayMagnifier != null) {
displayMagnifier.setForceShowMagnifiableBoundsLocked(show);
displayMagnifier.showMagnificationBoundsIfNeeded();
}
}
public void handleWindowObserverOfEmbeddedDisplayLocked(int embeddedDisplayId,
WindowState parentWindow) {
if (embeddedDisplayId == Display.DEFAULT_DISPLAY || parentWindow == null) {
return;
}
// Finds the parent display of this embedded display
final int parentDisplayId;
WindowState candidate = parentWindow;
while (candidate != null) {
parentWindow = candidate;
candidate = parentWindow.getDisplayContent().getParentWindow();
}
parentDisplayId = parentWindow.getDisplayId();
// Uses the observer of parent display
final WindowsForAccessibilityObserver windowsForA11yObserver =
mWindowsForAccessibilityObserver.get(parentDisplayId);
if (windowsForA11yObserver != null) {
windowsForA11yObserver.addEmbeddedDisplay(embeddedDisplayId);
// Replaces the observer of embedded display to the one of parent display
mWindowsForAccessibilityObserver.put(embeddedDisplayId, windowsForA11yObserver);
}
}
private static void populateTransformationMatrixLocked(WindowState windowState,
Matrix outMatrix) {
windowState.getTransformationMatrix(sTempFloats, outMatrix);
}
void dump(PrintWriter pw, String prefix) {
for (int i = 0; i < mDisplayMagnifiers.size(); i++) {
final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i);
if (displayMagnifier != null) {
displayMagnifier.dump(pw, prefix
+ "Magnification display# " + mDisplayMagnifiers.keyAt(i));
}
}
pw.println(prefix
+ "mWindowsForAccessibilityObserver=" + mWindowsForAccessibilityObserver);
}
private void removeObserverOfEmbeddedDisplay(WindowsForAccessibilityObserver
observerOfParentDisplay) {
final IntArray embeddedDisplayIdList =
observerOfParentDisplay.getAndClearEmbeddedDisplayIdList();
for (int index = 0; index < embeddedDisplayIdList.size(); index++) {
final int embeddedDisplayId = embeddedDisplayIdList.get(index);
mWindowsForAccessibilityObserver.remove(embeddedDisplayId);
}
}
private static boolean isEmbeddedDisplay(DisplayContent dc) {
final Display display = dc.getDisplay();
return display.getType() == Display.TYPE_VIRTUAL && dc.getParentWindow() != null;
}
/**
* This class encapsulates the functionality related to display magnification.
*/
private static final class DisplayMagnifier {
private static final String LOG_TAG = TAG_WITH_CLASS_NAME ? "DisplayMagnifier" : TAG_WM;
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 mDisplayContext;
private final WindowManagerService mService;
private final MagnifiedViewport mMagnifedViewport;
private final Handler mHandler;
private final DisplayContent mDisplayContent;
private final Display mDisplay;
private final MagnificationCallbacks mCallbacks;
private final long mLongAnimationDuration;
private boolean mForceShowMagnifiableBounds = false;
public DisplayMagnifier(WindowManagerService windowManagerService,
DisplayContent displayContent,
Display display,
MagnificationCallbacks callbacks) {
mDisplayContext = windowManagerService.mContext.createDisplayContext(display);
mService = windowManagerService;
mCallbacks = callbacks;
mDisplayContent = displayContent;
mDisplay = display;
mHandler = new MyHandler(mService.mH.getLooper());
mMagnifedViewport = new MagnifiedViewport();
mLongAnimationDuration = mDisplayContext.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
}
public void setMagnificationSpecLocked(MagnificationSpec spec) {
mMagnifedViewport.updateMagnificationSpecLocked(spec);
mMagnifedViewport.recomputeBoundsLocked();
mService.applyMagnificationSpecLocked(mDisplay.getDisplayId(), spec);
mService.scheduleAnimationLocked();
}
public void setForceShowMagnifiableBoundsLocked(boolean show) {
mForceShowMagnifiableBounds = show;
mMagnifedViewport.setMagnifiedRegionBorderShownLocked(show, true);
}
public boolean isForceShowingMagnifiableBoundsLocked() {
return mForceShowMagnifiableBounds;
}
public void onRectangleOnScreenRequestedLocked(Rect rectangle) {
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();
mService.scheduleAnimationLocked();
}
public void onRotationChangedLocked(DisplayContent displayContent) {
if (DEBUG_ROTATION) {
final int rotation = displayContent.getRotation();
Slog.i(LOG_TAG, "Rotation: " + Surface.rotationToString(rotation)
+ " displayId: " + displayContent.getDisplayId());
}
mMagnifedViewport.onRotationChangedLocked(displayContent.getPendingTransaction());
mHandler.sendEmptyMessage(MyHandler.MESSAGE_NOTIFY_ROTATION_CHANGED);
}
public void onAppWindowTransitionLocked(int displayId, int transition) {
if (DEBUG_WINDOW_TRANSITIONS) {
Slog.i(LOG_TAG, "Window transition: "
+ AppTransition.appTransitionToString(transition)
+ " displayId: " + displayId);
}
final boolean magnifying = mMagnifedViewport.isMagnifyingLocked();
if (magnifying) {
switch (transition) {
case WindowManager.TRANSIT_ACTIVITY_OPEN:
case WindowManager.TRANSIT_TASK_OPEN:
case WindowManager.TRANSIT_TASK_TO_FRONT:
case WindowManager.TRANSIT_WALLPAPER_OPEN:
case WindowManager.TRANSIT_WALLPAPER_CLOSE:
case WindowManager.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_DRAWN_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_ABOVE_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_APPLICATION_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_QS_DIALOG:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL: {
Rect magnifiedRegionBounds = mTempRect2;
mMagnifedViewport.getMagnifiedFrameInContentCoordsLocked(
magnifiedRegionBounds);
Rect touchableRegionBounds = mTempRect1;
windowState.getTouchableRegion(mTempRegion1);
mTempRegion1.getBounds(touchableRegionBounds);
if (!magnifiedRegionBounds.intersect(touchableRegionBounds)) {
mCallbacks.onRectangleOnScreenRequested(
touchableRegionBounds.left,
touchableRegionBounds.top,
touchableRegionBounds.right,
touchableRegionBounds.bottom);
}
} break;
} break;
}
}
}
public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
if (spec != null && !spec.isNop()) {
if (!windowState.shouldMagnify()) {
return null;
}
}
return spec;
}
public void getMagnificationRegionLocked(Region outMagnificationRegion) {
// Make sure we're working with the most current bounds
mMagnifedViewport.recomputeBoundsLocked();
mMagnifedViewport.getMagnificationRegionLocked(outMagnificationRegion);
}
public void destroyLocked() {
mMagnifedViewport.destroyWindow();
}
// Can be called outside of a surface transaction
public void showMagnificationBoundsIfNeeded() {
mHandler.obtainMessage(MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED)
.sendToTarget();
}
public void drawMagnifiedRegionBorderIfNeededLocked(SurfaceControl.Transaction t) {
mMagnifedViewport.drawWindowIfNeededLocked(t);
}
void dump(PrintWriter pw, String prefix) {
mMagnifedViewport.dump(pw, prefix);
}
private final class MagnifiedViewport {
private final SparseArray<WindowState> mTempWindowStates =
new SparseArray<WindowState>();
private final RectF mTempRectF = new RectF();
private final Point mTempPoint = new Point();
private final Matrix mTempMatrix = new Matrix();
private final Region mMagnificationRegion = new Region();
private final Region mOldMagnificationRegion = new Region();
private final Path mCircularPath;
private final MagnificationSpec mMagnificationSpec = MagnificationSpec.obtain();
private final float mBorderWidth;
private final int mHalfBorderWidth;
private final int mDrawBorderInset;
private final ViewportWindow mWindow;
private boolean mFullRedrawNeeded;
private int mTempLayer = 0;
public MagnifiedViewport() {
mBorderWidth = mDisplayContext.getResources().getDimension(
com.android.internal.R.dimen.accessibility_magnification_indicator_width);
mHalfBorderWidth = (int) Math.ceil(mBorderWidth / 2);
mDrawBorderInset = (int) mBorderWidth / 2;
mWindow = new ViewportWindow(mDisplayContext);
if (mDisplayContext.getResources().getConfiguration().isScreenRound()) {
mCircularPath = new Path();
mDisplay.getRealSize(mTempPoint);
final int centerXY = mTempPoint.x / 2;
mCircularPath.addCircle(centerXY, centerXY, centerXY, Path.Direction.CW);
} else {
mCircularPath = null;
}
recomputeBoundsLocked();
}
public void getMagnificationRegionLocked(@NonNull Region outMagnificationRegion) {
outMagnificationRegion.set(mMagnificationRegion);
}
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() || isForceShowingMagnifiableBoundsLocked(), true);
}
}
public void recomputeBoundsLocked() {
mDisplay.getRealSize(mTempPoint);
final int screenWidth = mTempPoint.x;
final int screenHeight = mTempPoint.y;
mMagnificationRegion.set(0, 0, 0, 0);
final Region availableBounds = mTempRegion1;
availableBounds.set(0, 0, screenWidth, screenHeight);
if (mCircularPath != null) {
availableBounds.setPath(mCircularPath, availableBounds);
}
Region nonMagnifiedBounds = mTempRegion4;
nonMagnifiedBounds.set(0, 0, 0, 0);
SparseArray<WindowState> visibleWindows = mTempWindowStates;
visibleWindows.clear();
populateWindowsOnScreenLocked(visibleWindows);
final int visibleWindowCount = visibleWindows.size();
for (int i = visibleWindowCount - 1; i >= 0; i--) {
WindowState windowState = visibleWindows.valueAt(i);
if ((windowState.mAttrs.type == TYPE_MAGNIFICATION_OVERLAY)
|| ((windowState.mAttrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0)) {
continue;
}
// Consider the touchable portion of the window
Matrix matrix = mTempMatrix;
populateTransformationMatrixLocked(windowState, matrix);
Region touchableRegion = mTempRegion3;
windowState.getTouchableRegion(touchableRegion);
Rect touchableFrame = mTempRect1;
touchableRegion.getBounds(touchableFrame);
RectF windowFrame = mTempRectF;
windowFrame.set(touchableFrame);
windowFrame.offset(-windowState.getFrameLw().left,
-windowState.getFrameLw().top);
matrix.mapRect(windowFrame);
Region windowBounds = mTempRegion2;
windowBounds.set((int) windowFrame.left, (int) windowFrame.top,
(int) windowFrame.right, (int) windowFrame.bottom);
// Only update new regions
Region portionOfWindowAlreadyAccountedFor = mTempRegion3;
portionOfWindowAlreadyAccountedFor.set(mMagnificationRegion);
portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
if (windowState.shouldMagnify()) {
mMagnificationRegion.op(windowBounds, Region.Op.UNION);
mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
} else {
nonMagnifiedBounds.op(windowBounds, Region.Op.UNION);
availableBounds.op(windowBounds, Region.Op.DIFFERENCE);
}
// If the navigation bar window doesn't have touchable region, count
// navigation bar insets into nonMagnifiedBounds. It happens when
// navigation mode is gestural.
if (isUntouchableNavigationBar(windowState, mTempRegion3)) {
final Rect navBarInsets = getNavBarInsets(mDisplayContent);
nonMagnifiedBounds.op(navBarInsets, Region.Op.UNION);
availableBounds.op(navBarInsets, Region.Op.DIFFERENCE);
}
// Count letterbox into nonMagnifiedBounds
if (windowState.isLetterboxedForDisplayCutoutLw()) {
Region letterboxBounds = getLetterboxBounds(windowState);
nonMagnifiedBounds.op(letterboxBounds, Region.Op.UNION);
availableBounds.op(letterboxBounds, Region.Op.DIFFERENCE);
}
// Update accounted bounds
Region accountedBounds = mTempRegion2;
accountedBounds.set(mMagnificationRegion);
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;
}
}
}
visibleWindows.clear();
mMagnificationRegion.op(mDrawBorderInset, mDrawBorderInset,
screenWidth - mDrawBorderInset, screenHeight - mDrawBorderInset,
Region.Op.INTERSECT);
final boolean magnifiedChanged =
!mOldMagnificationRegion.equals(mMagnificationRegion);
if (magnifiedChanged) {
mWindow.setBounds(mMagnificationRegion);
final Rect dirtyRect = mTempRect1;
if (mFullRedrawNeeded) {
mFullRedrawNeeded = false;
dirtyRect.set(mDrawBorderInset, mDrawBorderInset,
screenWidth - mDrawBorderInset,
screenHeight - mDrawBorderInset);
mWindow.invalidate(dirtyRect);
} else {
final Region dirtyRegion = mTempRegion3;
dirtyRegion.set(mMagnificationRegion);
dirtyRegion.op(mOldMagnificationRegion, Region.Op.XOR);
dirtyRegion.getBounds(dirtyRect);
mWindow.invalidate(dirtyRect);
}
mOldMagnificationRegion.set(mMagnificationRegion);
final SomeArgs args = SomeArgs.obtain();
args.arg1 = Region.obtain(mMagnificationRegion);
mHandler.obtainMessage(
MyHandler.MESSAGE_NOTIFY_MAGNIFICATION_REGION_CHANGED, args)
.sendToTarget();
}
}
private Region getLetterboxBounds(WindowState windowState) {
final ActivityRecord appToken = windowState.mActivityRecord;
if (appToken == null) {
return new Region();
}
mDisplay.getRealSize(mTempPoint);
final Rect letterboxInsets = appToken.getLetterboxInsets();
final int screenWidth = mTempPoint.x;
final int screenHeight = mTempPoint.y;
final Rect nonLetterboxRect = mTempRect1;
final Region letterboxBounds = mTempRegion3;
nonLetterboxRect.set(0, 0, screenWidth, screenHeight);
nonLetterboxRect.inset(letterboxInsets);
letterboxBounds.set(0, 0, screenWidth, screenHeight);
letterboxBounds.op(nonLetterboxRect, Region.Op.DIFFERENCE);
return letterboxBounds;
}
public void onRotationChangedLocked(SurfaceControl.Transaction t) {
// If we are showing the magnification border, hide it immediately so
// the user does not see strange artifacts during rotation. The screenshot
// used for rotation already has the border. After the rotation is complete
// we will show the border.
if (isMagnifyingLocked() || isForceShowingMagnifiableBoundsLocked()) {
setMagnifiedRegionBorderShownLocked(false, false);
final long delay = (long) (mLongAnimationDuration
* mService.getWindowAnimationScaleLocked());
Message message = mHandler.obtainMessage(
MyHandler.MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED);
mHandler.sendMessageDelayed(message, delay);
}
recomputeBoundsLocked();
mWindow.updateSize(t);
}
public void setMagnifiedRegionBorderShownLocked(boolean shown, boolean animate) {
if (shown) {
mFullRedrawNeeded = true;
mOldMagnificationRegion.set(0, 0, 0, 0);
}
mWindow.setShown(shown, animate);
}
public void getMagnifiedFrameInContentCoordsLocked(Rect rect) {
MagnificationSpec spec = mMagnificationSpec;
mMagnificationRegion.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;
}
public void drawWindowIfNeededLocked(SurfaceControl.Transaction t) {
recomputeBoundsLocked();
mWindow.drawIfNeeded(t);
}
public void destroyWindow() {
mWindow.releaseSurface();
}
private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
mTempLayer = 0;
mDisplayContent.forAllWindows((w) -> {
if (w.isOnScreen() && w.isVisibleLw()
&& (w.mAttrs.alpha != 0)) {
mTempLayer++;
outWindows.put(mTempLayer, w);
}
}, false /* traverseTopToBottom */ );
}
void dump(PrintWriter pw, String prefix) {
mWindow.dump(pw, prefix);
}
private final class ViewportWindow {
private static final String SURFACE_TITLE = "Magnification Overlay";
private final Region mBounds = new Region();
private final Rect mDirtyRect = new Rect();
private final Paint mPaint = new Paint();
private final SurfaceControl mSurfaceControl;
private final Surface mSurface = mService.mSurfaceFactory.get();
private final AnimationController mAnimationController;
private boolean mShown;
private int mAlpha;
private boolean mInvalidated;
public ViewportWindow(Context context) {
SurfaceControl surfaceControl = null;
try {
mDisplay.getRealSize(mTempPoint);
surfaceControl = mDisplayContent
.makeOverlay()
.setName(SURFACE_TITLE)
.setBufferSize(mTempPoint.x, mTempPoint.y) // not a typo
.setFormat(PixelFormat.TRANSLUCENT)
.setCallsite("ViewportWindow")
.build();
} catch (OutOfResourcesException oore) {
/* ignore */
}
mSurfaceControl = surfaceControl;
final SurfaceControl.Transaction t = mService.mTransactionFactory.get();
final int layer =
mService.mPolicy.getWindowLayerFromTypeLw(TYPE_MAGNIFICATION_OVERLAY) *
WindowManagerService.TYPE_LAYER_MULTIPLIER;
t.setLayer(mSurfaceControl, layer).setPosition(mSurfaceControl, 0, 0);
InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
mDisplayContent.getDisplayId(), "Magnification Overlay");
t.apply();
mSurface.copyFrom(mSurfaceControl);
mAnimationController = new AnimationController(context,
mService.mH.getLooper());
TypedValue typedValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorActivatedHighlight,
typedValue, true);
final int borderColor = context.getColor(typedValue.resourceId);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mBorderWidth);
mPaint.setColor(borderColor);
mInvalidated = true;
}
public void setShown(boolean shown, boolean animate) {
synchronized (mService.mGlobalLock) {
if (mShown == shown) {
return;
}
mShown = shown;
mAnimationController.onFrameShownStateChanged(shown, animate);
if (DEBUG_VIEWPORT_WINDOW) {
Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
}
}
}
@SuppressWarnings("unused")
// Called reflectively from an animator.
public int getAlpha() {
synchronized (mService.mGlobalLock) {
return mAlpha;
}
}
public void setAlpha(int alpha) {
synchronized (mService.mGlobalLock) {
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 (mService.mGlobalLock) {
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(SurfaceControl.Transaction t) {
synchronized (mService.mGlobalLock) {
mDisplay.getRealSize(mTempPoint);
t.setBufferSize(mSurfaceControl, mTempPoint.x, mTempPoint.y);
invalidate(mDirtyRect);
}
}
public void invalidate(Rect dirtyRect) {
if (dirtyRect != null) {
mDirtyRect.set(dirtyRect);
} else {
mDirtyRect.setEmpty();
}
mInvalidated = true;
mService.scheduleAnimationLocked();
}
public void drawIfNeeded(SurfaceControl.Transaction t) {
synchronized (mService.mGlobalLock) {
if (!mInvalidated) {
return;
}
mInvalidated = false;
if (mAlpha > 0) {
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 (Surface.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);
t.show(mSurfaceControl);
} else {
t.hide(mSurfaceControl);
}
}
}
public void releaseSurface() {
mService.mTransactionFactory.get().remove(mSurfaceControl).apply();
mSurface.release();
}
void dump(PrintWriter pw, String prefix) {
pw.println(prefix
+ " mBounds= " + mBounds
+ " mDirtyRect= " + mDirtyRect
+ " mWidth= " + mSurfaceControl.getWidth()
+ " mHeight= " + mSurfaceControl.getHeight());
}
private final class AnimationController extends Handler {
private static final String PROPERTY_NAME_ALPHA = "alpha";
private static final int MIN_ALPHA = 0;
private static final int MAX_ALPHA = 255;
private static final int MSG_FRAME_SHOWN_STATE_CHANGED = 1;
private final ValueAnimator mShowHideFrameAnimator;
public AnimationController(Context context, Looper looper) {
super(looper);
mShowHideFrameAnimator = ObjectAnimator.ofInt(ViewportWindow.this,
PROPERTY_NAME_ALPHA, MIN_ALPHA, MAX_ALPHA);
Interpolator interpolator = new DecelerateInterpolator(2.5f);
final long longAnimationDuration = context.getResources().getInteger(
com.android.internal.R.integer.config_longAnimTime);
mShowHideFrameAnimator.setInterpolator(interpolator);
mShowHideFrameAnimator.setDuration(longAnimationDuration);
}
public void onFrameShownStateChanged(boolean shown, boolean animate) {
obtainMessage(MSG_FRAME_SHOWN_STATE_CHANGED,
shown ? 1 : 0, animate ? 1 : 0).sendToTarget();
}
@Override
public void handleMessage(Message message) {
switch (message.what) {
case MSG_FRAME_SHOWN_STATE_CHANGED: {
final boolean shown = message.arg1 == 1;
final boolean animate = message.arg2 == 1;
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);
}
}
} break;
}
}
}
}
}
private class MyHandler extends Handler {
public static final int MESSAGE_NOTIFY_MAGNIFICATION_REGION_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_MAGNIFICATION_REGION_CHANGED: {
final SomeArgs args = (SomeArgs) message.obj;
final Region magnifiedBounds = (Region) args.arg1;
mCallbacks.onMagnificationRegionChanged(magnifiedBounds);
magnifiedBounds.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;
mCallbacks.onRectangleOnScreenRequested(left, top, right, bottom);
args.recycle();
} break;
case MESSAGE_NOTIFY_USER_CONTEXT_CHANGED: {
mCallbacks.onUserContextChanged();
} break;
case MESSAGE_NOTIFY_ROTATION_CHANGED: {
final int rotation = message.arg1;
mCallbacks.onRotationChanged(rotation);
} break;
case MESSAGE_SHOW_MAGNIFIED_REGION_BOUNDS_IF_NEEDED : {
synchronized (mService.mGlobalLock) {
if (mMagnifedViewport.isMagnifyingLocked()
|| isForceShowingMagnifiableBoundsLocked()) {
mMagnifedViewport.setMagnifiedRegionBorderShownLocked(true, true);
mService.scheduleAnimationLocked();
}
}
} break;
}
}
}
}
static boolean isUntouchableNavigationBar(WindowState windowState,
Region touchableRegion) {
if (windowState.mAttrs.type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR) {
return false;
}
// Gets the touchable region.
windowState.getTouchableRegion(touchableRegion);
return touchableRegion.isEmpty();
}
static Rect getNavBarInsets(DisplayContent displayContent) {
final InsetsSource source = displayContent.getInsetsStateController().getRawInsetsState()
.peekSource(ITYPE_NAVIGATION_BAR);
return source != null ? source.getFrame() : EMPTY_RECT;
}
/**
* This class encapsulates the functionality related to computing the windows
* reported for accessibility purposes. These windows are all windows a sighted
* user can see on the screen.
*/
private static final class WindowsForAccessibilityObserver {
private static final String LOG_TAG = TAG_WITH_CLASS_NAME ?
"WindowsForAccessibilityObserver" : TAG_WM;
private static final boolean DEBUG = false;
private final SparseArray<WindowState> mTempWindowStates = new SparseArray<>();
private final Set<IBinder> mTempBinderSet = new ArraySet<>();
private final RectF mTempRectF = new RectF();
private final Matrix mTempMatrix = new Matrix();
private final Point mTempPoint = new Point();
private final Region mTempRegion = new Region();
private final Region mTempRegion1 = new Region();
private final WindowManagerService mService;
private final Handler mHandler;
private final WindowsForAccessibilityCallback mCallback;
private final int mDisplayId;
private final long mRecurringAccessibilityEventsIntervalMillis;
private final IntArray mEmbeddedDisplayIdList = new IntArray(0);
// Set to true if initializing window population complete.
private boolean mInitialized;
public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
int displayId,
WindowsForAccessibilityCallback callback) {
mService = windowManagerService;
mCallback = callback;
mDisplayId = displayId;
mHandler = new MyHandler(mService.mH.getLooper());
mRecurringAccessibilityEventsIntervalMillis = ViewConfiguration
.getSendRecurringAccessibilityEventsInterval();
computeChangedWindows(true);
}
public void performComputeChangedWindowsNotLocked(boolean forceSend) {
mHandler.removeMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS);
computeChangedWindows(forceSend);
}
public void scheduleComputeChangedWindowsLocked() {
if (!mHandler.hasMessages(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS)) {
mHandler.sendEmptyMessageDelayed(MyHandler.MESSAGE_COMPUTE_CHANGED_WINDOWS,
mRecurringAccessibilityEventsIntervalMillis);
}
}
IntArray getAndClearEmbeddedDisplayIdList() {
final IntArray returnedArray = new IntArray(mEmbeddedDisplayIdList.size());
returnedArray.addAll(mEmbeddedDisplayIdList);
mEmbeddedDisplayIdList.clear();
return returnedArray;
}
void addEmbeddedDisplay(int displayId) {
if (displayId == mDisplayId) {
return;
}
mEmbeddedDisplayIdList.add(displayId);
}
/**
* Check if windows have changed, and send them to the accessibility subsystem if they have.
*
* @param forceSend Send the windows the accessibility even if they haven't changed.
*/
public void computeChangedWindows(boolean forceSend) {
if (DEBUG) {
Slog.i(LOG_TAG, "computeChangedWindows()");
}
List<WindowInfo> windows = new ArrayList<>();
final int topFocusedDisplayId;
IBinder topFocusedWindowToken = null;
synchronized (mService.mGlobalLock) {
// Do not send the windows if there is no top focus as
// the window manager is still looking for where to put it.
// We will do the work when we get a focus change callback.
final WindowState topFocusedWindowState = getTopFocusWindow();
if (topFocusedWindowState == null) {
if (DEBUG) {
Slog.d(LOG_TAG, "top focused window is null, compute it again later");
}
return;
}
final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
if (dc == null) {
//It should not happen because it is created while adding the callback.
Slog.w(LOG_TAG, "display content is null, should be created later");
return;
}
final Display display = dc.getDisplay();
display.getRealSize(mTempPoint);
final int screenWidth = mTempPoint.x;
final int screenHeight = mTempPoint.y;
Region unaccountedSpace = mTempRegion;
unaccountedSpace.set(0, 0, screenWidth, screenHeight);
final SparseArray<WindowState> visibleWindows = mTempWindowStates;
populateVisibleWindowsOnScreenLocked(visibleWindows);
Set<IBinder> addedWindows = mTempBinderSet;
addedWindows.clear();
boolean focusedWindowAdded = false;
final int visibleWindowCount = visibleWindows.size();
HashSet<Integer> skipRemainingWindowsForTasks = new HashSet<>();
// Iterate until we figure out what is touchable for the entire screen.
for (int i = visibleWindowCount - 1; i >= 0; i--) {
final WindowState windowState = visibleWindows.valueAt(i);
final Region regionInScreen = new Region();
computeWindowRegionInScreen(windowState, regionInScreen);
if (windowMattersToAccessibility(windowState, regionInScreen, unaccountedSpace,
skipRemainingWindowsForTasks)) {
addPopulatedWindowInfo(windowState, regionInScreen, windows, addedWindows);
updateUnaccountedSpace(windowState, regionInScreen, unaccountedSpace,
skipRemainingWindowsForTasks);
focusedWindowAdded |= windowState.isFocused();
} else if (isUntouchableNavigationBar(windowState, mTempRegion1)) {
// If this widow is navigation bar without touchable region, accounting the
// region of navigation bar inset because all touch events from this region
// would be received by launcher, i.e. this region is a un-touchable one
// for the application.
unaccountedSpace.op(getNavBarInsets(dc), unaccountedSpace,
Region.Op.REVERSE_DIFFERENCE);
}
if (unaccountedSpace.isEmpty() && focusedWindowAdded) {
break;
}
}
for (int i = dc.mShellRoots.size() - 1; i >= 0; --i) {
final WindowInfo info = dc.mShellRoots.valueAt(i).getWindowInfo();
if (info == null) {
continue;
}
info.layer = addedWindows.size();
windows.add(info);
addedWindows.add(info.token);
}
// Remove child/parent references to windows that were not added.
final int windowCount = windows.size();
for (int i = 0; i < windowCount; i++) {
WindowInfo window = windows.get(i);
if (!addedWindows.contains(window.parentToken)) {
window.parentToken = null;
}
if (window.childTokens != null) {
final int childTokenCount = window.childTokens.size();
for (int j = childTokenCount - 1; j >= 0; j--) {
if (!addedWindows.contains(window.childTokens.get(j))) {
window.childTokens.remove(j);
}
}
// Leave the child token list if empty.
}
}
visibleWindows.clear();
addedWindows.clear();
// Gets the top focused display Id and window token for supporting multi-display.
topFocusedDisplayId = mService.mRoot.getTopFocusedDisplayContent().getDisplayId();
topFocusedWindowToken = topFocusedWindowState.mClient.asBinder();
}
mCallback.onWindowsForAccessibilityChanged(forceSend, topFocusedDisplayId,
topFocusedWindowToken, windows);
// Recycle the windows as we do not need them.
clearAndRecycleWindows(windows);
mInitialized = true;
}
private boolean windowMattersToAccessibility(WindowState windowState,
Region regionInScreen, Region unaccountedSpace,
HashSet<Integer> skipRemainingWindowsForTasks) {
final RecentsAnimationController controller = mService.getRecentsAnimationController();
if (controller != null && controller.shouldIgnoreForAccessibility(windowState)) {
return false;
}
if (windowState.isFocused()) {
return true;
}
// If the window is part of a task that we're finished with - ignore.
final Task task = windowState.getTask();
if (task != null && skipRemainingWindowsForTasks.contains(task.mTaskId)) {
return false;
}
// Ignore non-touchable windows, except the split-screen divider, which is
// occasionally non-touchable but still useful for identifying split-screen
// mode.
if (((windowState.mAttrs.flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
&& (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
return false;
}
// If the window is completely covered by other windows - ignore.
if (unaccountedSpace.quickReject(regionInScreen)) {
return false;
}
// Add windows of certain types not covered by modal windows.
if (isReportedWindowType(windowState.mAttrs.type)) {
return true;
}
return false;
}
private void updateUnaccountedSpace(WindowState windowState, Region regionInScreen,
Region unaccountedSpace, HashSet<Integer> skipRemainingWindowsForTasks) {
if (windowState.mAttrs.type
!= WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY) {
// Account for the space this window takes if the window
// is not an accessibility overlay which does not change
// the reported windows.
unaccountedSpace.op(regionInScreen, unaccountedSpace,
Region.Op.REVERSE_DIFFERENCE);
// If a window is modal it prevents other windows from being touched
if ((windowState.mAttrs.flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)) == 0) {
if (!windowState.hasTapExcludeRegion()) {
// Account for all space in the task, whether the windows in it are
// touchable or not. The modal window blocks all touches from the task's
// area.
unaccountedSpace.op(windowState.getDisplayFrameLw(), unaccountedSpace,
Region.Op.REVERSE_DIFFERENCE);
} else {
// If a window has tap exclude region, we need to account it.
final Region displayRegion = new Region(windowState.getDisplayFrameLw());
final Region tapExcludeRegion = new Region();
windowState.getTapExcludeRegion(tapExcludeRegion);
displayRegion.op(tapExcludeRegion, displayRegion,
Region.Op.REVERSE_DIFFERENCE);
unaccountedSpace.op(displayRegion, unaccountedSpace,
Region.Op.REVERSE_DIFFERENCE);
}
final Task task = windowState.getTask();
if (task != null) {
// If the window is associated with a particular task, we can skip the
// rest of the windows for that task.
skipRemainingWindowsForTasks.add(task.mTaskId);
} else if (!windowState.hasTapExcludeRegion()) {
// If the window is not associated with a particular task, then it is
// globally modal. In this case we can skip all remaining windows when
// it doesn't has tap exclude region.
unaccountedSpace.setEmpty();
}
}
}
}
private void computeWindowRegionInScreen(WindowState windowState, Region outRegion) {
// Get the touchable frame.
Region touchableRegion = mTempRegion1;
windowState.getTouchableRegion(touchableRegion);
// Map the frame to get what appears on the screen.
Matrix matrix = mTempMatrix;
populateTransformationMatrixLocked(windowState, matrix);
forEachRect(touchableRegion, rect -> {
// Move to origin as all transforms are captured by the matrix.
RectF windowFrame = mTempRectF;
windowFrame.set(rect);
windowFrame.offset(-windowState.getFrameLw().left, -windowState.getFrameLw().top);
matrix.mapRect(windowFrame);
// Union all rects.
outRegion.union(new Rect((int) windowFrame.left, (int) windowFrame.top,
(int) windowFrame.right, (int) windowFrame.bottom));
});
}
private static void addPopulatedWindowInfo(WindowState windowState, Region regionInScreen,
List<WindowInfo> out, Set<IBinder> tokenOut) {
final WindowInfo window = windowState.getWindowInfo();
window.regionInScreen.set(regionInScreen);
window.layer = tokenOut.size();
out.add(window);
tokenOut.add(window.token);
}
private static void clearAndRecycleWindows(List<WindowInfo> windows) {
final int windowCount = windows.size();
for (int i = windowCount - 1; i >= 0; i--) {
windows.remove(i).recycle();
}
}
private static boolean isReportedWindowType(int windowType) {
return (windowType != WindowManager.LayoutParams.TYPE_WALLPAPER
&& windowType != WindowManager.LayoutParams.TYPE_BOOT_PROGRESS
&& windowType != WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY
&& windowType != WindowManager.LayoutParams.TYPE_DRAG
&& windowType != WindowManager.LayoutParams.TYPE_INPUT_CONSUMER
&& windowType != WindowManager.LayoutParams.TYPE_POINTER
&& windowType != TYPE_MAGNIFICATION_OVERLAY
&& windowType != WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY
&& windowType != WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY
&& windowType != WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION);
}
private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
final List<WindowState> tempWindowStatesList = new ArrayList<>();
final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId);
if (dc == null) {
return;
}
dc.forAllWindows(w -> {
if (w.isVisibleLw()) {
tempWindowStatesList.add(w);
}
}, false /* traverseTopToBottom */);
// Insert the re-parented windows in another display below their parents in
// default display.
mService.mRoot.forAllWindows(w -> {
final WindowState parentWindow = findRootDisplayParentWindow(w);
if (parentWindow == null) {
return;
}
if (w.isVisibleLw() && tempWindowStatesList.contains(parentWindow)) {
tempWindowStatesList.add(tempWindowStatesList.lastIndexOf(parentWindow), w);
}
}, false /* traverseTopToBottom */);
for (int i = 0; i < tempWindowStatesList.size(); i++) {
outWindows.put(i, tempWindowStatesList.get(i));
}
}
private WindowState findRootDisplayParentWindow(WindowState win) {
WindowState displayParentWindow = win.getDisplayContent().getParentWindow();
if (displayParentWindow == null) {
return null;
}
WindowState candidate = displayParentWindow;
while (candidate != null) {
displayParentWindow = candidate;
candidate = displayParentWindow.getDisplayContent().getParentWindow();
}
return displayParentWindow;
}
private WindowState getTopFocusWindow() {
return mService.mRoot.getTopFocusedDisplayContent().mCurrentFocus;
}
@Override
public String toString() {
return "WindowsForAccessibilityObserver{"
+ "mDisplayId=" + mDisplayId
+ ", mEmbeddedDisplayIdList="
+ Arrays.toString(mEmbeddedDisplayIdList.toArray())
+ ", mInitialized=" + mInitialized
+ '}';
}
private class MyHandler extends Handler {
public static final int MESSAGE_COMPUTE_CHANGED_WINDOWS = 1;
public MyHandler(Looper looper) {
super(looper, null, false);
}
@Override
@SuppressWarnings("unchecked")
public void handleMessage(Message message) {
switch (message.what) {
case MESSAGE_COMPUTE_CHANGED_WINDOWS: {
computeChangedWindows(false);
} break;
}
}
}
}
}