If I have these two geofences, after registering these geofences I should get notified when I'm entering or exiting the circumference of these circles. However, I don't want my App to send a notification if I'm moving through the common area, i.e. from once circle to another.
Is it possible? If so, then how?
You will have to use a class for monitoring your fencing:
public class GeofenceReceiver extends BroadcastReceiver {
Context context;
Intent broadcastIntent = new Intent();
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
broadcastIntent.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES);
if (LocationClient.hasError(intent)) {
handleError(intent);
} else {
handleEnterExit(intent);
}
}
private void handleError(Intent intent){
// Get the error code
int errorCode = LocationClient.getErrorCode(intent);
// Get the error message
String errorMessage = LocationServiceErrorMessages.getErrorString(
context, errorCode);
// Log the error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_error_detail,
errorMessage));
// Set the action and error message for the broadcast intent
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_ERROR)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_STATUS, errorMessage);
// Broadcast the error *locally* to other components in this app
LocalBroadcastManager.getInstance(context).sendBroadcast(
broadcastIntent);
}
private void handleEnterExit(Intent intent) {
// Get the type of transition (entry or exit)
int transition = LocationClient.getGeofenceTransition(intent);
// Test that a valid transition was reported
if ((transition == Geofence.GEOFENCE_TRANSITION_ENTER)
|| (transition == Geofence.GEOFENCE_TRANSITION_EXIT)) {
// Post a notification
List<Geofence> geofences = LocationClient
.getTriggeringGeofences(intent);
String[] geofenceIds = new String[geofences.size()];
String ids = TextUtils.join(GeofenceUtils.GEOFENCE_ID_DELIMITER,
geofenceIds);
String transitionType = GeofenceUtils
.getTransitionString(transition);
for (int index = 0; index < geofences.size(); index++) {
Geofence geofence = geofences.get(index);
...do something with the geofence entry or exit. I'm saving them to a local sqlite db
}
// Create an Intent to broadcast to the app
broadcastIntent
.setAction(GeofenceUtils.ACTION_GEOFENCE_TRANSITION)
.addCategory(GeofenceUtils.CATEGORY_LOCATION_SERVICES)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_ID, geofenceIds)
.putExtra(GeofenceUtils.EXTRA_GEOFENCE_TRANSITION_TYPE,
transitionType);
LocalBroadcastManager.getInstance(MyApplication.getContext())
.sendBroadcast(broadcastIntent);
// Log the transition type and a message
Log.d(GeofenceUtils.APPTAG, transitionType + ": " + ids);
Log.d(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_notification_text));
// In debug mode, log the result
Log.d(GeofenceUtils.APPTAG, "transition");
// An invalid transition was reported
} else {
// Always log as an error
Log.e(GeofenceUtils.APPTAG,
context.getString(R.string.geofence_transition_invalid_type,
transition));
}
}
//Posts a notification in the notification bar when a transition
private void sendNotification(String transitionType, String locationName) {
// Create an explicit content Intent that starts the main Activity
Intent notificationIntent = new Intent(context, MainActivity.class);
// Construct a task stack
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
// Adds the main Activity to the task stack as the parent
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack
PendingIntent notificationPendingIntent = stackBuilder
.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions
// >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(
context);
// Set the notification contents
builder.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(transitionType + ": " + locationName)
.setContentText(
context.getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Get an instance of the Notification manager
NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
You should create listeners for each one of the areas you wish to monitor, lets say listener1 and listener2. To optimize both areas and integrate it, the best approach is to make a grid using MongoDB, which in such case even allows you to integrate more than two points, as you build a grid.
Assuming that you're going to get out a polygon in the form of some Lat-Lon points, then you can generate a grid like the following:
# Method to get the min and max values for the polygon
def get_bounding_box(coords)
# get max and min coords
max = coords.inject({lat:0, lon:0}) do |max, c|
max[:lon] = c[0] if c[0] > max[:lon]
max[:lat] = c[1] if c[1] > max[:lat]
max
end
min = coords.inject({lat:MAX_LAT, lon:MAX_LON}) do |min, c|
min[:lon] = c[0] if c[0] < min[:lon]
min[:lat] = c[1] if c[1] < min[:lat]
min
end
# add a little padding to the max and min
max.each {|k, v| max[k] += 1 }
min.each {|k, v| min[k] -= 1 }
{min: min, max: max}
end
def generate_grid(bounds)
lon_range = bounds[:min][:lon]...bounds[:max][:lon]
lat_range = bounds[:min][:lat]...bounds[:max][:lat]
grid = []
lon_range.each do |lon|
lat_range.each do |lat|
grid << [lon + 0.25, lat + 0.25]
grid << [lon + 0.25, lat + 0.75]
grid << [lon + 0.75, lat + 0.25]
grid << [lon + 0.75, lat + 0.75]
end
end
grid
end
Such approach allows you to achieve very efficient geofencing with smart grids for monitoring target areas:
Most recently MongoDB also added support for Android, thus providing an easy way for your Android App back-end integration. In fact geofencing development with smart distributed data is expected to have a growing number of applications.
This may be an alternative:
All geofences have an id; I am not sure they need to be unique, but for this discussion let's say they must be unique. In your two geofence example let's use ids "fenceHome-1" and "fenceHome-2" for the ones shown and a third one called "someOther-1" which is not shown.
Now what you can do is create a variable to store the current geofence the user is in. In this example it will be a String with the geofence id. Let's call it
String currentGeofence = new String();
When the user enters a new geofence you can now check if the geofenceIds are the same.
/**
geofenceEntered get from the Intent. Should be "fenceHome-1" or "fenceHome-2" or "someOther=1"
*/
public void enteredGeoFence(String geofenceEntered) {
// strip off the "-1" or "-2"
geofenceEntered = geofenceEntered.stripOff();
if (currentGoofence.equals(geofenceEntered) == false} {
// user entered a new geofence Ex: "SomeOther-1" to "fenceHome-1"
sendNotification(geofenceEntered, .....);
currentGeofence = geofencecEntered;
} else {
// user entered a geofence with in the same 'area'. Ex: "fenceHome-1" to "fenceHome-2"
// do nothing
}
}
That is how I would do it. Fiddling with all that math is way too hard. Just set a clever naming convention for you geofence ids. The key is the naming of the geofences.
In the real world currentGeofence would need to be a Collection as a user may be in multiple geofences and the geofenceExit() would have to remove from currentGeofence.
One other thing to remember of the Android Notification manager is: if you send the same notification twice it will only send one notification. This could be used to your advantage.
very schematically:
boolean isTransition1, isTransition2, isTransition, insideCircle1, insideCircle2, insideUnion, insideUnionPrev;
if (isTransition1 | isTransition2) {
insideCircle1 = (dist(currPosition, centerCircle1) < radius1);
insideCircle2 = (dist(currPosition, centerCircle2) < radius2);
insideUnionPrev = insideUnion;
insideUnion = insideCircle1 | insideCircle;
isTransition = (insideUnion != insideUnionPrev);
if (isTransition & insideUnion) println("Moved into region");
if (isTransition & !insideUnion) println("Moved out of region");
}
Related
I am trying to create a notification service in my android app that always keeps listening to my RabbitMQ server for new messages. I want it to be able to send notifications even from background. Basically I am trying to create a notification communication between two client side application (App1 and App2) through Rabbit MQ and send notifications to both the apps in case of an event.
I have implemented it using JOB Service class but it is not consistent and it stops after sometime. Can someone please help me in understanding the architecture in a better way. How can I achieve something like Firebase Messaging Service but through Rabbit MQ?
Sample code that I have used below:
public class StoreOrderJobService extends JobService {
private static final String TAG = "JobService";
Random random = new Random();
SharedPrefManager prefManager;
private boolean jobCancelled = false;
#Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "Job Started");
prefManager = new SharedPrefManager(this);
subscribeStore(prefManager.getUserId(), jobParameters);
return true;
}
private void subscribeStore(String storeId, JobParameters parameters) {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost(HOST_IP);
factory.setAutomaticRecoveryEnabled(false);
String queueName = prefManager.getSessionId();
if (queueName != null) {
Thread subscribeStoreThread = new Thread(new Runnable() {
#Override
public void run() {
Log.d(TAG, "Job Started");
try {
if (jobCancelled) {
return;
}
Connection connection = factory.newConnection();
Channel channel = connection.createChannel();
Log.d("OrderService", "Session Id " + queueName);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, "store_test", storeId);
DeliverCallback deliverCallback = (consumerTag, delivery) -> {
String message = new String(delivery.getBody(), "UTF-8");
Log.d("OrderService", "Received message " + message);
Envelope envelope = delivery.getEnvelope();
String routingKey = envelope.getRoutingKey();
if (routingKey.equals(storeId)) {
channel.basicAck(envelope.getDeliveryTag(), true);
String message_new = new String(delivery.getBody(), "UTF-8");
Gson gson = new Gson();
OrderSubscribePayload payload = gson.fromJson(message_new, OrderSubscribePayload.class);
Log.d("order Service", "Order Id " + payload.getOrderId());
sendOrderNotification(random.nextInt(), payload);
}
};
channel.basicConsume(queueName, false, deliverCallback, consumerTag -> {
});
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (RuntimeException e) {
e.printStackTrace();
}
}
});
subscribeStoreThread.start();
}
}
private void sendOrderNotification(int id, OrderSubscribePayload payload) {
Log.d("Service", "sendOrderNotification " + payload.getOrderId());
Intent contextIntent = new Intent(this, OrderDetails.class);
Bundle args = new Bundle();
args.putSerializable("orderDetails", (Serializable) payload);
contextIntent.putExtra("Bundle", args);
int iUniqueId = (int) (System.currentTimeMillis() & 0xfffffff);
PendingIntent pIntent = PendingIntent.getActivity(this, iUniqueId, contextIntent, 0);
Notification n = new NotificationCompat.Builder(this, ManagedApplication.CHANNEL_ORDER_ID)
.setContentTitle("New Order")
.setContentText("Received New Order")
.setSmallIcon(R.drawable.ic_stat_name)
.setContentIntent(pIntent)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setColor(getResources().getColor(R.color.color_primary))
.setCategory(NotificationCompat.CATEGORY_REMINDER)
.build();
NotificationManager notificationManager =
(NotificationManager) getSystemService(Service.NOTIFICATION_SERVICE);
notificationManager.notify(id, n);
}
#Override
public boolean onStopJob(JobParameters jobParameters) {
Log.d(TAG, "Job Cancelled");
jobCancelled = true;
return true;
}
}
I am calling this job on users login as below:
private void startNotificationJob() {
ComponentName componentName = new ComponentName(this, StoreOrderJobService.class);
JobInfo info = new JobInfo.Builder(123, componentName)
.setPersisted(true)
.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
.setPeriodic(15 * 60 * 1000)
.build();
JobScheduler jobScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
int result = jobScheduler.schedule(info);
if (result == JobScheduler.RESULT_SUCCESS) {
Log.d("JOB Scheduler", "Job Scheduled");
} else Log.d("JOB Scheduler", "Job Scheduled Failed");
}
I have implemented it using JOB Service class but it is not consistent and it stops after sometime.
Nothing will be keeping your process running, which you will need for your desired functionality. Nothing will be keeping the CPU powered on all the time, which also will be needed for your desired functionality.
Please bear in mind that what you want is rather harmful to the battery life.
How can I achieve something like Firebase Messaging Service but through Rabbit MQ?
Use a foreground service, with a continuous partial WakeLock, in a separate process (android:process manifest attribute) than the UI of your app. You may also need code to deal with re-establishing your MQ connection as the device changes connectivity (e.g., from WiFi to mobile data) or loses and then regains connectivity.
You will also need to ask your users to go into the Settings app and try to opt your app out of all battery optimizations. Note that this will not be possible on all devices.
You will also need to ask your users to never kill your app's task. The separate process may help on some devices if users forget, but that behavior seems to vary by device.
And you will need to take into account that your solution will not be reliable, because some device manufacturers to prevent developers from doing the sorts of things that you want to do, as those things are bad for battery life.
I check it many times using Logs and prevent the notify() method of manager to be called multiple times if not necessary to avoid some overhead. Now I have first notification with a 0% progress then again I create a new notification with a 0% progress again, unfortunately only one notification is shown even their id is unique but later on when the first notification progress gets updated example from 0% to 25% then that's the only time it will show the desired output, a 2 notification with different progress value. I am using only one Notification, Notification Manager, and in Notification Builder since I do not want to create an overlap animation of notification when it gets updated. Is this expected behavior when in the foreground?
public abstract class BaseTaskService extends Service {
private static final String TAG = "BaseTaskService";
private static final String CHANNEL_ID_DEFAULT = "Upload and Download";
private int queue = 0;
private FirebaseFirestore mDatabase;
private final List<Integer> listOfTaskID = new ArrayList<>();
private final SparseIntArray totalUnitList = new SparseIntArray();
private final SparseIntArray completedUnitList = new SparseIntArray();
private Notification notification;
private NotificationManager notificationManager;
private final NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this, CHANNEL_ID_DEFAULT);
public void taskStarted(int id, boolean isUpload) {
//Increase the number of task
changeNumberOfTasks(1);
//Check if the task is new or not, if new then start a foreground service using id for it and add it to the list
if (!listOfTaskID.contains(id)){
listOfTaskID.add(id);
startForeground(id, notification);
Log.d(TAG, "Foreground Task Created : ID = " + id);
}
//If called by Upload Service, start the service once as a foreground per post
//If called by Download Service, start the service once as a foreground per file
if (isUpload){
//Set a total unit of files per post since one post could incorporate numerous images or files
totalUnitList.append(id, totalUnitList.get(id, 0) + 1);
Log.d(TAG, "Total Units For " + id + ": (" + totalUnitList.get(id) + ")");
}
}
public void taskCompleted() {
changeNumberOfTasks(-1);
}
private synchronized void changeNumberOfTasks(int delta) {
//Update the queue by adding delta value which could be 1 or -1
//Queue will display the overall upload or download of file from different tasks
queue += delta;
Log.d(TAG, "Overall Number of Remaining Task: " + queue);
//If there are no tasks left in queue, stop the service :)
if (queue <= 0) {
Log.d(TAG, "Stopping...");
//In Upload Service if there is no task in our queue it means that all request was finished
//so we need to reset the list of post's total task and completed task to zero
totalUnitList.clear();
completedUnitList.clear();
//Clear all of the id task
listOfTaskID.clear();
//Stop the foreground and remove all notification
stopForeground(true);
//Stop this service, calling this method will dismiss the very recent notification.
stopSelf();
}
}
#Override
public void onCreate() {
super.onCreate();
mDatabase = FirebaseFirestore.getInstance();
if (!isNotificationChannelEnabled(CHANNEL_ID_DEFAULT))
Toast.makeText(this, "Please turn on the notification in the app settings.", Toast.LENGTH_SHORT).show();
}
/*
We could use this line but unfortunately it will no longer work on Android O and above so we'll use the hashcode below.
This line is suppose to use for separating/detaching the Foreground notification from a Service
so that generating a separated unique id for PendingIntent and Finished notification is no longer needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
stopForeground(STOP_FOREGROUND_DETACH);
else
ServiceCompat.stopForeground(this, STOP_FOREGROUND_DETACH);
*/
//For Android O and above
private void createDefaultChannel() {
// Since Android Oreo notification channel is needed.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
//If null then initialize the Notification Manager
if (notificationManager == null)
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID_DEFAULT,
"Upload and Download",
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
}
public boolean isNotificationChannelEnabled(String channelId){
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if(channelId != null) {
NotificationManager manager = (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = manager.getNotificationChannel(channelId);
return channel.getImportance() != NotificationManager.IMPORTANCE_NONE;
}
return false;
} else {
return NotificationManagerCompat.from(this).areNotificationsEnabled();
}
}
/**
* Show notification with a progress bar.
* Updating the progress happens here
* This is for DOWNLOAD SERVICE
*/
void showProgressNotification(String caption, long completedUnits, long totalUnits, int id) {
createDefaultChannel();
//If null then initialize the Notification Manager
if (notificationManager == null)
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//Compute the progress
int percentComplete = 0;
if (totalUnits > 0) {
percentComplete = (int) (100 * completedUnits / totalUnits);
}
//To update and separate the notification progress according to its task
notification = notificationBuilder
.setProgress(100, percentComplete, false)
.setContentInfo(String.valueOf(percentComplete +"%"))
.setSmallIcon(R.drawable.ic_file_upload_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(caption)
.setAutoCancel(false)
.setOngoing(true)
.build();
if (!listOfTaskID.contains(id))
Log.d(TAG, "Download Notification Created: ID = " + id);
else
Log.d(TAG, "Download Notification Updated: ID = " + id);
//Notify the manager that we have a new update with notification
notificationManager.notify(id, notification);
}
/**
* Show notification with a progress bar.
* Updating the progress happens here
* This is for UPLOAD SERVICE
*/
void showProgressNotification(String caption, final String path, final int id, boolean isComplete, String title, String desc) {
createDefaultChannel();
//If null then initialize the Notification Manager
if (notificationManager == null)
notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
//Increment only if it is a successful task
if (isComplete)
completedUnitList.append(id, completedUnitList.get(id,0) + 1);
//Update and compute the progress
double percentComplete = 0;
if (totalUnitList.get(id, 0) > 0) {
//Perform this line if and only the total task is not equal to zero since dividing a number by zero is Error
percentComplete = (100 / totalUnitList.get(id)) * completedUnitList.get(id, 0);
}
notification = notificationBuilder
.setProgress(100, (int) percentComplete, false)
.setContentInfo(String.valueOf((int) percentComplete +"%"))
.setSmallIcon(R.drawable.ic_file_upload_white_24dp)
.setContentTitle(getString(R.string.app_name))
.setContentText(caption)
.setAutoCancel(false)
.setOngoing(true)
.build();
//This if condition is use to avoid repetitive call of notify() and will be triggered only if new task is created
if (!isComplete && !listOfTaskID.contains(id)){
Log.d(TAG, "Upload Notification Created: ID = " + id);
//Notify the manager that we have a new notification
notificationManager.notify(id, notification);
}
else if (isComplete){
Log.d(TAG, "Upload Notification Updated: ID = " + id);
//Notify the manager that we have a new update with notification
notificationManager.notify(id, notification);
//Check now if the number of completed task is equal to the number of total task if yes then show a finish notification
if (completedUnitList.get(id) == totalUnitList.get(id)){
Map<String, Object> details = new HashMap<>();
details.put(getResources().getString(R.string.Description), desc);
//We will use milliseconds to calculate how long is the post and for query
details.put(getResources().getString(R.string.Time_Posted), String.valueOf(new Date().getTime()));
details.put(getResources().getString(R.string.file), true);
if (title != null){
details.put(getResources().getString(R.string.Title),title);
details.put(getResources().getString(R.string.SU).toLowerCase(), Objects.requireNonNull(FirebaseAuth.getInstance().getCurrentUser()).getUid());
}
else
details.put(getResources().getString(R.string.uid), Objects.requireNonNull(FirebaseAuth.getInstance().getCurrentUser()).getUid());
//Make Intent to MainActivity
final Intent intent = new Intent(BaseTaskService.this, SUMain.class)
.putExtra(UploadService.DATA_COLLECTION, path)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
mDatabase.document(path).set(details).addOnSuccessListener(new OnSuccessListener<Void>() {
#Override
public void onSuccess(Void aVoid) {
showFinishedNotification(getString(R.string.upload_success), intent, true, id, true);
}
}).addOnFailureListener(new OnFailureListener() {
#Override
public void onFailure(#NonNull Exception e) {
showFinishedNotification(getString(R.string.upload_failure), intent, false, id, true);
}
});
}
}
}
/**
* Show notification that the activity finished.
*/
void showFinishedNotification(String caption, Intent intent, boolean isSuccess, int id, boolean isUpload) {
createDefaultChannel();
//Since calling a stopSelf() method will kill the service itself and dismissed the very recent Finished notification which is wrong in our case.
//Create a new id for Finished notification that is not bounded from the id of the progress notification, service, and foreground.
String uri = isUpload ? String.valueOf(intent.getParcelableExtra(UploadService.FILE_URI)) : String.valueOf(intent.getParcelableExtra(DownloadService.DOWNLOAD_URI));
//Use the hashcode of current timestamp mixed with some string to make it unique.
int newID = (uri + System.currentTimeMillis()).hashCode();
//Make PendingIntent for notification with the new generated unique id
PendingIntent pendingIntent = PendingIntent.getActivity(this, newID, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
int icon = isSuccess ? R.drawable.ic_done : R.drawable.ic_error_white_24dp;
notification = notificationBuilder
.setProgress(0, 0, false)
.setContentTitle(getString(R.string.app_name))
.setContentIntent(pendingIntent)
.setContentText(caption)
.setContentInfo(null)
.setAutoCancel(true)
.setSmallIcon(icon)
.setOngoing(false)
.build();
//Remove the first notification that has a incremental id which is the notification with progress
notificationManager.cancel(id);
//Show a new notification after removing the progress notification with the new generated unique id
notificationManager.notify(newID, notification);
Log.d(TAG, "Finished Notification: ID = " + newID);
}}
I'm writing an android app that tracks the user's location and shows the distance, time and price of the trip in a notification, all this tracked in a ForegroundService. The service tracks the location, price and the time, and I'm updating the notification every second.
I get this TransactionTooLargeException in production for the very first notification update, it only happens on Samsung devices running Android 8.0+.
This is what's happening:
I call the start method from the service:
public void start(MeasureService service) {
service.startForeground(NOTIFICATION_ID, getNotification(0, 0, 0));
}
I call the update method from the service:
public void update(int price, float meters, long time) {
mNotificationManager.notify(NOTIFICATION_ID, getNotification(price, meters, time));
}
Actually, these calls are called right after one another, the crash is coming from the update method. Can this (calling them one after the other) be a problem?
this is the getNotification:
private Notification getNotification(int price, float meters, long seconds) {
if (mNotificationBuilder == null) {
createNotificationBuilder();
}
return mNotificationBuilder
.setCustomContentView(getSmallView(price))
.setCustomBigContentView(getBigView(price, seconds, meters))
.build();
}
where the getSmallView and getBigView methods are like this:
private RemoteViews getSmallView(int price) {
String priceText = ...;
mSmallView.setTextViewText(R.id.price, priceText);
return mSmallView;
}
private RemoteViews getBigView(int price, long seconds, float meters) {
String priceText = ...;
String timeText = ...;
String distanceText = ...;
mBigView.setTextViewText(R.id.price, priceText);
mBigView.setTextViewText(R.id.time, timeText);
mBigView.setTextViewText(R.id.distance, distanceText);
return mBigView;
}
and this is how I create the notificationBuilder:
private void createNotificationBuilder() {
NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW);
((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
mNotificationBuilder = new NotificationCompat.Builder(mContext, NOTIFICATION_CHANNEL_ID);
mNotificationBuilder = mNotificationBuilder
.setSmallIcon(R.drawable.icon)
.setOngoing(true)
.setContentIntent(getOpenMeasurePageIntent());
}
and the getOpenMeasurePageIntent:
private PendingIntent getOpenMeasurePageIntent() {
Intent launchMeasureIntent = new Intent(mContext, MainActivity.class);
launchMeasureIntent.setAction(...);
launchMeasureIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(mContext, 0, launchMeasureIntent, 0);
}
This is the crash log I get:
Caused by: java.lang.RuntimeException: android.os.TransactionTooLargeException: data parcel size 560988 bytes
16 at android.app.NotificationManager.notifyAsUser(NotificationManager.java:319)
17 at android.app.NotificationManager.notify(NotificationManager.java:284)
18 at android.app.NotificationManager.notify(NotificationManager.java:268)
19 at com.myapp.service.measure.MyNotification.void update(int,float,long) <- this is called from the timer
I found a lot of similar issues online, but they are usually about passing big chunks of data in the intent, which I believe I'm not doing.
Any idea what I might be doing wrong?
i think problem is in updating Notification every seconds.
Solution
i suggest you should update notification only when data likes(distance, price) changed.
GcmListenerService is not called when application is in background or when phone is locked or in sleep mode but notification is fired. How this will be called
When App is in foreground its working ideally.
Code for GcmListenerService is following
public class MyGcmListenerService extends GcmListenerService {
private static final String TAG = "MyGcmListenerService";
LocalDataBaseManager mDbManager;
String message;
Random randomNumber;
long ID;
/**
* Called when message is received.
*
* #param from SenderID of the sender.
* #param data Data bundle containing message data as key/value pairs.
* For Set of keys use data.keySet().
*/
// [START receive_message]
#Override
public void onMessageReceived(String from, Bundle data) {
String message ;
String title;
// ID = Utils.getIDForPush("pushId",this);
// if(ID == 0){
// ID = 1;
// }else {
// ID += 1;
// }
// Utils.saveIDForPush("pushId",ID,this);
Bundle bundle = data.getBundle("notification");
if(bundle!= null){
message = bundle.getString("body");
title = bundle.getString("title");
Log.d(TAG, "From: " + from);
Log.d(TAG, "Message: " + message);}
else {
message ="";
title = "NCMS";
}
mDbManager = LocalDataBaseManager.getInstance(this);
if (from.startsWith("/topics/")) {
Calendar c = Calendar.getInstance();
SimpleDateFormat s = new SimpleDateFormat("ddMMyyyyhhmmss");
String format = s.format(new Date());
ID = Long.parseLong(format);
String date = new SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.ENGLISH).format(new Date());
Warnings warnings = new Warnings();
warnings.setWARNING_ID(ID);
warnings.setWARNING_EN(message);
warnings.setWARNING_AR(message);
warnings.setSTART_DATE_TIME(date);
warnings.setNotification_type(String.valueOf(Constant.NotificationType.PUSH));
warnings.setSEVERITY("");
warnings.setEND_DATE_TIME("");
warnings.setUPDATE_NO("");
mDbManager.insertNotificationInfo(warnings);
// message received from some topic.
} else {
// normal downstream message.
}
// [START_EXCLUDE]
/**
* Production applications would usually process the message here.
* Eg: - Syncing with server.
* - Store message in local database.
* - Update UI.
*/
/**
* In some cases it may be useful to show a notification indicating to the user
* that a message was received.
*/
// KeyguardManager km = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);
// boolean locked = km.inKeyguardRestrictedInputMode();
//
// String release = android.os.Build.VERSION.RELEASE;
//
//
// if (Integer.parseInt(String.valueOf(release.charAt(0))) < 5 && locked) {
//
// this.stopService(new Intent(this, NotificationService.class));
// Intent serviceIntent = new Intent(this, NotificationService.class);
// this.startService(serviceIntent);
//
// }
sendNotification(title,message);
// [END_EXCLUDE]
}
// [END receive_message]
/**
* Create and show a simple notification containing the received GCM message.
*
* #param message GCM message received.
*/
private void sendNotification(String title,String message) {
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("message",message);
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.ncms_launcher)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
Manifest info for this service is following
<service
android:name=".gcm.MyGcmListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
What I am missing here.
It appears the heart of this issue is actually a server-side issue. If the server is sending notification messages, onMessageReceived won't be called if the app is in the background. The server should actually be sending data messages.
GCM Docs discuss the difference.
Basically, the payload of the message should have a data key such as
{
"to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"data" : {
"Nick" : "Mario",
"body" : "great match!",
"Room" : "PortugalVSDenmark"
},
}
and NOT a notification key such as
{
"to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
"notification" : {
"body" : "great match!",
"title" : "Portugal vs. Denmark",
"icon" : "myicon"
}
}
More specifically, the GCM Docs state that messages sent including both data and notification payloads will be treated differently depending on if the app is in the foreground or background:
App behavior when receiving messages that include both notification and data payloads depends on whether the app is in the background, or the foreground —essentially, whether or not it is active at the time of receipt.
When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification.
When in the foreground, your app receives a bundle with both payloads available.
This github thread also has a good explanation:
So there are two kinds of GCM messages:
Notification Messages - these are intended to generate a notification with no intermediate processing by the application. They only hit onMessageReceived if the app is running.
Data Messages - these are intended to silently pass data to the app's messaging service. They hit onMessageReceived even if the app is in the background. The service may then choose to generate a notification using the normal system notification APIs, or it may choose to handle the message silently.
The Issue I was facing was when Application is in background or force closed then notification is fired but not through GcmListenService but through GCMReceiver
So I extended GCMReceiver and made it something like this with this when application is in foreground or in background or force killed. it will be called
GCMListenerService after modification is as following
public class MyGcmListenerService extends GcmListenerService {
private static final String TAG = "MyGcmListenerService";
LocalDataBaseManager mDbManager;
String message;
Random randomNumber;
long ID;
/**
* Called when message is received.
*
* #param from SenderID of the sender.
* #param data Data bundle containing message data as key/value pairs.
* For Set of keys use data.keySet().
*/
// [START receive_message]
#Override
public void onMessageReceived(String from, Bundle data) {
String message ;
String title;
// ID = Utils.getIDForPush("pushId",this);
// if(ID == 0){
// ID = 1;
// }else {
// ID += 1;
// }
// Utils.saveIDForPush("pushId",ID,this);
Bundle bundle = data.getBundle("notification");
if(bundle!= null){
message = bundle.getString("body");
title = bundle.getString("title");
Log.d(TAG, "From: " + from);
Log.d(TAG, "Message: " + message);}
else {
message ="";
title = "NCMS";
}
mDbManager = LocalDataBaseManager.getInstance(this);
if (from.startsWith("/topics/")) {
Calendar c = Calendar.getInstance();
SimpleDateFormat s = new SimpleDateFormat("ddMMyyyyhhmmss");
String format = s.format(new Date());
ID = Long.parseLong(format);
String date = new SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.ENGLISH).format(new Date());
Warnings warnings = new Warnings();
warnings.setWARNING_ID(ID);
warnings.setWARNING_EN(message);
warnings.setWARNING_AR(message);
warnings.setSTART_DATE_TIME(date);
warnings.setNotification_type(String.valueOf(Constant.NotificationType.PUSH));
warnings.setSEVERITY("");
warnings.setEND_DATE_TIME("");
warnings.setUPDATE_NO("");
mDbManager.insertNotificationInfo(warnings);
// message received from some topic.
} else {
// normal downstream message.
}
// [START_EXCLUDE]
/**
* Production applications would usually process the message here.
* Eg: - Syncing with server.
* - Store message in local database.
* - Update UI.
*/
/**
* In some cases it may be useful to show a notification indicating to the user
* that a message was received.
*/
// KeyguardManager km = (KeyguardManager) this.getSystemService(Context.KEYGUARD_SERVICE);
// boolean locked = km.inKeyguardRestrictedInputMode();
//
// String release = android.os.Build.VERSION.RELEASE;
//
//
// if (Integer.parseInt(String.valueOf(release.charAt(0))) < 5 && locked) {
//
// this.stopService(new Intent(this, NotificationService.class));
// Intent serviceIntent = new Intent(this, NotificationService.class);
// this.startService(serviceIntent);
//
// }
sendNotification(title,message);
// [END_EXCLUDE]
}
// [END receive_message]
/**
* Create and show a simple notification containing the received GCM message.
*
* #param message GCM message received.
*/
private void sendNotification(String title,String message) {
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra("message",message);
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.ncms_launcher)
.setContentTitle(title)
.setContentText(message)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
}
and GCM Receiver is as following
public class GcmBroadcastReceiver extends GcmReceiver {
LocalDataBaseManager mDbManager;
#Override
public void onReceive(Context context, Intent intent) {
mDbManager = LocalDataBaseManager.getInstance(context);
Bundle bundle = intent.getExtras();
bundle.keySet();
Set<String> keySet = bundle.keySet();
if(keySet != null && keySet.isEmpty() == false) {
Iterator<String> it = keySet.iterator();
int i = 0;
while(it.hasNext()){
String key = it.next();
String desc = bundle.getString(key);
Log.d("BroadCast Values",key +" "+desc);
}
}
Log.d("", "In Receive Method of Broadcast Receiver");
if (bundle != null && bundle.containsKey("gcm.notification.body")) {
String message = bundle.getString("gcm.notification.body","");
Long ID = new Date().getTime();
String date = new SimpleDateFormat("dd-MM-yyyy HH:mm", Locale.ENGLISH).format(new Date());
Warnings warnings = new Warnings();
warnings.setWARNING_ID(ID);
warnings.setWARNING_EN(message);
warnings.setWARNING_AR(message);
warnings.setSTART_DATE_TIME(date);
warnings.setNotification_type(String.valueOf(Constant.NotificationType.PUSH));
warnings.setSEVERITY("");
warnings.setEND_DATE_TIME("");
warnings.setUPDATE_NO("");
mDbManager.insertNotificationInfo(warnings);
// message received from some topic.
}
super.onReceive(context, intent);
// ComponentName cn = new ComponentName(context.getPackageName(), RegistrationIntentService.class.getName());
// startWakefulService(context, intent.setComponent(cn));
// setResultCode(Activity.RESULT_OK);
}
}
Manifest changes for GCMReceiver is following
<receiver
android:name=".gcm.GcmBroadcastReceiver"
android:exported="true"
android:permission="com.google.android.c2dm.permission.SEND" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
<category android:name="com.uae.ncms" />
</intent-filter>
</receiver>
<service
android:name=".gcm.MyGcmListenerService"
android:exported="false" >
<intent-filter>
<action android:name="com.google.android.c2dm.intent.RECEIVE" />
</intent-filter>
</service>
I am successfully sending multi-part (concatenated) messages through android using Xamarin Studio and the SMS.Telephony.SmsManager Android library.
To send a message I am doing the following:
var longMessage = "This is a cØncatenated message sent to you through an android. This should appear as a single message. Hopefully it's as easy as that. It even has a function to break the message up. It probably took me longer to install Xamarin then it did to write the code and send the actual message";
var smsMgr = Android.Telephony.SmsManager.Default;
System.Collections.Generic.IList<string> parts = smsMgr.DivideMessage(longMessage);
IList<PendingIntent> pendingIntents = new List<PendingIntent>(parts.Count);
for (int i = 0; i < parts.Count; i++)
{
var intent = new Intent(DeliveryIntentAction);
intent.PutExtra("MessagePartText", parts[i]);
intent.PutExtra("MessagePartId", i.ToString());
PendingIntent pi = PendingIntent.GetBroadcast(this, 0, intent, 0);
pendingIntents.Add(pi);
}
smsMgr.SendMultipartTextMessage("17057178131",null, parts, pendingIntents, null);
I then have a receiver for the pending Intents that looks like this:
[BroadcastReceiver(Enabled = true)] //(Exported = true, Permission = "//receiver/#android:android.permission.SEND_SMS")]
[IntentFilter(new[] { DeliveryIntentAction }, Priority = int.MaxValue)]
public class SMSSentReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
if (intent.GetStringExtra ("MessagePartId") != null)
lbl.Text += " Sent Response " + intent.GetStringExtra ("MessagePartId") + System.Environment.NewLine;
...
However, each time OnRecieve is called, intent.GetStringExtra("MessagePartId") has a value of "0" and the MessagePartText is the first part, not the part number/text that belongs to the message part sent.
Can anyone see why this may be the case?
Thank you
Using
PendingIntent pi = PendingIntent.GetBroadcast(this, 0, intent, 0);
attempts to override the existing pendingIntent.
To create a NEW intent and have it waiting, I had to change the requestCode parameter of GetBroadcast.
PendingIntent pi = PendingIntent.GetBroadcast(this, i, intent, 0);
Thanks