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