9

The problem

If the cache directory is full, trying to execute a simple request will fail without sending the DownloadManager.ACTION_DOWNLOAD_COMPLETE broadcast.

Note: The problem is general but can be mostly reproduced on low-end devices with limited cache (/data/data/com.android.providers.downloads/cache) size.

The code

The receiver is configured correctly, as I'm still getting the broadcast when the operation succeeds and fails for other reasons.

    DownloadManager.Request request = new DownloadManager.Request(Uri.parse("http://www.apkmirror.com/wp-content/themes/APKMirror/download.php?id=44753"));

    request.setTitle("Facebook");

    DownloadManager downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);

    downloadManager.enqueue(request);

The desired solution

I'm interested in a solution to the specific issue, or more info if you have encountered it as well.
I'm not looking for a solution that will require me to stop using the DownloadManager or add the WRITE_EXTERNAL_STORAGE permission.

Logs

When the cache is getting full and lastly when it can hold no more you can observe the following log entrance (filtered with downloadmanager)

11-08 08:47:06.079 830-14261/? I/DownloadManager: Download 135 starting
11-08 08:47:06.989 830-14261/? W/DownloadManager: Downloads data dir: /data/data/com.android.providers.downloads/cache is running low on space. space available (in bytes): -6994124
11-08 08:47:06.999 830-14261/? I/DownloadManager: discardPurgeableFiles: destination = 2, targetBytes = 10485760
11-08 08:47:06.999 830-14261/? I/DownloadManager: Purged files, freed 0 for 10485760 requested
11-08 08:47:07.309 830-14261/? W/DownloadManager: Aborting request for download 135: not enough free space in the filesystem rooted at: /data/data/com.android.providers.downloads/cache and unable to free any more
11-08 08:47:07.319 830-14261/? I/DownloadManager: Download 135 finished with status INSUFFICIENT_SPACE_ERROR

Here is a DEMO PROJECT that can demonstrate the issue. Remember that the cache directory has to be full by that point (by non-purgeable items, which from my experience basically means, aborted downloads)

Alex.F
  • 5,648
  • 3
  • 39
  • 63
  • Thanks @Yvette I'll try to expand on that, ideally I want to be notified that the download failed. – Alex.F Nov 08 '15 at 15:32

3 Answers3

3

Since DownloadManager is a system ContentProvider in essence, you can register your own ContentObserver to it, So when the download provider updates, it'll choose to notify the observer in the case of INSUFFICIENT_SPACE.

final DownloadManager downloadManager = (DownloadManager)context.getSystemService(Context.DOWNLOAD_SERVICE);
        context.getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"),
                true, new ContentObserver(null) {
                    @Override
                    public void onChange(boolean selfChange) {
                        super.onChange(selfChange);
                        Cursor localCursor = downloadManager.query(
                                new DownloadManager.Query());
                        if (localCursor.getCount() == 0) {
                            localCursor.close();
                        }
                        localCursor.moveToFirst();
                        do {
                            if ((localCursor.getInt(localCursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) & DownloadManager.STATUS_FAILED )!=0) {
                                // Download failed, go see why
                                if (localCursor.getInt(localCursor.getColumnIndex(DownloadManager.COLUMN_REASON)) == DownloadManager.ERROR_INSUFFICIENT_SPACE){
                                    Log.w("DownloadStatus", " Download failed with ERROR_INSUFFICIENT_SPACE");
                                }
                            }
                        }while (localCursor.moveToNext());
                    }
                });

Note that don't set query filter with status DownloadManager.STATUS_FAILED, as DownloadManager weirdly only consider status between 400 and 600 to be failed status, but INSUFFICIENT_SPACE has error code 198...

android.app.DownloadManager.Request:

Cursor runQuery(ContentResolver resolver, String[] projection, Uri baseUri) {
 .....
 if ((mStatusFlags & STATUS_FAILED) != 0) {
                    parts.add("(" + statusClause(">=", 400)
                              + " AND " + statusClause("<", 600) + ")");
                }
}
Gracie
  • 190
  • 1
  • 10
0

You can receive INSUFFICIENT_SPACE_ERROR broadcast by getting reason of DownloadManager.STATUS_FAILED by following code

private void DownloadStatus(Cursor cursor, long DownloadId){

        //column for download  status
        int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
        int status = cursor.getInt(columnIndex);
        //column for reason code if the download failed or paused
        int columnReason = cursor.getColumnIndex(DownloadManager.COLUMN_REASON);
        int reason = cursor.getInt(columnReason);
        //get the download filename
        int filenameIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME);
        String filename = cursor.getString(filenameIndex);

        String statusText = "";
        String reasonText = "";

        switch(status){
            case DownloadManager.STATUS_FAILED:
                statusText = "STATUS_FAILED";
                switch(reason){
                    case DownloadManager.ERROR_CANNOT_RESUME:
                        reasonText = "ERROR_CANNOT_RESUME";
                        break;
                    case DownloadManager.ERROR_DEVICE_NOT_FOUND:
                        reasonText = "ERROR_DEVICE_NOT_FOUND";
                        break;
                    case DownloadManager.ERROR_FILE_ALREADY_EXISTS:
                        reasonText = "ERROR_FILE_ALREADY_EXISTS";
                        break;
                    case DownloadManager.ERROR_FILE_ERROR:
                        reasonText = "ERROR_FILE_ERROR";
                        break;
                    case DownloadManager.ERROR_HTTP_DATA_ERROR:
                        reasonText = "ERROR_HTTP_DATA_ERROR";
                        break;
                    case DownloadManager.ERROR_INSUFFICIENT_SPACE:
                        reasonText = "ERROR_INSUFFICIENT_SPACE";
                        break;
                    case DownloadManager.ERROR_TOO_MANY_REDIRECTS:
                        reasonText = "ERROR_TOO_MANY_REDIRECTS";
                        break;
                    case DownloadManager.ERROR_UNHANDLED_HTTP_CODE:
                        reasonText = "ERROR_UNHANDLED_HTTP_CODE";
                        break;
                    case DownloadManager.ERROR_UNKNOWN:
                        reasonText = "ERROR_UNKNOWN";
                        break;
                }
                break;
            case DownloadManager.STATUS_PAUSED:
                statusText = "STATUS_PAUSED";
                switch(reason){
                    case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
                        reasonText = "PAUSED_QUEUED_FOR_WIFI";
                        break;
                    case DownloadManager.PAUSED_UNKNOWN:
                        reasonText = "PAUSED_UNKNOWN";
                        break;
                    case DownloadManager.PAUSED_WAITING_FOR_NETWORK:
                        reasonText = "PAUSED_WAITING_FOR_NETWORK";
                        break;
                    case DownloadManager.PAUSED_WAITING_TO_RETRY:
                        reasonText = "PAUSED_WAITING_TO_RETRY";
                        break;
                }
                break;
            case DownloadManager.STATUS_PENDING:
                statusText = "STATUS_PENDING";
                break;
            case DownloadManager.STATUS_RUNNING:
                statusText = "STATUS_RUNNING";
                break;
            case DownloadManager.STATUS_SUCCESSFUL:
                statusText = "STATUS_SUCCESSFUL";
                reasonText = "Filename:\n" + filename;
                break;
        }
}
Meesam Abbas
  • 126
  • 7
  • 1
    If I'm not getting a broadcast with`DownloadManager.ACTION_DOWNLOAD_COMPLETE` how would I know when to check the content provider? Can you be more specific as to what the intent filter would look like for the broadcast? – Alex.F Sep 04 '16 at 08:02
  • @Alex.F `DownloadManager.STATUS_FAILED` – Meesam Abbas Jan 12 '17 at 07:36
  • 1
    @MeesamAbbas We can only check the status on onReceive method of BroadcastReceiver. But the real problem is onReceive method is not called when there is no space. We can just see in the log as *Finished with status INSUFFICIENT_SPACE_ERROR*. – Thamilan S Mar 27 '17 at 06:39
  • @MeesamAbbas I hope OP clearly mentioned this in his question – Thamilan S Mar 27 '17 at 06:41
0

I think Gracie solution needs some improvements like unregistering and checking downloads ids.

If you don't use any restriction for the request you can try to check file size after enqueuing - its not perfect solution:

private static void checkFileSize(Context context, DownloadManager downloadManager, long myDownloadReference, DownloadInfo downloadInfo) {
    DownloadManager.Query query = new DownloadManager.Query();
    query.setFilterById(myDownloadReference);

    Handler handler = new Handler();
    handler.postDelayed(() -> {

        Cursor cursor = downloadManager.query(query);
        if (!cursor.moveToFirst()) {
            KLog.i("download list is empty");
            return;
        }

        int size = cursor.getInt(cursor
                .getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES));
        int status = cursor.getInt(cursor
                .getColumnIndex(DownloadManager.COLUMN_STATUS));
        int reason = cursor.getInt(cursor
                .getColumnIndex(DownloadManager.COLUMN_REASON));

        cursor.close();

        if (reason == DownloadManager.ERROR_INSUFFICIENT_SPACE)
            DownloadedReceiver.handleInsufficientSpace(context, downloadInfo);

    }, 1500);
}
Mateusz Kaflowski
  • 2,221
  • 1
  • 29
  • 35