Clicking on notification doesn't open mentioned activity - java

I am trying to open an Activity when the notification is clicked and below is my code.
Intent intent = new Intent(this.getApplicationContext(), NotificationActivity.class);
intent.putExtra("msgBody",messageBody);
intent.putExtra(Constants.NOTIF_INTENT_TYPE,Constants.NOTIF_INTENT_TYPE);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|Intent.FLAG_ACTIVITY_SINGLE_TOP
|Intent.FLAG_ACTIVITY_CLEAR_TOP); //Tried with many options here
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 , intent,
PendingIntent.FLAG_CANCEL_CURRENT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.otp_icon)
.setContentTitle("Push MSG")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, notificationBuilder.build());
Android Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.com.pushapp">
<uses-sdk
android:minSdkVersion="17"
android:targetSdkVersion="21" />
<supports-screens
android:anyDensity="true"
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_LOGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:name=".AndroidPushApp"
android:allowBackup="true"
android:icon="#drawable/ic_launcher">
<activity
android:name=".PushSplashScreen"
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=".MainApplicationScreen"
android:screenOrientation="portrait"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
<activity
android:name=".StartActivity"
android:launchMode="singleTask"
android:screenOrientation="portrait"
android:uiOptions="splitActionBarWhenNarrow"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</intent-filter>
</activity>
<service android:name=".MyFirebaseMessagingService">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<service android:name=".MyFirebaseInstanceIDService">
<intent-filter>
<action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
</intent-filter>
</service>
<meta-data
android:name="com.google.android.gms.version"
android:value="#integer/google_play_services_version" />
<activity
android:name=".NotificationActivity"
android:exported="true"
android:label="#string/title_activity">
<intent-filter>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
Whenever I get notification from FCM I am calling this notification. The NotificationActivity is not opening whenever I click on notification, rather the app is opening(splash screen->starting activity of my usual app flow). Whenever I get notification while the app is already open, the NotificationActivity is getting opened, but not when app is not already opened. Could someone please help me on resolving this?
Note: Please I am reiterating that NotificationActivity.class is not getting opened when clicked on notification when app is not already opened state.

According to FCM Documentation, for receiving and handling messages,
If you want to receive notifications when your app is in the
foreground, you need to add some message handling logic.
To receive messages, use a service that extends
FirebaseMessagingService. Your service should override the
onMessageReceived callback, which is provided for most message types,
with the following exceptions:
1). Notifications delivered when your app is in the background. In
this case, the notification is delivered to the device’s system tray.
A user tap on a notification opens the app launcher by default.
2). Messages with both notification and data payload, both background
and foreground. In this case, the notification is delivered to the
device’s system tray, and the data payload is delivered in the extras
of the intent of your launcher Activity.
So Basically, we have two types of Payloads
1). Notification Payload
2). Data Payload
3). Both (an additional type we can consider).
Now let's discuss one by one these payloads. Before that you need to understand how can you send these Payloads to your app. All you have to do is to make use of any tool that can perform HTTP POST Request. In my case, I am using the Postman tool, a Google Chrome Plugin.
Before making a HTTP Post Request for FCM, you have to consider three things:
1). HTTP Post Request URL : https://fcm.googleapis.com/fcm/send
2). Request Headers :
i). Content-Type : application/json
ii). Authorization : key = YOUR_SERVER_KEY
Below is the screenshot for the same to show how it looks.
3). Body : In this we are going to have JSON for Notification and Data Payloads.
So starting with Notification Payload, the simplest of all. In this case, onMessageReceived() is called only when the app is in Foreground, For all other cases, it's a System Tray Notification, which opens the Launcher Activity when clicked. This is helpful when you don't want to control Notifications by your own and not much data to deal with when Notification comes. You can even control the sound, icon and click_action(only when the app is in Foreground) without writing any code in your onMessageReceived(). One example of a body of such HTTP POST Request is attached in the screenshot below.
For opening desired Activity when sending click_action parameter, you have to use the below code in your onMessageReceived().
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
if (null != remoteMessage.getNotification().getClickAction()) {
startActivity(remoteMessage.getNotification().getClickAction(), null, this);
}
}
and below is your startActivity() method :
public void startActivity(String className, Bundle extras, Context context) {
Class cls = null;
try {
cls = Class.forName(className);
} catch (ClassNotFoundException e) {
//means you made a wrong input in firebase console
}
Intent intent = new Intent(context, cls);
if (null != extras) {
intent.putExtras(extras);
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
}
NOTE : This click_action key will work only when the app is in
Foreground, for all other case when the app is in Background and
closed, it doesn't work. It doesn't even open the Launcher Activity,
in case of Background and Closed, if this parameter is specified.
Now comes the Data Payload. This is similar to the one we have in GCM. This is very important if we want to handle all the Notification stuff by ourselve same as we all were doing in case of GCM. Example of a body of such HTTP POST Request is shown below.
So in this case, onMessageReceived() is called everytime and this will work in the same way as that of GCM, so helpful to all of us. You have to Override onMessageReceived() as shown below.
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Map<String, String> data = remoteMessage.getData();
if (null != data && 0 < data.size()) {
if (data.containsKey("custom_key_1")) {
sendNotification(data.get("custom_key_1"));
}
}
}
private void sendNotification(String messageBody) {
Intent intent = new Intent(this, DesiredActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.ic_stat_ic_notification)
.setContentTitle("FCM Message")
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
Last but not the least, we can send both Notification and Data Payloads as well. In this case, onMessageReceived() is called when the app is in Foreground. For background and closed state, Notification comes in the system tray similar to Notification Payload but the only difference is we can have data extras as well that we can use to redirect user to a desired Activity, when clicked on a Notification. Below is the example of a body of such HTTP POST Request.Example of a body of such HTTP POST Request is shown below.
When clicking on a Notification on System Tray, it will open the Launcher Activity and You need to Override onCreate() of your Launcher Activity to get the data extras and redirect user to the desired Activity. Below is the code, you have to write in onCreate() of your Activity to redirect user to the desired Activity.
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getIntent().hasExtra("custom_key_1") && getIntent().getStringExtra("custom_key_1")
.equals("custom_value_1")){
startActivity(new Intent(this, DesiredActivity.class));
finish();
return;
}
// other operations
}
Another case to this type is, when your Launcher Activity is defined as launchMode="true" in the manifest and when the Notification Arrives, your Launcher Activity is in the Foreground. So when you click on the Notification, you have to Override the onNewIntent() method in your Launcher Activity to open the desired Activity. Below is the sample code for the same.
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (getIntent().hasExtra("custom_key_1") && getIntent().getStringExtra("custom_key_1")
.equals("custom_value_1")) {
startActivity(new Intent(this, DesiredActivity.class));
finish();
}
}
So in short, I would say it's good to go with the Data Payload type as it provides more flexibility and control over the Notification and more importantly as we all are used to GCM, so this type is what we all would like to prefer.
Note : Some devices are having issue receiving Notifications in
Background as I found some queries over same here. Moreover at the
time, I was investigating these cases, my ASUS phone was not receiving
notifications in Background for any of the types mentioned above. So
not sure what's the issue with these devices.

I had the same problem in my app
This link helped me:
https://developer.android.com/training/notify-user/navigation
What you need to do is define parent activity for your desired activity in Manifest:
<activity
android:name=".MainActivity"
android:label="#string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- MainActivity is the parent for ResultActivity -->
<activity
android:name=".ResultActivity"
android:parentActivityName=".MainActivity" />
And then use TaskStackBuilder in onMessageReceived method to create pending intent
// Create an Intent for the activity you want to start
Intent resultIntent = new Intent(this, ResultActivity.class);
// Create the TaskStackBuilder and add the intent, which inflates the back stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addNextIntentWithParentStack(resultIntent);
// Get the PendingIntent containing the entire back stack
PendingIntent resultPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// then use this pending intent to build your notification

You have to use FLAG_UPDATE_CURRENT in pendingIntent.
PendingIntent pendingIntent = PendingIntent.getActivity(this, notificationId /* Request code */, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
and also pass the same id to notificationManager
notificationManager.notify(notificationId /* ID of notification */, notificationBuilder.build());

You can specify any Activity to be receiver for push notifications:
<intent-filter>
<action android:name="PACKAGE_NAME.MESSAGE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
This intent filter for the activity specifies which activity will be launched in response to push notification (PACKAGE_NAME is your Android app package)
So you can add this intent filter in your Activity which you want to open on the click of Push notification.

If you read the firebase docs in detail, there are two types of payloads
Data payload
Notification payload
The data payload triggers the onMessageReceived() callback when app is both foreground and background. This is not the case with notification payload, which triggers the callback in only foreground state. So, if you use the data payload this problem should be solved.

Check this code and let me know.
Intent intent = new Intent(this, LoginActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
pendingIntent = PendingIntent.getActivity(this, 0, intent,
PendingIntent.FLAG_ONE_SHOT);

Thats the intented behaviour. If your app is in background, notification is created by android system which does not have your pendingIntent action.So it does not work. In the foreground case it works because notification is created by your code.
Please check the doc in the below link.
https://firebase.google.com/docs/notifications/android/console-device#receive_and_handle_messages

Sorry not to add a comment as I'm a fairly new comer.
You can just do the following two things for further investigating:
After the notification has been created, use shell command "adb shell dumpsys activity i [your package name]" to see your notification in detail, to confirm it is really what you want.
Remember to replace "[your package name]" with your own package name;
Tracking the event log during the time you are reproducing this using "adb logcat -v threadtime -b events".
Post both of these and we might get something useful about what is going wrong under the hood.

Set your pending intent like below
Intent intent = new Intent(this.getApplicationContext(), NotificationActivity.class);
intent.putExtra("msgBody",messageBody);
intent.putExtra(Constants.NOTIF_INTENT_TYPE,Constants.NOTIF_INTENT_TYPE);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
add it to your notification using
.setContentIntent(pendingIntent);

I use this in my FirebaseMessagingService:
/**
* Create and show a simple notification containing the received FCM message.
*
* #param messageBody FCM message body received.
*/
private void sendNotification(String title, String messageBody, String data) {
Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri= RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = (NotificationCompat.Builder) new NotificationCompat.Builder(getApplicationContext())
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(this.NOTIFICATION_SERVICE);
notificationManager.notify(id++ /* ID of notification */, notificationBuilder.build());
}

Related

how we open activity when app is closed in android

I used calling functionality in my application. when the app is in the foreground then the application is working well. but when the app is in the background then how to open incoming call Activity in android. When a push notification is appeared then open incoming call Activity in android. how to perform this task?
Android 10 (API level 29) and higher place restrictions on when apps can start activities when the app is running in the background. These restrictions help minimize interruptions for the user and keep the user more in control of what's shown on their screen.
https://developer.android.com/guide/components/activities/background-starts
You have to call it in firebase messaging service.
Intent intent = new Intent(this, IncomingCallActivity.class);
intent.putExtra(Constants.KEY_FIRST_NAME, firstName);
intent.putExtra(Constants.KEY_LAST_NAME, lastName);
intent.putExtra(Constants.KEY_EMAIL, email);
intent.putExtra(Constants.KEY_ID, id);
intent.putExtra(Constants.KEY_DP, dp);
intent.putExtra(Constants.CALL_TYPE, callType);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.startActivity(intent);
in manifest
<activity
android:name="IncomingCallActivity"
android:screenOrientation="portrait"
android:showOnLockScreen="true"
android:showWhenLocked="true"
android:turnScreenOn="true" />
<service
android:name="MessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Notifications may have a pending intent attached. This pending intent may start an activity. See Notification.Builder.setContentIntent()
You can add a Pending Intent to your notification, so when user clicks on it, your intent will be launched.
To create a Pending Intent you should:
Create a normal Intent to your destination Activity.
val goActivity = Intent(context, Activity::class.java)
Create a Pending Intent passing a context, a request code to identify the Pending Intent, your normal Intent and a flag to configure the way your Intent is launched.
val goActivityPending = PendingIntent.getActivity(context, pendingIntentCode, goActivity, PendingIntent.FLAG_NEW_TASK)
Add your Pending Intent to your notification builder with method setContentIntent.
val notification = NotificationCompat.Builder(context, channelId) .setContentTitle(context.getString(R.string.notificationTitle)) .setContentText(context.getString(R.string.notificationContent)) .setContentIntent(goActivityPending)
That's it! When you click the notification, your activity will be oppened. You can also use addAction() to add buttons to your notification with different Pending Intents.
If you want to know more about Intent flags, read this: Pending Intent Flags

BroadcastReceiver not called when app is killed

My BroadcastReceiver is not being called, when the app is swiped away. It works fine, when its in the foreground/background.
Im trying to create an alarm clock.
Here is the alarm code:
AlarmManager alarmManager = (AlarmManager)
context.getSystemService(Context.ALARM_SERVICE);
Intent myIntent = new Intent(context, AlarmBCReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context.getApplicationContext(),
0, myIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (10*1000), pendingIntent);
Toast.makeText(context, "Alarm set", Toast.LENGTH_SHORT).show();
The receiver class's onReceive method:
Log.i("BCReceiver", "Received");
Intent startIntent = context
.getPackageManager()
.getLaunchIntentForPackage(context.getPackageName());
startIntent.setAction(Intent.ACTION_MAIN);
startIntent.setFlags(
Intent.FLAG_ACTIVITY_REORDER_TO_FRONT |
Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
);
context.startActivity(startIntent);
And finally the manifest file:
<receiver
android:name=".AlarmBCReceiver"
android:enabled="true"
android:exported="true" />
<activity
android:name=".MainScreenActivity"
android:label="#string/app_name"
android:theme="#style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
What I'm trying to accomplish is to restart the app, when onReceive(), even if the app was killed.
I've read that this should work, and I don't understand why it doesn't.
The logger shows nothing if the app is killed, so I think the onReceive method is not called at all. Note that I'm testing on a real device(Xiaomi Redmi 4x).
The app is written in AndroidStudio.
minSdkVersion 23
targetSdkVersion 27

Run Android app in background without user interaction

I have an android app which keeps updating logs every 5 minutes.
I used Alarm Manager to achieve this.
But the app kept opening and closing automatically.
Is there any way to do this in background??
you can use android Services for this task.
http://developer.android.com/guide/components/services.html
First of all, you have to user `Service: http://developer.android.com/guide/components/services.html
Next, you can start your program in background on BOOT event:
public class BootReciever extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Intent myIntent = new Intent(context, LogService.class);
context.startService(myIntent);
}
Declare theese in your manifest:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<!-- Declaring broadcast receiver for BOOT_COMPLETED event -->
<receiver android:name=".BootReciever" android:enabled="true" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>

Locations updates stay active when Android app is not longer active

I have the following problem: I send in regular interval GPS data from the mobile to a server. In fact I want this background service only running as long the user is willing to submit data. There should be some sort of confirmation dialog that from now on data are submitted and then these submissions should be active - event if the App is shutdown meanwhile - until the user again confirms that data should be no longer sended:
Here is my current entry in the AndroidManifest.xml:
....
<receiver
android:name="ch.bbcag.jumpin.app.receiver.gps.ConnectivityReceiver"
android:process=":gps_process" >
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.USER_PRESENT" />
</intent-filter>
</receiver>
<receiver
android:name="ch.bbcag.jumpin.app.service.LocationUpdaterService"
android:process=":gps_process" />
<service
android:name="ch.bbcag.jumpin.app.service.LocationUpdaterService"
android:process=":gps_process" />
....
GcmIntentService.java:
public class ConnectivityReceiver extends BroadcastReceiver {
private static final int EXEC_INTERVAL = AppConstants.GPS_REPEAT_SECONDS * 1000;
private final static String TAG = "ch.bbcag.jumpin.app.receiver.gps.ConnectivityReceiver";
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Received event");
final AlarmManager alarmManager = (AlarmManager) context
.getSystemService(Context.ALARM_SERVICE);
final PendingIntent wakeupIntent = PendingIntent.getService(context, 0,
new Intent(context, LocationUpdaterService.class),
PendingIntent.FLAG_UPDATE_CURRENT);
final boolean hasNetwork = !intent.getBooleanExtra(
ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
if (hasNetwork) {
context.startService(new Intent(context,LocationUpdaterService.class));
Calendar now = Calendar.getInstance();
now.add(Calendar.SECOND, AppConstants.GPS_REPEAT_SECONDS);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, now.getTimeInMillis(), EXEC_INTERVAL, wakeupIntent);
} else {
alarmManager.cancel(wakeupIntent);
}
}
}
The problem with this approach is that event the user is not using the App events are sent to the server. A simple solution would be in the class LocationUpdaterService to check in preferences if app should really send events. But the fact stays that LocationsUpdaterService is called regularly even if I don't use it (and Android wastes time to call this service).
How I can figure this out? Articles? Code examples are welcome!
How I can figure this out?
Only schedule your alarms when you need them. If you no longer need them, cancel the alarms.
Also, if you are going to use RTC_WAKEUP, either use WakefulBroadcastReceiver or my WakefulIntentService, otherwise your alarms will not be reliable.
Also also, your manifest is malformed. Either ch.bbcag.jumpin.app.service.LocationUpdaterService is a <receiver> or it is a <service>, but it cannot be both.
And also also also, dump the android:process=":gps_process" unless you have a clear understanding of why you are using it and its costs to you and the users.
I just have to read the documentation from Google to get aware how things are really working:
1) I don't want to start the Alarm when the device boots:
Solution:
(AndroidManifest.xml)
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<receiver android:name=".SampleBootReceiver"
android:enabled="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"></action>
</intent-filter>
</receiver>
Notice that in the manifest, the boot receiver is set to
android:enabled="false"
This means that the receiver will not be called unless the application explicitly enables it. This prevents the boot receiver from being called unnecessarily. Once you have started the your App you can:
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
From this point on the GPS locations are send in regular intervals to the server. Once you have done this, the next time the receiver will start on boot up. This is what i want to prevent.
2) Prevent automatically starting BootReceiver from boot:
For this purpose I'm looking for a possibility to get the shutdown event of the Android system:
<uses-permission android:name="android.permission.DEVICE_POWER" />
<receiver android:name=".ShutdownReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_SHUTDOWN" />
</intent-filter>
</receiver>
public class ShutdownReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ComponentName receiver = new ComponentName(context, SampleBootReceiver.class);
PackageManager pm = context.getPackageManager();
pm.setComponentEnabledSetting(receiver, PackageManager.COMPONENT_ENABLED_STATE_DISABLED,PackageManager.DONT_KILL_APP);
}
}
This has the effect that next time the mobile boots it will not start automatically the SampleBootReceiver until you explicitly specify this with the code:
pm.setComponentEnabledSetting(receiver,
PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
PackageManager.DONT_KILL_APP);
3) Conclusions:
a) Once your App is started the Receiver is activated: GPS coordinates go in regular intervals to the server
b) In your App you can make a setting to terminated the receiver or to enable it.
c) Finally if the handy shut down it's time to remove the receiver.
d) The next time you can restart with a)

Android 3.1 USB-Host - BroadcastReceiver does not receive USB_DEVICE_ATTACHED

I worked through the description and samples for USB host at developer.android.com to detect attached and detached USB-devices.
If I use an intent-filter in the manifest file to start my application when a device is attached, it works perfectly fine: Plug in, device is detected, android asks for permission to start the application, device information is displayed in a table.
The application I'm developing shouldn't be started/finished only if a device is attached/detached (e.g. data management purposes). Also I do not want the open-dialog to pop up if the app is already running. So I decided not to start the activity directly if a device is attached, but to register a BroadcastReceiver, which is (later) supposed to notify the activity if a device is at-/detached. This receiver recognizes the detach-action just fine, but not the attach-action.
Am I missing a permission or data attribute or something like that? The tutorial and samples don't say anything about additional necessary attributes.
Here is the manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="de.visira.smartfdr"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="12" />
<uses-feature android:name="android.hardware.usb.host" />
<application android:icon="#drawable/icon" android:label="#string/app_name">
<receiver android:name=".usb.Detector">
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
<action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="#xml/device_filter" />
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_DETACHED"
android:resource="#xml/device_filter" />
</receiver>
</application>
And the receiver:
public class FDRDetector extends BroadcastReceiver{
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Toast.makeText(context, "Action: " + action, 3).show();
// pops up only if action == DETACHED
}
I don't understand why the same intent-filter works, if I use them on an activity, but not if they are applied to a receiver? Even if I set up the receiver and filter in code, attaches are not recognized.
My work environment:
IDE: Eclipse 3.7 with Android Plugin
Device: Acer Iconia Tab A500
Android: 3.1
Thanks in advance
Aha! I figured it out. I was having the exact same problem.
The gist of it is - if you have your application launch automatically when a device is plugged in (using the manifest file), then it appears the Android system gets the ACTION_USB_DEVICE_ATTACHED intent, and then since it knows your application wants to run in that situation, it actually sends your application the android.intent.action.MAIN intent. It never sends the ACTION_USB_DEVICE_ATTACHED action to your application because it thinks it already knows what your application wants to do in that situation.
I've just now identified the problem, and I think I have a solution, but I can tell you what I've found:
Even if your app is running and in the foreground, when you plug in the USB device and the Android system gets the ACTION_USB_DEVICE_ATTACHED intent, it will call onResume() in your activity.
Unfortunately, you cannot just do this:
#Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
Log.d(TAG, "intent: " + intent);
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
//do something
}
}
Because the intent will come back as android.intent.action.MAIN, NOT ACTION_USB_DEVICE_ATTACHED.
An annoyingly, you also get android.intent.action.MAIN if you just leave the app, but don't unplug USB. I imagine putting the device to sleep and waking it back up will do the same thing.
So from what I have found, you can't get the intent directly, but it does appear that you can rely on onResume() being called when a USB device is plugged in, so the solution is to just check to see if USB is connected every time you get an onResume. You can also set a flag when USB is disconnected, because of course the USB disconnect intent fires just fine.
So in total, your broadcast receiver might look like this:
// BroadcastReceiver when remove the device USB plug from a USB port
BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
usbConnected=false;
}
}
};
You'd have this inside of onCreate:
// listen for new devices
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(mUsbReceiver, filter);
This goes inside of the activity tag in your manifest:
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="#xml/device_filter" />
You'll have a device_filter.xml file in your /res/xml/ folder that looks like this:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<usb-device vendor-id="1027" product-id="24577" />
<usb-device vendor-id="1118" product-id="688" />
</resources>
(of course with whatever vendor IDs and product IDs you need)
And then your onCreate looks something like this:
#Override
public void onResume() {
super.onResume();
Intent intent = getIntent();
Log.d(TAG, "intent: " + intent);
String action = intent.getAction();
if (usbConnected==false ) {
//check to see if USB is now connected
}
}
I don't have specific code for checking to see if USB is connected as I actually haven't delved into that yet. I'm using a library that will just connect if it can, so for my application I can just start that loop and I'm good.
Its also probably important to set the launchmode of your activity in the manifest to "singleTask" to prevent it from running again when its already running, or else plugging in a USB device will just launch a second instance of your application!
So my whole activity tag in my manifest looks like this:
<activity
android:label="#string/app_name"
android:name="com.awitness.common.TorqueTablet"
android:theme="#android:style/Theme.Holo.NoActionBar.Fullscreen"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"
android:launchMode="singleTask"
>
<intent-filter >
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.HOME"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="#xml/device_filter" />
</activity>
Anyway, I hope this helps someone! I was surprised that I was unable to find a solution for this already!
Just to follow on from #Gusdor's insightful comment (+1): I implemented a check in onNewIntent() that, as #Gusdor points out, is called when your activity launchMode is set as singleTask or singleTop. Then, rather than checking for boolean flags as the accepted answer suggests, simply pass on the intent to your USB broadcast receiver using a LocalBroadcastManager. For example,
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if (UsbManager.ACTION_USB_ACCESSORY_ATTACHED.equals(intent.getAction())) {
LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}
}
Then, wherever you're registering your existing (system) USB broadcast receiver, just register the same receiver with a local broadcast manager instance, i.e.,
#Override
protected void onResume() {
super.onResume();
myContext.registerReceiver(myUsbBroadcastReceiver, myIntent); // system receiver
LocalBroadcastManager.getInstance(myContext).registerReceiver(myUsbBroadcastReceiver, intent); // local receiver
}
#Override
protected void onPause() {
super.onResume();
myContext.unregisterReceiver(myUsbBroadcastReceiver); // system receiver
LocalBroadcastManager.getInstance(myContext).unregisterReceiver(myUsbBroadcastReceiver); // local receiver
}
You could send another system broadcast rather than a local broadcast, but I don't think you'll be able to use the action UsbManager.ACTION_USB_ACCESSORY_ATTACHED (system would see that as potential security risk), so you'd have to define your own action. No big deal, but why bother, especially as there's no IPC overhead with local broadcasts.
Creating the broadcast receiver within the application, and not the manifest, allows your application to only handle detached events while it is running. This way, detached events are only sent to the application that is currently running and not broadcast to all applications.

Categories

Resources