0

我有一个 Java 应用程序(API 23)。在其中我有一个MainActivity和一个AccessibilityService。在 AccessibilityService 中:

@Override
public void onServiceConnected() {
    oView = new LinearLayout(this);
    oView.setBackgroundColor(0x88ff0000); // The translucent red color
    WindowManager.LayoutParams params = new WindowManager.LayoutParams(
            width,
            height,
            xPos, yPos,
            WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
            WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE |
                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
                    WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
            PixelFormat.TRANSLUCENT
    );
    WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    wm.addView(oView, params);
} 

我需要调整width, height,使其覆盖整个屏幕,包括状态栏和导航栏xPosyPos这意味着我必须找到状态栏、屏幕和导航栏的尺寸(一旦有了这些值,我就可以计算出我需要的值)。

我找到了状态栏的高度?- 最好的答案似乎是

ViewCompat.setOnApplyWindowInsetsListener(rootView, new OnApplyWindowInsetsListener() {
                @Override
                public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
                    //THIS is the value you want.
                    int statusBarHeight = insets.getSystemWindowInsetTop();
                    int navigationBar = insets.getSystemWindowInsetBottom();

                    // Let the view handle insets as it likes.
                    return ViewCompat.onApplyWindowInsets(v, insets);
                }
            });

但是我rootView可以使用什么,因为不能保证我的 Activity 是可见的?我是否必须将视图的创建从内部移动onServiceConnected()到内部onApplyWindowInsets()

这真的是最好的方法吗?

针对上述问题要记住的后续问题 - 取决于上述内容以及它是否也回答了这些问题,我将在这里或在一个单独的问题中提出:

屏幕旋转时会发生什么?如果状态栏和/或导航栏消失(例如,如果它进入全屏状态)怎么办?似乎我需要处理这个问题,重新读取高度,然后重新创建视图。欢迎任何关于如何做到这一点的想法或指导。

=== 更新 - 到目前为止的进展 ===

我以为我只需要屏幕大小以及有关状态栏的信息(大小、位置),但导航栏有时位于屏幕的左侧,所以我需要它,而且键盘也可能会影响事情。

我可以得到屏幕尺寸

    Display myDisplay = wm.getDefaultDisplay();
    Display.Mode mode = myDisplay.getMode();
    int height = mode.getPhysicalHeight();
    int width = mode.getPhysicalWidth();

我仍在研究各种位置和尺寸。我尝试创建一个视图然后阅读插图,但它给了我错误的答案。因此,除非出现更好的答案,否则我可能不得不使用

    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }

但是,如果状态栏被隐藏,在某些情况下可能会给出错误的答案,并且它不会告诉我它在屏幕上的位置。我仍在努力;有一些事情要尝试,所以我们拭目以待。

可以使用 onConfigurationChanged() 处理服务中的旋转 - 请参阅如何使用服务来监控 Android 中的方向变化

希望对某人有所帮助-请拼​​命寻找那些最后的作品。

4

1 回答 1

0

更新:屏幕倒置时出现问题。请参阅查找没有插入的 Android 导航栏的位置(在服务内)

我目前正在通过我自己的研究结合https://stackoverflow.com/a/55348825/1910690研究解决方案

但我真的希望我可以只使用 Insets 或类似的东西。

===============

我尝试了一堆似乎失败的事情,因为我没有活动;如果我再四处寻找,我可能会使其与 Insets 一起使用,我仍然希望使用它们得到答案,因为它们更正式并且允许缺口等。

在此期间,我终于将各种答案拼凑在一起,得出以下结论。它可能适用于普通服务以及 AccessibilityService。

关键是在onGlobalLayout()里面CreateLayoutHelperWnd()你有关于屏幕大小、方向、状态栏和导航栏可见性和大小的完整信息,并且你知道你刚刚启动了你的服务或者发生了一些变化。我发现它有时会被调用两次,即使没有任何改变,可能是因为有时它会“隐藏状态+导航栏”然后“改变方向”(两者的结果相同),有时它会反过来(不同的结果(班级)。

请注意,这是我第一次认真涉足 Android 和 Java,如果做得不好,请道歉 - 欢迎评论。它也不处理错误检查(例如未找到状态栏)。另请注意,我没有在多种设备上进行过广泛的测试,我不知道它对折叠设备、多显示器甚至分屏有什么作用;但我试图解释我在各种解决方案中看到的问题(例如,在某些设备上关闭状态栏高度时有时会给出错误的答案,所以我尝试检查它是打开还是关闭)。如果需要,我会在测试更多时更新。

在 AccessibilityService 中:

// global variables
int statusBarHeight = -1;
int navigationBarHeight = -1;
int screenWidth = -1;
int screenHeight = -1;
View layoutHelperWnd;

@Override
public void onServiceConnected() {
    WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    CreateLayoutHelperWnd(wm);
}

public void onUnbind() {
    WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
    DestroyLayoutHelperWnd(wm);
}

private void DestroyLayoutHelperWnd(WindowManager wm) {
    wm.removeView(layoutHelperWnd);
}
private void CreateLayoutHelperWnd(WindowManager wm) {
    final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
    p.type = Build.VERSION.SDK_INT < Build.VERSION_CODES.O ?
            WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY :
            WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    p.gravity = Gravity.RIGHT | Gravity.TOP;
    p.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
    p.width = WindowManager.LayoutParams.MATCH_PARENT;
    p.height = WindowManager.LayoutParams.MATCH_PARENT;
    p.format = PixelFormat.TRANSPARENT;
    layoutHelperWnd = new View(this); //View helperWnd;

    wm.addView(layoutHelperWnd, p);
    final ViewTreeObserver vto = layoutHelperWnd.getViewTreeObserver();
    vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        @Override
        public void onGlobalLayout() {
            // catches orientation change and fullscreen/not fullscreen change

            // read basic screen setup - not sure if needed every time, but can't hurt
            WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
            ReadScreenDimensions(wm);   // sets up screenWidth and screenHeight
            statusBarHeight = GetStatusBarHeight();
            navigationBarHeight = GetNavigationBarHeight();

            int windowHeight = layoutHelperWnd.getHeight();
            int windowWidth = layoutHelperWnd.getWidth();

            Boolean isFullScreen, isStatusBar, isNavigationBar;
            isFullScreen = isStatusBar = isNavigationBar = false;

            Configuration config = getResources().getConfiguration();
            if (config.orientation == ORIENTATION_LANDSCAPE) {
                // landscape - screenWidth is for comparison to windowHeight (top to bottom), status bar may be on top, nav bar may be on right
                if (screenWidth != windowHeight)
                    isStatusBar = true;

                if (screenHeight != windowWidth)
                    isNavigationBar = true;
            }
            else {
                // portrait, square, unknown - screenHeight is for comparison to windowHeight (top to bottom), status bar may be on top, nav bar may be on bottom
                if (screenHeight != windowHeight) {
                    int difference = screenHeight - windowHeight;
                    if (difference == statusBarHeight)
                        isStatusBar = true;
                    else if (difference == navigationBarHeight)
                        isNavigationBar = true;
                    else {
                        isStatusBar = true;
                        isNavigationBar = true;
                    }
                }
            }

            if (!isStatusBar && !isNavigationBar)
                isFullScreen = true;

            Log.v(TAG,"Screen change:\nScreen W,H: (" + screenWidth + ", " + screenHeight + ")\nOrientation: " + (config.orientation == ORIENTATION_LANDSCAPE ? "Landscape" : config.orientation == ORIENTATION_PORTRAIT ? "Portrait" : "Unknown") +
                    "\nWindow W,H: (" + windowWidth + ", " + windowHeight + ")\n" + "Status bar H: " + statusBarHeight + ", present: " + isStatusBar + "\nNavigation bar H: " + navigationBarHeight + ", present: " + isNavigationBar + "\nFullScreen: " + isFullScreen);

            // do any updates required to the service's assets here
        }
    });
}

public void ReadScreenDimensions(WindowManager wm) {
    Display myDisplay = wm.getDefaultDisplay();
    Display.Mode mode = myDisplay.getMode();
    screenHeight = mode.getPhysicalHeight();
    screenWidth = mode.getPhysicalWidth();
}

public int GetStatusBarHeight() {
    // returns 0 for no result found
    int result = 0;
    int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
    if (resourceId > 0) {
        result = getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}
public int GetNavigationBarHeight() {
    // returns 0 for no result found
    int result = 0;
    int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
    if (resourceId > 0) {
        return getResources().getDimensionPixelSize(resourceId);
    }
    return result;
}

您还需要添加到您的清单(对于应用程序,而不是服务):

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

然后在您的主要活动调用中,然后根据返回的内容做一些有意义的事情(在某些情况下isSystemAlertPermissionGranted()可能等到)。onActivityResult()呃,这个我基本上是从各个地方拿来批发的,没怎么改过。:)

public static int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE= 1234;

public boolean isSystemAlertPermissionGranted() {
    // if this doesn't work, try https://stackoverflow.com/questions/46208897/android-permission-denied-for-window-type-2038-using-type-application-overlay
    if (Build.VERSION.SDK_INT >= 23) {
        if (!Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
            return false;
        }
        else
        {
            Log.v(TAG,"Permission is granted");
            return true;
        }
    }
    else { //permission is automatically granted on sdk<23 upon installation
        Log.v(TAG, "Permission is granted");
        return true;
    }
}

@Override
protected void onActivityResult(int requestCode, int resultCode,  Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
        // Check if the app get the permission
        if (Settings.canDrawOverlays(this)) {
            // Run your logic with newly-granted permission.
        } else {
            // Permission not granted. Change your logic accordingly.
            // App can re-request permission anytime.
        }
    }
}
于 2021-01-20T06:41:18.027 回答