Is there a way to get a LifecycleOwner in FirebaseMessagingService - firebase-cloud-messaging

I'm developing a chat app and I'm using Firebase Cloud Messaging for notifications.
I found that it was best to save my notifications (notification info) in Local database i.e Room so it help me to handle the badge counts and the clearing of specific chat notifications.
Steps:
Setup my FirebaseMessagingService and tested. (Getting my notifications successfully);
Setup Room database and tested to insert and get all data (LiveData) (working good);
I want to observe the liveData inside MyFirebaseMessagingService but to do so, I need a LivecycleOwner and I don't have any idea from where I will get it.
I searched on google but the only solution was to use a LifecycleService, but I need FirebaseMessagingService for my notification purpose.
this is my code:
//Room Database class
private static volatile LocalDatabase INSTANCE;
private static final int NUMBER_OF_THREADS = 4;
public static final ExecutorService taskExecutor =
Executors.newFixedThreadPool(NUMBER_OF_THREADS);
public static LocalDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
synchronized (RoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
LocalDatabase.class, "local_database")
.build();
}
}
}
return INSTANCE;
}
public abstract NotificationDao dao();
//DAO interface
#Insert
void insert(NotificationEntity notificationEntity);
#Query("DELETE FROM notificationentity WHERE trade_id = :tradeId")
int clearByTrade(String tradeId);
#Query("SELECT * FROM notificationentity")
LiveData<List<NotificationEntity>> getAll();
//Repository class{}
private LiveData<List<NotificationEntity>> listLiveData;
public Repository() {
firestore = FirebaseFirestore.getInstance();
storage = FirebaseStorage.getInstance();
}
public Repository(Application application) {
LocalDatabase localDb = LocalDatabase.getDatabase(application);
dao = localDb.dao();
listLiveData = dao.getAll();
}
...
public void saveNotificationInfo(#NonNull NotificationEntity entity){
LocalDatabase.taskExecutor.execute(() -> {
try {
dao.insert(entity);
H.debug("NotificationData saved in local db");
}catch (Exception e){
H.debug("Failed to save NotificationData in local db: "+e.getMessage());
}
});
}
public LiveData<List<NotificationEntity>> getNotifications(){return listLiveData;}
public void clearNotificationInf(#NonNull String tradeId){
LocalDatabase.taskExecutor.execute(() -> {
try {
H.debug("trying to delete rows for id :"+tradeId+"...");
int n = dao.clearByTrade(tradeId);
H.debug("Cleared: "+n+" notification info from localDatabase");
}catch (Exception e){
H.debug("Failed clear NotificationData in local db: "+e.getMessage());
}
});
}
//ViewModel class{}
private Repository rep;
private LiveData<List<NotificationEntity>> list;
public VModel(#NonNull Application application) {
super(application);
rep = new Repository(application);
list = rep.getNotifications();
}
public void saveNotificationInfo(Context context, #NonNull NotificationEntity entity){
rep.saveNotificationInfo(entity);
}
public LiveData<List<NotificationEntity>> getNotifications(){
return rep.getNotifications();
}
public void clearNotificationInf(Context context, #NonNull String tradeId){
rep.clearNotificationInf(tradeId);
}
and finally the FiebaseMessagingService class{}
private static final String TAG = "MyFireBaseService";
private static final int SUMMARY_ID = 999;
private SoundManager sm;
private Context context;
private final String GROUP_KEY = "com.opendev.xpresso.group_xpresso_group_key";
private Repository rep;
private NotificationDao dao;
#Override
public void onCreate() {
super.onCreate();
context = this;
rep = new Repository();
}
/**
* Called if InstanceID token is updated. This may occur if the security of
* the previous token had been compromised. Note that this is called when the InstanceID token
* is initially generated so this is where you would retrieve the token.
*/
#Override
public void onNewToken(#NonNull String s) {
super.onNewToken(s);
}
#Override
public void onMessageReceived(#NonNull RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
H.debug("OnMessageReceived...");
try {
Map<String, String> data = remoteMessage.getData();
if (Objects.requireNonNull(data.get("purpose")).equals("notify_message")) {
String ChatId
if ((chatId=data.get("chatId"))==null){
H.debug("onMessageReceived: tradeId null! Aborting...");
return;
}
FirebaseFirestore db = FirebaseFirestore.getInstance();
Task<DocumentSnapshot> tradeTask = db.collection("activeTrades").document(chatTask).get();
Task<DocumentSnapshot> userTask = db.collection("users")
.document(FirebaseAuth.getInstance().getCurrentUser().getUid()).get();
Tasks.whenAllSuccess(chatTask, userTask).addOnSuccessListener(objects -> {
if (!((DocumentSnapshot)objects.get(0)).exists() || !((DocumentSnapshot)objects.get(1)).exists()){
H.debug("OnMessageReceived: querying data failed: NOT EXISTS");
return;
}
Chat chat = ((DocumentSnapshot)objects.get(0)).toObject(Trade.class);
MainActivity.USER = ((DocumentSnapshot)objects.get(1)).toObject(User.class);
//Now we got all the needed info we cant process the notification
//Saving the notification locally and updating badge count
//then notify for all the notification in localDatabase
NotificationEntity entity = new NotificationEntity();
entity.setNotificationId(getNextNotificationId());
entity.setTradeId(tradeId);
entity.setChanelId(context.getResources().getString(R.string.channel_id));
entity.setTitle(data.get("title"));
entity.setMessage(data.get("message"));
entity.setPriority(NotificationCompat.PRIORITY_HIGH);
entity.setCategory(NotificationCompat.CATEGORY_MESSAGE);
rep.saveNotificationInfo(entity);
rep.getNotifications().observe(HOW_TO_GET_THE_LIVECYCLE_OWNER, new Observer<List<NotificationEntity>>() {
#Override
public void onChanged(List<NotificationEntity> notificationEntities) {
//
}
});
}).addOnFailureListener(e -> H.debug("OnMessageReceived: querying data failed: "+e.getMessage()));
}
}catch (Exception e){H.debug(e.getMessage());}
}

Updated,
Because It is not recommended to use a LiveData object inside of a FirebaseMessagingService because a FirebaseMessagingService is not a part of the Android activity lifecycle and therefore does not have a lifecycle owner. Instead of trying to use LiveData inside of the FirebaseMessagingService, you could consider using a different approach to handle badge count and clearing specific chat notifications.
So I used a broadcast receiver to receive the notifications. Then I could set the broadcast receiver in my FirebaseMessagingService, and it will receive the notifications and update the badge count in local Room database.
I created a Broadcast Receiver for this, and in onReceive method I send a Intent to a service and handled the badge logic in service.
I'm answering my own question just to show my alternative workaround.
I believe the liveDataObserver still the best way for me but until someone help me by giving me the solution to get LivecycleOwner in FirebaseMessagingService, I'm going to use custom listener for my insert() and my getAll()
like follow
public interface RoomInsertListener{
void onInsert();
}
public interface RoomGetListener{
void onGet(List<NotificationEntity> list);
}
Then use it in FirebaseMessagingService as follow
NotificationEntity entity = new NotificationEntity();
entity.setNotificationId(getNextNotificationId());
entity.setTradeId(tradeId);
entity.setChanelId(context.getResources().getString(R.string.channel_id));
entity.setTitle(data.get("title"));
entity.setMessage(data.get("message"));
entity.setPriority(NotificationCompat.PRIORITY_HIGH);
entity.setCategory(NotificationCompat.CATEGORY_MESSAGE);
rep.saveNotificationInfo(entity, () -> rep.getNotifications(list -> {
ShortcutBadger.applyCount(context, list.size());
H.debug(list.size()+" notifications in Database: applied badge count...");
for (NotificationEntity e:list){
H.debug("id:"+e.getNotificationId()+" trade: "+e.getTradeId());
}
}));

Related

Pending Intent not triggering Intent Service in Geofencing

I am using an intent service to create a notification when user enters in the defined geofenced area.The problem is that when I first run the application it works fine and I am getting the pending-intent on my Intent Service, but after some days(2-3), I am not getting the required intent on the Intent Service.
I have no clue why it stopped working after some days. If I launch the application, it will start normally again but then stopped again after some days.
Here is my activity code --
public class MainActivity extends AppCompatActivity implements View.OnClickListener, GoogleApiClient.OnConnectionFailedListener, GoogleApiClient.ConnectionCallbacks, ResultCallback, OnRequestPermissionsResultCallback {
GoogleApiClient mGoogleApiClient;
Location mGeoLocation;
Geofence mGeofence;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION}, 100);
}
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions, #NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
mGeoLocation = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
mGoogleApiClient.connect();
}
#Override
protected void onStart() {
super.onStart();
}
#Override
protected void onStop() {
mGoogleApiClient.disconnect();
super.onStop();
}
#Override
public void onClick(View v) {
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
Toast.makeText(MainActivity.this,"",Toast.LENGTH_SHORT);
Log.e("Here I am","Using geofencing in my mobile on 'onConnectionFailed' of main activity");
}
#Override
public void onConnected(#Nullable Bundle bundle) {
if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
return;
}
if (mGeoLocation != null) {
mGeofence = new Geofence.Builder()
.setRequestId("Appstudioz")
.setCircularRegion(mGeoLocation.getLatitude(), mGeoLocation.getLongitude(), 100)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER | Geofence.GEOFENCE_TRANSITION_EXIT)
.build();
mGoogleApiClient.connect();
Intent intent = new Intent(this, MyIntentServiceGeoFencing.class);
PendingIntent pendingIntent = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER);
builder.addGeofence(mGeofence);
LocationServices.GeofencingApi.addGeofences(mGoogleApiClient, builder.build(), pendingIntent).setResultCallback(this);
}
}
#Override
public void onConnectionSuspended(int i) {
Log.e("Here I am","Using geofencing in my mobile 'onConnectionSuspended' of main activity");
}
#Override
public void onResult(#NonNull Result result) {
Log.e("Here I am","Using geofencing in my mobile 'onResult' of main activity");
}
}
And this is my Intent Service --
public class MyIntentServiceGeoFencing extends IntentService {
public MyIntentServiceGeoFencing() {
super("MyIntentServiceGeoFencing");
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
String message="";
Log.e("Here I am","Using geofencing in my mobile 'In intent Service'");
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if(geofencingEvent.getGeofenceTransition()== Geofence.GEOFENCE_TRANSITION_ENTER)
{
message="Entering Appstudioz";
}
else if(geofencingEvent.getGeofenceTransition()== Geofence.GEOFENCE_TRANSITION_EXIT)
{
message="Exiting Appstudioz";
}
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(this)
.setSmallIcon(R.drawable.cast_ic_notification_small_icon)
.setContentTitle("Geofence Notification")
.setContentText(message);
// Sets an ID for the notification
int mNotificationId = 001;
// Gets an instance of the NotificationManager service
NotificationManager mNotifyMgr = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
// Builds the notification and issues it.
mNotifyMgr.notify(mNotificationId, mBuilder.build());
if(message.equals("Entering Appstudioz")) {
((AudioManager) getSystemService(AUDIO_SERVICE)).setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
}
else
{
((AudioManager) getSystemService(AUDIO_SERVICE)).setRingerMode(AudioManager.RINGER_MODE_NORMAL);
}
}
}
}
I have found my solution. Following are the reasons why the App was not getting Pending Intents according to the official google documentation -
1.The device is rebooted.
2.The app is uninstalled and re-installed.
3.The app's data is cleared.
4.Google Play services data is cleared.
5.The app has received a GEOFENCE_NOT_AVAILABLE alert.(When Android Location Provider(GPS) gets switched off)
You have to re-register the geofence after these events.
In my case device rebooting and location provider(GPS) getting switched off, were the reasons for not getting the pending intents.
Service will stop running when you kill your application, So, you can use broadcast receiver to fix this problem
public class GeofenceReceiver extends BroadcastReceiver
implements
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener,
ResultCallback<Status>{
GoogleApiClient mGoogleApiClient;
PendingIntent mGeofencePendingIntent ;
Context mContext;
#Override
public void onReceive(Context context, Intent intent) {
mContext = context;
mGoogleApiClient = new GoogleApiClient.Builder(mContext)
.addOnConnectionFailedListener(this)
.addConnectionCallbacks(this)
.addApi(LocationServices.API)
.build();
mGoogleApiClient.connect();
}
#Override
public void onConnected(#Nullable Bundle bundle) {
try {
LocationServices.GeofencingApi.addGeofences(
mGoogleApiClient,
// The GeofenceRequest object.
getGeofencingRequest(),
getGeofencePendingIntent()
).setResultCallback(this); // Result processed in onResult().
} catch (SecurityException securityException) {
Log.i(getClass().getSimpleName(),securityException.getMessage());
}
}
// Catch exception generated if the app does not use ACCESS_FINE_LOCATION permission.
#Override
public void onConnectionSuspended(int i) {
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
}
/**
* Runs when the result of calling addGeofences() and removeGeofences() becomes available.
* Either method can complete successfully or with an error.
*
* Since this activity implements the {#link ResultCallback} interface, we are required to
* define this method.
*
* #param status The Status returned through a PendingIntent when addGeofences() or
* removeGeofences() get called.
*/
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(getClass().getSimpleName(),"Success");
} else {
// Get the status code for the error and log it using a user-friendly message.
Log.i(getClass().getSimpleName(),getErrorString(status.getStatusCode()));
}
}
private GeofencingRequest getGeofencingRequest() {
GeofencingRequest.Builder builder = new GeofencingRequest.Builder();
builder.setInitialTrigger(GeofencingRequest.INITIAL_TRIGGER_ENTER | GeofencingRequest.INITIAL_TRIGGER_DWELL);
builder.addGeofences(getGeofecne());
return builder.build();
}
private List<Geofence> getGeofecne(){
List<Geofence> mGeofenceList = new ArrayList<>();
//add one object
mGeofenceList.add(new Geofence.Builder()
// Set the request ID of the geofence. This is a string to identify this
// geofence.
.setRequestId("key")
// Set the circular region of this geofence.
.setCircularRegion(
25.768466, //lat
47.567625, //long
50) // radios
// Set the expiration duration of the geofence. This geofence gets automatically
// removed after this period of time.
//1000 millis * 60 sec * 5 min
.setExpirationDuration(1000 * 60 * 5)
// Set the transition types of interest. Alerts are only generated for these
// transition. We track entry and exit transitions in this sample.
.setTransitionTypes(
Geofence.GEOFENCE_TRANSITION_DWELL)
//it's must to set time in millis with dwell transition
.setLoiteringDelay(3000)
// Create the geofence.
.build());
return mGeofenceList;
}
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(mContext, GeofenceTransitionsIntentService.class);
return PendingIntent.getService(mContext, 0, intent, PendingIntent.
FLAG_UPDATE_CURRENT);
}
}
and here your notification service
public class GeofenceTransitionsIntentService extends IntentService {
protected static final String TAG = "GeofenceTransitionsIS";
/**
* This constructor is required, and calls the super IntentService(String)
* constructor with the name for a worker thread.
*/
public GeofenceTransitionsIntentService() {
// Use the TAG to name the worker thread.
super(TAG);
}
/**
* Handles incoming intents.
* #param intent sent by Location Services. This Intent is provided to Location
* Services (inside a PendingIntent) when addGeofences() is called.
*/
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
Log.e(TAG, getErrorString(geofencingEvent.getErrorCode()));
return;
}
// Get the transition type.
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_DWELL) {
// Get the transition details as a String.
String geofenceTransitionDetails = "Discount 10% for you";
// Send notification and log the transition details.
sendNotification(geofenceTransitionDetails);
Log.i(TAG, geofenceTransitionDetails);
} else {
// Log the error.
Log.e(TAG, getString(R.string.geofence_transition_invalid_type + geofenceTransition));
}
}
public static String getErrorString(int errorCode) {
switch (errorCode) {
case GeofenceStatusCodes.GEOFENCE_NOT_AVAILABLE:
return "not Available";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_GEOFENCES:
return "Too many Geofences";
case GeofenceStatusCodes.GEOFENCE_TOO_MANY_PENDING_INTENTS:
return "Too many Pending Intents";
default:
return "unknown geofence error";
}
}
/**
* Posts a notification in the notification bar when a transition is detected.
* If the user clicks the notification, control goes to the MainActivity.
*/
private void sendNotification(String notificationDetails) {
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Add 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(this);
// Define the notification settings.
builder.setSmallIcon(R.drawable.common_google_signin_btn_icon_dark_normal)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.drawable.cast_abc_scrubber_primary_mtrl_alpha))
.setColor(Color.RED)
.setContentTitle(notificationDetails)
.setContentText(getString(R.string.geofence_transition_notification_text))
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
}
for more information check out my repo there is full example
https://github.com/3zcs/Geofence

Unpredictable result of DriveId.getResourceId() in Google Drive Android API

The issue is that the 'resourceID' from 'DriveId.getResourceId()' is not available (returns NULL) on newly created files (product of 'DriveFolder.createFile(GAC, meta, cont)'). If the file is retrieved by a regular list or query procedure, the 'resourceID' is correct.
I suspect it is a timing/latency issue, but it is not clear if there is an application action that would force refresh. The 'Drive.DriveApi.requestSync(GAC)' seems to have no effect.
UPDATE (07/22/2015)
Thanks to the prompt response from Steven Bazyl (see comments below), I finally have a satisfactory solution using Completion Events. Here are two minified code snippets that deliver the ResourceId to the app as soon as the newly created file is propagated to the Drive:
File creation, add change subscription:
public class CreateEmptyFileActivity extends BaseDemoActivity {
private static final String TAG = "_X_";
#Override
public void onConnected(Bundle connectionHint) { super.onConnected(connectionHint);
MetadataChangeSet meta = new MetadataChangeSet.Builder()
.setTitle("EmptyFile.txt").setMimeType("text/plain")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFile(getGoogleApiClient(), meta, null,
new ExecutionOptions.Builder()
.setNotifyOnCompletion(true)
.build()
)
.setResultCallback(new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (result.getStatus().isSuccess()) {
DriveId driveId = result.getDriveFile().getDriveId();
Log.d(TAG, "Created a empty file: " + driveId);
DriveFile file = Drive.DriveApi.getFile(getGoogleApiClient(), driveId);
file.addChangeSubscription(getGoogleApiClient());
}
}
});
}
}
Event Service, catches the completion:
public class ChngeSvc extends DriveEventService {
private static final String TAG = "_X_";
#Override
public void onCompletion(CompletionEvent event) { super.onCompletion(event);
DriveId driveId = event.getDriveId();
Log.d(TAG, "onComplete: " + driveId.getResourceId());
switch (event.getStatus()) {
case CompletionEvent.STATUS_CONFLICT: Log.d(TAG, "STATUS_CONFLICT"); event.dismiss(); break;
case CompletionEvent.STATUS_FAILURE: Log.d(TAG, "STATUS_FAILURE"); event.dismiss(); break;
case CompletionEvent.STATUS_SUCCESS: Log.d(TAG, "STATUS_SUCCESS "); event.dismiss(); break;
}
}
}
Under normal circumstances (wifi), I get the ResourceId almost immediately.
20:40:53.247﹕Created a empty file: DriveId:CAESABiiAiDGsfO61VMoAA==
20:40:54.305: onComplete, ResourceId: 0BxOS7mTBMR_bMHZRUjJ5NU1ZOWs
... done for now.
ORIGINAL POST, deprecated, left here for reference.
I let this answer sit for a year hoping that GDAA will develop a solution that works. The reason for my nagging is simple. If my app creates a file, it needs to broadcast this fact to its buddies (other devices, for instance) with an ID that is meaningful (that is ResourceId). It is a trivial task under the REST Api where ResourceId comes back as soon as the file is successfully created.
Needles to say that I understand the GDAA philosophy of shielding the app from network primitives, caching, batching, ... But clearly, in this situation, the ResourceID is available long before it is delivered to the app.
Originally, I implemented Cheryl Simon's suggestion and added a ChangeListener on a newly created file, hoping to get the ResourceID when the file is propagated. Using classic CreateEmptyFileActivity from android-demos, I smacked together the following test code:
public class CreateEmptyFileActivity extends BaseDemoActivity {
private static final String TAG = "CreateEmptyFileActivity";
final private ChangeListener mChgeLstnr = new ChangeListener() {
#Override
public void onChange(ChangeEvent event) {
Log.d(TAG, "event: " + event + " resId: " + event.getDriveId().getResourceId());
}
};
#Override
public void onConnected(Bundle connectionHint) { super.onConnected(connectionHint);
MetadataChangeSet meta = new MetadataChangeSet.Builder()
.setTitle("EmptyFile.txt").setMimeType("text/plain")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFile(getGoogleApiClient(), meta, null)
.setResultCallback(new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (result.getStatus().isSuccess()) {
DriveId driveId = result.getDriveFile().getDriveId();
Log.d(TAG, "Created a empty file: " + driveId);
Drive.DriveApi.getFile(getGoogleApiClient(), driveId).addChangeListener(getGoogleApiClient(), mChgeLstnr);
}
}
});
}
}
... and was waiting for something to happen. File was happily uploaded to the Drive within seconds, but no onChange() event. 10 minutes, 20 minutes, ... I could not find any way how to make the ChangeListener to wake up.
So the only other solution, I could come up was to nudge the GDAA. So I implemented a simple handler-poker that tickles the metadata until something happens:
public class CreateEmptyFileActivity extends BaseDemoActivity {
private static final String TAG = "CreateEmptyFileActivity";
final private ChangeListener mChgeLstnr = new ChangeListener() {
#Override
public void onChange(ChangeEvent event) {
Log.d(TAG, "event: " + event + " resId: " + event.getDriveId().getResourceId());
}
};
static DriveId driveId;
private static final int ENOUGH = 4; // nudge 4x, 1+2+3+4 = 10seconds
private static int mWait = 1000;
private int mCnt;
private Handler mPoker;
private final Runnable mPoke = new Runnable() { public void run() {
if (mPoker != null && driveId != null && driveId.getResourceId() == null && (mCnt++ < ENOUGH)) {
MetadataChangeSet meta = new MetadataChangeSet.Builder().build();
Drive.DriveApi.getFile(getGoogleApiClient(), driveId).updateMetadata(getGoogleApiClient(), meta).setResultCallback(
new ResultCallback<DriveResource.MetadataResult>() {
#Override
public void onResult(DriveResource.MetadataResult result) {
if (result.getStatus().isSuccess() && result.getMetadata().getDriveId().getResourceId() != null)
Log.d(TAG, "resId COOL " + result.getMetadata().getDriveId().getResourceId());
else
mPoker.postDelayed(mPoke, mWait *= 2);
}
}
);
} else {
mPoker = null;
}
}};
#Override
public void onConnected(Bundle connectionHint) { super.onConnected(connectionHint);
MetadataChangeSet meta = new MetadataChangeSet.Builder()
.setTitle("EmptyFile.txt").setMimeType("text/plain")
.build();
Drive.DriveApi.getRootFolder(getGoogleApiClient())
.createFile(getGoogleApiClient(), meta, null)
.setResultCallback(new ResultCallback<DriveFileResult>() {
#Override
public void onResult(DriveFileResult result) {
if (result.getStatus().isSuccess()) {
driveId = result.getDriveFile().getDriveId();
Log.d(TAG, "Created a empty file: " + driveId);
Drive.DriveApi.getFile(getGoogleApiClient(), driveId).addChangeListener(getGoogleApiClient(), mChgeLstnr);
mCnt = 0;
mPoker = new Handler();
mPoker.postDelayed(mPoke, mWait);
}
}
});
}
}
And voila, 4 seconds (give or take) later, the ChangeListener delivers a new shiny ResourceId. Of course, the ChangeListener becomes thus obsolete, since the poker routine gets the ResourceId as well.
So this is the answer for those who can't wait for the ResourceId. Which brings up the follow-up question:
Why do I have to tickle metadata (or re-commit content), very likely creating unnecessary network traffic, to get onChange() event, when I see clearly that the file has been propagated a long time ago, and GDAA has the ResourceId available?
ResourceIds become available when the newly created resource is committed to the server. In the case of a device that is offline, this could be arbitrarily long after the initial file creation. It will happen as soon as possible after the creation request though, so you don't need to do anything to speed it along.
If you really need it right away, you could conceivably use the change notifications to listen for the resourceId to change.

Start Activity with UI updated from notification if service running

I'm making kind-of an audio player. Currently I have a MediaPlayer running in the Activity itself (which I know is bad). There is a SeekBar on the screen which gets updated as the music plays, like so:
private Runnable mUpdateTimeTask = new Runnable() {
public void run()
{
long totalDuration = mp.getDuration();
long currentDuration = mp.getCurrentPosition();
songTotalDurationLabel.setText("" + utils.millisecondsToTimer(totalDuration));
songCurrentDurationLabel.setText("" + utils.millisecondsToTimer(currentDuration));
int progress = (int)(utils.getProgressPercentage(currentDuration, totalDuration));
songProgressBar.setProgress(progress);
if(mp.isPlaying())
mHandler.postDelayed(this, 100);
else
mHandler.removeCallbacks(mUpdateTimeTask);
}
};
Once the user presses the back button or kills it from the recent apps list, the music stops.
Now I want the music to run in the background, so looking around the internet I found to run it in a Service, and calling startService() from Activity. Also I have a notification come up when music is playing and removed when it is paused.
I understand from a service I'll get the music to play even when app gets closed. But what I didn't understand is, if the user taps on the notification given the service is running, the activity restarts with the SeekBar at progress = 0.
How do I get the UI to update the SeekBar to the correct value from the Service after the activity restarts?
Figured it out!
The solution is to get the running services using the ActivityManager and find your service like this
private boolean fooRunning()
{
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for(RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE))
{
if("com.name.packagename.foo".equals(service.service.getClassName()))
{
return true;
}
}
return false;
}
If this method returns true, bind to the service and get the current position from the MediaPlayer object
public void bindToService()
{
if(fooRunning())
{
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
serviceExists = true;
}
else
serviceExists = false;
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder serviceBinder)
{
bar binder = (bar) serviceBinder;
mService = binder.getService();
if(serviceExists)
{
int getProgress = mService.mp.getCurrentPosition();
// mp is the MediaPlayer object in the service
seekbar.setProgress(getProgress);
}
}
#Override
public void onServiceDisconnected(ComponentName className)
{
}
};
The Service class is like this:
public class foo extends Service
{
private MediaPlayer mp = new MediaPlayer();
private final IBinder mBinder = new bar();
public class bar extends Binder
{
public foo getService()
{
return foo.this;
}
}
#Override
public IBinder onBind(Intent intent)
{
return mBinder;
}
}
Hope this helps someone!

How to correlate request & reply when using raw (not using Gateway) Spring Integration?

I am learning about Spring-Integration and have a basic understanding about Gateway and Service-Activators. I love the concept of Gateway. Spring Integration generates the proxy for gateway at run-time. This proxy hides all the messaging details from the consumer of the gateway. In addition, the generated proxy might also be co-relating request and reply.
With the objective of learning, I set out to implement request and reply correlation using raw Spring Integration features and not using Gateway. I am able to set the correlation identifier in the request header, but not able to specify correlation identifier while receiving reply for the channel. The following (at the end of the question) is the code snippet for the same. Also how does the correlation stuff works against a message broker (e.g. RabbitMQ)? Does RabbitMQ provides an ability to retrieve a message with a specific header (correlation identifier) in it?
public class RemoteProxyCalculatorService implements CalculatorService
{
public int Square(int n)
{
UUID uuid = SendRequest(n, "squareRequestChannel");
int squareOfn = ReceiveReply("squareReplyChannel", uuid);
return squareOfn;
}
private <T> UUID SendRequest(T payload, String requestChannel)
{
UUID requestID = UUID.randomUUID();
Message<T> inputMessage = MessageBuilder.withPayload(payload)
.setCorrelationId(requestID)
.build();
MessageChannel channel = (MessageChannel)context.getBean(requestChannel, MessageChannel.class);
channel.send(inputMessage);
return requestID;
}
#SuppressWarnings("unchecked")
private <T> T ReceiveReply(String replyChannel, UUID requestID)
{
//How to consume requestID so as to receive only the reply related to the request posted by this thread
PollableChannel channel = (PollableChannel)context.getBean(replyChannel);
Message<?> groupMessage = channel.receive();
return (T)groupMessage.getPayload();
}
private ClassPathXmlApplicationContext context;
}
Thanks.
The simplest way to correlate within an app doesn't even require a correlationId header. Instead you can create a QueueChannel instance (that you don't share) and provide that as s the replyChannel header on the Message you send. Whatever downstream component ultimately responds, it will find that header in the Message.
Regarding RabbitMQ, our outbound-gateway simply applies a similar technique, but using the replyTo property of the AMQP Message.
Hope that helps.
-Mark
Problem is with common reply channel. The solution (Mark suggested the similar) will look like this.
public class RemoteProxyCalculatorService
{
public int Square(int n)
{
PollableChannel replyChannel = SendRequest(n, "squareRequestChannel");
int squareOfn = ReceiveReply(replyChannel);
return squareOfn;
}
private <T> PollableChannel SendRequest(T payload, String requestChannel)
{
UUID requestID = UUID.randomUUID();
QueueChannel replyQueueChannel = new QueueChannel();
Message<T> inputMessage = MessageBuilder.withPayload(payload)
.setCorrelationId(requestID)
.setReplyChannel(replyQueueChannel)
.build();
MessageChannel channel = context.getBean(requestChannel, MessageChannel.class);
channel.send(inputMessage);
return replyQueueChannel;
}
#SuppressWarnings("unchecked")
private <T> T ReceiveReply(PollableChannel replyChannel)
{
Message<?> groupMessage = replyChannel.receive();
return (T) groupMessage.getPayload();
}
private ClassPathXmlApplicationContext context;
}
If you want to use common reply channel then I think this is what you are looking for.
public class RemoteProxyCalculatorService
{
public int Square(int n)
{
PollableChannel replyChannel = SendRequest(n, "squareRequestChannel");
int squareOfn = ReceiveReply(replyChannel);
return squareOfn;
}
private <T> PollableChannel SendRequest(T payload, String requestChannel)
{
UUID requestID = UUID.randomUUID();
Message<T> inputMessage = MessageBuilder.withPayload(payload)
.setCorrelationId(requestID)
.setReplyChannel(myMessageHandler.getSubscribedChannel())
.build();
// Create a Pollable channel for two things
// 1. Pollable channel is where this thread should look for reply.
QueueChannel replyQueueChannel = new QueueChannel();
// 2. Message Handler will send reply to this Pollable channel once it receives the reply using correlation Id.
myMessageHandler.add(requestID, replyQueueChannel);
MessageChannel channel = context.getBean(requestChannel, MessageChannel.class);
channel.send(inputMessage);
return replyQueueChannel;
}
#SuppressWarnings("unchecked")
private <T> T ReceiveReply(PollableChannel replyChannel)
{
Message<?> groupMessage = replyChannel.receive();
return (T) groupMessage.getPayload();
}
private ClassPathXmlApplicationContext context;
#Autowired
private MyMessageHandler myMessageHandler;
}
/**
* Message Handler
*
*/
public class MyMessageHandler implements MessageHandler
{
private final Map<Object, MessageChannel> idChannelsMap = new TreeMap<>();
private final Object lock = new Object();
private final SubscribableChannel subscribedChannel;
public MyMessageHandler(SubscribableChannel subscribedChannel)
{
this.subscribedChannel = subscribedChannel;
}
#Override
public void handleMessage(Message<?> message) throws MessagingException
{
synchronized (lock)
{
this.idChannelsMap.get(message.getHeaders().getCorrelationId()).send(message);
this.idChannelsMap.remove(message.getHeaders().getCorrelationId());
}
}
public void add(Object correlationId, MessageChannel messageChannel)
{
synchronized (lock)
{
this.idChannelsMap.put(correlationId, messageChannel);
}
}
public SubscribableChannel getSubscribedChannel()
{
return subscribedChannel;
}
}

GCM works on 4.1 but doesn't work on 2.3 android version

I am having problem with GCM, it works just fine on Nexus 7 but when I run it on any device with Gingerbread version onRegistered method is never called.
See my code implementation belowe:
GMCIntentService
public class GCMIntentService extends GCMBaseIntentService {
private static final String TAG = "GCMIntentService";
private RestHelper restRegisterGCM;
private String userRegisterGCMUrl = "User/SetGcm";
public GCMIntentService() {
super(AppSettings.SENDER_ID);
}
/**
* Method called on device registered
**/
#Override
protected void onRegistered(Context context, String registrationId) {
Log.i(TAG, "Device registered: regId = " + registrationId);
// Util.displayMessage(context, "Your device registred with GCM");
if (!GCMRegistrar.isRegisteredOnServer(this)) {
restRegisterGCM = new RestHelper(userRegisterGCMUrl, RequestMethod.POST, context);
restRegisterGCM.setHeader("UserName", AppSettings.getInstance().getUsername(context));
restRegisterGCM.setHeader("Password", AppSettings.getInstance().getPassword(context));
restRegisterGCM.setParameter("regId", registrationId);
restRegisterGCM.execute();
}
}
/**
* Method called on device un registred
* */
#Override
protected void onUnregistered(Context context, String registrationId) {
Log.i(TAG, "Device unregistered");
}
/**
* Method called on Receiving a new message
* */
#Override
protected void onMessage(Context context, Intent intent) {
Log.i(TAG, "Received message");
String message = intent.getExtras().getString("Message");
// notifies user
generateNotification(context, message);
}
/**
* Method called on receiving a deleted message
* */
#Override
protected void onDeletedMessages(Context context, int total) {
Log.i(TAG, "Received deleted messages notification");
}
/**
* Method called on Error
* */
#Override
public void onError(Context context, String errorId) {
Log.i(TAG, "Received error: " + errorId);
Toast.makeText(context, getString(R.string.gcm_error, errorId), Toast.LENGTH_SHORT).show();
}
#Override
protected boolean onRecoverableError(Context context, String errorId) {
// log message
Log.i(TAG, "Received recoverable error: " + errorId);
Toast.makeText(context, getString(R.string.gcm_recoverable_error, errorId), Toast.LENGTH_SHORT).show();
return super.onRecoverableError(context, errorId);
}
GMC registration method
private void registerGCM() {
// Make sure the device has the proper dependencies.
GCMRegistrar.checkDevice(this);
Boolean accountExists = false;
AccountManager am = AccountManager.get(getApplicationContext());
Account[] accounts = am.getAccounts();
for (Account account : accounts) {
if (account.type.equals("com.google")) {
accountExists = true;
break;
}
}
if (accountExists) {
// Get GCM registration id
String regId = GCMRegistrar.getRegistrationId(this);
// Check if regid already presents
if (regId.equals("")) {
// Registration is not present, register now with GCM
GCMRegistrar.register(this, AppSettings.SENDER_ID);
} else {
// Device is already registered on GCM
if (!GCMRegistrar.isRegisteredOnServer(this)) {
restRegisterGCM = new RestHelper(userRegisterGCMUrl, RequestMethod.POST, EvadoFilipActivity.this);
restRegisterGCM.setHeader("UserName", AppSettings.getInstance().getUsername(EvadoFilipActivity.this));
restRegisterGCM.setHeader("Password", AppSettings.getInstance().getPassword(EvadoFilipActivity.this));
restRegisterGCM.setParameter("regId", regId);
restRegisterGCM.setPostExecuteMethod(2);
restRegisterGCM.execute();
}
}
} else
Toast.makeText(this, R.string.gcm_google_account_missing, Toast.LENGTH_SHORT).show();
}
UPDATE:
I have renamed packages and forget to change it in my class:
public class GCMBroadcastReceiver extends com.google.android.gcm.GCMBroadcastReceiver{
#Override
protected String getGCMIntentServiceClassName(Context context) {
return "com.mypackage.services.GCMIntentService";
}
}
I had the very same problem.My code would work on nexus4(Kitkat) but would fail to get me a notification from the appln server(via gcm server).#Fr0g is correct for versions less that 4.0.4 you should make sure that you have your google account setup on your device for gcm to work.
I had google account on my galaxy ace(2.3.4) but the mistake I made was that my Account and Sync settings in my galaxy ace was 'Off'.When I turned it ON and ran my code, i received the notification.
Ensure that you have set up a user account on the device that you are testing on. GCM requires that a google account must be setup on the device that is registering for GCM, (also I think that this requirements is for android versions < 4.0)