DownloadManager - received ACTION_DOWNLOAD_COMPLETE, but status is STATUS_RUNNING - android

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.

Related

Android Oreo JobIntentService Keep running in background for Android 7 &below and crashing often in Android 8 & above

I have recently replaced all my service to foreground services and JobIntentService since there are some background execution limits (https://developer.android.com/about/versions/oreo/background) in oreo and above. As per documentation, JobIntentService acts like Intent Service for Android 7 & below and acts like JobScheduler for Android 8 & above. I have noticed there is an issue in new JobIntentService provided by Google.
Android 8 & above:
There is a crash happening continuously in android 8 and above. There was a ticket raised here mentioning about the same issue https://issuetracker.google.com/issues/63622293 and I have added a temp fix suggested by few geeks.
Android 7 & below:
JobIntentService which acts like Intent Service is not getting stopped once the work is done.
I have implemented JobIntentService within a service which triggers whenever some action is performed by a user.
Code
public class SampleJobIntentService extends FixedJobIntentService {
public static void postData(Context context, String data) {
Intent intent = new Intent(context, SampleJobIntentService.class);
intent.setAction(INITIAL_ACTION);
intent.putExtra(SAMPLE_ID, data);
SampleJobIntentService.enqueueWork(context,intent);
}
public static void enqueueWork(Context context, Intent work) {
SampleJobIntentService.enqueueWork(context, SampleJobIntentService.class, JOB_ID, work);
#Override
protected void onHandleWork(#NonNull Intent intent) {
if (intent != null) {
SampleRequest sampleRequest = requests.get(intent.getAction());
if (sampleRequest != null) {
try {
// perform some networking operations
} catch (Exception ex) {
Log.d("Error for intent ");
}
Log.i("send action ");
} else
Log.e("action not found for ");
}
}
}
To avoid the crash with JobIntentService, I took few references from https://issuetracker.google.com/issues/63622293
public abstract class FixedJobIntentService extends JobIntentService {
#Override
GenericWorkItem dequeueWork() {
try {
return new FixedGenericWorkItem(super.dequeueWork());
} catch (SecurityException ignored) {
doStopCurrentWork();
}
return null;
}
private class FixedGenericWorkItem implements GenericWorkItem {
final GenericWorkItem mGenericWorkItem;
FixedGenericWorkItem(GenericWorkItem genericWorkItem) {
mGenericWorkItem = genericWorkItem;
}
#Override
public Intent getIntent() {
if (mGenericWorkItem != null) {
return mGenericWorkItem.getIntent();
}
return null;
}
#Override
public void complete() {
try {
if (mGenericWorkItem != null) {
mGenericWorkItem.complete();
}
} catch (IllegalArgumentException ignored) {
doStopCurrentWork();
}
}
}
}
Well..., Its a lot big theory...!! It would not be able to put it all here. I will try my best which will make some your concepts clear.
I have already lost my 2 complete years in reading google documentations... Which are use-less... With no proper documentation and with no proper sample codes for its developers..!! So i mention this in every of my posts on stack-overflow, As it will help to save time of others..!!
It looks you are a good programmer; just need some hints to your posted question :
Hint-1 :
YOU :- I have recently replaced all my service to foreground services and
JobIntentService
foreground service :
If you need ALL THE TIME RUNNING PROCESS; WHICH WILL NEVER END... ONCE IT IS STARTED it is used in service which returns START_STICKY from its OnStartCommand. Which is again not advised to use as if you want to implement it at any cost ... then you will have to use a notification with setOngoing(true) Which end user would not be able to swipe away your notification, it will remain there forever....
Use of the foreground service :
There has been restrictions on receivers too; above Oreo onwards and you can not use all the receivers and intent actions by declaring it in manifest and by just making a receiver... I advice to just use BootComplete permission and use a single receiver which receives the boot_completed intent and calls a service if below O and calls a foreground service above O. Now from that foreground service you implement the runtime receivers for all and unregister it in Ondestroy methods. I have never found an official sample code for implementing runtime receiver and finally i have implemented it successfully by many months hard-work... Yes it was not a smart work due to google
When to use foreground service :
Only if you want to implement broadcast receivers.... If you do not want to implement any broadcast receivers; STAY AWAY.......
Hint-2 :
YOU :- I have recently replaced all my service to foreground services and
JobIntentService
** service has its quality of :**
Just doing a very tiny work... and just exit... it has to be exited by StopSelf()... Again, Services can cause data-loss if called multiple times... As same service thread can be run more than once... Again if you want a service to do a lot of work... Use START_STICKY... But again it is not recommended and i have suggested already, when to use it in Hint 1.
** Intentservice has its quality of :**
Doing a relatively long running tasks and it has property of execution serially only If you again and again calls the same intentService, then all calls will be kept in a queue and will be executed one by one after finishing one by one. Which is not the case in service as depicted above. It ends on its own... no need to end it by a developer..!!
** Unique Quality of all :**
Once they are crashed android can stop them calling in future without notifying you as it crashes the app. Need to be handled them with try-catch-exception to avoid crash. Again... If you are implementing threads within services then try-catch-exception will not save your application from being crashing...
** THEN WHAT THE HELL & HOW TO IMPLEMENT IT THEN :**
Use FireBaseJobScedular :-
Easy to use
Uses simple JobService
Can run longer or smaller time tasks... EVEN ALL THE TIME RUNNING TASK
EVEN SUPPORTED BY NON STANDARD COMPANIES like vivo, mi, oppo, one+3, ... which takes stock-android makes changes to it and gives names like FunTouchOs, ColorOs, OxygenOs
Just need to Do change battery settings to "Do not optimise this app"
Yes google supports it officially and recommends to use it
It Creates the instance of GooglePlyService and runs within it, And obviously non-standards companies too would not restrict google apps from being doing its tasks.
Works on Oreo .., Even i have tested it on Android P and works below Android version 5.0 as AlarmManager tasks.
Still i recommends to use minsdk above 16, target sdk 26 as if in case you wants to upload your app to google play it is compulsory now and that news would have been heard you. and compile sdk 26.
Just Bind Your JobService in manifest and use a single line permission of receive_boot_complete
Just schedule it ... And it will be started on every device in market from every manufacturer... even on cold boot and hot boot
It minimises a lot, lot and lot of code and you can focus on actual tasks.
Once task is finished you can return false to indicate task has been finished and it will end the JobService.
Why i am suggesting because i am CTO of a well-UNKNOwn company and has been experienced the problems caused by foreground service across the many types of android phone manufacturers... It is not the Apple and ios so we had to experienced it. Remain developer since past 18 years and i mostly codes today too... in all of the development projects and its development strategies are thought by me only.
Correct me ... too... As you have not mentioned what your tasks and
project is related to... and what you exactly wants to be done in a
foreground service and intent-service... Let me know..., It would be my pleasure to help you. It is a general theoretical answer rather than what you wants.... But for giving you actual answer i will need exact your project scope..
JobIntentService which acts like Intent Service is not getting stopped once the work is done
The issue is in your extended class FixedJobIntentService dequeueWork method.
Try changing it to something like below
GenericWorkItem superValue = super.dequeueWork();
if (superValue != null) {
return new FixedGenericWorkItem(superValue);
}
return null;
Looking at the JobIntentSerivce code, Work Items processor logic is below, i.e until there are no work items left in the queue all items are processed (i.e onHandleWork is called for each item)
while ((work = dequeueWork()) != null) {
if (DEBUG) Log.d(TAG, "Processing next work: " + work);
onHandleWork(work.getIntent());
if (DEBUG) Log.d(TAG, "Completing work: " + work);
work.complete();
}
Issue in your implementation is after processing the first work item, the super.dequeueWork() returns null, which you are not taking care of and just sending a new FixedGenericWorkItem object passing null value. You might observe that a null value is passed to your onHandleWork in your subsequent calls.
Hope this helps resolve your issue.
I think you just need this much of a code. Create a new Class MyJobIntentService and write this much of a code and call postData() to start your service.
public class MyJobIntentService extends JobIntentService {
public static void postData(Context context, String data) {
final Intent intent = new Intent(context, MyJobIntentService.class);
intent.setAction(INITIAL_ACTION);
intent.putExtra(SAMPLE_ID, data);
enqueueWork(context, MyJobIntentService.class, 1000, intent);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
Ln.d("Cancelled service");
super.onDestroy();
}
#Override
protected void onHandleWork(#NonNull Intent intent) {
if (intent != null) {
final SampleRequest sampleRequest = requests.get(intent.getAction());
if (sampleRequest != null) {
try {
// perform some networking operations
} catch (Exception ex) {
Log.d("Error for intent ");
}
Log.i("send action ");
} else {
Log.e("action not found for ");
}
}
}
}
And make sure to add your service in manifest file
<service
android:name="service.MyJobIntentService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE" />

Retrieving custom Future Android Ion by Koush

am new to Android. Sorry if this question is too simple. I have tried searching for a solution for weeks now. I am using Ion from https://github.com/koush/ion in my project. Uploads and downloads work well but when it comes to retrieving a specific custom Future after resuming the app I get stuck. I want to retrieve a single operation say an upload and stop it without affecting other uploads or vice versa for downloads.
The solution was to implement a broadcast receiver within creation of the process that returns a Future callback. This will allow you to cancel/stop its operation by just triggering the broadcast receiver
// set broadcast listeners
BroadcastReceiver broadcast = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String cancelled_msg_time = intent.getStringExtra("time");
// check if this download was cancelled
if (my_time.equals(cancelled_msg_time)) {
if (upload_started) {
// cancel asynctask
asycProcess.cancel(true);
}
// set flag as cancelled
cancelled = true;
}
}
};
// register the broadcast receiver to cancel downloads
IntentFilter intent_filter = new IntentFilter();
intent_filter.addAction("CANCEL_UPLOAD");
context.registerReceiver(broadcast, intent_filter);
Then your execution should somehow check status if it has been cancelled before proceeding
if (!cancelled) {
file_progress_handler = new FileProgressHandler();
// proceed with upload
}

How to force stop Intent Service in progress?

I have an intent service which downloads several gigabytes of videos. I have a "Stop" button, to stop the download if accidentally hit "Start" or whatever. I know this has been asked a couple of times but with no working answer for me.
I try to call stopService(), doesn't work. That just calls IntentService.OnDestroy().
I tried to call stopSelf() inside onDestroy, doesn't work either.
I tried to have something like a flag, but onHandleIntent doesn't get called if its already running, it waits till current work is finished and executes then. And even if this would have worked, I would have to have something like a giant if statement, that sucks
Is my only option really to rewrite it to a regular Service?
//Answer
public class SyncService extends IntentService {
boolean isCanceled;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent.hasExtra("action")) {
// Set the canceling flag
isCanceled= intent.getStringExtra("action").equals("cancel");
}
return super.onStartCommand(intent, flags, startId);
}
#Override
protected void onHandleIntent(Intent intent) {
// Clean up the possible queue
if (intent.hasExtra ("action")) {
boolean cancel = intent.getStringExtra ("action"). Equals ("cancel");
if (cancel) {
return;
}
}
...
Get your inputStream from HttpUrlConnection or whatever
...
while ((bytesRead = in.read(buffer)) > 0) {
if (isCanceled) {
isCanceled = false;
break;
}
...
}
}
}
And trigger it with
Intent intent = new Intent(context, SyncService.class);
intent.putExtra("action", "cancel");
context.startService(intent);
You have two separate issues, I would think:
How to stop the current download
How to stop queued up downloads, that should execute after the current one completes
The first one is going to have to be "something like a flag", that you check as you download the data. Otherwise, nothing is going to stop your download operation. If you are using a typical HttpUrlConnection recipe, you check that flag in your loop where you read from the HTTP InputStream and write to your FileOutputStream. You set that flag via a call to startService() with a particular Intent structure, identifying it as a "cancel" operation. You would need to override onStartCommand() in your IntentService, look at the Intent, use it to set the flag if it is the cancel Intent, or chain to the superclass for any other sort of Intent.
If you also may have other commands queued up (scenario #2), you would need to check that flag at the top of onHandleIntent() as well.
Given that you haven't posted how you're handling the video download exactly, this may not work (there would be some sort of loop inside onHandleIntent where the downloads are executed). You can use a static class variable inside the IntentService that holds the Stop/Start state of the download, so that it can be set by an Activity. Then, inside onHandleIntent, you would have to routinely check the state so it would know when to cancel the operations.

BroadcastReceiver onReceive() getting called twice

In my app, I am using DownloadManager, for downloading PDF's, which notifies the application via a BroadcastReceiver once the download is completed. My problem is the onReceive() method of BroadcastReceiver is getting called twice. The code is as follows:
In my list adapter, a for loop is run for downloading the selected pdf's. The downloading code is written in another class as follows:
public static void downloadCheat(final SherlockFragmentActivity activity, final String cheatName, String pathOnServer){
Request request = new Request(
Uri.parse(ApplicationConstants.CHEAT_DOWNLOAD_SERVER_URL
+ "/" + pathOnServer + cheatName + ".pdf"));
if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
request.setShowRunningNotification(true);
}
else {
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
}
final DownloadManager dm = (DownloadManager) activity
.getSystemService(Context.DOWNLOAD_SERVICE);
final long enqueue = dm.enqueue(request);
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
long i = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
System.out.println(i);
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
Query query = new Query();
query.setFilterById(enqueue);
Cursor c = dm.query(query);
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
}
}
//create custom notification
}
}
};
activity.registerReceiver(receiver, new IntentFilter(
DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
I am trying to add notifications for each pdf download. This works perfectly with download managers own internal notification for HoneyComb and above versions but for GingerBread it does not work and hence I have to push my own custom notification. So I need to determine the exact time when the pdf is downloaded completely. As of now I am able to push my own custom notification but the notifications come twice for every pdf download (As onReceive() is getting twice for each pdf). Can anyone please explain why onReceive() is called twice(for every pdf). Is there any workaround for this? Also could someone please recommend how the broadcast receiver can be un-registered in my case here?The above code is not a part of Activity, so I am not sure how to unregister the receiver.
Thanks for stopping by and reading the post.
You normally register receivers onResume() and unregister in onPause(). Are you doing so?
I think I may have originally misunderstood what you were trying to do. You should be able to call unregisterReceiver from onReceive. Does this do what you want?
You said you are downloading two pdfs. I only see one Download Request in your method. So I assume what you did is to call that method twice. If that is true, you actually registered two receiver to receive the ACTION_DOWNLOAD_COMPLETE event.
You only need to register once in onCreate or onStart or some methods else. For notification purpose, you can use intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) to get the download id, the id is unique for each download. You can use this id to get the information about the downloaded file and make your file-specific notification.
This looks like the same bug that is described here:
https://code.google.com/p/android/issues/detail?id=18462

Android: intentservice, how abort or skip a task in the handleintent queue

i have an activity ("ApplicationActivity") that call an intent service ("DownloadService")
The intentService download files from internet in background, but i want to be able to abort a specific download.........
So let's say that i put 5 files in queue: file 1,2,3,4,5
The intent service start downloading the number 1, then the second and so on....
1) Is there a way to say to the intent service abort what you are doing at the moment in the method handle event (in this case downloading file 1) and start downloading the next one?
2)Is it possible to remove element from the queue, so for example while is downloading file 1, remove the file 4 from the queue so that after the number 3 it goes straight to the 5?
Shortly, i need a way to comunicate with the queue to perform these 2 simple operations, but i didn't find nothing usefull on internet :(
Tnx
Thanks #user280560, I found a solution based on your comment :)
just to give a more specific example, where I wanted to clear the queue in certain cases.
First I copied the IntentService.java source from here to my project (no need to change names, you can keep IntentService.java, just import yours and not android's). Then I added this
public void clearQueue() {
Debug.PrintInfo(TAG, "All requests removed from queue");
mServiceHandler.removeMessages(0);
}
to my IntentService source.
Now, from my service that extends IntentService, I wanted to clear the queue when a certain action (login) was passed to the service, so I override the onStartMethod, like this:
#Override
public void onStart(Intent intent, int startId) {
if(intent.getAction().equals(ACTION_LOGIN)) {
//Login clears messages in the queue
clearQueue();
}
super.onStart(intent, startId);
}
Works like a charm :)
Hope it helps someone...
I create my own MyIntentService class copying the original one that is pretty short and modifying methods for my own purpose........in particular to dequeue an element you can use methods of ServiceHandler in my case
mServiceHandler.removeMessages(appId);
that Remove any pending posts of messages with a certain code 'what' that are in the message queue, this means that you have to label each message you add in the queue adding an identifier inside the "what" field of each message.....for example
public void onStart(Intent intent, int startId)
{
super.onStart(intent, startId);
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
msg.what = intent.getIntExtra("appId", 0); \\parameters that come from the outside
Extend the IntentService class and declare a list for cancelled item on it and whenever you want to cancel something add it to this list. finally before handling your intent make sure it has not been cancelled!
public class MyDownloadService extends IntentService {
private static List<String> canceledUrl;
public static void cancelDownload(String url){
canceledUrl.add(url);
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final String url = intent.getStringExtra(EXTRA_URL);
if(!canceledUrl.contains(url)) //if download has not been canceled already
downloadFile(url);
else
canceledUrl.remove(url);
}
}
}
I know this code works because i tested it before, But i'm not sure it's a right way to do it!
You can bind to the intentservice and create a method to cancel or de queu a download.
Here is a quick tutorial what you might need
As said by #JeffreyBlattman above, it is better to play safe by assigning your own "what" value to the message like this
#Override
public void onStart(#Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
msg.what = 0;
mServiceHandler.sendMessage(msg);
}
and clear the queue like mServiceHandler.removeMessages(0).
Hope that helps.

Categories

Resources