2

In my app, I need to monitor the status bar down so that I can do something in my code. In addition, I hope I can get the 'down' event in the whole application.

In an activity, I can monitor the event by rewrite the function 'onWindowFocusChanged', but I can't do this when the optionMenu was opened. I want a solution to get info when the status bar drop down during the option menu is opened. Global monitoring better.

Here, I tried the float window:

public static void preventStatusBarExpansion(Context context) {
        lbm = LocalBroadcastManager.getInstance(context);

        manager = ((WindowManager) context.getApplicationContext()
                .getSystemService(Context.WINDOW_SERVICE));

        localLayoutParams = new WindowManager.LayoutParams();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            localLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        } else {
            localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
        }

        localLayoutParams.gravity = Gravity.TOP;
        localLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
                | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;

        localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;


        int resId = context.getResources()
                .getIdentifier(STATUS_BAR_HEIGHT, DIMEN, DEF_PACKAGE);
        int result;
        if (resId > 0) {
            result = context.getResources()
                    .getDimensionPixelSize(resId)+10;
        } else {
            // Use Fallback size:
            result = 60; // 60px Fallback
        }

        localLayoutParams.height = result;
        localLayoutParams.format = PixelFormat.RGB_888;

        if (view == null) {
            view = new CustomViewGroup(context);
        }

        manager.addView(view, localLayoutParams);

    }

Unfortunately, my float window stayed under the status bar. Even I can't prevent the status bar expanding. Status bar work properly but my window does't work.

Here my float window is under the statusbar Here my float window is under the statusbar

I move the window in my screen, the float window over all window but just under the status bar. Is it normal? I move the window in my screen, the float window over all window but just under the status bar. Is it normal?

honghong
  • 21
  • 3
  • I am sorry but can you further clarify what do you mean by *status bar down*? – Enowneb Mar 27 '23 at 07:38
  • I am sorry that I didn't describe it exactly. Well, in my application, when user pulls down the status bar( may be he want to use the system setting short-cut), I can get this action and do something else. – honghong Mar 28 '23 at 05:31
  • ```onWindowFocusChanged()``` is probably the best way for your purpose ([Reference](https://stackoverflow.com/questions/12689497/how-to-tell-the-activity-has-been-covered-by-the-notification-area)). So you should consider having a customized Activity with this function overridden and your other Activity should extend it. Or if you simply want to prohibit user from accessing system setting through the status bar, you can consider [blocking the *pull down status bar* action](https://stackoverflow.com/questions/29969086/how-to-disable-status-bar-click-and-pull-down-in-android) for your App. – Enowneb Mar 28 '23 at 05:56
  • Okay, I've tried to cover the status bar with a float window, I think I can get info when user try to pull down the status bar so that I can start my task in the touchEvent function. However, my float window is under the status bar. When I tried to pull down, the float window couldn't get the touch event, instead, the status bar droppd down. – honghong Mar 28 '23 at 07:56
  • May I know why are you changing the height of your floating window? This ```result = context.getResources().getDimensionPixelSize(resId);``` does not work? And with the above code, you are observing that the floating window appears under the status bar? I have tried with the same but there should be a black window blocking the status bar. – Enowneb Mar 28 '23 at 09:05
  • Yeah, I just want the float window cover the status bar, because I will monitor the users' touch action when they try to poll down the status bar. I'm sorry that I forget to make the float window transparent. It's black because I want to see if it work during debugging. – honghong Mar 28 '23 at 09:47
  • Is it possible to capture the screen after you have implemented the above code? I have tried the same and I can see the black overlaying window covers the status bar fully. And yes you can monitor user's touch action in ```CustomViewGroup```. – Enowneb Mar 28 '23 at 09:52
  • I can't capture the user's touch unless the status bar disappear. – honghong Mar 28 '23 at 09:57
  • As you can see, I make the window's hight 10px more than the status bar, I can't move it when I touch the area of status bar, but I can move it if I touch the area a little lower. – honghong Mar 28 '23 at 10:00

1 Answers1

0

The following implementation is only working below Android 8. You can refer to the details here.

public static void preventStatusBarExpansion(Context context) {
    LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);

    WindowManager manager = ((WindowManager) context.getApplicationContext()
            .getSystemService(Context.WINDOW_SERVICE));

    WindowManager.LayoutParams localLayoutParams = new WindowManager.LayoutParams();
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        localLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
        localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
    }

    localLayoutParams.gravity = Gravity.TOP;
    localLayoutParams.flags =
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;

    localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;


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

    localLayoutParams.height = result;
    localLayoutParams.format = PixelFormat.RGB_888;

    CustomViewGroup view = new CustomViewGroup(context);
    manager.addView(view, localLayoutParams);
}

/**
 * This class creates the overlay on the status bar which stops it from expanding.
 */
public static class CustomViewGroup extends ViewGroup {

    public CustomViewGroup(Context context) {
        super(context);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.v("CustomViewGroup", "Pull down status bar action");
        return true;
    }
}

And for Android 8 or above, you may refer to the link above for the corresponding implementation. And that should be the one you have mentioned: onWindowFocusChanged(). You can implement onWindowFocusChanged() in a BaseActivity (For the basic of BaseActivity structure, you may refer to here), and all your other Activity should extend the BaseActivity.

BaseActivity.java

public abstract class BaseActivity extends Activity { 
    @Override
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState);
        setContentView(getLayoutResourceId()); 
    } 

    protected abstract int getLayoutResourceId();

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        Log.d("BaseActivity", "window focus changed");
    
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            collapseNow();
        }
    }

    public void collapseNow() {
        try {
            // Initialize 'collapseNotificationHandler'
            if (collapseNotificationHandler == null) {
                collapseNotificationHandler = new Handler();
            }

            // Post a Runnable with some delay - currently set to 300 ms
            collapseNotificationHandler.postDelayed(new Runnable() {

                @Override
                public void run() {
                    // Use reflection to trigger a method from 'StatusBarManager'
                    Object statusBarService = getSystemService("statusbar");
                    Class<?> statusBarManager = null;

                    try {
                        statusBarManager = Class.forName("android.app.StatusBarManager");
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }

                    Method collapseStatusBar = null;
                    try {
                        // Prior to API 17, the method to call is 'collapse()'
                        // API 17 onwards, the method to call is `collapsePanels()`
                        if (Build.VERSION.SDK_INT > 16) {
                            collapseStatusBar = statusBarManager.getMethod("collapsePanels");
                        } else {
                            collapseStatusBar = statusBarManager.getMethod("collapse");
                        }
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }

                    collapseStatusBar.setAccessible(true);

                    try {
                        collapseStatusBar.invoke(statusBarService);
                    } catch (IllegalArgumentException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                    // Currently, the delay is 10 ms. You can change this
                    // value to suit your needs.
                    collapseNotificationHandler.postDelayed(this, 10L);
                }
            }, 10L);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
} 

MainActivity.java

public class MainActivity extends BaseActivity { 
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    } 

    @Override
    protected int getLayoutResourceId(){ 
        return R.layout.activity_main;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.options_menu, menu);
        return true;
    }
}

options_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item android:id="@+id/item_a"
        android:title="A" />
    <item android:id="@+id/item_b"
        android:title="B" />
    <item android:id="@+id/item_c"
        android:title="C" />
    <item android:id="@+id/item_d"
        android:title="D" />
</menu>

The status bar should be closed automatically even though the option menu is opened: Option menu opened

Enowneb
  • 943
  • 1
  • 2
  • 11