Statistics
| Branch: | Tag: | Revision:

fdroidclient / F-Droid / src / org / fdroid / fdroid / net / AsyncDownloaderFromAndroid.java @ 69ecaf02

History | View | Annotate | Download (10.6 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 extends AsyncDownloader {
27
    private final Context context;
28
    private final DownloadManager dm;
29
    private File localFile;
30
    private String remoteAddress;
31
    private String appName;
32
    private String appId;
33
    private Listener listener;
34

    
35
    private long downloadId = -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
     * @param listener
45
     */
46
    public AsyncDownloaderFromAndroid(Context context, Listener listener, String appName, String appId, String remoteAddress, File localFile) {
47
        super(null, listener);
48
        this.context = context;
49
        this.appName = appName;
50
        this.appId = appId;
51
        this.remoteAddress = remoteAddress;
52
        this.listener = listener;
53
        this.localFile = localFile;
54

    
55
        if (appName == null || appName.trim().length() == 0) {
56
            this.appName = remoteAddress;
57
        }
58

    
59
        dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
60
    }
61

    
62
    @Override
63
    public void download() {
64
        // Check if the download is complete
65
        if ((downloadId = isDownloadComplete(context, appId)) > 0) {
66
            // clear the notification
67
            dm.remove(downloadId);
68

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

    
80
        // Check if the download is still in progress
81
        if (downloadId < 0) {
82
            downloadId = isDownloading(context, appId);
83
        }
84

    
85
        // Start a new download
86
        if (downloadId < 0) {
87
            // set up download request
88
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress));
89
            request.setTitle(appName);
90
            request.setDescription(appId); // we will retrieve this later from the description field
91
            this.downloadId = dm.enqueue(request);
92
        }
93

    
94
        context.registerReceiver(receiver,
95
                new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
96
    }
97

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

    
110
        try {
111
            while ((count = is.read(buffer, 0, buffer.length)) > 0) {
112
                os.write(buffer, 0, count);
113
            }
114
        } finally {
115
            os.close();
116
            is.close();
117
        }
118
    }
119

    
120
    @Override
121
    public int getBytesRead() {
122
        if (downloadId < 0) return 0;
123

    
124
        DownloadManager.Query query = new DownloadManager.Query();
125
        query.setFilterById(downloadId);
126
        Cursor c = dm.query(query);
127

    
128
        try {
129
            if (c.moveToFirst()) {
130
                // we use the description column to store the app id
131
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
132
                return c.getInt(columnIndex);
133
            }
134
        } finally {
135
            c.close();
136
        }
137

    
138
        return 0;
139
    }
140

    
141
    @Override
142
    public int getTotalBytes() {
143
        if (downloadId < 0) return 0;
144

    
145
        DownloadManager.Query query = new DownloadManager.Query();
146
        query.setFilterById(downloadId);
147
        Cursor c = dm.query(query);
148

    
149
        try {
150
            if (c.moveToFirst()) {
151
                // we use the description column to store the app id
152
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
153
                return c.getInt(columnIndex);
154
            }
155
        } finally {
156
            c.close();
157
        }
158

    
159
        return 0;
160
    }
161

    
162
    @Override
163
    public void attemptCancel(boolean userRequested) {
164
        try {
165
            context.unregisterReceiver(receiver);
166
        } catch (Exception e) {
167
            // ignore if receiver already unregistered
168
        }
169

    
170
        if (userRequested && downloadId >= 0) {
171
            dm.remove(downloadId);
172
        }
173
    }
174

    
175
    /**
176
     * Extract the appId from a given download id.
177
     * @param context
178
     * @param downloadId
179
     * @return - appId or null if not found
180
     */
181
    public static String getAppId(Context context, long downloadId) {
182
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
183
        DownloadManager.Query query = new DownloadManager.Query();
184
        query.setFilterById(downloadId);
185
        Cursor c = dm.query(query);
186

    
187
        try {
188
            if (c.moveToFirst()) {
189
                // we use the description column to store the app id
190
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
191
                return c.getString(columnIndex);
192
            }
193
        } finally {
194
            c.close();
195
        }
196

    
197
        return null;
198
    }
199

    
200
    /**
201
     * Extract the download title from a given download id.
202
     * @param context
203
     * @param downloadId
204
     * @return - title or null if not found
205
     */
206
    public static String getDownloadTitle(Context context, long downloadId) {
207
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
208
        DownloadManager.Query query = new DownloadManager.Query();
209
        query.setFilterById(downloadId);
210
        Cursor c = dm.query(query);
211

    
212
        try {
213
            if (c.moveToFirst()) {
214
                // we use the description column to store the app id
215
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
216
                return c.getString(columnIndex);
217
            }
218
        } finally {
219
            c.close();
220
        }
221

    
222
        return null;
223
    }
224

    
225
    /**
226
     * Get the downloadId from an Intent sent by the DownloadManagerReceiver
227
     * @param intent
228
     * @return
229
     */
230
    public static long getDownloadId(Intent intent) {
231
        if (intent != null) {
232
            if (intent.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) {
233
                // we have been passed a DownloadManager download id, so get the app id for it
234
                return intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
235
            }
236

    
237
            if (intent.hasExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS)) {
238
                // we have been passed multiple download id's - just return the first one
239
                long[] downloadIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
240
                if (downloadIds != null && downloadIds.length > 0) {
241
                    return downloadIds[0];
242
                }
243
            }
244
        }
245

    
246
        return -1;
247
    }
248

    
249
    /**
250
     * Check if a download is running for the app
251
     * @param context
252
     * @param appId
253
     * @return -1 if not downloading, else the downloadId
254
     */
255
    public static long isDownloading(Context context, String appId) {
256
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
257
        DownloadManager.Query query = new DownloadManager.Query();
258
        Cursor c = dm.query(query);
259
        int columnAppId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
260
        int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
261

    
262
        try {
263
            while (c.moveToNext()) {
264
                if (appId.equals(c.getString(columnAppId))) {
265
                    return c.getLong(columnId);
266
                }
267
            }
268
        } finally {
269
            c.close();
270
        }
271

    
272
        return -1;
273
    }
274

    
275
    /**
276
     * Check if a download for an app is complete.
277
     * @param context
278
     * @param appId
279
     * @return -1 if download is not complete, otherwise the download id
280
     */
281
    public static long isDownloadComplete(Context context, String appId) {
282
        DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
283
        DownloadManager.Query query = new DownloadManager.Query();
284
        query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
285
        Cursor c = dm.query(query);
286
        int columnAppId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
287
        int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
288

    
289
        try {
290
            while (c.moveToNext()) {
291
                if (appId.equals(c.getString(columnAppId))) {
292
                    return c.getLong(columnId);
293
                }
294
            }
295
        } finally {
296
            c.close();
297
        }
298

    
299
        return -1;
300
    }
301

    
302
    /**
303
     * Broadcast receiver to listen for ACTION_DOWNLOAD_COMPLETE broadcasts
304
     */
305
    BroadcastReceiver receiver = new BroadcastReceiver() {
306
        @Override
307
        public void onReceive(Context context, Intent intent) {
308
            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
309
                long dId = getDownloadId(intent);
310
                String appId = getAppId(context, dId);
311
                if (listener != null && dId == downloadId && appId != null) {
312
                    // our current download has just completed, so let's throw up install dialog
313
                    // immediately
314
                    try {
315
                        context.unregisterReceiver(receiver);
316
                    } catch (Exception e) {
317
                        // ignore if receiver already unregistered
318
                    }
319

    
320
                    // call download() to copy the file and start the installer
321
                    download();
322
                }
323
            }
324
        }
325
    };
326
}