How to find dimensions for status bar, navigation bar and screen in Android AccessibilityService

早过忘川 提交于 2021-01-29 09:02:07

问题


I have a Java app (API 23). In it I have a MainActivity and an AccessibilityService. In the 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);
} 

I need to adjust the width, height, xPos and yPos so it covers the whole screen including the status bar and navigation bar. That means I have to find the dimensions for the status bar, screen, and navigation bar (I can work out the values I need once I have those).

I found Height of statusbar? - best answer seemed to be

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);
                }
            });

But what rootView can I use, since there's no guarantee my Activity will be visible? And do I have to move the creation of the view from onServiceConnected() to inside onApplyWindowInsets()?

Is this really the best way to do this?

Follow up questions to bear in mind for the above - depending on the above and whether it answers them too, I will ask here or in a separate question:

What happens when the screen rotates? What if the status bar and/or navigation bar disappear (for example, if it goes fullscreen)? Seems like I need to handle that, re-read the heights, and re-create the view. Any thoughts or guidance on how to do that would be welcomed.

=== Update - Progress so far ===

I thought I only needed screen size plus info about the status bar (size, location), but Navigation Bar is sometimes on the left side of the screen so I need that, and Keyboard might affect things too.

I can get screen size with

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

I'm still working on the various location and sizes. I tried creating a view and then reading insets but it gave me the wrong answers. So unless a better answer arises I will probably have to use

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

But that can give wrong answers in some cases if Status bar is hidden, and it doesn't tell me where on the screen it is. I'm still working on that; got a few things to try, so we'll see.

Handling rotation in a service can be done using onConfigurationChanged() - see How do I use a service to monitor Orientation change in Android

Hope that helps someone - please, desperately searching for those final pieces.


回答1:


Update: There's a problem when the screen is rotated upside-down. See Find placement of Android Navigation Bar without Insets (inside Service)

I'm currently working on a solution with my own research combined with https://stackoverflow.com/a/55348825/1910690

But I really wish I could just use Insets, or something like that.

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

I tried a bunch of things that seemed to fail because I didn't have an activity; it's possible if I had poked around some more I would have made it work with Insets, and I still hope for an answer using them as they are more official and allow for notches etc.

In the meantime I finally pieced various answers together to make the following. It would probably work for a normal Service as well as an AccessibilityService.

The point is that at the end of onGlobalLayout() inside CreateLayoutHelperWnd() you have the complete information on screen size, orientation, status bar and navigation bar visibility and size, and you know that either you've just started your service or something changed. I found that it gets called twice sometimes even though nothing changed, probably because sometimes it does 'hide status+nav bar' and then 'change orientation' (same results for both), and sometimes it does it the other way around (different results) so I have wrapped the results in a class, and I compare the new results with the results from the last time and only do something if a result changed (that's why I use so many global variables, because those got moved later to the special class).

Please note that this is my first serious foray into Android and Java, so apologies if it's badly done - comments welcomed. It also doesn't handle error checking (eg StatusBar not found). Also note that I have not tested this extensively across multiple devices, I have no idea what it does with folding devices, multiple displays, or even split-screen; but I tried to account for the issues I saw in the various solutions (eg reading Status Bar height sometimes gives the wrong answer when it's closed on some devices, so I try to check if it is open or closed). I'll update if needed as I test more.

In the 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;
}

You will also need to add to your manifest (for the app, not the service):

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

and in your main activity call isSystemAlertPermissionGranted() and then do something meaningful depending on what is returned (possibly waiting until onActivityResult() in some cases). Er, I basically grabbed this wholesale from various places and have not changed it much or at all. :)

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.
        }
    }
}


来源:https://stackoverflow.com/questions/65541203/how-to-find-dimensions-for-status-bar-navigation-bar-and-screen-in-android-acce

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!