blob: 468e93a8f683c14d875056ddebaa62bd9bc6363e [file] [log] [blame]
/*
* Copyright (C) 2019 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 static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED;
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Region;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindow;
import android.view.WindowInfo;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.accessibility.AccessibilitySecurityPolicy.AccessibilityUserManager;
import com.android.server.wm.WindowManagerInternal;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* This class provides APIs for accessibility manager to manage {@link AccessibilityWindowInfo}s and
* {@link WindowInfo}s.
*/
public class AccessibilityWindowManager {
private static final String LOG_TAG = "AccessibilityWindowManager";
private static final boolean DEBUG = false;
private static int sNextWindowId;
private final Object mLock;
private final Handler mHandler;
private final WindowManagerInternal mWindowManagerInternal;
private final AccessibilityEventSender mAccessibilityEventSender;
private final AccessibilitySecurityPolicy mSecurityPolicy;
private final AccessibilityUserManager mAccessibilityUserManager;
// Connections and window tokens for cross-user windows
private final SparseArray<RemoteAccessibilityConnection>
mGlobalInteractionConnections = new SparseArray<>();
private final SparseArray<IBinder> mGlobalWindowTokens = new SparseArray<>();
// Connections and window tokens for per-user windows, indexed as one sparse array per user
private final SparseArray<SparseArray<RemoteAccessibilityConnection>>
mInteractionConnections = new SparseArray<>();
private final SparseArray<SparseArray<IBinder>> mWindowTokens = new SparseArray<>();
private RemoteAccessibilityConnection mPictureInPictureActionReplacingConnection;
// There is only one active window in the system. It is updated when the top focused window
// of the top focused display changes and when we receive a TYPE_WINDOW_STATE_CHANGED event.
private int mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
// There is only one top focused window in the system. It is updated when the window manager
// updates the window lists.
private int mTopFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
private int mAccessibilityFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
private long mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
// The top focused display and window token updated with the callback of window lists change.
private int mTopFocusedDisplayId;
private IBinder mTopFocusedWindowToken;
// The display has the accessibility focused window currently.
private int mAccessibilityFocusedDisplayId = Display.INVALID_DISPLAY;
private boolean mTouchInteractionInProgress;
/** List of Display Windows Observer, mapping from displayId -> DisplayWindowsObserver. */
private final SparseArray<DisplayWindowsObserver> mDisplayWindowsObservers =
new SparseArray<>();
/**
* Map of host view and embedded hierarchy, mapping from leash token of its ViewRootImpl.
* The key is the token from embedded hierarchy, and the value is the token from its host.
*/
private final ArrayMap<IBinder, IBinder> mHostEmbeddedMap = new ArrayMap<>();
/**
* Map of window id and view hierarchy.
* The key is the window id when the ViewRootImpl register to accessibility, and the value is
* its leash token.
*/
private final SparseArray<IBinder> mWindowIdMap = new SparseArray<>();
/**
* This class implements {@link WindowManagerInternal.WindowsForAccessibilityCallback} to
* receive {@link WindowInfo}s from window manager when there's an accessibility change in
* window and holds window lists information per display.
*/
private final class DisplayWindowsObserver implements
WindowManagerInternal.WindowsForAccessibilityCallback {
private final int mDisplayId;
private final SparseArray<AccessibilityWindowInfo> mA11yWindowInfoById =
new SparseArray<>();
private final SparseArray<WindowInfo> mWindowInfoById = new SparseArray<>();
private final List<WindowInfo> mCachedWindowInfos = new ArrayList<>();
private List<AccessibilityWindowInfo> mWindows;
private boolean mTrackingWindows = false;
private boolean mHasWatchOutsideTouchWindow;
/**
* Constructor for DisplayWindowsObserver.
*/
DisplayWindowsObserver(int displayId) {
mDisplayId = displayId;
}
/**
* Starts tracking windows changes from window manager by registering callback.
*
* @return true if callback registers successful.
*/
boolean startTrackingWindowsLocked() {
boolean result = true;
if (!mTrackingWindows) {
// Turns on the flag before setup the callback.
// In some cases, onWindowsForAccessibilityChanged will be called immediately in
// setWindowsForAccessibilityCallback. We'll lost windows if flag is false.
mTrackingWindows = true;
result = mWindowManagerInternal.setWindowsForAccessibilityCallback(
mDisplayId, this);
if (!result) {
mTrackingWindows = false;
Slog.w(LOG_TAG, "set windowsObserver callbacks fail, displayId:"
+ mDisplayId);
}
}
return result;
}
/**
* Stops tracking windows changes from window manager, and clear all windows info.
*/
void stopTrackingWindowsLocked() {
if (mTrackingWindows) {
mWindowManagerInternal.setWindowsForAccessibilityCallback(
mDisplayId, null);
mTrackingWindows = false;
clearWindowsLocked();
}
}
/**
* Returns true if windows changes tracking.
*
* @return true if windows changes tracking
*/
boolean isTrackingWindowsLocked() {
return mTrackingWindows;
}
/**
* Returns accessibility windows.
* @return accessibility windows.
*/
@Nullable
List<AccessibilityWindowInfo> getWindowListLocked() {
return mWindows;
}
/**
* Returns accessibility window info according to given windowId.
*
* @param windowId The windowId
* @return The accessibility window info
*/
@Nullable
AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) {
return mA11yWindowInfoById.get(windowId);
}
/**
* Returns the window info according to given windowId.
*
* @param windowId The windowId
* @return The window info
*/
@Nullable
WindowInfo findWindowInfoByIdLocked(int windowId) {
return mWindowInfoById.get(windowId);
}
/**
* Returns {@link AccessibilityWindowInfo} of PIP window.
*
* @return PIP accessibility window info
*/
@Nullable
AccessibilityWindowInfo getPictureInPictureWindowLocked() {
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
final AccessibilityWindowInfo window = mWindows.get(i);
if (window.isInPictureInPictureMode()) {
return window;
}
}
}
return null;
}
/**
* Sets the active flag of the window according to given windowId, others set to inactive.
*
* @param windowId The windowId
*/
void setActiveWindowLocked(int windowId) {
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
if (window.getId() == windowId) {
window.setActive(true);
mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
AccessibilityEvent.obtainWindowsChangedEvent(windowId,
AccessibilityEvent.WINDOWS_CHANGE_ACTIVE));
} else {
window.setActive(false);
}
}
}
}
/**
* Sets the window accessibility focused according to given windowId, others set
* unfocused.
*
* @param windowId The windowId
*/
void setAccessibilityFocusedWindowLocked(int windowId) {
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int i = 0; i < windowCount; i++) {
AccessibilityWindowInfo window = mWindows.get(i);
if (window.getId() == windowId) {
mAccessibilityFocusedDisplayId = mDisplayId;
window.setAccessibilityFocused(true);
mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
AccessibilityEvent.obtainWindowsChangedEvent(
windowId, WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
} else {
window.setAccessibilityFocused(false);
}
}
}
}
/**
* Computes partial interactive region of given windowId.
*
* @param windowId The windowId
* @param outRegion The output to which to write the bounds.
* @return true if outRegion is not empty.
*/
boolean computePartialInteractiveRegionForWindowLocked(int windowId,
@NonNull Region outRegion) {
if (mWindows == null) {
return false;
}
// Windows are ordered in z order so start from the bottom and find
// the window of interest. After that all windows that cover it should
// be subtracted from the resulting region. Note that for accessibility
// we are returning only interactive windows.
Region windowInteractiveRegion = null;
boolean windowInteractiveRegionChanged = false;
final int windowCount = mWindows.size();
final Region currentWindowRegions = new Region();
for (int i = windowCount - 1; i >= 0; i--) {
AccessibilityWindowInfo currentWindow = mWindows.get(i);
if (windowInteractiveRegion == null) {
if (currentWindow.getId() == windowId) {
currentWindow.getRegionInScreen(currentWindowRegions);
outRegion.set(currentWindowRegions);
windowInteractiveRegion = outRegion;
continue;
}
} else if (currentWindow.getType()
!= AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY) {
currentWindow.getRegionInScreen(currentWindowRegions);
if (windowInteractiveRegion.op(currentWindowRegions, Region.Op.DIFFERENCE)) {
windowInteractiveRegionChanged = true;
}
}
}
return windowInteractiveRegionChanged;
}
List<Integer> getWatchOutsideTouchWindowIdLocked(int targetWindowId) {
final WindowInfo targetWindow = mWindowInfoById.get(targetWindowId);
if (targetWindow != null && mHasWatchOutsideTouchWindow) {
final List<Integer> outsideWindowsId = new ArrayList<>();
for (int i = 0; i < mWindowInfoById.size(); i++) {
final WindowInfo window = mWindowInfoById.valueAt(i);
if (window != null && window.layer < targetWindow.layer
&& window.hasFlagWatchOutsideTouch) {
outsideWindowsId.add(mWindowInfoById.keyAt(i));
}
}
return outsideWindowsId;
}
return Collections.emptyList();
}
/**
* Callbacks from window manager when there's an accessibility change in windows.
*
* @param forceSend Send the windows for accessibility even if they haven't changed.
* @param topFocusedDisplayId The display Id which has the top focused window.
* @param topFocusedWindowToken The window token of top focused window.
* @param windows The windows for accessibility.
*/
@Override
public void onWindowsForAccessibilityChanged(boolean forceSend, int topFocusedDisplayId,
IBinder topFocusedWindowToken, @NonNull List<WindowInfo> windows) {
synchronized (mLock) {
if (DEBUG) {
Slog.i(LOG_TAG, "Display Id = " + mDisplayId);
Slog.i(LOG_TAG, "Windows changed: " + windows);
}
if (shouldUpdateWindowsLocked(forceSend, windows)) {
mTopFocusedDisplayId = topFocusedDisplayId;
mTopFocusedWindowToken = topFocusedWindowToken;
cacheWindows(windows);
// Lets the policy update the focused and active windows.
updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(),
windows);
// Someone may be waiting for the windows - advertise it.
mLock.notifyAll();
}
}
}
private boolean shouldUpdateWindowsLocked(boolean forceSend,
@NonNull List<WindowInfo> windows) {
if (forceSend) {
return true;
}
final int windowCount = windows.size();
// We computed the windows and if they changed notify the client.
if (mCachedWindowInfos.size() != windowCount) {
// Different size means something changed.
return true;
} else if (!mCachedWindowInfos.isEmpty() || !windows.isEmpty()) {
// Since we always traverse windows from high to low layer
// the old and new windows at the same index should be the
// same, otherwise something changed.
for (int i = 0; i < windowCount; i++) {
WindowInfo oldWindow = mCachedWindowInfos.get(i);
WindowInfo newWindow = windows.get(i);
// We do not care for layer changes given the window
// order does not change. This brings no new information
// to the clients.
if (windowChangedNoLayer(oldWindow, newWindow)) {
return true;
}
}
}
return false;
}
private void cacheWindows(List<WindowInfo> windows) {
final int oldWindowCount = mCachedWindowInfos.size();
for (int i = oldWindowCount - 1; i >= 0; i--) {
mCachedWindowInfos.remove(i).recycle();
}
final int newWindowCount = windows.size();
for (int i = 0; i < newWindowCount; i++) {
WindowInfo newWindow = windows.get(i);
mCachedWindowInfos.add(WindowInfo.obtain(newWindow));
}
}
private boolean windowChangedNoLayer(WindowInfo oldWindow, WindowInfo newWindow) {
if (oldWindow == newWindow) {
return false;
}
if (oldWindow == null) {
return true;
}
if (newWindow == null) {
return true;
}
if (oldWindow.type != newWindow.type) {
return true;
}
if (oldWindow.focused != newWindow.focused) {
return true;
}
if (oldWindow.token == null) {
if (newWindow.token != null) {
return true;
}
} else if (!oldWindow.token.equals(newWindow.token)) {
return true;
}
if (oldWindow.parentToken == null) {
if (newWindow.parentToken != null) {
return true;
}
} else if (!oldWindow.parentToken.equals(newWindow.parentToken)) {
return true;
}
if (oldWindow.activityToken == null) {
if (newWindow.activityToken != null) {
return true;
}
} else if (!oldWindow.activityToken.equals(newWindow.activityToken)) {
return true;
}
if (!oldWindow.regionInScreen.equals(newWindow.regionInScreen)) {
return true;
}
if (oldWindow.childTokens != null && newWindow.childTokens != null
&& !oldWindow.childTokens.equals(newWindow.childTokens)) {
return true;
}
if (!TextUtils.equals(oldWindow.title, newWindow.title)) {
return true;
}
if (oldWindow.accessibilityIdOfAnchor != newWindow.accessibilityIdOfAnchor) {
return true;
}
if (oldWindow.inPictureInPicture != newWindow.inPictureInPicture) {
return true;
}
if (oldWindow.hasFlagWatchOutsideTouch != newWindow.hasFlagWatchOutsideTouch) {
return true;
}
if (oldWindow.displayId != newWindow.displayId) {
return true;
}
return false;
}
/**
* Clears all {@link AccessibilityWindowInfo}s and {@link WindowInfo}s.
*/
private void clearWindowsLocked() {
final List<WindowInfo> windows = Collections.emptyList();
final int activeWindowId = mActiveWindowId;
// UserId is useless in updateWindowsLocked, when we update a empty window list.
// Just pass current userId here.
updateWindowsLocked(mAccessibilityUserManager.getCurrentUserIdLocked(), windows);
// Do not reset mActiveWindowId here. mActiveWindowId will be clear after accessibility
// interaction connection removed.
mActiveWindowId = activeWindowId;
mWindows = null;
}
/**
* Updates windows info according to specified userId and windows.
*
* @param userId The userId to update
* @param windows The windows to update
*/
private void updateWindowsLocked(int userId, @NonNull List<WindowInfo> windows) {
if (mWindows == null) {
mWindows = new ArrayList<>();
}
final List<AccessibilityWindowInfo> oldWindowList = new ArrayList<>(mWindows);
final SparseArray<AccessibilityWindowInfo> oldWindowsById = mA11yWindowInfoById.clone();
boolean shouldClearAccessibilityFocus = false;
mWindows.clear();
mA11yWindowInfoById.clear();
for (int i = 0; i < mWindowInfoById.size(); i++) {
mWindowInfoById.valueAt(i).recycle();
}
mWindowInfoById.clear();
mHasWatchOutsideTouchWindow = false;
final int windowCount = windows.size();
final boolean isTopFocusedDisplay = mDisplayId == mTopFocusedDisplayId;
final boolean isAccessibilityFocusedDisplay =
mDisplayId == mAccessibilityFocusedDisplayId;
// Modifies the value of top focused window, active window and a11y focused window
// only if this display is top focused display which has the top focused window.
if (isTopFocusedDisplay) {
if (windowCount > 0) {
// Sets the top focus window by top focused window token.
mTopFocusedWindowId = findWindowIdLocked(userId, mTopFocusedWindowToken);
} else {
// Resets the top focus window when stopping tracking window of this display.
mTopFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
// The active window doesn't need to be reset if the touch operation is progressing.
if (!mTouchInteractionInProgress) {
mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
}
// If the active window goes away while the user is touch exploring we
// reset the active window id and wait for the next hover event from
// under the user's finger to determine which one is the new one. It
// is possible that the finger is not moving and the input system
// filters out such events.
boolean activeWindowGone = true;
// We'll clear accessibility focus if the window with focus is no longer visible to
// accessibility services.
if (isAccessibilityFocusedDisplay) {
shouldClearAccessibilityFocus = mAccessibilityFocusedWindowId
!= AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
if (windowCount > 0) {
for (int i = 0; i < windowCount; i++) {
final WindowInfo windowInfo = windows.get(i);
final AccessibilityWindowInfo window;
if (mTrackingWindows) {
window = populateReportedWindowLocked(userId, windowInfo);
} else {
window = null;
}
if (window != null) {
// Flip layers in list to be consistent with AccessibilityService#getWindows
window.setLayer(windowCount - 1 - window.getLayer());
final int windowId = window.getId();
if (window.isFocused() && isTopFocusedDisplay) {
if (!mTouchInteractionInProgress) {
// This display is top one, and sets the focus window
// as active window.
mActiveWindowId = windowId;
window.setActive(true);
} else if (windowId == mActiveWindowId) {
activeWindowGone = false;
}
}
if (!mHasWatchOutsideTouchWindow && windowInfo.hasFlagWatchOutsideTouch) {
mHasWatchOutsideTouchWindow = true;
}
mWindows.add(window);
mA11yWindowInfoById.put(windowId, window);
mWindowInfoById.put(windowId, WindowInfo.obtain(windowInfo));
}
}
final int accessibilityWindowCount = mWindows.size();
if (isTopFocusedDisplay) {
if (mTouchInteractionInProgress && activeWindowGone) {
mActiveWindowId = mTopFocusedWindowId;
}
// Focused window may change the active one, so set the
// active window once we decided which it is.
for (int i = 0; i < accessibilityWindowCount; i++) {
final AccessibilityWindowInfo window = mWindows.get(i);
if (window.getId() == mActiveWindowId) {
window.setActive(true);
}
}
}
if (isAccessibilityFocusedDisplay) {
for (int i = 0; i < accessibilityWindowCount; i++) {
final AccessibilityWindowInfo window = mWindows.get(i);
if (window.getId() == mAccessibilityFocusedWindowId) {
window.setAccessibilityFocused(true);
shouldClearAccessibilityFocus = false;
break;
}
}
}
}
sendEventsForChangedWindowsLocked(oldWindowList, oldWindowsById);
final int oldWindowCount = oldWindowList.size();
for (int i = oldWindowCount - 1; i >= 0; i--) {
oldWindowList.remove(i).recycle();
}
if (shouldClearAccessibilityFocus) {
clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
}
}
private void sendEventsForChangedWindowsLocked(List<AccessibilityWindowInfo> oldWindows,
SparseArray<AccessibilityWindowInfo> oldWindowsById) {
List<AccessibilityEvent> events = new ArrayList<>();
// Sends events for all removed windows.
final int oldWindowsCount = oldWindows.size();
for (int i = 0; i < oldWindowsCount; i++) {
final AccessibilityWindowInfo window = oldWindows.get(i);
if (mA11yWindowInfoById.get(window.getId()) == null) {
events.add(AccessibilityEvent.obtainWindowsChangedEvent(
window.getId(), AccessibilityEvent.WINDOWS_CHANGE_REMOVED));
}
}
// Looks for other changes.
final int newWindowCount = mWindows.size();
for (int i = 0; i < newWindowCount; i++) {
final AccessibilityWindowInfo newWindow = mWindows.get(i);
final AccessibilityWindowInfo oldWindow = oldWindowsById.get(newWindow.getId());
if (oldWindow == null) {
events.add(AccessibilityEvent.obtainWindowsChangedEvent(
newWindow.getId(), AccessibilityEvent.WINDOWS_CHANGE_ADDED));
} else {
int changes = newWindow.differenceFrom(oldWindow);
if (changes != 0) {
events.add(AccessibilityEvent.obtainWindowsChangedEvent(
newWindow.getId(), changes));
}
}
}
final int numEvents = events.size();
for (int i = 0; i < numEvents; i++) {
mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(events.get(i));
}
}
private AccessibilityWindowInfo populateReportedWindowLocked(int userId,
WindowInfo window) {
final int windowId = findWindowIdLocked(userId, window.token);
if (windowId < 0) {
return null;
}
final AccessibilityWindowInfo reportedWindow = AccessibilityWindowInfo.obtain();
reportedWindow.setId(windowId);
reportedWindow.setType(getTypeForWindowManagerWindowType(window.type));
reportedWindow.setLayer(window.layer);
reportedWindow.setFocused(window.focused);
reportedWindow.setRegionInScreen(window.regionInScreen);
reportedWindow.setTitle(window.title);
reportedWindow.setAnchorId(window.accessibilityIdOfAnchor);
reportedWindow.setPictureInPicture(window.inPictureInPicture);
reportedWindow.setDisplayId(window.displayId);
final int parentId = findWindowIdLocked(userId, window.parentToken);
if (parentId >= 0) {
reportedWindow.setParentId(parentId);
}
if (window.childTokens != null) {
final int childCount = window.childTokens.size();
for (int i = 0; i < childCount; i++) {
final IBinder childToken = window.childTokens.get(i);
final int childId = findWindowIdLocked(userId, childToken);
if (childId >= 0) {
reportedWindow.addChild(childId);
}
}
}
return reportedWindow;
}
private int getTypeForWindowManagerWindowType(int windowType) {
switch (windowType) {
case WindowManager.LayoutParams.TYPE_APPLICATION:
case WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:
case WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_STARTING:
case WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL:
case WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL:
case WindowManager.LayoutParams.TYPE_BASE_APPLICATION:
case WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION:
case WindowManager.LayoutParams.TYPE_PHONE:
case WindowManager.LayoutParams.TYPE_PRIORITY_PHONE:
case WindowManager.LayoutParams.TYPE_TOAST:
case WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG: {
return AccessibilityWindowInfo.TYPE_APPLICATION;
}
case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG: {
return AccessibilityWindowInfo.TYPE_INPUT_METHOD;
}
case WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL:
case WindowManager.LayoutParams.TYPE_SEARCH_BAR:
case WindowManager.LayoutParams.TYPE_STATUS_BAR:
case WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE:
case WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL:
case WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL:
case WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY:
case WindowManager.LayoutParams.TYPE_SYSTEM_ALERT:
case WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG:
case WindowManager.LayoutParams.TYPE_SYSTEM_ERROR:
case WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY:
case WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:
case WindowManager.LayoutParams.TYPE_SCREENSHOT: {
return AccessibilityWindowInfo.TYPE_SYSTEM;
}
case WindowManager.LayoutParams.TYPE_DOCK_DIVIDER: {
return AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER;
}
case TYPE_ACCESSIBILITY_OVERLAY: {
return AccessibilityWindowInfo.TYPE_ACCESSIBILITY_OVERLAY;
}
default: {
return -1;
}
}
}
/**
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
void dumpLocked(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (mWindows != null) {
final int windowCount = mWindows.size();
for (int j = 0; j < windowCount; j++) {
if (j == 0) {
pw.append("Display[");
pw.append(Integer.toString(mDisplayId));
pw.append("] : ");
pw.println();
}
if (j > 0) {
pw.append(',');
pw.println();
}
pw.append("Window[");
AccessibilityWindowInfo window = mWindows.get(j);
pw.append(window.toString());
pw.append(']');
}
pw.println();
}
}
}
/**
* Interface to send {@link AccessibilityEvent}.
*/
public interface AccessibilityEventSender {
/**
* Sends {@link AccessibilityEvent} for current user.
*/
void sendAccessibilityEventForCurrentUserLocked(AccessibilityEvent event);
}
/**
* Wrapper of accessibility interaction connection for window.
*/
// In order to avoid using DexmakerShareClassLoaderRule, make this class visible for testing.
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public final class RemoteAccessibilityConnection implements IBinder.DeathRecipient {
private final int mUid;
private final String mPackageName;
private final int mWindowId;
private final int mUserId;
private final IAccessibilityInteractionConnection mConnection;
RemoteAccessibilityConnection(int windowId,
IAccessibilityInteractionConnection connection,
String packageName, int uid, int userId) {
mWindowId = windowId;
mPackageName = packageName;
mUid = uid;
mUserId = userId;
mConnection = connection;
}
int getUid() {
return mUid;
}
String getPackageName() {
return mPackageName;
}
IAccessibilityInteractionConnection getRemote() {
return mConnection;
}
void linkToDeath() throws RemoteException {
mConnection.asBinder().linkToDeath(this, 0);
}
void unlinkToDeath() {
mConnection.asBinder().unlinkToDeath(this, 0);
}
@Override
public void binderDied() {
unlinkToDeath();
synchronized (mLock) {
removeAccessibilityInteractionConnectionLocked(mWindowId, mUserId);
}
}
}
/**
* Constructor for AccessibilityManagerService.
*/
public AccessibilityWindowManager(@NonNull Object lock, @NonNull Handler handler,
@NonNull WindowManagerInternal windowManagerInternal,
@NonNull AccessibilityEventSender accessibilityEventSender,
@NonNull AccessibilitySecurityPolicy securityPolicy,
@NonNull AccessibilityUserManager accessibilityUserManager) {
mLock = lock;
mHandler = handler;
mWindowManagerInternal = windowManagerInternal;
mAccessibilityEventSender = accessibilityEventSender;
mSecurityPolicy = securityPolicy;
mAccessibilityUserManager = accessibilityUserManager;
}
/**
* Starts tracking windows changes from window manager for specified display.
*
* @param displayId The logical display id.
*/
public void startTrackingWindows(int displayId) {
synchronized (mLock) {
DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
if (observer == null) {
observer = new DisplayWindowsObserver(displayId);
}
if (observer.isTrackingWindowsLocked()) {
return;
}
if (observer.startTrackingWindowsLocked()) {
mDisplayWindowsObservers.put(displayId, observer);
}
}
}
/**
* Stops tracking windows changes from window manager, and clear all windows info for specified
* display.
*
* @param displayId The logical display id.
*/
public void stopTrackingWindows(int displayId) {
synchronized (mLock) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
if (observer != null) {
observer.stopTrackingWindowsLocked();
mDisplayWindowsObservers.remove(displayId);
}
}
}
/**
* Checks if we are tracking windows on any display.
*
* @return {@code true} if the observer is tracking windows on any display,
* {@code false} otherwise.
*/
public boolean isTrackingWindowsLocked() {
final int count = mDisplayWindowsObservers.size();
if (count > 0) {
return true;
}
return false;
}
/**
* Checks if we are tracking windows on specified display.
*
* @param displayId The logical display id.
* @return {@code true} if the observer is tracking windows on specified display,
* {@code false} otherwise.
*/
public boolean isTrackingWindowsLocked(int displayId) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
if (observer != null) {
return observer.isTrackingWindowsLocked();
}
return false;
}
/**
* Returns accessibility windows for specified display.
*
* @param displayId The logical display id.
* @return accessibility windows for specified display.
*/
@Nullable
public List<AccessibilityWindowInfo> getWindowListLocked(int displayId) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.get(displayId);
if (observer != null) {
return observer.getWindowListLocked();
}
return null;
}
/**
* Adds accessibility interaction connection according to given window token, package name and
* window token.
*
* @param window The window token of accessibility interaction connection
* @param leashToken The leash token of accessibility interaction connection
* @param connection The accessibility interaction connection
* @param packageName The package name
* @param userId The userId
* @return The windowId of added connection
* @throws RemoteException
*/
public int addAccessibilityInteractionConnection(@NonNull IWindow window,
@NonNull IBinder leashToken, @NonNull IAccessibilityInteractionConnection connection,
@NonNull String packageName, int userId) throws RemoteException {
final int windowId;
boolean shouldComputeWindows = false;
final IBinder token = window.asBinder();
final int displayId = mWindowManagerInternal.getDisplayIdForWindow(token);
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
final int resolvedUserId = mSecurityPolicy
.resolveCallingUserIdEnforcingPermissionsLocked(userId);
final int resolvedUid = UserHandle.getUid(resolvedUserId, UserHandle.getCallingAppId());
// Makes sure the reported package is one the caller has access to.
packageName = mSecurityPolicy.resolveValidReportedPackageLocked(
packageName, UserHandle.getCallingAppId(), resolvedUserId,
Binder.getCallingPid());
windowId = sNextWindowId++;
// If the window is from a process that runs across users such as
// the system UI or the system we add it to the global state that
// is shared across users.
if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection(
windowId, connection, packageName, resolvedUid, UserHandle.USER_ALL);
wrapper.linkToDeath();
mGlobalInteractionConnections.put(windowId, wrapper);
mGlobalWindowTokens.put(windowId, token);
if (DEBUG) {
Slog.i(LOG_TAG, "Added global connection for pid:" + Binder.getCallingPid()
+ " with windowId: " + windowId + " and token: " + token);
}
} else {
RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection(
windowId, connection, packageName, resolvedUid, resolvedUserId);
wrapper.linkToDeath();
getInteractionConnectionsForUserLocked(resolvedUserId).put(windowId, wrapper);
getWindowTokensForUserLocked(resolvedUserId).put(windowId, token);
if (DEBUG) {
Slog.i(LOG_TAG, "Added user connection for pid:" + Binder.getCallingPid()
+ " with windowId: " + windowId + " and token: " + token);
}
}
if (isTrackingWindowsLocked(displayId)) {
shouldComputeWindows = true;
}
registerIdLocked(leashToken, windowId);
}
if (shouldComputeWindows) {
mWindowManagerInternal.computeWindowsForAccessibility(displayId);
}
mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(token, windowId);
return windowId;
}
/**
* Removes accessibility interaction connection according to given window token.
*
* @param window The window token of accessibility interaction connection
*/
public void removeAccessibilityInteractionConnection(@NonNull IWindow window) {
synchronized (mLock) {
// We treat calls from a profile as if made by its parent as profiles
// share the accessibility state of the parent. The call below
// performs the current profile parent resolution.
mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(
UserHandle.getCallingUserId());
IBinder token = window.asBinder();
final int removedWindowId = removeAccessibilityInteractionConnectionInternalLocked(
token, mGlobalWindowTokens, mGlobalInteractionConnections);
if (removedWindowId >= 0) {
onAccessibilityInteractionConnectionRemovedLocked(removedWindowId, token);
if (DEBUG) {
Slog.i(LOG_TAG, "Removed global connection for pid:" + Binder.getCallingPid()
+ " with windowId: " + removedWindowId + " and token: "
+ window.asBinder());
}
return;
}
final int userCount = mWindowTokens.size();
for (int i = 0; i < userCount; i++) {
final int userId = mWindowTokens.keyAt(i);
final int removedWindowIdForUser =
removeAccessibilityInteractionConnectionInternalLocked(token,
getWindowTokensForUserLocked(userId),
getInteractionConnectionsForUserLocked(userId));
if (removedWindowIdForUser >= 0) {
onAccessibilityInteractionConnectionRemovedLocked(
removedWindowIdForUser, token);
if (DEBUG) {
Slog.i(LOG_TAG, "Removed user connection for pid:" + Binder.getCallingPid()
+ " with windowId: " + removedWindowIdForUser + " and userId:"
+ userId + " and token: " + window.asBinder());
}
return;
}
}
}
}
/**
* Resolves a connection wrapper for a window id.
*
* @param userId The user id for any user-specific windows
* @param windowId The id of the window of interest
*
* @return a connection to the window
*/
@Nullable
public RemoteAccessibilityConnection getConnectionLocked(int userId, int windowId) {
if (DEBUG) {
Slog.i(LOG_TAG, "Trying to get interaction connection to windowId: " + windowId);
}
RemoteAccessibilityConnection connection = mGlobalInteractionConnections.get(windowId);
if (connection == null && isValidUserForInteractionConnectionsLocked(userId)) {
connection = getInteractionConnectionsForUserLocked(userId).get(windowId);
}
if (connection != null && connection.getRemote() != null) {
return connection;
}
if (DEBUG) {
Slog.e(LOG_TAG, "No interaction connection to window: " + windowId);
}
return null;
}
private int removeAccessibilityInteractionConnectionInternalLocked(IBinder windowToken,
SparseArray<IBinder> windowTokens, SparseArray<RemoteAccessibilityConnection>
interactionConnections) {
final int count = windowTokens.size();
for (int i = 0; i < count; i++) {
if (windowTokens.valueAt(i) == windowToken) {
final int windowId = windowTokens.keyAt(i);
windowTokens.removeAt(i);
RemoteAccessibilityConnection wrapper = interactionConnections.get(windowId);
wrapper.unlinkToDeath();
interactionConnections.remove(windowId);
return windowId;
}
}
return -1;
}
/**
* Removes accessibility interaction connection according to given windowId and userId.
*
* @param windowId The windowId of accessibility interaction connection
* @param userId The userId to remove
*/
private void removeAccessibilityInteractionConnectionLocked(int windowId, int userId) {
IBinder window = null;
if (userId == UserHandle.USER_ALL) {
window = mGlobalWindowTokens.get(windowId);
mGlobalWindowTokens.remove(windowId);
mGlobalInteractionConnections.remove(windowId);
} else {
if (isValidUserForWindowTokensLocked(userId)) {
window = getWindowTokensForUserLocked(userId).get(windowId);
getWindowTokensForUserLocked(userId).remove(windowId);
}
if (isValidUserForInteractionConnectionsLocked(userId)) {
getInteractionConnectionsForUserLocked(userId).remove(windowId);
}
}
onAccessibilityInteractionConnectionRemovedLocked(windowId, window);
if (DEBUG) {
Slog.i(LOG_TAG, "Removing interaction connection to windowId: " + windowId);
}
}
/**
* Invoked when accessibility interaction connection of window is removed.
*
* @param windowId Removed windowId
* @param binder Removed window token
*/
private void onAccessibilityInteractionConnectionRemovedLocked(
int windowId, @Nullable IBinder binder) {
// Active window will not update, if windows callback is unregistered.
// Update active window to invalid, when its a11y interaction connection is removed.
if (!isTrackingWindowsLocked() && windowId >= 0 && mActiveWindowId == windowId) {
mActiveWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
if (binder != null) {
mWindowManagerInternal.setAccessibilityIdToSurfaceMetadata(
binder, AccessibilityWindowInfo.UNDEFINED_WINDOW_ID);
}
unregisterIdLocked(windowId);
}
/**
* Gets window token according to given userId and windowId.
*
* @param userId The userId
* @param windowId The windowId
* @return The window token
*/
@Nullable
public IBinder getWindowTokenForUserAndWindowIdLocked(int userId, int windowId) {
IBinder windowToken = mGlobalWindowTokens.get(windowId);
if (windowToken == null && isValidUserForWindowTokensLocked(userId)) {
windowToken = getWindowTokensForUserLocked(userId).get(windowId);
}
return windowToken;
}
/**
* Returns the userId that owns the given window token, {@link UserHandle#USER_NULL}
* if not found.
*
* @param windowToken The window token
* @return The userId
*/
public int getWindowOwnerUserId(@NonNull IBinder windowToken) {
return mWindowManagerInternal.getWindowOwnerUserId(windowToken);
}
/**
* Returns windowId of given userId and window token.
*
* @param userId The userId
* @param token The window token
* @return The windowId
*/
public int findWindowIdLocked(int userId, @NonNull IBinder token) {
final int globalIndex = mGlobalWindowTokens.indexOfValue(token);
if (globalIndex >= 0) {
return mGlobalWindowTokens.keyAt(globalIndex);
}
if (isValidUserForWindowTokensLocked(userId)) {
final int userIndex = getWindowTokensForUserLocked(userId).indexOfValue(token);
if (userIndex >= 0) {
return getWindowTokensForUserLocked(userId).keyAt(userIndex);
}
}
return -1;
}
/**
* Establish the relationship between the host and the embedded view hierarchy.
*
* @param host The token of host hierarchy
* @param embedded The token of the embedded hierarchy
*/
public void associateEmbeddedHierarchyLocked(@NonNull IBinder host, @NonNull IBinder embedded) {
// Use embedded window as key, since one host window may have multiple embedded windows.
associateLocked(embedded, host);
}
/**
* Clear the relationship by given token.
*
* @param token The token
*/
public void disassociateEmbeddedHierarchyLocked(@NonNull IBinder token) {
disassociateLocked(token);
}
/**
* Gets the parent windowId of the window according to the specified windowId.
*
* @param windowId The windowId to check
* @return The windowId of the parent window, or self if no parent exists
*/
public int resolveParentWindowIdLocked(int windowId) {
final IBinder token = getTokenLocked(windowId);
if (token == null) {
return windowId;
}
final IBinder resolvedToken = resolveTopParentTokenLocked(token);
final int resolvedWindowId = getWindowIdLocked(resolvedToken);
return resolvedWindowId != -1 ? resolvedWindowId : windowId;
}
private IBinder resolveTopParentTokenLocked(IBinder token) {
final IBinder hostToken = getHostTokenLocked(token);
if (hostToken == null) {
return token;
}
return resolveTopParentTokenLocked(hostToken);
}
/**
* Computes partial interactive region of given windowId.
*
* @param windowId The windowId
* @param outRegion The output to which to write the bounds.
* @return true if outRegion is not empty.
*/
public boolean computePartialInteractiveRegionForWindowLocked(int windowId,
@NonNull Region outRegion) {
windowId = resolveParentWindowIdLocked(windowId);
final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
if (observer != null) {
return observer.computePartialInteractiveRegionForWindowLocked(windowId, outRegion);
}
return false;
}
/**
* Updates active windowId and accessibility focused windowId according to given accessibility
* event and action.
*
* @param userId The userId
* @param windowId The windowId of accessibility event
* @param nodeId The accessibility node id of accessibility event
* @param eventType The accessibility event type
* @param eventAction The accessibility event action
*/
public void updateActiveAndAccessibilityFocusedWindowLocked(int userId, int windowId,
long nodeId, int eventType, int eventAction) {
// The active window is either the window that has input focus or
// the window that the user is currently touching. If the user is
// touching a window that does not have input focus as soon as the
// the user stops touching that window the focused window becomes
// the active one. Here we detect the touched window and make it
// active. In updateWindowsLocked() we update the focused window
// and if the user is not touching the screen, we make the focused
// window the active one.
switch (eventType) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: {
// If no service has the capability to introspect screen,
// we do not register callback in the window manager for
// window changes, so we have to ask the window manager
// what the focused window is to update the active one.
// The active window also determined events from which
// windows are delivered.
synchronized (mLock) {
if (!isTrackingWindowsLocked()) {
mTopFocusedWindowId = findFocusedWindowId(userId);
if (windowId == mTopFocusedWindowId) {
mActiveWindowId = windowId;
}
}
}
} break;
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER: {
// Do not allow delayed hover events to confuse us
// which the active window is.
synchronized (mLock) {
if (mTouchInteractionInProgress && mActiveWindowId != windowId) {
setActiveWindowLocked(windowId);
}
}
} break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
synchronized (mLock) {
if (mAccessibilityFocusedWindowId != windowId) {
clearAccessibilityFocusLocked(mAccessibilityFocusedWindowId);
setAccessibilityFocusedWindowLocked(windowId);
}
mAccessibilityFocusNodeId = nodeId;
}
} break;
case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED: {
synchronized (mLock) {
if (mAccessibilityFocusNodeId == nodeId) {
mAccessibilityFocusNodeId = AccessibilityNodeInfo.UNDEFINED_ITEM_ID;
}
// Clear the window with focus if it no longer has focus and we aren't
// just moving focus from one view to the other in the same window.
if ((mAccessibilityFocusNodeId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID)
&& (mAccessibilityFocusedWindowId == windowId)
&& (eventAction != AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS)) {
mAccessibilityFocusedWindowId = AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
mAccessibilityFocusedDisplayId = Display.INVALID_DISPLAY;
}
}
} break;
}
}
/**
* Callbacks from AccessibilityManagerService when touch explorer turn on and
* motion down detected.
*/
public void onTouchInteractionStart() {
synchronized (mLock) {
mTouchInteractionInProgress = true;
}
}
/**
* Callbacks from AccessibilityManagerService when touch explorer turn on and
* gesture or motion up detected.
*/
public void onTouchInteractionEnd() {
synchronized (mLock) {
mTouchInteractionInProgress = false;
// We want to set the active window to be current immediately
// after the user has stopped touching the screen since if the
// user types with the IME he should get a feedback for the
// letter typed in the text view which is in the input focused
// window. Note that we always deliver hover accessibility events
// (they are a result of user touching the screen) so change of
// the active window before all hover accessibility events from
// the touched window are delivered is fine.
final int oldActiveWindow = mActiveWindowId;
setActiveWindowLocked(mTopFocusedWindowId);
// If there is no service that can operate with interactive windows
// then we keep the old behavior where a window loses accessibility
// focus if it is no longer active. This still changes the behavior
// for services that do not operate with interactive windows and run
// at the same time as the one(s) which does. In practice however,
// there is only one service that uses accessibility focus and it
// is typically the one that operates with interactive windows, So,
// this is fine. Note that to allow a service to work across windows
// we have to allow accessibility focus stay in any of them. Sigh...
final boolean accessibilityFocusOnlyInActiveWindow = !isTrackingWindowsLocked();
if (oldActiveWindow != mActiveWindowId
&& mAccessibilityFocusedWindowId == oldActiveWindow
&& accessibilityFocusOnlyInActiveWindow) {
clearAccessibilityFocusLocked(oldActiveWindow);
}
}
}
/**
* Gets the id of the current active window.
*
* @return The userId
*/
public int getActiveWindowId(int userId) {
if (mActiveWindowId == AccessibilityWindowInfo.UNDEFINED_WINDOW_ID
&& !mTouchInteractionInProgress) {
mActiveWindowId = findFocusedWindowId(userId);
}
return mActiveWindowId;
}
private void setActiveWindowLocked(int windowId) {
if (mActiveWindowId != windowId) {
mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
AccessibilityEvent.obtainWindowsChangedEvent(
mActiveWindowId, AccessibilityEvent.WINDOWS_CHANGE_ACTIVE));
mActiveWindowId = windowId;
// Goes through all windows for each display.
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
observer.setActiveWindowLocked(windowId);
}
}
}
}
private void setAccessibilityFocusedWindowLocked(int windowId) {
if (mAccessibilityFocusedWindowId != windowId) {
mAccessibilityEventSender.sendAccessibilityEventForCurrentUserLocked(
AccessibilityEvent.obtainWindowsChangedEvent(
mAccessibilityFocusedWindowId,
WINDOWS_CHANGE_ACCESSIBILITY_FOCUSED));
mAccessibilityFocusedWindowId = windowId;
// Goes through all windows for each display.
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
observer.setAccessibilityFocusedWindowLocked(windowId);
}
}
}
}
/**
* Returns accessibility window info according to given windowId.
*
* @param windowId The windowId
* @return The accessibility window info
*/
@Nullable
public AccessibilityWindowInfo findA11yWindowInfoByIdLocked(int windowId) {
windowId = resolveParentWindowIdLocked(windowId);
final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
if (observer != null) {
return observer.findA11yWindowInfoByIdLocked(windowId);
}
return null;
}
/**
* Returns the window info according to given windowId.
*
* @param windowId The windowId
* @return The window info
*/
@Nullable
public WindowInfo findWindowInfoByIdLocked(int windowId) {
windowId = resolveParentWindowIdLocked(windowId);
final DisplayWindowsObserver observer = getDisplayWindowObserverByWindowIdLocked(windowId);
if (observer != null) {
return observer.findWindowInfoByIdLocked(windowId);
}
return null;
}
/**
* Returns focused windowId or accessibility focused windowId according to given focusType.
*
* @param focusType {@link AccessibilityNodeInfo#FOCUS_INPUT} or
* {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}
* @return The focused windowId
*/
public int getFocusedWindowId(int focusType) {
if (focusType == AccessibilityNodeInfo.FOCUS_INPUT) {
return mTopFocusedWindowId;
} else if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY) {
return mAccessibilityFocusedWindowId;
}
return AccessibilityWindowInfo.UNDEFINED_WINDOW_ID;
}
/**
* Returns {@link AccessibilityWindowInfo} of PIP window.
*
* @return PIP accessibility window info
*/
@Nullable
public AccessibilityWindowInfo getPictureInPictureWindowLocked() {
AccessibilityWindowInfo windowInfo = null;
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
if ((windowInfo = observer.getPictureInPictureWindowLocked()) != null) {
break;
}
}
}
return windowInfo;
}
/**
* Sets an IAccessibilityInteractionConnection to replace the actions of a picture-in-picture
* window.
*/
public void setPictureInPictureActionReplacingConnection(
@Nullable IAccessibilityInteractionConnection connection) throws RemoteException {
synchronized (mLock) {
if (mPictureInPictureActionReplacingConnection != null) {
mPictureInPictureActionReplacingConnection.unlinkToDeath();
mPictureInPictureActionReplacingConnection = null;
}
if (connection != null) {
RemoteAccessibilityConnection wrapper = new RemoteAccessibilityConnection(
AccessibilityWindowInfo.PICTURE_IN_PICTURE_ACTION_REPLACER_WINDOW_ID,
connection, "foo.bar.baz", Process.SYSTEM_UID, UserHandle.USER_ALL);
mPictureInPictureActionReplacingConnection = wrapper;
wrapper.linkToDeath();
}
}
}
/**
* Returns accessibility interaction connection for picture-in-picture window.
*/
@Nullable
public RemoteAccessibilityConnection getPictureInPictureActionReplacingConnection() {
return mPictureInPictureActionReplacingConnection;
}
/**
* Invokes {@link IAccessibilityInteractionConnection#notifyOutsideTouch()} for windows that
* have watch outside touch flag and its layer is upper than target window.
*/
public void notifyOutsideTouch(int userId, int targetWindowId) {
final List<Integer> outsideWindowsIds;
final List<RemoteAccessibilityConnection> connectionList = new ArrayList<>();
synchronized (mLock) {
final DisplayWindowsObserver observer =
getDisplayWindowObserverByWindowIdLocked(targetWindowId);
if (observer != null) {
outsideWindowsIds = observer.getWatchOutsideTouchWindowIdLocked(targetWindowId);
for (int i = 0; i < outsideWindowsIds.size(); i++) {
connectionList.add(getConnectionLocked(userId, outsideWindowsIds.get(i)));
}
}
}
for (int i = 0; i < connectionList.size(); i++) {
final RemoteAccessibilityConnection connection = connectionList.get(i);
if (connection != null) {
try {
connection.getRemote().notifyOutsideTouch();
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling notifyOutsideTouch()");
}
}
}
}
}
/**
* Returns the display ID according to given userId and windowId.
*
* @param userId The userId
* @param windowId The windowId
* @return The display ID
*/
public int getDisplayIdByUserIdAndWindowIdLocked(int userId, int windowId) {
final IBinder windowToken = getWindowTokenForUserAndWindowIdLocked(userId, windowId);
final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken);
return displayId;
}
/**
* Returns the display list including all displays which are tracking windows.
*
* @return The display list.
*/
public ArrayList<Integer> getDisplayListLocked() {
final ArrayList<Integer> displayList = new ArrayList<>();
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
displayList.add(observer.mDisplayId);
}
}
return displayList;
}
/**
* Gets current input focused window token from window manager, and returns its windowId.
*
* @param userId The userId
* @return The input focused windowId, or -1 if not found
*/
private int findFocusedWindowId(int userId) {
final IBinder token = mWindowManagerInternal.getFocusedWindowToken();
synchronized (mLock) {
return findWindowIdLocked(userId, token);
}
}
private boolean isValidUserForInteractionConnectionsLocked(int userId) {
return mInteractionConnections.indexOfKey(userId) >= 0;
}
private boolean isValidUserForWindowTokensLocked(int userId) {
return mWindowTokens.indexOfKey(userId) >= 0;
}
private SparseArray<RemoteAccessibilityConnection> getInteractionConnectionsForUserLocked(
int userId) {
SparseArray<RemoteAccessibilityConnection> connection = mInteractionConnections.get(
userId);
if (connection == null) {
connection = new SparseArray<>();
mInteractionConnections.put(userId, connection);
}
return connection;
}
private SparseArray<IBinder> getWindowTokensForUserLocked(int userId) {
SparseArray<IBinder> windowTokens = mWindowTokens.get(userId);
if (windowTokens == null) {
windowTokens = new SparseArray<>();
mWindowTokens.put(userId, windowTokens);
}
return windowTokens;
}
private void clearAccessibilityFocusLocked(int windowId) {
mHandler.sendMessage(obtainMessage(
AccessibilityWindowManager::clearAccessibilityFocusMainThread,
AccessibilityWindowManager.this,
mAccessibilityUserManager.getCurrentUserIdLocked(), windowId));
}
private void clearAccessibilityFocusMainThread(int userId, int windowId) {
final RemoteAccessibilityConnection connection;
synchronized (mLock) {
connection = getConnectionLocked(userId, windowId);
if (connection == null) {
return;
}
}
try {
connection.getRemote().clearAccessibilityFocus();
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling clearAccessibilityFocus()");
}
}
}
private DisplayWindowsObserver getDisplayWindowObserverByWindowIdLocked(int windowId) {
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
if (observer.findWindowInfoByIdLocked(windowId) != null) {
return mDisplayWindowsObservers.get(observer.mDisplayId);
}
}
}
return null;
}
/**
* Associate the token of the embedded view hierarchy to the host view hierarchy.
*
* @param embedded The leash token from the view root of embedded hierarchy
* @param host The leash token from the view root of host hierarchy
*/
void associateLocked(IBinder embedded, IBinder host) {
mHostEmbeddedMap.put(embedded, host);
}
/**
* Clear the relationship of given token.
*
* @param token The leash token
*/
void disassociateLocked(IBinder token) {
mHostEmbeddedMap.remove(token);
for (int i = mHostEmbeddedMap.size() - 1; i >= 0; i--) {
if (mHostEmbeddedMap.valueAt(i).equals(token)) {
mHostEmbeddedMap.removeAt(i);
}
}
}
/**
* Register the leash token with its windowId.
*
* @param token The token.
* @param windowId The windowID.
*/
void registerIdLocked(IBinder token, int windowId) {
mWindowIdMap.put(windowId, token);
}
/**
* Unregister the windowId and also disassociate its token.
*
* @param windowId The windowID
*/
void unregisterIdLocked(int windowId) {
final IBinder token = mWindowIdMap.get(windowId);
if (token == null) {
return;
}
disassociateLocked(token);
mWindowIdMap.remove(windowId);
}
/**
* Get the leash token by given windowID.
*
* @param windowId The windowID.
* @return The token, or {@code NULL} if this windowID doesn't exist
*/
IBinder getTokenLocked(int windowId) {
return mWindowIdMap.get(windowId);
}
/**
* Get the windowId by given leash token.
*
* @param token The token
* @return The windowID, or -1 if the token doesn't exist
*/
int getWindowIdLocked(IBinder token) {
final int index = mWindowIdMap.indexOfValue(token);
if (index == -1) {
return index;
}
return mWindowIdMap.keyAt(index);
}
/**
* Get the leash token of the host hierarchy by given token.
*
* @param token The token
* @return The token of host hierarchy, or {@code NULL} if no host exists
*/
IBinder getHostTokenLocked(IBinder token) {
return mHostEmbeddedMap.get(token);
}
/**
* Dumps all {@link AccessibilityWindowInfo}s here.
*/
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
final int count = mDisplayWindowsObservers.size();
for (int i = 0; i < count; i++) {
final DisplayWindowsObserver observer = mDisplayWindowsObservers.valueAt(i);
if (observer != null) {
observer.dumpLocked(fd, pw, args);
}
}
}
}