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
Related
I have a BroadcastReceiver which listens for a download complete action so it opens a file when download completes.
My issue with this is that the user may download a file and then minimize the screen which then unregisters the BroadcastReceiver, so the file isn't opened automatically when download completes.
How can I set up a stand alone BroadcastReceiver so that the image is opened even when the app is stopped/destroyed. I can't figure out how to make it listen to a download complete action.
private BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
long referenceId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (downloadReference == referenceId) {
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(referenceId);
Cursor cursor = downloadManager.query(query);
if (cursor.moveToFirst()) {
int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
Intent intentpic = new Intent();
intentpic.setAction(android.content.Intent.ACTION_VIEW);
intentpic.setDataAndType(Uri.fromFile(getActivity().getExternalFilesDir("/update/asd.jpg")), "image/*");
startActivity(intentpic);
if(receiverRegistered) {
getActivity().unregisterReceiver(downloadReceiver);
}
}
}
cursor.close();
Log.i(TAG, "Downloading of data just finished");
}
}
};
You shouldn't start activities when your app is in background. It is okay that your receiver is unregistered when user leaves the app.
Instead, store the information that the image is downloaded and then check it when the app is resumed. I don't know your use case, but, for example you can store this information to SharedPreferences and check it when the app is resumed. Then you can decide whether to start a new activity or not.
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
}
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've created a BroadcastReceiver to receive ACTION_DOWNLOAD_COMPLETE when my app starts downloading something using DownloadManager. As I want to capture ACTION_DOWNLOAD_COMPLETE only when downloading is started from my app, I've used LocalBroadcastManager.
But onReceiver is not being called at all. DownloadManager app shows that download is complete but onReceive is not triggered. When I use registerReceiver it works as expected. But this would let app being notified even if downloading is started by some other app. So LocalBroadcastManager is desired.
MainActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
downloadReceiver = new DownloadReceiver();
LocalBroadcastManager.getInstance(this).registerReceiver(downloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
if(FileHelper.isStorageAvailable()) {
DownloadManager.Request request = new DownloadManager.Request(Uri.parse("http://example.com/image.jpg"));
downloadManager.enqueue(request);
}
}
#Override
protected void onPause() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(downloadReceiver);
super.onPause();
}
DownloadReciever
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(action)) {
long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = downloadManager.query(query);
if (c.moveToFirst()) {
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS);
if (DownloadManager.STATUS_SUCCESSFUL == c.getInt(columnIndex)) {
String title = c.getString(c.getColumnIndex(DownloadManager.COLUMN_TITLE));
Toast.makeText(context, title, Toast.LENGTH_SHORT).show();
}
}
}
}
It simply don't call onRecieve as it should. Point out if I'm doing something wrong here. Been stuck here for quite time now. I can't use registerReceiver as I need to track download complete action only if my app starts downloading.
As I want to capture ACTION_DOWNLOAD_COMPLETE only when downloading is started from my app, I've used LocalBroadcastManager.
That is not going to work. DownloadManager does the download in a separate process, and it will use a system broadcast. The only broadcasts that you can receive via LocalBraodcastManager are the ones that you broadcast via LocalBroadcastManager.
I am using download manager in my activity to download a file. I want to receive the ACTION_DOWNLOAD_COMPLETE intent using a broadcast receiver and match the id of the download request with the id in the intent set by download manager to check if it is the same download.
There are two options:
1) receive the intent in the activity by making a intent filter and registering a broadcast receiver. Problem here is that the broadcast receiver exists within the activity and gets destroyed with it. So if the download doesn't get completed before the activity is destroyed there is no way to receive the intent.
this.downloadId = manager.enqueue(request);
downloadID is compared with the EXTRA_DOWNLOAD_ID of the intent in onReceive method to check if it is the same download. downloadId variable is accessible inside the onReceive method.
2) receive the broadcast via manifest. Problem here is that the onReceive method does not have access to the downloadId variable set inside the activity. So I cannot check if the download is the one I need.
Am I doing it wrong? Basically I want to know if the download was completed successfully or not even if the activity/service where the download started is destroyed.
I hope my question is clear enough.
surely your first option is not good because you must do some work as soon as your download is completed so i suggest you use the second approach and store the downloadid in sharedpreferance or in a file and in onRecieve method check that by stored value. you can also use database but if you want to store just one variable (downloadid) in it, it is not a good idea because you must create table and ...
so let's see some code:
in your activity after saving your downloadid to a variable put it in a SharedPreferences file like:
SharedPreferences settings = getSharedPreferences("DownloadIDS", 0);
SharedPreferences.Editor editor = settings.edit();
editor.putLong("savedDownloadIds", your download id);
editor.commit();
and in onReceive:
#Override
public void onReceive(Context context, Intent intent) {
SharedPreferences downloadids = context.getSharedPreferences("DownloadIDS", 0);
long savedDownloadIds = downloadids.getLong("savedDownloadIds", 0);
Bundle extras = intent.getExtras();
DownloadManager.Query q = new DownloadManager.Query();
Long downloaded_id = extras.getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
if(savedDownloadIds == downloaded_id ){ // so it is my file that has been completed
q.setFilterById(downloaded_id);
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
Cursor c = manager.query(q);
if (c.moveToFirst()) {
int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS));
if (status == DownloadManager.STATUS_SUCCESSFUL) {
// do any thing here
}
}
c.close();
}
}
//Fetching the download id received with the broadcast
long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);