Unable to resume state of activity on clicking notification - android

Context: There is a toggleButton which toggles between 'Record' and 'Stop'. When record is clicked, a notification is created to tell the user that there is a background service running, even if the user has hit the home button etc. and moved out of the app. On clicking the notification, the user should be able to come to the activity and 'Stop' the recording and save the file.
The problem: If i click record (and thus toggling the button), then hit the home button, and then click the notification, it comes to the activity, but the toggleButton is in 'Record' instead of 'Stop'. i.e. it has not retained the previous state. I presume this is because a new instance of the activity is created instead of reusing the existing one.
The code:
public void showRecordingNotification(){
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
mBuilder.setSmallIcon(R.mipmap.ic_launcher);
mBuilder.setContentTitle("Recording in progress!");
mBuilder.setContentText("Go to the app to stop.");
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent appPendingIntent=PendingIntent.getActivity(this,0,notificationIntent,PendingIntent.FLAG_UPDATE_CURRENT);
mBuilder.setContentIntent(appPendingIntent);
Notification notification= mBuilder.build();
notification.flags = Notification.FLAG_ONGOING_EVENT | Notification.FLAG_NO_CLEAR;
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
The android manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="space.wavicle.sensorvector2d" >
<uses-feature
android:name="android.hardware.sensor.accelerometer"
android:required="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name">
<service android:name=".DataLogger"/>
<activity
android:name=".MainActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ReplayActivity"
android:label="#string/title_activity_replay"
android:parentActivityName=".MainActivity" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="space.wavicle.sensorvector2d.MainActivity" />
</activity>
</application>
</manifest>
What I've tried: Googling around and hunting around SO, i found many people having similar sounding problems, and all solutions seemed to revolve around flags of the intents, of which i tried all the combinations that made sense. I also have android:launchMode="singleTask" (also tried android:launchMode="singleInstance") in the manifest. None of them seems to do the job.

you should ask yourself a different question in my opinion.
why does it matter for that button, if the activity is a new instance or not?
how exactly do you know your recording state? if you are saving the state in the activity, that means if the activity is somehow lost(crash) or killed, you lose your state. that's bad.
you say you have a service running in the background, I would let the service hold my state.
EDIT :
as for the instance issue, first of all, here's a link to google documentation so you can read further regarding that topic:
http://developer.android.com/guide/components/tasks-and-back-stack.html#ManifestForTasks
in short, there are 4 launchModes states, standard, singleTop, singleTask, singleInstance.
you can read the differences in the docs.
The way I see it I would recommend you to go with the "singleTask" state.
basically, in singleTask, if you open that activity again thru a notification for example, it will resume the current activity that is already open, you can debug it and see that you won't enter the usual onCreate, you will go thru onResume (and OnNewIntent ofc..)

Related

Open Android app (bring to front) when notification received

I have a simple React Native app, with a single activity MainActivity. The app is meant to be used for an online orders system, so it's meant to be "on" all the time. When the app receives a notification I want to open the app if killed, and bring to the foreground if in background. I have made this work in the past with the example code below but it doesn't work anymore, I don't know if it's an Android API change or what. Please also note I'm not an Android developer so bear that in mind.
I'm using OneSignal and here is my notification received handler:
public class SignalReceiver implements OneSignal.OSRemoteNotificationReceivedHandler {
#Override
public void remoteNotificationReceived(Context context, OSNotificationReceivedEvent notification) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
context.startActivity(intent);
Log.d("ONESIGNAL", "OPENED??");
}
}
and this is my AndroidManifest.xml excerpt. The only relevant part should be the <meta-data> tag as that was added as per OneSignal documentation, but pasting other parts in case relevant:
<application
android:name=".MainApplication"
android:label="#string/app_name"
android:icon="#mipmap/ic_launcher"
android:roundIcon="#mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="#style/AppTheme">
<activity
android:name=".MainActivity"
android:label="#string/app_name"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<meta-data
android:name="com.onesignal.NotificationServiceExtension"
android:value="com.packagename.SignalReceiver"
/>
</application>
The log message display in Logcat in Android Studio so the code is running all fine. I also get a notification 30 seconds after the log shows, so that is also very odd!
I have noticed com.onesignal.NotificationServiceExtension doesn't actually exist if I try to import in code, but I don't know if it's meant to.
Can someone see any problem with my code, and if yes how can I get it to bring the app to foreground?
Edit
I've tried this code in Android versions 11 and 12 on Samsung devices. Standard devices, not kiosk or managed.
I also just noticed if the app has been closed for a few minutes (over 5 minutes) then the app does indeed open up. But not if it's been used recently.

Android handling click on notification

I have this situation in my app: I have these activities
<activity
android:name=".presentation.view.start.view.activity.StartActivity"
android:screenOrientation="sensorPortrait"
android:theme="#style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".presentation.view.main.view.activity.MainActivity"
android:configChanges="locale"
android:screenOrientation="sensorPortrait"
android:theme="#style/AppThemeMultiStep"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".presentation.view.firstScreen.view.activity.FirstScreenActivity"
android:screenOrientation="sensorPortrait"
android:theme="#style/AppThemeMultiStep"
android:windowSoftInputMode="stateAlwaysHidden" />
<activity
android:name=".presentation.view.signup.view.activity.LoginViaPinActivity"
android:screenOrientation="sensorPortrait"
android:theme="#style/AppThemeMultiStep" />
StartActivity acts as a sort of "router" based on the application state:
If I am not logged in, it will launch FirstScreenActivity
If I am logged in, it will launch LoginViaPinActivity, which will login the user based on some logic and then launch MainActivity. At the end of all, MainActivity will be at the root of the activities stack.
At some point the app will receive a notification, and when I tap it I want this:
if the app is running and MainActivity is running, open MainActivity (this is easy, there are planty of ways I can to that with various flags) but if it's not running launch StartActivity (so that I can open the app based on all the startup logics implemented there).
So the options I can think of are:
know if an activity is running in order to fire an intent or another (I don't like static fields solutions like you read in many SO post related to this)
make StartActivity the root of the task and find a combination of intent flags which will act like "launch StartActivity, but if it is running at the root of a task, bring that task to front" (this would be my preferred option if possible)
any suggestion is very appreciated
How do you usually handle this kind of situations? (I don't think I'm the first in the world with this problem :) )
Here is my approach -
Make StartActivity as your router as you've said. Just make launchmode to singleTop in your manifest in order to user onNewIntent() method of an Activity.
You'll generate notification, sending some data with contentIntent - as a result clicking on notification you'll be redirected to StartActivity.
Two cases here -
If StartActivity is in stack
onNewIntent gets called with your new Intent - Choose your activity required to be open - If it is already in stack Bring it to front using FLAG_ACTIVITY_REORDER_TO_FRONT flag
If StartActivity is not running or not in stack
Receive bundle of intent that is being got from the intent via notification, parse data and open an activity accordingly.

Application abnormal behavior does not start activity mentioned in intent

I have 3 major class in the application
1) Intent service: where I receive push notification and open activity according to notification message and other two classes behavior. below is the code which does that
if(Global.isMainScreenRunning){
Intent intent = new Intent(this, MainScreen.class);
intent.setFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
} else if(!Global.NotificationScreenRunning){
Intent intent = new Intent(this, NotificationScreen.class);
intent.setFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
2) NotificationScreen : This is the mediator screen so if the application is not running this screen will be shown first and on click of yes button of this screen MainScreen will be opened and this screen will be finished.
3) Main screen: This is the main screen of the application which show map. its core behavior is that ts a launchmode="singletask" mentioned in menifest file, which means if this screen is running its hole data will be sent to onNewIntent() method rather than opening this screen again.
Now what is happening in flow is
Step 1: application is in background and push notification comes. condition run and the second condition gets success and notification screen intent is shot
step 2: In notification screen I click on ye button to move on to the next main screen
step 3: In Main screen I process this info and perform task or just close the application
Step 4: again a new notification is received and as the application is not running is goes to second condition and start the intent for notification screen but this time no notification screen is launched instead of providing its intent and main screen is launched which is wrong.
This is the abnormal behavior which I am facing that instead of providing class of notification screen for intent main screen is launched which is totally different behavior of application according to android.
Any help from any one who come across such problem will be greatly appreciated.
Edit
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.app"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="15"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_MOCK_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.front" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-feature android:name="android.hardware.microphone" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<permission android:name="com.example.app.permission.C2D_MESSAGE"
android:protectionLevel="signature" />
<uses-permission android:name="com.example.app.permission.C2D_MESSAGE" />
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:xlargeScreens="true" />
<application
android:allowBackup="true"
android:icon="#drawable/android_app_icon"
android:label="#string/app_name"
android:theme="#style/AppTheme" >
<activity
android:name=".SplashScreen"
android:configChanges="keyboardHidden|orientation"
android:label="#string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainScreen"
android:configChanges="keyboardHidden|orientation"
android:launchMode="singleTask"
android:excludeFromRecents="true"
android:screenOrientation="portrait" >
</activity>
<activity
android:name=".NotificationScreen"
android:configChanges="keyboardHidden|orientation"
android:excludeFromRecents="true"
android:screenOrientation="portrait" >
</activity>
<receiver
android:name=".pushnotification.GcmBroadcastReceiver"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.selebrety.app" />
</intent-filter>
</receiver>
<service android:name=".pushnotification.GcmIntentService" />
<meta-data android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
</application>
</manifest>
Second Edit
The mobile in which I am testing is "YU Yureka" here is its specification link. Current it have android 5.0.2 OS
Third Edit
To test this behavior I have debugged the code from eclipse debugger. To check this I have put a break point in NotificationScreen onResume and onCreate but it was never hit instead of onResume of MainScreen is hit.
Also I added a logs in if and else condition but still logs for else condition is printed.
Fourth Edit
Global.isMainScreenRunning: is global boolean variable which is done false in onPause of MainScreen and done true in onResume of MainScreen.
Global.NotificationScreenRunning: is global boolean variable which is done false in onPause of NotificationScreen and done true in onResume of NotificationScreen.
Global state is evil, and this question demonstrates why. To understand what's happening, you'll have to look at every place where your global flags are set or cleared, and analyze all the possible execution paths that might lead there. That's way beyond the scope of the kind of help you can get on SO. And there's an extra complication on Android, since the entire VM might be destroyed (and all your globals cleared) any time your app is in the background. Just as bad, the VM and the Application instance might be retained after you exit the last activity. When that happens, Application#onCreate(...) will not be called again the next time you start the app, and all your static globals will still be set to whatever they were when you last ran the app.
Save yourself a lot of hair-pulling and stop using global state.
You can try add android:taskAffinity=":main" to your MainScreen activity in manifest.
Maybe because the MainScreen still in a same task with the NotificationScreen so it's was call up all the task when NotificationScreen called again.
You can try this demo for more visual:
play app and code
Hope it help.
As I got your point you want to resume Notification Screen every time when you get notification in the app.For this I can suggest a solution this will make the application runs and allows you to achieve expected behavior but when you press home button your Main screen will no longer active.It will be killed.As we all know android maintains activity stacks for all acticities.So if you press device back button then activity will be poped out from stack and you will not face this problem.
I think this is a standard behavior of activity stacks for more information you can go with this link:
http://www.slideshare.net/RanNachmany/manipulating-android-tasks-and-back-stack
You can try this code snippet:
Intent i = new Intent(NotificationFullScreen.this,
MainActivity.class);
i.putExtra("FROM", "Notification");
i.putExtra("bookingId", bookingId);
i.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY
| Intent.FLAG_FROM_BACKGROUND);
startActivity(i);
NotificationFullScreen.this.finish();
Hi #Abhinbav Singh Maurya
I think the problem is that main screen is getting called from your splash screen declared in the manifest as the application launch screen, so when the app is killed and you are calling the intent this will get called.
Instead of that what can you do is broadcast the message to the splash screen and receive it in splash screen and based on condition call the intent from there.
Again here there is a thing that you should take care of, you have to broad cast the intent only after the receiver is registered in the SplahScreen, else it will never be received. so try to user handler.postDelayed method and broadcast after 1000 ms or 700 ms.
Intent mIntent = new Intent(context, SplashScreen.class);
mIntent.setAction(Intent.ACTION_MAIN);
mIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mIntent);
broadcastMessage(context, message);
I have answered a similar question in the following thread.
here
I think this is what you are looking for. Please let me know if this met your requirement or not, if you find the answer useful, if my answer helps you, up vote the answer so that it will be useful to others.
Cheers :)
Edit
I have did a bit research on it.
You can have two launcher activities according to this link.
Try it and let me know. Hope this helps you. :)
if(Global.isMainScreenRunning){
Intent intent = new Intent(this, MainScreen.class);
intent.setFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(Intent);//here is the problem change Intent to intent
} else if(!Global.NotificationScreenRunning){
Intent intent = new Intent(this, NotificationScreen.class);
intent.setFlag(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}

Avoid Activity resuming when app goes in background

In my application I have three activities:
<activity
android:name="com.example.myapp.SplashScreenActivity"
android:exported="true"
android:launchMode="singleInstance"
android:noHistory="true"
android:screenOrientation="sensorLandscape" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.example.myapp.MainActivity"
android:exported="false"
android:launchMode="singleInstance"
android:screenOrientation="sensorLandscape" >
</activity>
<activity
android:name="com.example.myapp.ListActivity"
android:exported="false"
android:launchMode="singleTop"
android:screenOrientation="sensorLandscape" >
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="com.example.myapp.MainActivity" />
</activity>
The first one is the LAUNCHER, SplashScreenActivity, which is a splash screen that disappears quite soon and it's not shown in recent activities, it starts MainActivity. In MainActivity users can select a category and ListActivity shows the items belonging to the given category. This is done with the following code:
Intent i = new Intent(getActivity(),ListActivity.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
i.putExtra("category",mCategory);
startActivity(i);
In ListActivity the onResume method checks for the "category" extra and shows data accordingly. Since Activity launchMode is singleTop, I've also overridden the onNewIntent method to set the new Intent of the Activity.
This works properly if the app doesn't go in background: in this case, when I restart MainActivity and select a category, ListActivity resumes the old Activity showing data belonging to the previously chosen category.
How should I fix flags/launchMode in such way that my app doesn't resume ListActivity with the old data loaded?
You should not use the special launch modes. These are rarely required and only in very specific circumstances. Remove all the launchMode specifiers from your manifest. You also don't need to use these flags when launching ListActivity from MainActivity:
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
Your use of singleInstance launch mode is causing you all these problems.
Assuming you would ditch all the launchModes and Flags, then activity lifecycle gives you all the information needed to implement application as you have described - maybe there is somethings you have not added?
Once application goes to background, then ListActivity will be allowed to save its state, even if system will kill your process (lack of memory etc.), then still - launching back your app will first bring back ListActivity with previously saved instance state. So you will not have behaviour as you describe : when I restart MainActivity and select a category, ListActivity resumes the old Activity showing data belonging to the previously chosen category.
Still, if you need to keep to your current design, then you should somehow inform ListActivity of new data changes, question is if ListActivity.onNewIntent is being called at all in the case your describe? Have you tried adding Intent.FLAG_ACTIVITY_SINGLE_TOP : as per this blog entry: http://www.acnenomor.com/1094151p2/bug-onnewintent-not-called-for-singletop-activity-with-intentflagactivitynewtask ?

starting an Activity from Broadcast Receiver brings up Default Activity

My app contains two activity, one widget and a broadcast receiver.
First activity is default one which is called by home launcher. Second activity is a reminder dialog activity which plays a MP3 and has a dismiss button. It will appear on home screen.
My widget is a custom clock which setup an alarm to call a broadcast receiver every minutes which is responsible to update the clock. There are some reminders at specific time which broadcast receiver calls the reminder activity.
My app now is fully working, but there is a small problem: When app is open in the background (It's not on screen), when broadcast receiver start the reminder activity, the default activity comes first and reminder activity is shown over that. But when I close the app, reminder activity appears on home screen and there is no problem.
This is the code I use to startActivity from receiver:
context.startActivity(new Intent(context, ActivityReminder.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP));
And this is android manifest:
<activity android:name=".MyActivity"
android:theme="#android:style/Theme.Black.NoTitleBar"
android:label="#string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<activity android:name=".ActivityReminder" android:label="#string/reminder" android:screenOrientation="portrait" android:theme="#style/Theme.CustomDialog" />
<receiver android:name=".widget"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
<meta-data android:name="android.appwidget.provider"
android:resource="#xml/widget" />
</receiver>
<receiver android:name=".UpdaterReceiver" />
For now I use this workaround in the default activity to solve the problem, it work just like closing the app:
#Override
protected void onPause()
{
super.onPause();
finish();
}
I found something more annoying. If the main activity is not closed, every call from broadcast receiver opens the activity again even it's open already. Although if the main activity is closed, subsequent call won't do anything.
Your problem is taskAffinity. When you launch ActivityReminder, Android tries to find an appropriate task to launch this into. If your app is not running, it won't find an appropriate task, so it will start a new one and the Activity will show up in a task by itself. However, if your app is already running in the background, Android will bring the existing task to the foreground and launch ActivityReminder into that task.
To solve the problem, add the following to the <activity> definition for ActivityReminder:
android:taskAffinity=""
This tells Android that it shouldn't look for a "matching" task to launch ActivityReminder into.

Categories

Resources