Statistics
| Branch: | Tag: | Revision:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
134
        return 0;
135
    }
136

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

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

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

    
155
        return 0;
156
    }
157

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

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

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

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

    
191
        return null;
192
    }
193

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

    
204
        try {
205
            if (c.moveToFirst()) {
206
                // we use the description column to store the app id
207
                int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
208
                return c.getString(columnIndex);
209
            }
210
        } finally {
211
            c.close();
212
        }
213

    
214
        return null;
215
    }
216

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

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

    
236
        return -1;
237
    }
238

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

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

    
260
        return -1;
261
    }
262

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

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

    
285
        return -1;
286
    }
287

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

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