I am developing android native applications. My requirement is to get the current activity of the device like running,in_vehicle,still and so on. I used ActivityRecognitionAPI and set the pendingintent to receive the activity changes through IntentService. I gave 5 seconds interval for each updated. Its failed to provide the activity changes in certain period of time and again started providing activity. After that i preferred Awareness SnapshotAPI to get the activity state. Its also same result, failed to provide the activity regularly. Both APIs sometimes providing and many times not. I used GooglePlayServices 10.2.0 version for my developemnt. Anyone tell what is the reason for these things and solution to get regular activity updates..
Thanks in advance.
I am trying to show my app user's activity like he is walking, running and so on. This is my requirement.
Way 1:
//Google API client using ActivityRecognition.API
Intent intent = new Intent(mContext, RequestActivityService.class);
PendingIntent callbackIntent = PendingIntent.getService(mContext, 111,
intent, PendingIntent.FLAG_UPDATE_CURRENT);
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(googleApiClient,5000, callbackIntent);
public class RequestActivityService extends IntentService {
public RequestActivityService() {
super("RequestActivityService");
}
#Override
protected void onHandleIntent(Intent intent) {
if (ActivityRecognitionResult.hasResult(intent)) {
ActivityRecognitionResult result = ActivityRecognitionResult
.extractResult(intent);
DetectedActivity mostProbableActivity = result
.getMostProbableActivity();
int confidence = mostProbableActivity.getConfidence();
int activityType = mostProbableActivity.getType();
String activityName = getNameFromType(activityType);
}
}
** I tried this way to get activity updates. It started providing the activity info, but Intent service not called sometimes and again started firing the intent. So I am not able to show the correct activity information.
Way 2:
//Google API client using Awareness.API
Awareness.SnapshotApi.getDetectedActivity(googleApiClient).
setResultCallback(new ResultCallback<DetectedActivityResult>() {
#Override
public void onResult(#NonNull DetectedActivityResult detectedActivityResult) {
if (!detectedActivityResult.getStatus().isSuccess()) {
Log.i("Could not get the activity","");
return;
}
ActivityRecognitionResult ar = detectedActivityResult.getActivityRecognitionResult();
DetectedActivity probableActivity = ar.getMostProbableActivity();
String activityType = RequestActivityService.getNameFromType(probableActivity.getType());
int activityConfidence = probableActivity.getConfidence();
}
});
** I tried this another way, using this Api we can call this method recursively and get the activity information frequently. But Its also sometime providing and sometimes detectedActivityResult.getStatus() not success. If I am trying to get the status code and status message, status code returns 15 and status message will be null.
Both these ways are failed to give regular updates of activity and more over I used GooglePlayService 10.2.0 version. I have tested the GoogleSamples of ActivityRecognition from Github. That is also same result. I don't know what do to achieve my requirement. Hope now u can understand my requirement and things which I faced.
ActivityRecognitionAPI from Google doesn't give updates regularly. From what I have noticed, if there is a change in the confidence level of the activity or the activity changes altogether, then it'll send an update. Even then, it'll take time to ensure the data read isn't just noise of some kind.
I'm not sure why you need regular updates, but I would suggest changing the logic to accept the last given activity as the current activity, and only act upon the changes (i.e., STILL -> IN_VEHICLE).
Call this method again after getting response:
I would suggest to set detectionIntervalMillis to 0. That will lead to get data ASAP.( be aware of battery consumption )
ActivityRecognition.ActivityRecognitionApi.requestActivityUpdates(googleApiClient,o, callbackIntent);
if there are no changes for long time, it will not send updates till change occur...
Related
I have an app that should show a notification every 2 hours and should stop if user has already acted upon the notif. Since background services are history now, I thought of using WorkManager ("android.arch.work:work-runtime:1.0.0-beta01") for the same.
My problem is that although the work manager is successfully showing the notifications when app is running, but it won't show notification consistently in the following cases(I reduced the time span from 2 hours to 2 minutes to check the consistency):
when app is killed from the background.
device is in screen off.
state device is in unplugged state(i.e not charging).
By consistency , i mean that the notifications show at least once in the given time span. for 2 minutes time span, the freq of notifications went from once every 4 minutes to completely not show any notification at all. for 2 hours timespan( the timespan that i actually want), its been 4 hours and i haven't got a single notification. Here is the Code i am using for calling WorkManger:
public class CurrentStreakActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
setDailyNotifier();
...
}
private void setDailyNotifier() {
Constraints.Builder constraintsBuilder = new Constraints.Builder();
constraintsBuilder.setRequiresBatteryNotLow(false);
constraintsBuilder.setRequiredNetworkType(NetworkType.NOT_REQUIRED);
constraintsBuilder.setRequiresCharging(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
constraintsBuilder.setRequiresDeviceIdle(false);
}
Constraints constraints =constraintsBuilder.build();
PeriodicWorkRequest.Builder builder = new PeriodicWorkRequest
.Builder(PeriodicNotifyWorker.class, 2, TimeUnit.HOURS);
builder.setConstraints(constraints);
WorkRequest request = builder.build();
WorkManager.getInstance().enqueue(request);
}
....
}
Here is the worker class(i can post showNotif(..) and setNotificationChannel(...) too if they might be erroronous):
public class PeriodicNotifyWorker extends Worker {
private static final String TAG = "PeriodicNotifyWorker";
public PeriodicNotifyWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
Log.e(TAG, "PeriodicNotifyWorker: constructor called" );
}
#NonNull
#Override
public Result doWork() {
// Log.e(TAG, "doWork: called" );
SharedPreferences sp =
getApplicationContext().getSharedPreferences(Statics.SP_FILENAME, Context.MODE_PRIVATE);
String lastcheckin = sp.getString(Statics.LAST_CHECKIN_DATE_str, Statics.getToday());
// Log.e(TAG, "doWork: checking shared preferences for last checkin:"+lastcheckin );
if (Statics.compareDateStrings(lastcheckin, Statics.getToday()) == -1) {
Log.e(TAG, "doWork: last checkin is smaller than today's date, so calling creating notification" );
return createNotificationWithButtons(sp);
}
else {
Log.e(TAG, "doWork: last checkin is bigger than today's date, so no need for notif" );
return Result.success();
}
}
private Result createNotificationWithButtons(SharedPreferences sp) {
NotificationManager manager =
(NotificationManager) getApplicationContext().getSystemService((NOTIFICATION_SERVICE));
String channel_ID = "100DaysOfCode_ID";
if (manager != null) {
setNotificationChannel(manager,channel_ID);
showNotif(manager, channel_ID, sp);
return Result.success();
}
else {
return Result.failure();
}
I am using a xiaomi miA2 androidOne device with Android Pie(SDK 28). There are a few other things that are troubling me:
What can i possibly do to know if my WorkManager is running? Other that just wait for 2 hours and hope for a notification. I actually tried something like that, keeping my phone connected to pc and checking android studio's logcat every now and then. It DOES run all the logs when the worker is actually called, but i don't think that's a correct way to test it, or is it?
In the above Code, the setDailyNotifier() is called from the onCreate() every time the app is opened. Isn't it Wrong? shouldn't there be some unique id for every WorkRequest and a check function like WorkManger.isRequestRunning(request.getID) which could let us check if a worker is already on the given task??If this was a case of AsyncTask, then boy we would have a mess.
I have also checked #commonsware's answer here about wakelock when screen is off, but i remember that work manager does use alarm manager in the inside when available. So what am I missing here?
Few comments:
WorkManager has a minimum periodic interval of 15minutes and does not guarantee to execute your task at a precise time. You can read more about this on this blog.
All the usual background limitation you've on newer Android releases are still relevant when you use WorkManager to schedule your tasks. WorkManager guarantees that the task are executed even if the app is killed or the device is restated, but it cannot guarantee the exact execution.
There's one note about the tasks being rescheduled when your app is killed. Some OEM have done modification to the OS and the Launcher app that prevents WorkManager to be able to accomplish these functionality.
Here's the issuetracker discussion:
Yes, it's true even when the phone is a Chinese phone.
The only issue that we have come across is the case where some Chinese OEMs treat swipe to dismiss from Recents as a force stop. When that happens, WorkManager will reschedule all pending jobs, next time the app starts up. Given that this is a CDD violation, there is not much more that WorkManager can do given its a client library.
To add to this, if a device manufacturer has decided to modify stock Android to force-stop the app, WorkManager will stop working (as will JobScheduler, alarms, broadcast receivers, etc.). There is no way to work around this. Some device manufacturers do this, unfortunately, so in those cases WorkManager will stop working until the next time the app is launched.
As of now , i have this app installed for last 8 days and i can confirm that the code is correct and app is working fine. as said by pfmaggi , the minimum time interval for work manager to schedule the work is 15 minutes, so there is a less chance that the WorkManager would have worked as expected in my testing conditions( of 2 minutes ) . Here are some of my other observations:
Like I said in the question that i was unable to recieve a notification for 4 hours even though i have passed the repeat interval as 2 hours. This was because of Flex Time. I passed in the flex time of 15 minutes and now it shows notifications between correct time interval. so i will be marking pfmaggi's answer as correct.
The problem of repeated work request can be solved by replacing WorkManager.getInstance().enqueue(request) with WorkManager.getInstance().enqueueUniqueWork(request,..)
I was still unable to find a way to test the work manager in the way i have described.
On our application there's a service that is normally started during Application.OnCreate (directly calling context.startService) and also later on via AlarmManager (refactor is in progress to migrate some of its work to JobScheduler).
Our application also have a BroadcastReceiver that gets launched with its direct intent.
Given the new limitations in Android Oreo (https://developer.android.com/about/versions/oreo/android-8.0-changes.html) we're having an issue as follows:
app/process is in background/dead
BroadcastReceiver gets fired by the OS
Application.onCreate() executes before the BroadcastReceiver
Application.onCreate() code tries to run the Service
this leads to crash with "IllegalStateException: Not allowed to start service Intent".
I'm aware of the new recommended ways of launching a Service as answered by CommonsWare here https://stackoverflow.com/a/44505719/906362, but for this specific case, I simply want to have if(process in foreground) { startService }. I'm currently using the following method and it seems to work:
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean isProcessInForeground_V21(Context context) {
ActivityManager am = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
List<ActivityManager.AppTask> tasks = am.getAppTasks();
return tasks.size() > 0;
}
But I can't find the exact checks Android Oreo is doing (I got as far as here https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ContextImpl.java on the startServiceCommon method, but from there requireForeground flag seems to go to some native implementation)
So my question:
For the specific purpose of Android Oreo new limitations, how to check if my process is foreground before calling startService?
To continue your investigation: (TL;DR: see between horizontal lines at the bottom)
Disclaimer, I don't know too much about Android, I just like digging in the source code.
Note: you can also navigate the code in Android Studio if you jump to file instead of class:
or searching for text in Project and Libraries.
IActivityManager is defined by AIDL, that's why there are no sources for it:
https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/app/IActivityManager.aidl#145
Based on how AIDL needs to be implemented I found that ActivityManagerService extends IActivityManager.Stub (God bless Google indexing).
Note I also found this, which might be an interesting read if you're really interested how things work internally.
https://programmer.group/android-9.0-source-app-startup-process.html
ActivityManagerService sources reveal that in Oreo startService is forwarded to ActiveServices which is located in the same package.
Assuming we're looking for an exception like this:
java.lang.IllegalStateException: Not allowed to start service Intent {...}: app is in background uid UidRecord{af72e61 u0a229 CAC bg:+3m52s273ms idle procs:1 seq(0,0,0)}
we have to continue down the rabbit hole: requireForeground gets assigned to fgRequired parameter and the message is here. The condition to allow this depends on the start mode returned by ActivityManagerService.getAppStartModeLocked(packageTargetSdk = 26 or greater, disabledOnly = false, forcedStandby = false).
There are 4 start modes:
APP_START_MODE_NORMAL (needs to be different than this, i.e. !=)
APP_START_MODE_DELAYED (this is ok, i.e. return null)
APP_START_MODE_DELAYED_RIGID
APP_START_MODE_DISABLED
Ephemeral apps will immediately return APP_START_MODE_DISABLED, but assuming this is a normal app, we end up in appServicesRestrictedInBackgroundLocked.
Note: this is where some of the whitelist mentioned in https://stackoverflow.com/a/46445436/253468 is decided.
Since all branches but last return APP_START_MODE_NORMAL, this redirects to appRestrictedInBackgroundLocked where we find our most likely suspect:
int appRestrictedInBackgroundLocked(int uid, String packageName, int packageTargetSdk) {
// Apps that target O+ are always subject to background check
if (packageTargetSdk >= Build.VERSION_CODES.O) {
return ActivityManager.APP_START_MODE_DELAYED_RIGID;
}
So the reason for denial is simply targeting O. I think the final answer to your question of how the OS decides if your app is foreground or background is this condition in getAppStartModeLocked
UidRecord uidRec = mActiveUids.get(uid);
if (uidRec == null || alwaysRestrict || uidRec.idle) {
My guess is that a missing record means it's not running (but then how is it starting a service?!), and idle means it's backgrounded. Notice that in my exception message the UidRecord is saying that it's idle and has been backgrounded for 3m52s.
I peeked into your getAppTasks and it's based on TaskRecord.effectiveUid, so I'm guessing that's quite close to listing UidRecords for your app.
Not sure if this helps, but I'll post it anyway, so if anyone wants to investigate more, they have more info.
I have an application that according to some events, changes a normal notification to text-to-speech in order to since sometimes the phone isn't available to users, and it'll be safer not to handle the phone.
For example, when you're driving, this is dangerous, so i want to turn the notifications to text-to-speech.
I've looked for a long time some explanation for turning text-to-speech when driving, but i can't find any reference for that no where i search.
For generating text-to-speech, i have this part, which works fine :
private TextToSpeech mTextToSpeech;
public void sayText(Context context, final String message) {
mTextToSpeech = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
#Override
public void onInit(int status) {
try {
if (mTextToSpeech != null && status == TextToSpeech.SUCCESS) {
mTextToSpeech.setLanguage(Locale.US);
mTextToSpeech.speak(message, TextToSpeech.QUEUE_ADD, null);
}
} catch (Exception ex) {
System.out.print("Error handling TextToSpeech GCM notification " + ex.getMessage());
}
}
});
}
But, i don't know how to check if i'm currently driving or not.
As Ashwin suggested, you can use Activity recognition Api, but there's a downside of that, the driving samples you'll receive, has a field of 'confidence' which isn't always accurate, so you'll have to do extra work(such as check locations to see if you actually moved) in order to fully know if the user moved.
You can use google's FenceApi which allows you to define a fence of actions such as driving, walking, running, etc. This api launched recently. If you want a sample for using it, you can use this answer.
You can pull this git project (everything free), which does exactly what you want : adds to the normal notification a text-to-speech when you're driving.
In order to know whether you are driving or not you can use Activity Recognition API
Here is a great tutorial that might help you out Tutorial and Source Code
I am using History API of Google Fit to get the step count into my app. I have run a service and the step count is updated after a regular interval. When my app is in foreground, it works fine and I get the step count but when my app goes background, services works fine and Fitness.HistoryApi.readData does not fetch the steps. I require the steps count even when the app is in background or even when the app is killed.
Here is my problem code snippet:
private class InsertAndVerifyDataTask extends AsyncTask<String,String,String> {
protected String doInBackground(String...params) {
readRequest = queryFitnessData();
dataReadResult =
Fitness.HistoryApi.readData(mClient, readRequest).await(1, TimeUnit.MINUTES);
if(dataReadResult!=null) {
printData(dataReadResult);
}
return "s";
}
protected void onPostExecute(String result){
if(result.equals("s")) {
compareData();
}
}
}
And on debug the method Fitness.HistoryApi.readData gives status as TIME_OUT when app is in background or killed.
Please help me out, I am just a beginner in Android.
You might want to use the Fitness.RecordingApi to keep recording your steps even when the app is killed.
If you are using the SensorsApi to get the step count, it wont work when your app is killed. RecordingApi is like telling the SDK- "Hey!, keep recording the steps for me even when I'm gone". And when the user comes back to your app, you can fetch the data using the getDailyTotal or readData.
See reference here
Warning: however,RecordingApi takes some time for update- you dont know at what time interval the data is updated- which means user might not be seeing the updated data. See here.
Ok, so I am trying to figure at how to get an up-to-date list of available chromecast devices, I'm doing this so that my app can check when the chromecast is not in use and then open my receiver app.
I am having some unexpected behaviour from the code below:
public class MainActivity extends Activity {
...
#Override
public void onCreate(Bundle savedInstanceState) {
...
mMediaRouterCallback = new MyMediaRouterCallback();
mMediaRouter = MediaRouter.getInstance(context);
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(CastMediaControlIntent.categoryForCast(context.getString(R.string.app_id)))
.build();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
}
This adds a MediaRouter callback to the MediaRouter. I have chosen to use the active scan flag.
private class MyMediaRouterCallback extends MediaRouter.Callback {
...
#Override
public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
Log.d(TAG, "Description 1 " + info.getDescription());
mSelectedDevice = CastDevice.getFromBundle(info.getExtras());
Log.d(TAG, "Description 2 " + mSelectedDevice.toString());
if(info.getDescription().equals("Chromecast")) {
// code to launch chromecast receiver app here.
}
}
}
My implementation of the MediaRouter.Callback overrides onRouteAdded, it simply prints some information about the devices it has found Description 1 describes the receiver app the device is using, description 2 gives its name.
However when this code is run initially the same device is discovered twice printing:
07-05 21:01:12.270: D/MainActivity(9730): Description 1 Casting HelloText
07-05 21:01:12.280: D/MainActivity(9730): Description 2 "Downstairs"
07-05 21:01:12.280: D/MainActivity(9730): Description 1 Casting HelloText
07-05 21:01:12.280: D/MainActivity(9730): Description 2 "Downstairs"
Then periodically the onRouteAdded callback is called sometimes only listing the device once, other times listing the device twice. My understanding however is that this callback should only be called when a new route is added.
I want to find all the available devices on command, not at random intervals that I can't control, what do I need to be doing? I can't find a callback that seems to be appropriate for this situation (such as whenever devices update/change), nor can I find a way to list them without using callbacks, so I'm a bit stuck.
(I have been basing these tests of the HelloText-Android example found here https://github.com/googlecast/CastHelloText-android, also I started this (my first android project) only a couple of days ago, so I apologise if I am missing something horrendously obvious)
Thanks in advance.
Call getRoutes() to get the list of known routes at the point in time that you desire. Iterate over them. Call matchesSelector() on each to filter out those that match your desired control category.
If you are listening for "onRouteAdded()", you would also need to listen to "onRouteRemoved()" to do a correct bookkeeping; if a device is added, it can be removed and added again so if you just listen to onRouteAdded(), it may seem it is being added multiple times. Getting the list from MedaiaRoute.getRoutes() might be easier if you don't want to be notified immediately and only want to know the list at certain points on demand.