/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.utils; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.support.annotation.Nullable; import android.support.v4.os.BuildCompat; import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import com.android.talkback.R; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class WindowManager { public static final int WRONG_WINDOW_TYPE = -1; private static final int WRONG_INDEX = -1; private static final int PREVIOUS = -1; private static final int CURRENT = 0; private static final int NEXT = 1; private final boolean mIsInRTL; private final List<AccessibilityWindowInfo> mWindows = new ArrayList<AccessibilityWindowInfo>(); public static class WindowPositionComparator implements Comparator<AccessibilityWindowInfo> { private final boolean mIsInRTL; private Rect mRectA = new Rect(); private Rect mRectB = new Rect(); public WindowPositionComparator(boolean isInRTL) { mIsInRTL = isInRTL; } @Override public int compare(AccessibilityWindowInfo windowA, AccessibilityWindowInfo windowB) { windowA.getBoundsInScreen(mRectA); windowB.getBoundsInScreen(mRectB); if (mRectA.top != mRectB.top) { return mRectA.top - mRectB.top; } else { return mIsInRTL ? mRectB.right - mRectA.right : mRectA.left - mRectB.left; } } } public WindowManager(boolean isInRTL) { mIsInRTL = isInRTL; } /** * Set windows that would be used by WindowManager * @param windows Set the windows on the screen. */ public void setWindows(List<AccessibilityWindowInfo> windows) { // Copy list not to sort the original one. mWindows.clear(); mWindows.addAll(windows); Collections.sort(mWindows, new WindowPositionComparator(mIsInRTL)); } /** * Returns whether accessibility focused window has AccessibilityWindowInfo.TYPE_APPLICATION * type. */ public boolean isApplicationWindowFocused() { return isFocusedWindowType(AccessibilityWindowInfo.TYPE_APPLICATION); } /** * Returns whether accessibility focused window has * AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER type. */ public boolean isSplitScreenDividerFocused() { return isFocusedWindowType(AccessibilityWindowInfo.TYPE_SPLIT_SCREEN_DIVIDER); } private boolean isFocusedWindowType(int windowType) { AccessibilityWindowInfo info = getCurrentWindow(false /* useInputFocus */); return info != null && info.getType() == windowType; } /** * returns true if there is no window with windowType after baseWindow */ public boolean isLastWindow(AccessibilityWindowInfo baseWindow, int windowType) { int index = getWindowIndex(baseWindow); if (index == WRONG_INDEX) { return true; } int count = mWindows.size(); for (int i = index + 1; i < count; i++) { AccessibilityWindowInfo window = mWindows.get(i); if (window != null && window.getType() == windowType) { return false; } } return true; } /** * returns true if there is no window with windowType before baseWindow */ public boolean isFirstWindow(AccessibilityWindowInfo baseWindow, int windowType) { int index = getWindowIndex(baseWindow); if (index <= 0) { return true; } for (int i = index - 1; i > 0; i--) { AccessibilityWindowInfo window = mWindows.get(i); if (window != null && window.getType() == windowType) { return false; } } return true; } /** * @return window that currently accessibilityFocused window. * If there is no accessibility focused window it returns first window that has TYPE_APPLICATION * or null if there is no window with TYPE_APPLICATION type */ public AccessibilityWindowInfo getCurrentWindow(boolean useInputFocus) { int currentWindowIndex = getFocusedWindowIndex(mWindows, AccessibilityNodeInfo.FOCUS_ACCESSIBILITY); if (currentWindowIndex != WRONG_INDEX) { return mWindows.get(currentWindowIndex); } if (!useInputFocus) { return null; } currentWindowIndex = getFocusedWindowIndex(mWindows, AccessibilityNodeInfo.FOCUS_INPUT); if (currentWindowIndex != WRONG_INDEX) { return mWindows.get(currentWindowIndex); } return null; } /** * @return window that is previous relatively currently accessibilityFocused window. * If there is no accessibility focused window it returns first window that has TYPE_APPLICATION * or null if there is no window with TYPE_APPLICATION type */ public AccessibilityWindowInfo getPreviousWindow(AccessibilityWindowInfo pivotWindow) { return getWindow(pivotWindow, PREVIOUS); } /** * Gets the window whose anchor equals the given node. */ public AccessibilityWindowInfo getAnchoredWindow( @Nullable AccessibilityNodeInfoCompat targetAnchor) { if (!BuildCompat.isAtLeastN() || targetAnchor == null) { return null; } int windowCount = mWindows.size(); for (int i = 0; i < windowCount; ++i) { AccessibilityWindowInfo window = mWindows.get(i); if (window != null) { AccessibilityNodeInfo anchor = window.getAnchor(); if (anchor != null) { try { if (anchor.equals(targetAnchor.getInfo())) { return window; } } finally { anchor.recycle(); } } } } return null; } public boolean isInputWindowOnScreen() { if (mWindows == null) { return false; } for (AccessibilityWindowInfo window : mWindows) { if (window != null && window.getType() == AccessibilityWindowInfo.TYPE_INPUT_METHOD) { return true; } } return false; } public int getWindowType(int windowId) { if (mWindows != null) { for (AccessibilityWindowInfo window : mWindows) { if (window != null && window.getId() == windowId) { return window.getType(); } } } return WRONG_WINDOW_TYPE; } public boolean isStatusBar(int windowId) { if (mWindows == null || mWindows.size() == 0) { return false; } return mWindows.get(0).getId() == windowId && mWindows.get(0).getType() == AccessibilityWindowInfo.TYPE_SYSTEM; } public boolean isNavigationBar(int windowId) { if (mWindows == null || mWindows.size() < 2) { return false; } int lastIndex = mWindows.size() - 1; return mWindows.get(lastIndex).getId() == windowId && mWindows.get(lastIndex).getType() == AccessibilityWindowInfo.TYPE_SYSTEM; } /** * @return window that is next relatively currently accessibilityFocused window. * If there is no accessibility focused window it returns first window that has TYPE_APPLICATION * or null if there is no window with TYPE_APPLICATION type */ public AccessibilityWindowInfo getNextWindow(AccessibilityWindowInfo pivotWindow) { return getWindow(pivotWindow, NEXT); } private AccessibilityWindowInfo getWindow(AccessibilityWindowInfo pivotWindow, int direction) { if (mWindows == null || pivotWindow == null || (direction != NEXT && direction != PREVIOUS)) { return null; } int currentWindowIndex = getWindowIndex(pivotWindow); int resultIndex; if (direction == NEXT) { resultIndex = getNextWindowIndex(currentWindowIndex); } else { resultIndex = getPreviousWindowIndex(currentWindowIndex); } if (resultIndex == WRONG_INDEX) { return null; } return mWindows.get(resultIndex); } private int getNextWindowIndex(int currentIndex) { int size = mWindows.size(); if (size == 0 || currentIndex < 0 || currentIndex >= size) { return WRONG_INDEX; } currentIndex++; if (currentIndex > size - 1) { currentIndex = 0; } return currentIndex; } private int getPreviousWindowIndex(int currentIndex) { int size = mWindows.size(); if (size == 0 || currentIndex < 0 || currentIndex >= size) { return WRONG_INDEX; } currentIndex--; if (currentIndex < 0) { currentIndex = size - 1; } return currentIndex; } private int getWindowIndex(AccessibilityWindowInfo windowInfo) { if (mWindows == null || windowInfo == null) { return WRONG_INDEX; } int windowSize = mWindows.size(); for (int i = 0; i < windowSize; i++) { if (windowInfo.equals(mWindows.get(i))) { return i; } } return WRONG_INDEX; } private static int getFocusedWindowIndex(List<AccessibilityWindowInfo> windows, int focusType) { if (windows == null) { return WRONG_INDEX; } for (int i = 0, size = windows.size(); i < size; i++) { AccessibilityWindowInfo window = windows.get(i); if (window == null) { continue; } if (focusType == AccessibilityNodeInfo.FOCUS_ACCESSIBILITY && window.isAccessibilityFocused()) { return i; } else if (focusType == AccessibilityNodeInfo.FOCUS_INPUT && window.isFocused()) { return i; } } return WRONG_INDEX; } private static AccessibilityWindowInfo getDefaultWindow(List<AccessibilityWindowInfo> windows) { if (windows.size() == 0) { return null; } for (AccessibilityWindowInfo window : windows) { if (window != null && window.getType() == AccessibilityWindowInfo.TYPE_APPLICATION) { return window; } } return windows.get(0); } public static CharSequence formatWindowTitleForFeedback( CharSequence windowTitle, Context context) { if (windowTitle == null) { return context.getString(R.string.untitled_window); } return windowTitle; } }