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!
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.
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.
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)
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.
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");
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
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.
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);
Related
Is there any way to, in my app, to redirect a user to a specific settings 'page'? My app works as a lock screen app, so I want to be able to redirect the user directly to the "Lock Screen" section of the Android settings. (Preferably via a button or something similar)
ACTION_SECURITY_SETTINGS Intent:
Intent intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
startActivity(intent);
For complete Settings Intents
I managed to find the correct answer in an old Stackoverflow-post from a while back. The code snippet now looks like this:
Intent intent = new Intent(DevicePolicyManager.ACTION_SET_NEW_PASSWORD);
startActivity(intent);
val intent = Intent(Settings.ACTION_SECURITY_SETTINGS)
intent.flags =
Intent.FLAG_ACTIVITY_NEW_TASK or
Intent.FLAG_ACTIVITY_CLEAR_TOP
startActivity(intent)
Note the flags. These are optional, but necessary to remove the previous settings screen from the screen if it was previously open (this is needed if your application wants to open multiple settings screens).
I have a simple task - activate an app from the push on a particular activity (not on the start activity)
Imaging I have 3 activities in the app:
A (splash)
B (items list)
C (selected item details)
Some pre-requirements:
With push I'm getting the id of item to select.
On the splash I'm forcing an authentication.
One of the conditions - I couldn't move authentication let's say to another activity or to application service for example.
Now I could create several statements. When I tap on push to activate the app:
When push is arrived the PushIntentService generates a notification which specifies item id in intent extras If the app was terminated I should start the app from the activity A (to force authentication)
If the app was backgrounded (works in background) I should re-activate it at the same place (to skip re-authentication)
Once the app is activate I will navigate to Activity C with item id fetched from extras.
Right now I'm using the following code to generate the notification (item 1, Xamarin.Android syntax):
var resultIntent = new Intent(Application.Context, typeof(SplashScreen));
resultIntent.AddFlags(ActivityFlags.ClearTop | ActivityFlags.SingleTop);
if (extras.ContainsKey("ItemId"))
{
var itemId = extras["ItemId"];
resultIntent.PutExtra("ItemId", itemId);
}
var resultPendingIntent = PendingIntent.GetActivity(Application.Context, 0, resultIntent, 0);
builder.SetContentIntent(resultPendingIntent);
var notification = builder.Build();
This notification works absolutely fine in all cases but I see here one issue.
I'm restarting the app from the very beginning every time I'm tapping on a notification.
What I want is when the app is backgrounded I need just to activate it (like iOS does) and navigate to required page (faster activation and avoid re-authentication).
How can I achieve this and modify the code above?
Create a new Activity for this. The notification should start that Activity (without any flags).
In onCreate() of this new Activity, do something like this:
super.onCreate(...);
if (!isTaskRoot() && alreadyAuthenticated) {
// Go directly to details page
Intent redirectIntent = new Intent(this, Details.class)
redirect.putExtra("id", itemId);
startActivity(redirectIntent);
else {
// This means the app was not running, so redirect to Splash
Intent redirectIntent = new Intent(this, Splash.class)
startActivity(redirectIntent);
}
finish();
isTaskRoot() will return true if the app was not running when the user clicked on the Notification. If the app was already running, it should return false.
To test if you are already authenticated, you could call a static method or check a static variable or maybe you have some other method of doing this. Depending on what you want the Activity stack to look like if the user was already in the "item details" or "item list" activites, you may want to add SINGLE_TOP and/or CLEAR_TOP flags when redirecting to the item details Activity.
Hopefully you get the point.
I found the answer on how to simulate launcher icon tap intent. I'm using it to create pending intent for my push:
var launchIntent = PackageManager.GetLaunchIntentForPackage(PackageName);
This is exactly what I wanted and it works perfectly fine in my case.
I have two exit scenarios in my application :-
1) whenever user enters wrong password for 3 consecutive times, my app should exit and gps notifications must start. For this i have written code as follows :-
if(value.length() >= authenticationCode.length() && !value.equals(authenticationCode))
{
dataEnteredEditText.setError("Invalid Code!!!");
dataEnteredEditText.setText(AntiTheftConstant.EMPTY_STRING);
countAttempts++;
if(countAttempts == 3)
{
SharedPreferences.Editor editor = sharedPref.edit();
editor.putBoolean(AntiTheftConstant.USER_AUTHENTICATION_STATUS, false);
editor.commit();
registerForLocationUpdates();
dataEnteredEditText.removeTextChangedListener(this);
startProcessForAuthenticationFailure();
moveTaskToBack(true);
}
}
moveTaskToBack() minimizes my activity and moves it to background. But i don't want this sort of behaviour. I want my application to completely exit with no acitivities in minimized state or in background.
2) User is asked for one time password(OTP) and if he enters correct password GPS notification should stop and my application should again exit. For this scenarion i have written following code:-
if((authenticationCode.equals(sharedPref.getString(AntiTheftConstant.GENERATED_PASSWORD, AntiTheftConstant.DEFAULT_APPLICATION_AUTHENTICATION_CODE))))
{
editor.putBoolean(AntiTheftConstant.USER_AUTHENTICATION_STATUS, true);
editor.commit();
stopService(new Intent(applicationContext, EmailMonitoringService.class));
stopService(new Intent(applicationContext, LocationDetector.class));
AlarmManager am=(AlarmManager)applicationContext.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(applicationContext, TheftAlarmReceiver.class);
PendingIntent pi = PendingIntent.getBroadcast(applicationContext, 0, i, PendingIntent.FLAG_UPDATE_CURRENT);
am.cancel(pi);
// Intent intent = new Intent(Intent.ACTION_MAIN);
//
// intent.addCategory(Intent.CATEGORY_HOME);
//
// intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// startActivity(intent);
this.finish();
}
I have tried both code blocks below:-
this.finish()
And
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
But none of them is working fine. Both code blocks above are moving my current activity to background but none of them makes my application exit gracefully.
If you see any problem with above approaches please do suggest. Thanks in advance.
finish() will push the application to the background. It doesn't "exit" the application per-se, it rather closes the current activity. If it's the only open activity, it "closes" the application.
Android decides when to actually release the resources associated with the activity and application and good practice says: "let Android decide when to drop the resources".
For more info, read this.
If you really have to clear the application completely (can be dangerous if you don't close certain resources and events first) it can technically be done using android.os.Process.killProcess(android.os.Process.myPid());
try this
You must call this.finish() after super.onCreate(...)
If you want to finish something in an Activity you should just use:
finish();
If you want to finish something in a Fragment you should use:
getActivity().finish();
I am puzzled over this behavior I am experiencing in an application I am developing...
Short:
Intent data is not clearing out when the user presses the back button to leave the application and then presses the recent button to re-enter the application. (Every other case, the intent data is cleared out)
Long:
I have an application with a splash screen that is used to collect data that is passed in from a URI scheme. I then setup an intent to forward the data to the main activity. The main activity has fragments and is based off the master/detail template.
The intent data is cleared out in all cases, such as pressing the home button and then going back to the application, pressing the recent apps button and then going back to the application, etc. The only case where the intent data is not cleared out is when the user presses the back button and then the recent apps button to get back into the application.
Relevant snippets of code that involve the intents:
// Splash Screen Activity
#Override
protected void onPostExecute(Void result) {
// Data is done downloading, pass notice and app ids to next activity
Intent intent = new Intent(getBaseContext(), ListActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("id1", id1);
intent.putExtra("id2", id2);
intent.putExtra("id3", id3);
startActivity(intent);
finish();
}
// ListActivity retrieving intent data
Intent intent = getIntent();
if (intent != null) {
this.id1 = intent.getExtras().getString("id1");
this.id2 = intent.getExtras().getString("id2");
this.id3 = intent.getExtras().getString("id3");
}
// ListActivity clearing intent data
#Override
public void onPause() {
super.onPause();
// Clear intent data
Intent intent = getIntent();
intent.putExtra("id1", "");
intent.putExtra("id2", "");
intent.putExtra("id3", "");
}
I want to note that I have also tried using intent.removeExtra("id1") but that too did not work.
Any idea what is going on? It is as if Android is keeping the old intent even though onPause() is always called to clear the intent data.
actually this is due to Android starting the app from the history hence the intent extras are still in there
refer to this questions Android: Starting app from 'recent applications' starts it with the last set of extras used in an intent
so adding this conditional to handle this special case fixed it for me
int flags = getActivity().getIntent().getFlags();
if ((flags & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) != 0) {
// The activity was launched from history
// remove extras here
}
I believe the difference here is that the Back key is actually causing your Activity to finish, where as pressing Home causes your activity to be paused, but not finished.So when your process is brought back to the front in the Home case, it is simply resuming an already existing Activity instance, whereas in the Back case, the system is instantiating a new copy of your Activity, calling onCreate(), and handing it a fresh copy of the last Intent recorded for that activity.
In onPause() you are clearing the extras in a "copy" of the Intent. You can try adding
setIntent(intent);
to onPause() after you've cleared the extras (although calling removeExtra() would probably also work instead of setting extras to empty strings).
NOTE:
However, I would suggest that this design is flawed. You shouldn't use the Intent to keep track of state in your application. You should save some state in shared preferences because this will survive your app being killed/restarted, a reboot of the phone, or whatever.
The problem is that the new Intent is not persisted, so that If the user presses the HOME button and your app goes to the background and then Android kills your app because it is not active, when the user returns to the app, Android will create a new process for your app and it will recreate the activity using the original Intent.
Also, if you read the documentation for getIntent() it says that it returns the Intent that started the activity.
To get around the issue I was facing, I opted to use SharedPreferences as a means to pass data between activities.
I know SharedPreferences isn't typically used for this purpose, but it solved my issue and works.
I am still not completely sure of this opening a new screen with a new intent. I have two problems. 1 is getting it to work and the second is more theory.
Firstly I have two packages com.quiz.max and com.reason.max both have activities names accordingly eg Quiz and Reason respectively. Here is the on click code I am trying to execute at the moment in quiz to go to reason.
Intent intent = new Intent();
intent.setClassName("com.reason.max", "com.reason.max.Reason");
this.startActivityForResult(intent, requestCode);
Secondly I heard if I start this intent then everytime i click the button a new intent is created. Does this mean if the user goes to reason page and navigates back and clicks the button again they actually create a new intent instead of going back to the already active one. Thus dozens could be opened via this method. Therefore should I close each reason intent once navigated back or is this a redundant point?
Max
I think you want
Intent intent = new Intent(this, Reason.class);
startActivityForResult(intent, requestCode);
Secondly, you don't "start an intent". You use an intent to ask an Activity to start, in this case the Reason activity. And yes, the default behavior is to start a new instance of the activity each time it is requested.
You can alter this behavior with the launchMode.
Make sure you read and understand the Activity lifecycle. You don't need to worry about too many Activities in existence, Android will handle that for you, but you should properly save state and clean up connections in the appropriate lifecycle methods.