Statistics
| Branch: | Tag: | Revision:

fdroidclient / F-Droid / src / org / fdroid / fdroid / net / AsyncDownloaderFromAndroid.java @ 0a9941d9

History | View | Annotate | Download (10.3 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 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
    public AsyncDownloaderFromAndroid(Context context, Listener listener, String appName, String appId, String remoteAddress, File localFile) {
45
        this.context = context;
46
        this.appName = appName;
47
        this.appId = appId;
48
        this.remoteAddress = remoteAddress;
49
        this.listener = listener;
50
        this.localFile = localFile;
51

    
52
        if (appName == null || appName.trim().length() == 0) {
53
            this.appName = 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 ((downloadId = isDownloadComplete(context, appId)) > 0) {
63
            // clear the notification
64
            dm.remove(downloadId);
65

    
66
            try {
67
                // write the downloaded file to the expected location
68
                ParcelFileDescriptor fd = dm.openDownloadedFile(downloadId);
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 (downloadId < 0) {
79
            downloadId = isDownloading(context, appId);
80
        }
81

    
82
        // Start a new download
83
        if (downloadId < 0) {
84
            // set up download request
85
            DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress));
86
            request.setTitle(appName);
87
            request.setDescription(appId); // we will retrieve this later from the description field
88
            this.downloadId = 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 (downloadId < 0) return 0;
118

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

    
123
        try {
124
            if (c.moveToFirst()) {
125
                // we use the description column to store the app id
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 (downloadId < 0) return 0;
139

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

    
144
        try {
145
            if (c.moveToFirst()) {
146
                // we use the description column to store the app id
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 && downloadId >= 0) {
166
            dm.remove(downloadId);
167
        }
168
    }
169

    
170
    /**
171
     * Extract the appId from a given download id.
172
     * @return - appId or null if not found
173
     */
174
    public static String getAppId(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 app id
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
                // we use the description column to store the app id
206
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
207
                return c.getString(columnIndex);
208
            }
209
        } finally {
210
            c.close();
211
        }
212

    
213
        return null;
214
    }
215

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

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

    
235
        return -1;
236
    }
237

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

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

    
259
        return -1;
260
    }
261

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

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

    
284
        return -1;
285
    }
286

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

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