17

I am working on a lockscreen app and I need to disable the ability to pull down the notification/status bar at the top of the screen. There is an app called Holo Locker and what this app does is when the user pulls down from the top of the screen, it just sets the bar back up to the top of the screen and making it impossible to pull the drawer down.

I have no idea where to start. Any help would be great! Thanks!

JoxTraex
  • 13,423
  • 6
  • 32
  • 45
DDukesterman
  • 1,391
  • 5
  • 24
  • 42

6 Answers6

32

This is possible using reflection. There are plenty of problems though.

There's no way to check if the notification panel is open, or opening. So, we'll have to rely on Activity#onWindowFocusChanged(boolean). And this is where the problems begin.

What the method does:

public void onWindowFocusChanged (boolean hasFocus)

Called when the current Window of the activity gains or loses focus. This is the best indicator of whether this activity is visible to the user.

So, we'll have to figure out a way to distinguish between focus-loss due to showing of notification panel, and focus-loss because of other events.

Some events that will trigger onWindowFocusChanged(boolean):

  • Window focus is lost when an activity is sent to background (user switching apps, or pressing the home button)

  • Since Dialogs and PopupWindows open in their own separate windows, the activity's window focus will be lost when these are displayed.

  • Another instance in which the activity's window will lose focus is when a spinner is clicked upon, displaying a PopupWindow.

Your activity may not have to deal with all of these issues. The following example handles a subset of them:

Firstly, you need the EXPAND_STATUS_BAR permission:

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

Next, declare these class-scope variable in your activity:

// To keep track of activity's window focus
boolean currentFocus;

// To keep track of activity's foreground/background status
boolean isPaused;

Handler collapseNotificationHandler;

Override onWindowFocusChanged(boolean):

@Override
public void onWindowFocusChanged(boolean hasFocus) {

    currentFocus = hasFocus;

    if (!hasFocus) {

        // Method that handles loss of window focus
        collapseNow();
    }
}

Define collapseNow():

public void collapseNow() {

    // Initialize 'collapseNotificationHandler'
    if (collapseNotificationHandler == null) {
        collapseNotificationHandler = new Handler();
    }

    // If window focus has been lost && activity is not in a paused state
    // Its a valid check because showing of notification panel
    // steals the focus from current activity's window, but does not 
    // 'pause' the activity
    if (!currentFocus && !isPaused) {

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

                // Check if the window focus has been returned
                // If it hasn't been returned, post this Runnable again
                // Currently, the delay is 100 ms. You can change this
                // value to suit your needs.
                if (!currentFocus && !isPaused) {
                    collapseNotificationHandler.postDelayed(this, 100L);
                }

            }
        }, 300L);
    }   
}

Handle activity's onPause() and onResume():

@Override
protected void onPause() {
    super.onPause();

    // Activity's been paused      
    isPaused = true;
}

@Override
protected void onResume() {
    super.onResume();

    // Activity's been resumed
    isPaused = false;
}

Hope this is close to what you are looking for.

Note: The flicker that happens when you slide the notification bar and hold on to it is unfortunately unavoidable. Its appearance can however be controlled/improved using 'better' values for handler-delays. This is issue is also present in the Holo Locker app.

DeltaCap019
  • 6,532
  • 3
  • 48
  • 70
Vikram
  • 51,313
  • 11
  • 93
  • 122
  • @yahya No, not at all. We're using reflection because the method we need to invoke is not part of public API. – Vikram Jan 27 '14 at 16:46
  • @Vikram i modified this method by using "disable" method on StatusBarManager and it required STATUS_BAR permission which is a system permission and cannot be achieved on non-rooted devices. So i couldn't manage to disable pulling down. Because as i see, your way is collapsing continuously instead of disabling... So user will be able to pull it down – yahya Jan 27 '14 at 19:11
  • @yahya You're absolutely right. My answer is a hack at best. But OP _did_ request this functionality by referencing the `Holo Locker` app (in the question). So, I would say that this is the closest you can get to a disabled status bar on a stock device. – Vikram Feb 01 '14 at 08:48
  • That's a great explanation and works like a charm. I have one query though, if you can help me with that. Prior to API level 17 the method **collapse** not only prevents from status bar swipe but it also hides recent applications panel forcefully. I wanted to achieve the same with API level 17 but **collapsePanels** is not giving me that behavior. It would be great if I can have some guidance on that. – Abdul Rehman Feb 20 '14 at 07:52
  • @AbdulRehman `Prior to API level 17 the method collapse not only prevents from status bar swipe but it also hides recent applications panel forcefully.` I wasn't aware of this distinction between the two methods. Both these methods delegate to `IStatusBarService` - and from here on, I could not interpret the chain of events. _Possible explanation:_ If I'm not mistaken, recent applications were accessed by long pressing the home button prior to API 17 - activity did lose focus but was not paused (guess). Whereas, API 17 onwards, activity _is_ paused when recent apps are accessed... (contd.) – Vikram Feb 26 '14 at 05:33
  • @AbdulRehman ........ If you're in the mood to experiment, see if changing `if (!currentFocus && !isPaused)` to `if (!currentFocus)` does prevent the _Recent Applications_ dialog from opening/sticking around. This, of course, is not a solution. Even if it works for recent applications dialog, you have no way of knowing why your activity is in the paused state - could be recent apps, could be a lot of events. I'll try this when I get a chance. If you get to it first, let me know. – Vikram Feb 26 '14 at 05:47
  • @Vikram `recent applications were accessed by long pressing the home button prior to API 17`. Recent apps panel is accessed in two different ways depending on the device you are accessing it on.For Tablets we have a separate control where as for smartphones we usually long press the home button to pop it up. I couldn't find any solution to handle this as of yet. I had to hide both these panels to restrict user only play with apps allowed to him by launcher. – Abdul Rehman Feb 26 '14 at 05:49
  • @AbdulRehman `Whereas, API 17 onwards, activity is paused when recent apps are accessed`..... for clarity: [activity is paused] ==> [`collapseNow()` fails at `if (!currentFocus && !isPaused)`]. – Vikram Feb 26 '14 at 05:50
  • We can no longer use `"statusbar"` as a parameter for `getSystemServices`. Is there anything we can do instead? – Mehrdad Salimi Jul 03 '18 at 07:29
  • It does not work: "statusbar" as a parameter for getSystemServices. my scene is: compileSdkVersion 27, minSdkVersion 21,targetSdkVersion 21. Is there more guide, Thanks! – Aaron Oct 05 '18 at 02:30
9

If you just want to prevent user from opening the status, try this :

getWindow().addFlags(WindowManager.LayoutParams.[TYPE_SYSTEM_OVERLAY][1]);

Usage :

super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);
setContentView(R.layout.your_layout);

Add before Based on your specific need you can choose an appropriate WindowManager.LayoutParams.

Hope this helps.

ZZeyaNN
  • 538
  • 8
  • 13
  • 1
    this works nice, im a bit new to android dev, how could i activate this on the home screen ? – Hayden Thring May 10 '16 at 10:28
  • I don't really understand this: do I just add "getWindow().addFlags(WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY);" to my code? (which does not work) – Obiwahn May 25 '16 at 09:41
  • 1
    This work perfectly ! Thanks ! Now I just need to find a way to disable sound and power buttons :D +1 – Ektos974 Jan 30 '17 at 13:58
  • 3
    this code work but i need my activity button and other layout in working condition but after using this we are not able to do that, thanks – nilesh prajapati Oct 05 '17 at 10:53
  • 2
    This works to disable the notification bar, but it also disables everything else too. I don't see how this could be a helpful solution in any case. – Jeff.H Feb 19 '18 at 21:18
  • Works like a charm for our use case. Thank you – E. Williams Oct 27 '21 at 16:16
8
        private void disablePullNotificationTouch() {
                WindowManager manager = ((WindowManager) getApplicationContext()
                        .getSystemService(Context.WINDOW_SERVICE));
                WindowManager.LayoutParams localLayoutParams = new WindowManager.LayoutParams();
                localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
                localLayoutParams.gravity = Gravity.TOP;
                localLayoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |

                        // this is to enable the notification to recieve touch events
                        WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |

                        // Draws over status bar
                        WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;

                localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
                localLayoutParams.height = (int) (25 * getResources()
                        .getDisplayMetrics().scaledDensity);
                localLayoutParams.format = PixelFormat.RGBX_8888;
                customViewGroup view = new customViewGroup(this);
                manager.addView(view, localLayoutParams);
            }

    //Add this class in your project
    public 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", "**********Intercepted");
            return true;
        }
Hanzala
  • 1,965
  • 1
  • 16
  • 43
NitinM
  • 303
  • 4
  • 12
  • There is just a small change to be done for completely disable the pull localLayoutParams.format = PixelFormat.RGBX_8888; This will also disable the notification panel from being pulled – NitinM Mar 22 '16 at 09:55
  • i tried adding this code to a blank project but get "error: cannot find symbol class customViewGroup" – Hayden Thring May 10 '16 at 10:38
  • @HaydenThring : Edited the answer, please check – NitinM Jun 28 '17 at 08:06
2

I tried the accepted answer but I was still able to pull down and quickly change some settings.

There is a different solution which prevents the user from pulling the menu down completely, which I preferred to the accepted answer here.

Community
  • 1
  • 1
0
WindowManager manager = ((WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE));

WindowManager.LayoutParams localLayoutParams = new WindowManager.LayoutParams();
localLayoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
localLayoutParams.gravity = Gravity.TOP;
localLayoutParams.flags = 
    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |

    // this is to enable the notification to receive touch events
    WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL |

    // Draws over status bar
    WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;

localLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
localLayoutParams.height = (int) (40 * getResources().getDisplayMetrics().scaledDensity);
localLayoutParams.format = PixelFormat.TRANSPARENT;

blockingView = new CustomViewGroup(this);
manager.addView(blockingView, localLayoutParams);

Go with this and refer the link for Class customViewGroup

ColdFire
  • 6,764
  • 6
  • 35
  • 51
Chandsi Gupta
  • 107
  • 2
  • 5
-3

That sounds like an absolutely great idea. You would be breaking the default OS functionality that the user expects to be there. Luckily I don't think it is possible, but you achieve something similar makin your activity FullScreen.

public class FullScreen extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
                                WindowManager.LayoutParams.FLAG_FULLSCREEN);

        setContentView(R.layout.main);
    }
}

Do let me know if you find a better solution than this, i will be eager to know that.

Thank you.

Ankit Dhadse
  • 1,566
  • 1
  • 15
  • 19