Statistics
| Branch: | Tag: | Revision:

fdroidclient / F-Droid / src / org / fdroid / fdroid / net / AsyncDownloaderFromAndroid.java @ 7b773f94

History | View | Annotate | Download (10.7 KB)

1
package org.fdroid.fdroid.net;
2

    
3
import android.annotation.TargetApi;
4
import android.app.DownloadManager;
5
import android.content.BroadcastReceiver;
6
import android.content.Context;
7
import android.content.Intent;
8
import android.content.IntentFilter;
9
import android.database.Cursor;
10
import android.net.Uri;
11
import android.os.Build;
12
import android.os.ParcelFileDescriptor;
13

    
14
import java.io.File;
15
import java.io.FileDescriptor;
16
import java.io.FileInputStream;
17
import java.io.FileOutputStream;
18
import java.io.IOException;
19
import java.io.InputStream;
20
import java.io.OutputStream;
21

    
22
/**
23
 * A downloader that uses Android's DownloadManager to perform a download.
24
 */
25
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
26
public class AsyncDownloaderFromAndroid implements AsyncDownloader {
27
    private final Context context;
28
    private final DownloadManager dm;
29
    private File localFile;
30
    private String remoteAddress;
31
    private String downloadTitle;
32
    private String uniqueDownloadId;
33
    private Listener listener;
34

    
35
    private long downloadManagerId = -1;
36

    
37
    /**
38
     * Normally the listener would be provided using a setListener method.
39
     * However for the purposes of this async downloader, it doesn't make
40
     * sense to have an async task without any way to notify the outside
41
     * world about completion. Therefore, we require the listener as a
42
     * parameter to the constructor.
43
     */
44
    public AsyncDownloaderFromAndroid(Context context, Listener listener, String downloadTitle, String downloadId, String remoteAddress, File localFile) {
45
        this.context = context;
46
        this.downloadTitle = downloadTitle;
47
        this.uniqueDownloadId = downloadId;
48
        this.remoteAddress = remoteAddress;
49
        this.listener = listener;
50
        this.localFile = localFile;
51

    
52
        if (downloadTitle == null || downloadTitle.trim().length() == 0) {
53
            this.downloadTitle = remoteAddress;
54
        }
55

    
56
        dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
57
    }
58

    
59
    @Override
60
    public void download() {
61
        // Check if the download is complete
62
        if ((downloadManagerId = isDownloadComplete(context, uniqueDownloadId)) > 0) {
63
            // clear the notification
64
            dm.remove(downloadManagerId);
65

    
66
            try {
67
                // write the downloaded file to the expected location
68
                ParcelFileDescriptor fd = dm.openDownloadedFile(downloadManagerId);
69
                copyFile(fd.getFileDescriptor(), localFile);
70
                listener.onDownloadComplete();
71
            } catch (IOException e) {
72
                listener.onErrorDownloading(e.getLocalizedMessage());
73
            }
74
            return;
75
        }
76

    
77
        // Check if the download is still in progress
78
        if (downloadManagerId < 0) {
79
            downloadManagerId = isDownloading(context, uniqueDownloadId);
80
        }
81

    
82
        // Start a new download
83
        if (downloadManagerId < 0) {
84
            // set up download request
85
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress));
86
            request.setTitle(downloadTitle);
87
            request.setDescription(uniqueDownloadId); // we will retrieve this later from the description field
88
            this.downloadManagerId = dm.enqueue(request);
89
        }
90

    
91
        context.registerReceiver(receiver,
92
                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
93
    }
94

    
95
    /**
96
     * Copy input file to output file
97
     * @throws IOException
98
     */
99
    private void copyFile(FileDescriptor inputFile, File outputFile) throws IOException {
100
        InputStream is = new FileInputStream(inputFile);
101
        OutputStream os = new FileOutputStream(outputFile);
102
        byte[] buffer = new byte[1024];
103
        int count = 0;
104

    
105
        try {
106
            while ((count = is.read(buffer, 0, buffer.length)) > 0) {
107
                os.write(buffer, 0, count);
108
            }
109
        } finally {
110
            os.close();
111
            is.close();
112
        }
113
    }
114

    
115
    @Override
116
    public int getBytesRead() {
117
        if (downloadManagerId < 0) return 0;
118

    
119
        DownloadManager.Query query = new DownloadManager.Query();
120
        query.setFilterById(downloadManagerId);
121
        Cursor c = dm.query(query);
122

    
123
        try {
124
            if (c.moveToFirst()) {
125
                // we use the description column to store the unique id of this download
126
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
127
                return c.getInt(columnIndex);
128
            }
129
        } finally {
130
            c.close();
131
        }
132

    
133
        return 0;
134
    }
135

    
136
    @Override
137
    public int getTotalBytes() {
138
        if (downloadManagerId < 0) return 0;
139

    
140
        DownloadManager.Query query = new DownloadManager.Query();
141
        query.setFilterById(downloadManagerId);
142
        Cursor c = dm.query(query);
143

    
144
        try {
145
            if (c.moveToFirst()) {
146
                // we use the description column to store the unique id for this download
147
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
148
                return c.getInt(columnIndex);
149
            }
150
        } finally {
151
            c.close();
152
        }
153

    
154
        return 0;
155
    }
156

    
157
    @Override
158
    public void attemptCancel(boolean userRequested) {
159
        try {
160
            context.unregisterReceiver(receiver);
161
        } catch (Exception e) {
162
            // ignore if receiver already unregistered
163
        }
164

    
165
        if (userRequested && downloadManagerId >= 0) {
166
            dm.remove(downloadManagerId);
167
        }
168
    }
169

    
170
    /**
171
     * Extract the uniqueDownloadId from a given download id.
172
     * @return - uniqueDownloadId or null if not found
173
     */
174
    public static String getDownloadId(Context context, long downloadId) {
175
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
176
        DownloadManager.Query query = new DownloadManager.Query();
177
        query.setFilterById(downloadId);
178
        Cursor c = dm.query(query);
179

    
180
        try {
181
            if (c.moveToFirst()) {
182
                // we use the description column to store the unique id for this download
183
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
184
                return c.getString(columnIndex);
185
            }
186
        } finally {
187
            c.close();
188
        }
189

    
190
        return null;
191
    }
192

    
193
    /**
194
     * Extract the download title from a given download id.
195
     * @return - title or null if not found
196
     */
197
    public static String getDownloadTitle(Context context, long downloadId) {
198
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
199
        DownloadManager.Query query = new DownloadManager.Query();
200
        query.setFilterById(downloadId);
201
        Cursor c = dm.query(query);
202

    
203
        try {
204
            if (c.moveToFirst()) {
205
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
206
                return c.getString(columnIndex);
207
            }
208
        } finally {
209
            c.close();
210
        }
211

    
212
        return null;
213
    }
214

    
215
    /**
216
     * Get the downloadManagerId from an Intent sent by the DownloadManagerReceiver
217
     */
218
    public static long getDownloadId(Intent intent) {
219
        if (intent != null) {
220
            if (intent.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) {
221
                // we have been passed a DownloadManager download id, so get the unique id for that download
222
                return intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
223
            }
224

    
225
            if (intent.hasExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS)) {
226
                // we have been passed multiple download id's - just return the first one
227
                long[] downloadIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
228
                if (downloadIds != null && downloadIds.length > 0) {
229
                    return downloadIds[0];
230
                }
231
            }
232
        }
233

    
234
        return -1;
235
    }
236

    
237
    /**
238
     * Check if a download is running for the specified id
239
     * @return -1 if not downloading, else the id from the Android download manager
240
     */
241
    public static long isDownloading(Context context, String uniqueDownloadId) {
242
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
243
        DownloadManager.Query query = new DownloadManager.Query();
244
        Cursor c = dm.query(query);
245
        int columnUniqueDownloadId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
246
        int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
247

    
248
        try {
249
            while (c.moveToNext()) {
250
                if (uniqueDownloadId.equals(c.getString(columnUniqueDownloadId))) {
251
                    return c.getLong(columnId);
252
                }
253
            }
254
        } finally {
255
            c.close();
256
        }
257

    
258
        return -1;
259
    }
260

    
261
    /**
262
     * Check if a specific download is complete.
263
     * @return -1 if download is not complete, otherwise the download id
264
     */
265
    public static long isDownloadComplete(Context context, String uniqueDownloadId) {
266
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
267
        DownloadManager.Query query = new DownloadManager.Query();
268
        query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
269
        Cursor c = dm.query(query);
270
        int columnUniqueDownloadId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
271
        int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
272

    
273
        try {
274
            while (c.moveToNext()) {
275
                if (uniqueDownloadId.equals(c.getString(columnUniqueDownloadId))) {
276
                    return c.getLong(columnId);
277
                }
278
            }
279
        } finally {
280
            c.close();
281
        }
282

    
283
        return -1;
284
    }
285

    
286
    /**
287
     * Broadcast receiver to listen for ACTION_DOWNLOAD_COMPLETE broadcasts
288
     */
289
    BroadcastReceiver receiver = new BroadcastReceiver() {
290
        @Override
291
        public void onReceive(Context context, Intent intent) {
292
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
293
                long dId = getDownloadId(intent);
294
                String downloadId = getDownloadId(context, dId);
295
                if (listener != null && dId == AsyncDownloaderFromAndroid.this.downloadManagerId && downloadId != null) {
296
                    // our current download has just completed, so let's throw up install dialog
297
                    // immediately
298
                    try {
299
                        context.unregisterReceiver(receiver);
300
                    } catch (Exception e) {
301
                        // ignore if receiver already unregistered
302
                    }
303

    
304
                    // call download() to copy the file and start the installer
305
                    download();
306
                }
307
            }
308
        }
309
    };
310
}