My app is setting an alarm:
AlarmManager mgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
mgr.setRepeating(
AlarmManager.RTC_WAKEUP,
firstRun,
interval,
makePendingIntent(context));
Works fine. If I go into system settings -> applications, and force-stop my app, it seems that this also has the effect of canceling any alarms I had scheduled. Is this true? If so, I'm in a weird situation where my last-known settings are that the user had in fact set the alarm, but the system may have canceled behind my back, so I'm now showing the user the wrong status as to whether the alarm is set or not.
Thanks
Yes, it's true. All you can do, as far as I know, is to make your status right. To check if your alarms are still there you have to take 2 steps:
Atempt to create your PendingIntent with FLAG_NO_CREATE - the function checkPendingIntent will be exactly like makePendingIntent except for this flag in PendingIntent.getBroadcast and a check of the result - if the alarm has been canceled in an ordinary way (by yourself, of course if you called cancel() for your PendingIntents) or if your app crashed without Force Stop (i.e. with an uncaught exception), PendingIntent.getBroadcast will return null.
If the PendingIntent exists your alarm might be set. To check it you have to dump system information about all alarms and search for yours there. To dump the info you have to call
String collectAlarmsInfo() {
StringBuilder result = new StringBuilder();
try {
Process process = Runtime.getRuntime().exec("dumpsys alarm");
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
result.append(line);
result.append("\n");
}
} catch (IOException e) {
Log.e(TAG, "Could not retrieve data", e);
}
return result.toString();
}
To use dumpsys you need to have user-permission DUMP. When you have the dump you can identify you alarms in it by you package name, so the check will be
boolean alarmIsSet = collectAlarmsInfo().contains(<your package name>);
Related
I have a broadcast receiver that listens for power connection events. Whenever the device is connected to power, I attempt to transfer files from the APP to a Server in a machine running Ubuntu. The files are transferred over Bluetooth. Since the transfer of files is important, if for any reason the transfer has an error, or the connection is not successful in a first attempt, I retry it up to 6 times allowing 3 minutes between attempt.
In the beginning, I was using an asynctask which was simply maintained alive as long as we still have retries available and the file transfer has not been successfully done. I read, that having an asynctask in a broadcast receiver is not a good practice which makes total sense, especially since I'm forcing the task to run for long periods of time. Therefore, I decided to change to a JobIntentService such that every time a power connection event was captured by the receiver, I would issue the job that will transfer files to my computer. Within the job, right after the file transfer is finished or failed, I would set an alarm that will send a pending intent to the broadcast and call the job again.
I was running this and I have noticed that (as different from before) I've gotten too many "Connection reset by peer" errors during the transfer, which makes me wonder if the Job is being stopped before its completed or something like that?. Those errors used not to happen in my previous implementation. Then, I also noticed that for some reason the OS seems to have launched the JobIntentService again by itself (there was no event that launched it) which caused inconsistencies on my code and caused me to lose some files (I'm not supposed to allow multiple instances of this job running at the same time)
My question is, why do you think the service was restarted? is it possible for the JobIntentService to be finished and restarted by the OS during the BT transfer? The files are heavy so they take several minutes to transfer from the app to the machine. I was thinking of trying a foreground service instead of the JobIntent and having a notification for the service or going back to my previous implementation.
Any suggestions?
This is how I call the Intent Job.
FileTransferJob.isJobAlreadyRunning = true;
Intent intent = new Intent(context, FileTransferJob.class);
intent.putExtra(TRANSFER_DATA_RETRIES, retries);
FileTransferJob.enqueueWork(context,intent);
This is the JobIntentService class
public class FileTransferJob extends JobIntentService {
/**
* Unique job ID for this service.
*/
public static boolean isJobAlreadyRunning = false; //This flag will remain true as soon as this JOB is called and as long as retries are still available
public static final int JOB_ID = 1000;
public static int MAX_NUM_OF_RETRIES = 6;//How many times are we going to retry to send the data
private int MINUTES_TO_WAIT = 3; //The minutes we wait between each attempt
public String TAG = "FileTransferJob";
/**
* Convenience method for enqueuing work in to this service.
*/
public static void enqueueWork(Context context, Intent work) {
enqueueWork(context, FileTransferJob.class, JOB_ID, work);
}
#Override
protected void onHandleWork(Intent intent) {
int retriesRemaining = intent.getIntExtra(TRANSFER_DATA_RETRIES,1); //Get the number of retries we have. Default to 1 (this one)
Log.d(TAG, "onHandleWork: About to attempt transfer with remaining retries " + String.valueOf(retriesRemaining));
try {
BluetoothFileTransfer btio = new BluetoothFileTransfer();
Log.d(TAG, "onHandleWork: About to send data over Bluetooth");
btio.sendData(FileTransferJob.this.getApplicationContext());
FileTransferJob.isJobAlreadyRunning = false; //Success, then this is no longer running
Log.d(TAG, "onHandleWork: The data has been sent over Bluetooth");
}catch (Exception e){
Log.d(TAG, "onHandleWork: There was a problem with the BT transfer: " + e.getMessage());
retriesRemaining--; //We reduce the number of retries we have
//If no more retries available, simply do nothing
if (retriesRemaining > 0) {
Log.d(TAG, "onHandleWork: Setting up alarm. Retries ramaining: " + String.valueOf(retriesRemaining));
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent alarmIntent = new Intent(this.getApplicationContext(), DataCollectReceiver.class);
alarmIntent.setAction(TRANSFER_DATA);
alarmIntent.putExtra(TRANSFER_DATA_RETRIES, retriesRemaining);
PendingIntent alarmPendingIntent = PendingIntent.getBroadcast( this.getApplicationContext(), PENDING_INTENT_CODE_FILE_TRANSFER_JOB, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
int totalTime = MINUTES_TO_WAIT*60*1000;
if(alarmManager != null){
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + totalTime,
alarmPendingIntent);
Log.d(TAG, "onHandleWork: Alarm is set, waiting " + String.valueOf(totalTime) + " minutes for next attempt...");
}else{
Log.d(TAG, "onHandleWork: Alarm could not be set. Alarm manager is NULL");
}
}else{
Log.d(TAG, "onHandleWork: There are no more retries");
FileTransferJob.isJobAlreadyRunning = false;
}
}
}
#Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: The file transfer JOB has finished");
}
}
The logcat. The highlighted section shows what I believe is the OS creating a new instance of the JobService and running it.
Let me try to answer it as i have noticed this behavior. The JobIntentService/JobService/Worker will run only for 10 mins after that they will be stopped and you can get a call back on onStopJob/onStopCurrentWork in case of JobService/JobIntentService and OnStopped in case of Worker.
Though the android document has explained this behavior for Worker only but JobService/JobIntentServie both behaves the same way
A Worker is given a maximum of ten minutes to finish its execution and return a ListenableWorker.Result. After this time has expired, the Worker will be signalled to stop.
Hence i can assume that your task is not finished within 10 mins and Android is destroying the JobIntentService.
Now the thing is that All of these Jobservice/JobIntentService/Worker are started again (If stopped prematurely) after the exponential backoff time i.e. 30secs , 1 min, 2 mins,4 mins...
Although the weird part is that the old thread which died after running 10 mins started as explained but as the call back comes again on HandleWork it starts another thread again which duplicates the work done by the thread and that is why i think you see inconsistencies.
The suggestion is that you break your work in such a way that can be finished withing the 10 mins window. Or We can wait for Google team to fix this.
I have a BroadcastReceiver registered in manifest for ACTION_DOWNLOAD_COMPLETE broadcast.
when I receive this broadcast, I extract download id:
public class DownloadCompleteBroadcastReceiver
extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
handleDownloadCompleteReceiver(context, intent);
}
}
}
private void handleDownloadCompleteReceiver(Context context, Intent intent) {
long enqueueId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (enqueueId != -1) {
Intent startServiceIntent = new Intent(context, HandleAPKDownloadCompleteIntentService.class);
startServiceIntent.putExtra(HandleAPKDownloadCompleteIntentService.EXTRA_ENQUEUE_ID, enqueueId);
context.startService(startServiceIntent);
}
}
}
I'm getting a valid value for the enqueueId and starting IntentServiceto handle the file been downloaded:
#Override
protected void onHandleIntent(Intent intent) {
long enqueueId = intent.getLongExtra(EXTRA_ENQUEUE_ID, -1);
if (enqueueId == -1) {
return;
}
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(enqueueId);
Cursor c = dm.query(query);
if (c != null) {
if (c.moveToFirst()) {
int statusColumnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
int downloadManagerDownloadStatus = c.getInt(statusColumnIndex);
if (DownloadManager.STATUS_SUCCESSFUL == downloadManagerDownloadStatus) {
...
...
}
else if (DownloadManager.STATUS_FAILED == downloadManagerDownloadStatus) {
...
...
}
else {
reportToGoogleAnalyticsUnexpectedStatus(downloadManagerDownloadStatus);
}
}
c.close();
}
}
at this point downloadManagerDownloadStatus = 2, which according to the documentation is STATUS_RUNNING
it does not make any sense, because the broadcast ACTION_DOWNLOAD_COMPLETE already been called, so the download should not be running.
I see this happening a lot of times in google analytics, but cannot reproduce.
any idea why it happens?
any idea how to reproduce?
should I consider this state as success or failure of the download?
I really don't understand if consider such download as success or not, because from one side - the download complete broadcast fired, but from the other hand the status is running.
point that worth mentioning: I'm using download manager intensively: starts 10-15 downloads at once in trigger to particular flow in the app,
Thanks in advance.
You mention that you are commencing multiple downloads.
point that worth mentioning: I'm using download manager intensively:
starts 10-15 downloads at once in trigger to particular flow in the
app,
Now to explain clearly what is happening in the onHandleIntent.
Taken from the android docs
protected abstract void onHandleIntent (Intent intent)
This method is invoked on the worker thread with a request to
process. Only one Intent is processed at a time, but the processing
happens on a worker thread that runs independently from other
application logic. So, if this code takes a long time, it will hold
up other requests to the same IntentService, but it will not hold up
anything else. When all requests have been handled, the
IntentService stops itself, so you should not call stopSelf().
It is quite possible that you are tripping the DownloadManager.ACTION_DOWNLOAD_COMPLETE with one or more successful downloads, and then the thread is then in a STATUS_RUNNING state, with a multiple of reasons the download is not completing.
As mentioned by the other answer, you may have run out of memory. I'd suggest logging your app at each stage and see exactly what is being downloaded and where it is getting stuck. Then investigate why it is stopping.
The behavior seems odd. You could try the below to get just the two cases you are interested in (in addition to the setFilterById()):
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL | DownloadManager.STATUS_FAILED);
But this is not going to shine any light on why you are getting what you are getting. I would suggest you add the below log to see what's in that cursor, that will give you a better idea.
DatabaseUtils.dumpCursorToString(cursor)
UPDATE: This is worth checking out: DownloadManager.ACTION_DOWNLOAD_COMPLETE broadcast receiver receiving same download id more than once with different download statuses in Android
As I used the DownloadManager before.I think you can pay attention to these points:
1.DownloadManager seems to have relationship with the DownloadProvider.apk,when the process of this app is killed,there maybe will be something wrong with DownloadManager.
2.When you don't have enough storage space,you will have this:
Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR
this kind of situation maybe will tell you the status is Downloads.Impl.STATUS_RUNNING.
I have been using this a couple of times with similar problems. Make sure to double check everything like:
android.permission.INTERNET
query.setFilterByStatus(DownloadManager.STATUS_PAUSED|DownloadManager.STATUS_PENDING|DownloadManager.STATUS_RUNNING|DownloadManager.STATUS_SUCCESSFUL);
Check this good DownloadManager tutorial.
I hope this helps in any way.
I am creating a custom launcher in android application and , I want to track actual time which application is opened.
suppose i open Facebook for 10 minute and I went ideal for 5 minute how to calculate 5 minute which is used by user.
Thanks
You can define two buttons to start and stop your service. Fire an intent on Start button something like this:
Intent startServiceIntent = new Intent();
startServiceIntent.setClass(this, MainService.class);
startServiceIntent.setComponent(new ComponentName(this, MainService.class));
bindService(startServiceIntent, mConnection, Context.BIND_AUTO_CREATE);
In the service, you can define broadcast receivers to detect screen on-off events.
private BroadcastReceiver screenWakeUp = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
if (intent.getAction().equals("android.intent.action.SCREEN_ON")) {
isRunningForegroundAppsThread = true;
isScreenOn = true;
startThread();
}
}
};
From here, you can start a thread which constantly runs inside a loop and checks whether the application being used by the user has changed meanwhile. If it has, you may update time for that app in the map else you can just continue. At the SCREEN_OFF event, you may stop the thread as user is no more using the phone.
You can retrieve the application name using this:
ActivityManager am = (ActivityManager) mContext.getSystemService(ACTIVITY_SERVICE);
PackageManager pm = mContext.getPackageManager();
ApplicationInfo af = null;
List<ActivityManager.RunningTaskInfo> taskInfo = am.getRunningTasks(1);
// String activity = taskInfo.get(0).topActivity.getClassName();
ComponentName c = taskInfo.get(0).topActivity;
String packageName = c.getPackageName();
try {
af = pm.getApplicationInfo(packageName, 0);
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String app_Name_current = (String) pm.getApplicationLabel(af);
It solely depends on your requirement, and if needed the time can be stored in a DB. Also, this records just the time for apps which require screen to be ON, so you'll have to separately maintain time for background apps and store that in a separate map maybe. At the end, when you press the stop button, you can display the results in a listview or whatever UI element you consider suitable for this.
I know this is not complete code, but I guess it gives you an idea of how to go about it. Also, I'd say that though this might solve the problem, but it's not a good way of achieving the same, as PackageManager objects are heavyweight and dealing with them continuously is expensive operation.
The above approach only works on pre-L devices as ActivityManager has been deprecated in API level 21. So you have to use UsageStats class for Lollypop devices.
I have an alarm application. I generally know the lifecycle of the receiver and how to use WakeLock.
Today however I was contacted by an user that have sent me a really strange log and complained that his alarm hasn't started until he have had unlocked the phone by himself. I used to have problems with phones going back to sleep after receiver completed its work and before activity was started, but creating WakeLock in the receiver seemed to fix the problem. At least until today - from log it seems that onReceive method wasn't called at all until user has unlocked phone by himself.
Facts:
it is the first case I have heard of
it has happened a few times to the user, but not every time
log is prepared by adding text to SQLite database. It doesn't seem to delay application in any significant way
infomation from onReceive was recorded over 100 seconds after expected alarm start time. It is the first method call in onReceive
alarm was started just after user has unlocked the phone
I use AlarmManager.RTC_WAKEUP flag
user says he doesn't have any custom rom. I wait for answer if he has any custom/special lockscreen
phone model is Sony Xperia U ST25A, Android 4.0.4
Any ideas what could be causing this problem? Is it possible that BroadcastReceiver's "inside" WakeLock doesn't work somehow?
EDIT:
I would like to emphasize the issue here - BroadcastReceiver should keep phone awake during its whole onReceive method. However in my case, it is either that
phone falls to sleep before onReceive methods end (even before finishing "logging call")
phone is not awaken by receiver at all
Also, I would like to point out the fact that user has stated clearly - alarm has started precisely when he has unlocked phone by himself. Couple of times.
Some code:
#Override
public void onReceive(Context context, Intent intent) {
Logger.initialize(context, "AlarmReceiver");
...
}
Logger methods:
public synchronized static void initialize(Context context, String text) {
try {
if (mInstance == null) { // this is the block that is runned
BugSenseHandler.initAndStartSession(context, BUGSENSE_ID);
mInstance = new Logger(context);
log("== Logger initialized == from "
+ (text != null ? text : "")); // it stores times as well. Said
// that alarm was started over 100
// seconds after it should
} else {
log("logger initialized again from "
+ (text != null ? text : ""));
}
} catch (Exception e) {
try {
BugSenseHandler.sendException(e);
mInstance = null;
} catch (Exception e2) {
}
}
}
Take a look at WakefulIntentService from Commonsware
I have an Android app that needs to sync to the internet, but as soon as the phone goes to sleep I can't access the internet. It only happens when the user uses the "battery mode", when it turns off the data after 15 minutes. I wrote a test app and its turning the data on, but it still does connect to the server.
What I tried:
When I turn the data manually off, then the app is turning it on and it works
I also tried WakeLock, but it did not help.
The alarm works as expected, even when the phone goes to sleep for hours
Tested on Motorola Atrix Android 2.3.3. I can't rely on Wifi. In real life it will sync every week. How can we make it possible?
AlarmManager:
alarm_manager = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, AlarmReceiver.class);
PendingIntent pending = PendingIntent.getBroadcast(this, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarm_manager.setRepeating(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis(), 15000, pending);
AlarmReceiver:
public class AlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.d("MYTAG", "RECEIVED getMobileDataEnabled: " + getMobileDataEnabled(context));
if (!isOnline(context)) {
Log.d("MYTAG", "NO INET");
if (turnOnInet(context)) {
Log.d("MYTAG", "INET IS ON");
}
}
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://xxx.xxx.xxx.xxx/ping/pong/moto/");
try {
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(1);
nameValuePairs.add(new BasicNameValuePair("short_code", "ROFL"));
httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs));
httpclient.execute(httppost);
Log.d("MYTAG", "POST FINISHED");
}
catch (Exception e) {
Log.e("MYTAG", "MYTAG", e);
}
}
public boolean isOnline(Context context) {
ConnectivityManager cm = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo netInfo = cm.getActiveNetworkInfo();
if (netInfo != null){
Log.d("MYTAG", "isAvailable: "+netInfo.isAvailable());
}
if (netInfo != null && netInfo.isConnectedOrConnecting()) {
return true;
}
return false;
}
public boolean turnOnInet(Context context) {
ConnectivityManager mgr = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (mgr == null) {
Log.d("MYTAG", "ConnectivityManager == NULL");
return false;
}
try {
Method setMobileDataEnabledMethod = mgr.getClass().getDeclaredMethod("setMobileDataEnabled", boolean.class);
if (null == setMobileDataEnabledMethod) {
Log.d("MYTAG", "setMobileDataEnabledMethod == null");
return false;
}
setMobileDataEnabledMethod.invoke(mgr, true);
}
catch(Exception e) {
Log.e("MYTAG", "MYTAG", e);
return false;
}
return true;
}
private boolean getMobileDataEnabled(Context context) {
ConnectivityManager mgr = (ConnectivityManager)context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
if (mgr == null) {
Log.d("MYTAG", "getMobileDataEnabled ConnectivityManager == null");
return false;
}
try {
Method method = mgr.getClass().getMethod("getMobileDataEnabled");
return (Boolean) method.invoke(mgr);
} catch (Exception e) {
Log.e("MYTAG", "MYTAG", e);
return false;
}
}
}
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
First, you need to get that HttpPost code out of the BroadcastReceiver and into an IntentService. Never do network I/O on the main application thread, and onReceive() is called on the main application thread. For example, if you take too long, Android will terminate your code partway through your Internet operation.
Second, given the IntentService, you need to use a WakeLock. That may steer you to use my WakefulIntentService, which handles both problems. Or, use WakefulBroadcastReceiver, which has the same purpose.
Third, delete turnOnInet() and getMobileDataEnabled(). You do not need them, they are unreliable, and in particular turnOnInet() is user-hostile -- if the user wanted mobile data on, they would have turned it on.
Now, given all of that, in your onHandleIntent() of your IntentService() (or your doWakefulWork() of your WakefulIntentService), if you do not have an Internet connection right away, as a temporary workaround, SystemClock.sleep() for a second and try again, repeating a few times in a loop. If you find that you are getting Internet access after a bit, then you can consider getting more sophisticated (e.g., listening for connectivity change broadcasts rather than polling, though this would drive you away from WakefulIntentService and into a regular Service with your own background thread and a state machine for WakeLock management). Or, just stick with the sleep() -- it's unlikely to be the end of the world if you tie up this background thread for a few seconds. If you do not get connectivity after a modest amount of time, though, please do not keep trying indefinitely, as there are any number of reasons why you might not get a connection, including user-driven bandwidth management on Android 4.0+.
I'd suggest to change a bit that approach, which is not bad at all, it's even nice to make sure that you are always synced, with the only problem that you are not giving the user the chance to decide, since if I, as an user, decide to turn off my data I just don't want anybody to turn it on. There could be several reasons for that, and any of them should be enough, but say that you get out of the country and you have no international data plan and by accident or default you have Data roaming activated.
If I'd discover that some certain app turned my data on and spent a sensitive amount of money I'd be pretty pissed, and I'll be personal.
A more proper approach and straight solution as well would be to make a hard/full sync from time to time whenever the user opens your app or has access to some wifi connection (ConnectivityManager is your friend) based on some easy conditions (last time sync longer than a week, outdated saved data, inconsistencies, etc) and make a soft sync (update data in the background) in the rest of cases.
Moreover syncing periodically means wasting user data in case that user doesn't use the app.
Ultimately, this turns your app into perfect candidate to be shut down by the system every once in a while.
Hope it helps. Keep us updated with your progress.
Related read: Optimizing downloads for efficient network access
Not an exact answer - but this approach would absolutely ruin the battery life. The whole point of sleep is to save battery power and this app would do nothing more than be an annoyance for customers no matter how useful the features are.
What I would suggest is that if it is absolutely necessary to connect to the internet while the app is not in use - set a trigger for when the phone wakes up. If it is not completely necessary it probably would be best to simply reconnect to the internet every time the app is opened.