14

I am having an inconsistent user experience due to the way android navigates back from Android Settings.

In my application the user needs to give my app access to ACTION_USAGE_ACCESS_SETTINGS, which I access with the following:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);

After toggling the setting to on for my application, I need the user to return to my application. The only way to do this that I know of is for them to press the back button on the phone ( would love to know if it is possible to return automatically after the setting has been toggled!!!?!).

Now one of two things will happen:

1) The user has not used android settings recently, so it was not already open ( ie open in the open app drawer). The first press of the back button will take them to my application as desired.

2) The user had used android settings recently. Thus settings was already open in the application drawer. Now when the user presses back, Android will take them back through each setting page they had been using recently (ie the back button takes them through their history in the android settings pages). It may take 2, 3 or 4 presses of the back button to leave Android settings, and return to my application. This is obviously terrible UI/UX, and I was wondering if there is a better way?

I have noticed that when installing Google apps, after toggling the setting to ON, it automatically exits and returns to the application that called the setting. Being able to do that would be ideal, but I just cant work it out.

Thanks!

Denysole
  • 3,903
  • 1
  • 20
  • 28
Geordie Wicks
  • 1,065
  • 1
  • 11
  • 27
  • In general, you can help your cause by getting rid of `FLAG_ACTIVITY_NEW_TASK`, as you specifically are trying to have this be in your own task. In practice, you can't guarantee BACK button behavior, as that is under the control of the app that you're linking to. – CommonsWare Nov 25 '17 at 22:04
  • have you tried this https://stackoverflow.com/a/32983128/6021469? – Shweta Chauhan Nov 26 '17 at 02:41
  • @CommonsWare, thanks, that change means I only have to press the back button twice, which improves upon the worst case scenario. I don't suppose you know why when installing Google Apps, it will immediately revert back to the calling application after toggling setting to the ON position? (ie you slide to ON, then without pressing back button it switches back to the original application) Is this something only Google can achieve, or is there some way to change my application to make that possible? – Geordie Wicks Nov 26 '17 at 21:42
  • "that change means I only have to press the back button twice" -- where does the first BACK press take you? – CommonsWare Nov 26 '17 at 21:47
  • The intent launches to the "Apps with usage access" settings page, I then click on my App, which takes me to the "Usage Access" page for my app. I can then toggle "Permit usage Access" for my app to "ON". Pressing back once takes me back to the "Apps with usage access" settings page. Pressing a second time take me back to the calling activity of my application. – Geordie Wicks Nov 26 '17 at 21:56
  • I have tried using new ComponentName to directly go to the usage access for my application using my package name, but it appears that is not permitted. Thank you for you help btw, I really appreciate it! – Geordie Wicks Nov 26 '17 at 21:58

8 Answers8

7

Ok, so after trying about a millions things, I have come up with 3 different ways to improve my problem.

1) Was provided by @CommonsWare, by removing the FLAG_ACTIVITY_NEW_TASK, it prevented an inconsistent number of back presses needed, and means every time the user would only have to press back twice.

2) Upon further research into the flags, I found that using three combined:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                    intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                    startActivity(intent);

reduced the number of back presses required to 1 single back press. This is simple and reasonable user friendly.

3) Using a handler to continuously check for the required permission after the settings intent has fired. This seems a like a bit of a hack, and I can't believe there is not a better way to do this, but it works exactly as it works when using a Google App. Ie, as soon as you switch the toggle to on, it exits the Android Settings page, and returns to your application, where you left off. I am using:

Handler handler = new Handler();

Runnable checkSettingOn = new Runnable() {

    @Override
    //@TargetApi(23)
    public void run() {
        Log.d(TAG, "run: 1");
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.d(TAG, "run: 2");
            return;
        }
        if (isAccessGranted()) {
            Log.d(TAG, "run: 3");
            //You have the permission, re-launch MainActivity
            Intent i = new Intent(MainActivity.this, MainActivity.class);
            Log.d(TAG, "run: 4");
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            startActivity(i);
            return;
        }
        handler.postDelayed(this, 200);
    }
};

Then when you fire the intent to get to the Android settings page, just be sure to start the handler:

handler.postDelayed(checkSettingOn, 1000);

Hope this helps someone else with a similar issue.

Geordie Wicks
  • 1,065
  • 1
  • 11
  • 27
  • Good news: there's a built-in way to detect when the user grants usage access. See https://stackoverflow.com/a/54838247/238753. – Sam Feb 23 '19 at 04:43
  • Approach #2 doesn't work for cases where Settings app is already running and the ActionBar back is used in the Settings app... (at least for Locale page) – Leo K Jun 29 '20 at 18:07
  • Can we do startActivityForResult() to go back to the App after enabling location setting ? – K Pradeep Kumar Reddy Jul 02 '20 at 06:54
2

After you have started Settings activity you can run periodical task, that is invoked every, for example 500ms, and checks if permission is granted.
Mark your setup activity with flag singleTask/singleInstance, and start it, if permission is granted. Existing instance of activity will be moved to top.

I did this for notifications access permission.

Ufkoku
  • 2,384
  • 20
  • 44
  • 1
    thanks, this is what I ended up going with, it seems like a bit of a hack, but it is the most end-user friendly solution I think. I'm accepting my answer as it has more detail, but giving you the bounty, I hope that is OK and within the rules. – Geordie Wicks Nov 30 '17 at 22:46
  • Can we do startActivityForResult() to go back to the App after enabling location setting ? – K Pradeep Kumar Reddy Jul 02 '20 at 06:55
  • @KPradeepKumarReddy when you use `startActivityForResult` it just means that started activity will provide a result back to starter activity during started activity finishing. – Ufkoku Jul 02 '20 at 07:56
1

Extending upon Ufkoku's answer, I used a periodic check on the status of the permission. To get the user back, I used startActivityForResult and finishActivity.

Sample Code : I used this for the "USAGE_ACCESS_SETTINGS".

var intent = Intent("android.settings.USAGE_ACCESS_SETTINGS")
intent.data = Uri.fromParts("package", "com.example.app", null)
startActivityForResult(intent,101)

var timer = Timer(true)
timer.scheduleAtFixedRate(object: TimerTask(){
    override fun run(){
        if(checkUsageAccessPermission()){ // checkUsageAccessPermission() is a helper function
            finishActivity(101)
            cancel()                    
        }
    }
}, 0, 1000)
  • `finishActivity(101)` does't navigate back to our screen it simply finish the activity. I replaced that with `val i = Intent(this@Permission, Permission::class.java) i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) startActivity(i)` and that worked for me – Ghayas Aug 03 '22 at 10:52
1

The solution from @Geordie Wicks was close to what I was looking for, but it seems like having the Android Settings open before launching your intent would cause the back button to take you to the previous Android Settings screen.

My solution involves two more flags, Intent.FLAG_ACTIVITY_CLEAR_TOP and Intent.FLAG_ACTIVITY_CLEAR_TASK.

You can define your intent as below:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
                startActivity(intent);

From my experience, this will make it so that a back button press will take you back to your application regardless of whether or not the Android Settings were open beforehand.

Jose Jaime
  • 33
  • 3
  • Not working on samsung tab active 2 (Android 9): Going to all-files-permission: 1 backpress to return Then to overlay permision: 2 backpresses (1st going to all files again) Then to write-settings: 3 backpresses (going to the other 2 first) – FrankKrumnow May 24 '23 at 11:09
0

You can do one thing here. you have to confirm that you only open the settings component that you require to be opened .... Do not open the main settings app. Open only a single component within settings app that you want. So in that way when ever you will press back, you will jump back to your application.

For example if i want to open bluetooth settings in settings application, I wont open the main Settings app instead i will open only bluetooth component of settings. In that way when i press back i will return to my own app because i haven't open the main setting app so i do not need to navigate in it.

ComponentName cn = new ComponentName("com.android.settings", 
                   "com.android.settings.bluetooth.BluetoothSettings");
MezzDroid
  • 560
  • 4
  • 11
  • Thanks, but unfortunately you cannot directly access USAGE_SETTINGS page for your application using this method. :( You have to first go to the generic usage settings page, then select your application. – Geordie Wicks Nov 26 '17 at 22:12
  • No there are many ways .... Download Activity Launcher App ..... it is an app that gives you packages name for all the activities within your phone. Find the usage Settings and click on it. It will give you a package name place it in Component name and you are good to go ! – MezzDroid Nov 26 '17 at 22:43
  • Ok, I tried that, and it gives a ActivityNotFoundException. – Geordie Wicks Nov 27 '17 at 00:18
  • that is a very interesting app though thanks, I think I might be able to use that to bypass some of my problems, and get the info directly without needed to actually grant the app the required permission. Kind of seems like a security hole? The app can directly access the usage settings, without needing to ask for permission? – Geordie Wicks Nov 27 '17 at 00:27
  • Yes it actually does and it cant give that error i think you are not placing the package name correctly ....i have tried it many times. It doesn't fail. – MezzDroid Nov 27 '17 at 00:30
  • Activity not found means that package name you are trying to access was not found .. so its a mistake on your end. – MezzDroid Nov 27 '17 at 00:33
  • Kindly dont forget upvote if my answer helped . Thankyou ! – MezzDroid Nov 27 '17 at 00:34
  • Ok maybe I am doing something stupid. I am using: intent.setComponent(new ComponentName("myapp.com.stats","com.android.settings.Settings$SecuritySettingsActivity"));, and it gives the exception – Geordie Wicks Nov 27 '17 at 00:37
  • This comment would suggest it is not acutally possible for anything with secure settings:https://stackoverflow.com/questions/32822101/how-to-programmatically-open-the-permission-screen-for-a-specific-app-on-android/32983128#32983128 – Geordie Wicks Nov 27 '17 at 00:39
  • you are setting component as your own package that is wrong try like this: intent.setComponent(new ComponentName("com.android.settings.Settin‌​gs","com.android.settings.Settin‌​gs$SecuritySettingsA‌​ctivity")) ; – MezzDroid Nov 27 '17 at 00:40
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/159865/discussion-between-geordie-wicks-and-mezzydroid). – Geordie Wicks Nov 27 '17 at 00:45
0

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); Uri uri = Uri.fromParts("package", getPackageName(), null); intent.setData(uri); startActivity(intent);

This is also the simplest way to navigate it

Dheeraj Gupta
  • 405
  • 1
  • 4
  • 12
0

Sorry if the answer was late but I left my answer here for others to overcome this problems. To me the problems might be ourself not from Android. For e.g I have a listener, which listen to the state of the switch

setOnCheckedChangeListener { _, _ ->
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                intent.data =
                    Uri.fromParts(
                        "package",
                        navigator.activeFragment?.activity?.packageName,
                        null
                    )
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK

                navigator.activeFragment?.startActivity(intent)
            }

What happened with this code?

The user go to Setting and then navigate back. If you have set something that update the UI like belows here after user go back, then this will cause multiple listener listen to the change of switch

    override fun onResume() {
    super.onResume()
    if (!isFirst) {
        // Notify that user might have change the notification setting
        (binding.rvSetting2.adapter as SettingAdapter).notifyItemChanged(0)
    }
    isFirst = false

My solution

You may add this line inside the listener, this will remove the current switch listener, which can avoid go to Setting after and after when switch change.

                setOnCheckedChangeListener { _, _ ->
                val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
                intent.data =
                    Uri.fromParts(
                        "package",
                        navigator.activeFragment?.activity?.packageName,
                        null
                    )
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK

                navigator.activeFragment?.startActivity(intent)

                // Remove the current listener in case of user went back from Setting and call on bind view holder again
                // This cause multiple listener listen to the change of this switch
                setOnCheckedChangeListener(null)
            }

I know that this is just my personal solution, but if you share similar ways, this might be help.

Hoang Thinh
  • 111
  • 1
  • 12
-1

You have to confirm that you only open the settings of your app that you require to be opened. Do not open the main settings app. Open only your app settings that you want. So in that way whenever you will press back, you will jump back to your application.

Uri uri = Uri.fromParts("package", getPackageName(), null);
Intent intent = new Intent();                            
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(uri);
startActivity(intent);
Denysole
  • 3,903
  • 1
  • 20
  • 28
  • Can we do startActivityForResult() to go back to the App after enabling location setting ? – K Pradeep Kumar Reddy Jul 02 '20 at 06:55
  • What you said is not the question asked. It requires Settings.ACTION_USAGE_ACCESS_SETTINGS, if you pass Settings.ACTION_USAGE_ACCESS_SETTINGS instead of Settings.ACTION_APPLICATION_DETAILS_SETTINGS in your code then your code crashes! – Kishan Solanki Nov 06 '20 at 06:48