29

It is known that when we deny permissions at runtime in Android 6.0 and resume the app from the recent menu, the app process is killed and the app is forcibly restarted. This is allegedly in order to prevent any security snafus:

enter image description here

It is remarkable that, when resumed, the app restarts from the last Activity we left it on. The OS apparently keeps track of the last Activity the user visited.

Unfortunately, this is a problem, as it breaks the flow of the business logic. The user cannot be allowed to access the app midway in this manner.

My question is, is there a way to force the app to restart from the first Activity of the application, instead of the one the user left it on?

Are there any callbacks associated with an application restart / process kill / permissions toggle?

Is this a wrong-headed approach? If so, how? And what would be the correct approach?

This behaviour has of course been observed before:

1. Android Preview M: activity recreates after permission grant

2. Application is getting killed after enable/disable permissions from settings on Android M nexus 6

3. Android Marshmallow: permissions change for running app

4. All the permissions of my app are revoked after pressing “Reset app preferences”

5. Android 6 permission - Crashes when toggling permission in Setting and go back to app

Community
  • 1
  • 1
Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
  • 1
    You shouldn't need to know whether your app has a given permission up until the point where you try to use some feature that require that permission. At that point you take the usual steps: check and (if necessary), request. – Michael Aug 29 '16 at 10:36
  • @Michael: I do understand that. What I am trying to do is prevent the app from restarting from the last `Activity` the user left it on. Instead it should restart from the first `Activity` (i.e. the one that has an `intent-filter` with `action` `MAIN` and `category` `LAUNCHER`). Is there a way to achieve this? – Yash Sampat Aug 29 '16 at 10:57
  • 4
    Well, I haven't tried this, but I suppose you could call `checkSelfPermission` from `onResume`, and if it fails, start your root activity with `FLAG_ACTIVITY_CLEAR_TASK` and `FLAG_ACTIVITY_NEW_TASK` set. – Michael Aug 29 '16 at 10:59
  • "Are there any callbacks associated with toggling the permissions at runtime?" -- no. "is there a way to force the app to restart from the first Activity of the application, and not the one the user left it on?" -- nothing automatic. Michael's solution might work, though I would do something to educate the user (e.g., explanatory dialog) before dumping back in some earlier activity. – CommonsWare Aug 29 '16 at 11:04
  • What's wrong with just finishing the current `Activity`. This go back to the start idea is not really how the Activity stack is supposed to work on Android. – Xaver Kapeller Sep 05 '16 at 15:45
  • @XaverKapeller: I would urge you to try this with a few apps. Chances are that this kind of behavior can potentially be a major source of bugs, and/or compromise the business logic of the app. This is why I'm asking for the app to be restarted from the first `Activity`. – Yash Sampat Sep 05 '16 at 16:04
  • No it does not, I would argue the exact opposite. Your job as a developer is to work together with the Android framework - not to look for ways to make it work like you want it to. If you are looking for an optimal solution that just works than don't try to kill the `Activity` stack. Instead change your implementation to and adapt to the way Android wants you to do this. – Xaver Kapeller Sep 05 '16 at 16:08
  • For example you talk about how you want to prevent the app from restarting from its last `Activity` when in fact nothing like that happens. That `Activity` never stopped being in the foreground and if you want to change that you either need to start a new `Activity` and go somewhere else or call `finish()` to return to the `Activity` before that - the reset and return to start that you want is not an option here. That's not how you are supposed to use that. – Xaver Kapeller Sep 05 '16 at 16:12
  • i would suggest, do not let the user get to that activity. What i mean is, if you want that the user cannot navigate ahead or back from a particular activity, then basically you can ask for that permission on the 1st activity itself. This would be bad practice of course, but depending on your question, i would say that you can use this. – vibhor_shri Sep 05 '16 at 16:15
  • for eg, if you have 7 activities, activity 4 is where you want the permission, if denied you want to load your app from activity 1. Then y not request permission on activity 1 itself? since you do not want the user to either come to activity 4 or activities 3 or 2. – vibhor_shri Sep 05 '16 at 16:17
  • @XaverKapeller: `when in fact nothing like that happens` I would urge you to read the posts I linked to if you don't believe me - others have observed this happening. And you can observe it it yourself - debug any app, and when you follow the steps I have mentioned, the app is restarted and the debug session is terminated. See for yourself ... – Yash Sampat Sep 05 '16 at 16:18
  • @vibhor_shri: interesting idea, but the problem occurs when the user minimizes the app, turns a permission off (in the app settings) and then resumes the app. Your solution will not prevent this problem from occurring ... – Yash Sampat Sep 05 '16 at 16:20
  • I am aware of that. That's not what this is about. Everything here works exactly as it is supposed to. Apps can be killed and restarted at any time without rime or reason - that's just what mobile operating systems do - Android gives you the tools to deal with that more than adequately. You just have to use them. What happens in that case is that the instance state is restored. If you implement your stuff correctly you shouldn't even notice that anything at all happens. – Xaver Kapeller Sep 05 '16 at 16:21
  • @XaverKapeller: `Apps can be killed and restarted at any time without rime or reason` - except that in this case, the app is restarted not randomly, but ***deterministically***, every time we turn off a permission. There is causality here, not randomness. And I'm trying to deal with that causality because it breaks the flow of my business logic. – Yash Sampat Sep 05 '16 at 16:27
  • It doesn't matter what causes this. What changes if you know that it happens deterministically? I would argue it gets easier because you absolutely know at what point you have to be sure that you can safely save and restore the state because as you say - it happens everytime. – Xaver Kapeller Sep 05 '16 at 16:29
  • @Y.S. did you figure out elegant solution for this? – Niko Nov 02 '16 at 08:06
  • 1
    @Niko: I do not even have an "inelegant" solution for this as of now. Do you?? – Yash Sampat Nov 02 '16 at 08:39
  • @Y.S. Only ugly and fast solution I can figure out is System.exit(0). This is not a very common scenario that user will go to app settings and revoke permission. – Niko Nov 02 '16 at 08:53
  • @Niko: yes, but how do you know that the user has revoked a permission? As far as I know, there is no broadcast or callback for this. When and how do you call `System.exit(0)` ?? – Yash Sampat Nov 02 '16 at 09:42
  • @Y.S. I check this out with 2 flags. First is that Activity onCreate parameter Bundle savedInstanceState != null, second variable is my internal business logic related. If you don't have such second variable, you could store permissions to shared preferences and on launch check if they have changed and then store static flag that something is gonna go wrong if I let my activity stack to "restart" in wrong order. – Niko Nov 02 '16 at 09:56
  • @Niko: I understand ... Though I should point out (and you probably know this) that `savedInstanceState` may be non-null for other reasons, or may be null even after a permission reset ... Without a callback or a broadcast, it is impossible to know for sure, I think. – Yash Sampat Nov 02 '16 at 09:59
  • @Y.S. Yes I do know it. Therefore I have a second variable in the check as well. Gotta refactor this thing later when got time, but this solution works for me now. – Niko Nov 02 '16 at 10:04
  • Wish I had that problem. My activity onDestroy gets called and I WANT the activity to restart on granted permission! Doesn't happen and I am trying to get it to happen. – Brian Reinhold Jan 24 '17 at 12:31
  • @BrianReinhold: It normally should. You may have enabled 'Don't keep Activities' in Developer Settings. If so, please disable it. – Yash Sampat Jan 24 '17 at 12:40
  • @Y.S. did u solve this problem ?? – Jeeva Jul 18 '17 at 13:22
  • @Jeeva: I'm afraid not ... :\ – Yash Sampat Jul 18 '17 at 14:16
  • @Y.S. Have you find any solution for this issue because right now I facing the same issue but I haven't found any proper solution. – Darshan Mistry Sep 24 '18 at 12:42
  • @DarshanMistry: If there were a system action broadcast when the user toggles a permission (something like **`ACTION_MY_PACKAGE_REPLACED`** or **`ACTION_LOCALE_CHANGED`**), then we might have a way to respond. Unfortunately there is no such [**documented**] action broadcast .... :\ – Yash Sampat Sep 24 '18 at 13:33
  • @Y.S. No there is no broadcast fire when the user toggles a permission. – Darshan Mistry Sep 24 '18 at 13:37
  • I faced the same issue. Did you find any solution ? – deniz baş Jan 25 '21 at 22:07

5 Answers5

1

Did u mean this?

MainActivity:

public class MainActivity extends AppCompatActivity {

TextView txt;
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    txt = (TextView)findViewById(R.id.txt);

    txt.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            Intent intent = new Intent(MainActivity.this, ActivityB.class);
            intent.putExtra("from","MainActivity");
            startActivity(intent);
            finish();
        }
    });
}
}

ActivityB :

    public class ActivityB extends AppCompatActivity {

String intentTxt="";
final int MY_PERMISSIONS_REQUEST_PHONE_CALL = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_b);
    intentTxt = getIntent().getStringExtra("from");
}


@Override
protected void onResume() {
    super.onResume();
    if(intentTxt.equals("MainActivity")) {
        if (ContextCompat.checkSelfPermission(ActivityB.this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(ActivityB.this, new String[]{android.Manifest.permission.CALL_PHONE},
                    MY_PERMISSIONS_REQUEST_PHONE_CALL);
        }
    }
        intentTxt = "";
}

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_PHONE_CALL: {
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + "32434"));
                startActivity(intent);

            } else {

                Intent intent = new Intent(ActivityB.this,MainActivity.class);
                startActivity(intent);
                finish();
            }
            return;
        }
    }
}
}

You can start the splashActivity/HomeActivity from onRequestPermissionsResult of ActivityB.Check for permission in onResume of ActivityB so that the permission dialog appears when the ActivityB is called for the first time and also when resumed.

1

You should be handling Activity (re)creation using the Bundle provided by onCreate, saved by onSaveInstanceState, more information here.

Pass in the Bundle all you need in order to recover from a previous state and restore your UI seamlessly. Your Activity can be recreated by many reasons, cancellation of permissions is just one of them, screen rotation is another, so as long as you can survive one, you can survive all of them.

Michel Feinstein
  • 13,416
  • 16
  • 91
  • 173
0

I don't know if anyone is still looking for an answer on this, but I'd solve by reversing the logic, instead of waiting for a flag indicating the restart/recreate due to the denial of permission, pass a flag indicating that the Activity was started by another Activity and not recreated by the system. So if FLAG doesn't come in extras then the activities stack must be destroyed and first activity lauched.

Hope it helps someone.

phsa
  • 61
  • 7
0

You can follow below steps for that.

  1. Create a Base Activity that extends AppcompatActivity for maintaining all permissions and check permissions onResume().
  2. Extend Base Activity from all activities.
  3. If any permission is denied, you can do whatever you want from Base Activity.

Happy Coding! :)

Monim Kaiser
  • 65
  • 1
  • 2
  • 7
0

You should put code to check the permission status in your onResume callback. If the user is switching to the System settings permission activity, your Activity will get paused. If the user enables a permission and then returns to your Activity, then onResume will be called, and you will have an opportunity to check for the new permission at that point. Then, you can do whatever you need to restart your activity, such as calling startActivity with an Intent that has FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_CLEAR_TASK and launches your Activity again.

override fun onResume() {
  super.onResume()

  // check for permission
  checkPermissionsAndRestartIfNecessary()
}

private fun checkPermissionsAndRestartIfNecessary() {
  if (ContextCompat.checkSelfPermission(...) {
    ...
  }
}
Jschools
  • 2,698
  • 1
  • 17
  • 18