am cc167f03: (-s ours) am bb611587: (-s ours) Import translations. DO NOT MERGE
[android/platform/packages/providers/DownloadProvider.git] / src / com / android / providers / downloads / DownloadInfo.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.providers.downloads;
18
19 import android.app.DownloadManager;
20 import android.content.ContentResolver;
21 import android.content.ContentUris;
22 import android.content.ContentValues;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.database.Cursor;
26 import android.net.ConnectivityManager;
27 import android.net.NetworkInfo;
28 import android.net.NetworkInfo.DetailedState;
29 import android.net.Uri;
30 import android.os.Environment;
31 import android.provider.Downloads;
32 import android.provider.Downloads.Impl;
33 import android.text.TextUtils;
34 import android.util.Pair;
35
36 import com.android.internal.annotations.GuardedBy;
37 import com.android.internal.util.IndentingPrintWriter;
38
39 import java.io.CharArrayWriter;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.List;
44 import java.util.concurrent.Executor;
45 import java.util.concurrent.ExecutorService;
46 import java.util.concurrent.Future;
47
48 /**
49  * Details about a specific download. Fields should only be mutated by updating
50  * from database query.
51  */
52 public class DownloadInfo {
53     // TODO: move towards these in-memory objects being sources of truth, and
54     // periodically pushing to provider.
55
56     public static class Reader {
57         private ContentResolver mResolver;
58         private Cursor mCursor;
59
60         public Reader(ContentResolver resolver, Cursor cursor) {
61             mResolver = resolver;
62             mCursor = cursor;
63         }
64
65         public DownloadInfo newDownloadInfo(
66                 Context context, SystemFacade systemFacade, DownloadNotifier notifier) {
67             final DownloadInfo info = new DownloadInfo(context, systemFacade, notifier);
68             updateFromDatabase(info);
69             readRequestHeaders(info);
70             return info;
71         }
72
73         public void updateFromDatabase(DownloadInfo info) {
74             info.mId = getLong(Downloads.Impl._ID);
75             info.mUri = getString(Downloads.Impl.COLUMN_URI);
76             info.mNoIntegrity = getInt(Downloads.Impl.COLUMN_NO_INTEGRITY) == 1;
77             info.mHint = getString(Downloads.Impl.COLUMN_FILE_NAME_HINT);
78             info.mFileName = getString(Downloads.Impl._DATA);
79             info.mMimeType = Intent.normalizeMimeType(getString(Downloads.Impl.COLUMN_MIME_TYPE));
80             info.mDestination = getInt(Downloads.Impl.COLUMN_DESTINATION);
81             info.mVisibility = getInt(Downloads.Impl.COLUMN_VISIBILITY);
82             info.mStatus = getInt(Downloads.Impl.COLUMN_STATUS);
83             info.mNumFailed = getInt(Downloads.Impl.COLUMN_FAILED_CONNECTIONS);
84             int retryRedirect = getInt(Constants.RETRY_AFTER_X_REDIRECT_COUNT);
85             info.mRetryAfter = retryRedirect & 0xfffffff;
86             info.mLastMod = getLong(Downloads.Impl.COLUMN_LAST_MODIFICATION);
87             info.mPackage = getString(Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
88             info.mClass = getString(Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
89             info.mExtras = getString(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS);
90             info.mCookies = getString(Downloads.Impl.COLUMN_COOKIE_DATA);
91             info.mUserAgent = getString(Downloads.Impl.COLUMN_USER_AGENT);
92             info.mReferer = getString(Downloads.Impl.COLUMN_REFERER);
93             info.mTotalBytes = getLong(Downloads.Impl.COLUMN_TOTAL_BYTES);
94             info.mCurrentBytes = getLong(Downloads.Impl.COLUMN_CURRENT_BYTES);
95             info.mETag = getString(Constants.ETAG);
96             info.mUid = getInt(Constants.UID);
97             info.mMediaScanned = getInt(Constants.MEDIA_SCANNED);
98             info.mDeleted = getInt(Downloads.Impl.COLUMN_DELETED) == 1;
99             info.mMediaProviderUri = getString(Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
100             info.mIsPublicApi = getInt(Downloads.Impl.COLUMN_IS_PUBLIC_API) != 0;
101             info.mAllowedNetworkTypes = getInt(Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES);
102             info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0;
103             info.mAllowMetered = getInt(Downloads.Impl.COLUMN_ALLOW_METERED) != 0;
104             info.mTitle = getString(Downloads.Impl.COLUMN_TITLE);
105             info.mDescription = getString(Downloads.Impl.COLUMN_DESCRIPTION);
106             info.mBypassRecommendedSizeLimit =
107                     getInt(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
108
109             synchronized (this) {
110                 info.mControl = getInt(Downloads.Impl.COLUMN_CONTROL);
111             }
112         }
113
114         private void readRequestHeaders(DownloadInfo info) {
115             info.mRequestHeaders.clear();
116             Uri headerUri = Uri.withAppendedPath(
117                     info.getAllDownloadsUri(), Downloads.Impl.RequestHeaders.URI_SEGMENT);
118             Cursor cursor = mResolver.query(headerUri, null, null, null, null);
119             try {
120                 int headerIndex =
121                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER);
122                 int valueIndex =
123                         cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_VALUE);
124                 for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
125                     addHeader(info, cursor.getString(headerIndex), cursor.getString(valueIndex));
126                 }
127             } finally {
128                 cursor.close();
129             }
130
131             if (info.mCookies != null) {
132                 addHeader(info, "Cookie", info.mCookies);
133             }
134             if (info.mReferer != null) {
135                 addHeader(info, "Referer", info.mReferer);
136             }
137         }
138
139         private void addHeader(DownloadInfo info, String header, String value) {
140             info.mRequestHeaders.add(Pair.create(header, value));
141         }
142
143         private String getString(String column) {
144             int index = mCursor.getColumnIndexOrThrow(column);
145             String s = mCursor.getString(index);
146             return (TextUtils.isEmpty(s)) ? null : s;
147         }
148
149         private Integer getInt(String column) {
150             return mCursor.getInt(mCursor.getColumnIndexOrThrow(column));
151         }
152
153         private Long getLong(String column) {
154             return mCursor.getLong(mCursor.getColumnIndexOrThrow(column));
155         }
156     }
157
158     /**
159      * Constants used to indicate network state for a specific download, after
160      * applying any requested constraints.
161      */
162     public enum NetworkState {
163         /**
164          * The network is usable for the given download.
165          */
166         OK,
167
168         /**
169          * There is no network connectivity.
170          */
171         NO_CONNECTION,
172
173         /**
174          * The download exceeds the maximum size for this network.
175          */
176         UNUSABLE_DUE_TO_SIZE,
177
178         /**
179          * The download exceeds the recommended maximum size for this network,
180          * the user must confirm for this download to proceed without WiFi.
181          */
182         RECOMMENDED_UNUSABLE_DUE_TO_SIZE,
183
184         /**
185          * The current connection is roaming, and the download can't proceed
186          * over a roaming connection.
187          */
188         CANNOT_USE_ROAMING,
189
190         /**
191          * The app requesting the download specific that it can't use the
192          * current network connection.
193          */
194         TYPE_DISALLOWED_BY_REQUESTOR,
195
196         /**
197          * Current network is blocked for requesting application.
198          */
199         BLOCKED;
200     }
201
202     /**
203      * For intents used to notify the user that a download exceeds a size threshold, if this extra
204      * is true, WiFi is required for this download size; otherwise, it is only recommended.
205      */
206     public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
207
208     public long mId;
209     public String mUri;
210     @Deprecated
211     public boolean mNoIntegrity;
212     public String mHint;
213     public String mFileName;
214     public String mMimeType;
215     public int mDestination;
216     public int mVisibility;
217     public int mControl;
218     public int mStatus;
219     public int mNumFailed;
220     public int mRetryAfter;
221     public long mLastMod;
222     public String mPackage;
223     public String mClass;
224     public String mExtras;
225     public String mCookies;
226     public String mUserAgent;
227     public String mReferer;
228     public long mTotalBytes;
229     public long mCurrentBytes;
230     public String mETag;
231     public int mUid;
232     public int mMediaScanned;
233     public boolean mDeleted;
234     public String mMediaProviderUri;
235     public boolean mIsPublicApi;
236     public int mAllowedNetworkTypes;
237     public boolean mAllowRoaming;
238     public boolean mAllowMetered;
239     public String mTitle;
240     public String mDescription;
241     public int mBypassRecommendedSizeLimit;
242
243     public int mFuzz;
244
245     private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>();
246
247     /**
248      * Result of last {@link DownloadThread} started by
249      * {@link #startDownloadIfReady(ExecutorService)}.
250      */
251     @GuardedBy("this")
252     private Future<?> mSubmittedTask;
253
254     @GuardedBy("this")
255     private DownloadThread mTask;
256
257     private final Context mContext;
258     private final SystemFacade mSystemFacade;
259     private final DownloadNotifier mNotifier;
260
261     private DownloadInfo(Context context, SystemFacade systemFacade, DownloadNotifier notifier) {
262         mContext = context;
263         mSystemFacade = systemFacade;
264         mNotifier = notifier;
265         mFuzz = Helpers.sRandom.nextInt(1001);
266     }
267
268     public Collection<Pair<String, String>> getHeaders() {
269         return Collections.unmodifiableList(mRequestHeaders);
270     }
271
272     public String getUserAgent() {
273         if (mUserAgent != null) {
274             return mUserAgent;
275         } else {
276             return Constants.DEFAULT_USER_AGENT;
277         }
278     }
279
280     public void sendIntentIfRequested() {
281         if (mPackage == null) {
282             return;
283         }
284
285         Intent intent;
286         if (mIsPublicApi) {
287             intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
288             intent.setPackage(mPackage);
289             intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, mId);
290         } else { // legacy behavior
291             if (mClass == null) {
292                 return;
293             }
294             intent = new Intent(Downloads.Impl.ACTION_DOWNLOAD_COMPLETED);
295             intent.setClassName(mPackage, mClass);
296             if (mExtras != null) {
297                 intent.putExtra(Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS, mExtras);
298             }
299             // We only send the content: URI, for security reasons. Otherwise, malicious
300             //     applications would have an easier time spoofing download results by
301             //     sending spoofed intents.
302             intent.setData(getMyDownloadsUri());
303         }
304         mSystemFacade.sendBroadcast(intent);
305     }
306
307     /**
308      * Returns the time when a download should be restarted.
309      */
310     public long restartTime(long now) {
311         if (mNumFailed == 0) {
312             return now;
313         }
314         if (mRetryAfter > 0) {
315             return mLastMod + mRetryAfter;
316         }
317         return mLastMod +
318                 Constants.RETRY_FIRST_DELAY *
319                     (1000 + mFuzz) * (1 << (mNumFailed - 1));
320     }
321
322     /**
323      * Returns whether this download should be enqueued.
324      */
325     private boolean isReadyToDownload() {
326         if (mControl == Downloads.Impl.CONTROL_PAUSED) {
327             // the download is paused, so it's not going to start
328             return false;
329         }
330         switch (mStatus) {
331             case 0: // status hasn't been initialized yet, this is a new download
332             case Downloads.Impl.STATUS_PENDING: // download is explicit marked as ready to start
333             case Downloads.Impl.STATUS_RUNNING: // download interrupted (process killed etc) while
334                                                 // running, without a chance to update the database
335                 return true;
336
337             case Downloads.Impl.STATUS_WAITING_FOR_NETWORK:
338             case Downloads.Impl.STATUS_QUEUED_FOR_WIFI:
339                 return checkCanUseNetwork(mTotalBytes) == NetworkState.OK;
340
341             case Downloads.Impl.STATUS_WAITING_TO_RETRY:
342                 // download was waiting for a delayed restart
343                 final long now = mSystemFacade.currentTimeMillis();
344                 return restartTime(now) <= now;
345             case Downloads.Impl.STATUS_DEVICE_NOT_FOUND_ERROR:
346                 // is the media mounted?
347                 return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
348             case Downloads.Impl.STATUS_INSUFFICIENT_SPACE_ERROR:
349                 // avoids repetition of retrying download
350                 return false;
351         }
352         return false;
353     }
354
355     /**
356      * Returns whether this download has a visible notification after
357      * completion.
358      */
359     public boolean hasCompletionNotification() {
360         if (!Downloads.Impl.isStatusCompleted(mStatus)) {
361             return false;
362         }
363         if (mVisibility == Downloads.Impl.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) {
364             return true;
365         }
366         return false;
367     }
368
369     /**
370      * Returns whether this download is allowed to use the network.
371      */
372     public NetworkState checkCanUseNetwork(long totalBytes) {
373         final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mUid);
374         if (info == null || !info.isConnected()) {
375             return NetworkState.NO_CONNECTION;
376         }
377         if (DetailedState.BLOCKED.equals(info.getDetailedState())) {
378             return NetworkState.BLOCKED;
379         }
380         if (mSystemFacade.isNetworkRoaming() && !isRoamingAllowed()) {
381             return NetworkState.CANNOT_USE_ROAMING;
382         }
383         if (mSystemFacade.isActiveNetworkMetered() && !mAllowMetered) {
384             return NetworkState.TYPE_DISALLOWED_BY_REQUESTOR;
385         }
386         return checkIsNetworkTypeAllowed(info.getType(), totalBytes);
387     }
388
389     private boolean isRoamingAllowed() {
390         if (mIsPublicApi) {
391             return mAllowRoaming;
392         } else { // legacy behavior
393             return mDestination != Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING;
394         }
395     }
396
397     /**
398      * Check if this download can proceed over the given network type.
399      * @param networkType a constant from ConnectivityManager.TYPE_*.
400      * @return one of the NETWORK_* constants
401      */
402     private NetworkState checkIsNetworkTypeAllowed(int networkType, long totalBytes) {
403         if (mIsPublicApi) {
404             final int flag = translateNetworkTypeToApiFlag(networkType);
405             final boolean allowAllNetworkTypes = mAllowedNetworkTypes == ~0;
406             if (!allowAllNetworkTypes && (flag & mAllowedNetworkTypes) == 0) {
407                 return NetworkState.TYPE_DISALLOWED_BY_REQUESTOR;
408             }
409         }
410         return checkSizeAllowedForNetwork(networkType, totalBytes);
411     }
412
413     /**
414      * Translate a ConnectivityManager.TYPE_* constant to the corresponding
415      * DownloadManager.Request.NETWORK_* bit flag.
416      */
417     private int translateNetworkTypeToApiFlag(int networkType) {
418         switch (networkType) {
419             case ConnectivityManager.TYPE_MOBILE:
420                 return DownloadManager.Request.NETWORK_MOBILE;
421
422             case ConnectivityManager.TYPE_WIFI:
423                 return DownloadManager.Request.NETWORK_WIFI;
424
425             case ConnectivityManager.TYPE_BLUETOOTH:
426                 return DownloadManager.Request.NETWORK_BLUETOOTH;
427
428             default:
429                 return 0;
430         }
431     }
432
433     /**
434      * Check if the download's size prohibits it from running over the current network.
435      * @return one of the NETWORK_* constants
436      */
437     private NetworkState checkSizeAllowedForNetwork(int networkType, long totalBytes) {
438         if (totalBytes <= 0) {
439             // we don't know the size yet
440             return NetworkState.OK;
441         }
442
443         if (ConnectivityManager.isNetworkTypeMobile(networkType)) {
444             Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile();
445             if (maxBytesOverMobile != null && totalBytes > maxBytesOverMobile) {
446                 return NetworkState.UNUSABLE_DUE_TO_SIZE;
447             }
448             if (mBypassRecommendedSizeLimit == 0) {
449                 Long recommendedMaxBytesOverMobile = mSystemFacade
450                         .getRecommendedMaxBytesOverMobile();
451                 if (recommendedMaxBytesOverMobile != null
452                         && totalBytes > recommendedMaxBytesOverMobile) {
453                     return NetworkState.RECOMMENDED_UNUSABLE_DUE_TO_SIZE;
454                 }
455             }
456         }
457
458         return NetworkState.OK;
459     }
460
461     /**
462      * If download is ready to start, and isn't already pending or executing,
463      * create a {@link DownloadThread} and enqueue it into given
464      * {@link Executor}.
465      *
466      * @return If actively downloading.
467      */
468     public boolean startDownloadIfReady(ExecutorService executor) {
469         synchronized (this) {
470             final boolean isReady = isReadyToDownload();
471             final boolean isActive = mSubmittedTask != null && !mSubmittedTask.isDone();
472             if (isReady && !isActive) {
473                 if (mStatus != Impl.STATUS_RUNNING) {
474                     mStatus = Impl.STATUS_RUNNING;
475                     ContentValues values = new ContentValues();
476                     values.put(Impl.COLUMN_STATUS, mStatus);
477                     mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);
478                 }
479
480                 mTask = new DownloadThread(mContext, mSystemFacade, mNotifier, this);
481                 mSubmittedTask = executor.submit(mTask);
482             }
483             return isReady;
484         }
485     }
486
487     /**
488      * If download is ready to be scanned, enqueue it into the given
489      * {@link DownloadScanner}.
490      *
491      * @return If actively scanning.
492      */
493     public boolean startScanIfReady(DownloadScanner scanner) {
494         synchronized (this) {
495             final boolean isReady = shouldScanFile();
496             if (isReady) {
497                 scanner.requestScan(this);
498             }
499             return isReady;
500         }
501     }
502
503     public boolean isOnCache() {
504         return (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION
505                 || mDestination == Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION
506                 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING
507                 || mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
508     }
509
510     public Uri getMyDownloadsUri() {
511         return ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, mId);
512     }
513
514     public Uri getAllDownloadsUri() {
515         return ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, mId);
516     }
517
518     @Override
519     public String toString() {
520         final CharArrayWriter writer = new CharArrayWriter();
521         dump(new IndentingPrintWriter(writer, "  "));
522         return writer.toString();
523     }
524
525     public void dump(IndentingPrintWriter pw) {
526         pw.println("DownloadInfo:");
527         pw.increaseIndent();
528
529         pw.printPair("mId", mId);
530         pw.printPair("mLastMod", mLastMod);
531         pw.printPair("mPackage", mPackage);
532         pw.printPair("mUid", mUid);
533         pw.println();
534
535         pw.printPair("mUri", mUri);
536         pw.println();
537
538         pw.printPair("mMimeType", mMimeType);
539         pw.printPair("mCookies", (mCookies != null) ? "yes" : "no");
540         pw.printPair("mReferer", (mReferer != null) ? "yes" : "no");
541         pw.printPair("mUserAgent", mUserAgent);
542         pw.println();
543
544         pw.printPair("mFileName", mFileName);
545         pw.printPair("mDestination", mDestination);
546         pw.println();
547
548         pw.printPair("mStatus", Downloads.Impl.statusToString(mStatus));
549         pw.printPair("mCurrentBytes", mCurrentBytes);
550         pw.printPair("mTotalBytes", mTotalBytes);
551         pw.println();
552
553         pw.printPair("mNumFailed", mNumFailed);
554         pw.printPair("mRetryAfter", mRetryAfter);
555         pw.printPair("mETag", mETag);
556         pw.printPair("mIsPublicApi", mIsPublicApi);
557         pw.println();
558
559         pw.printPair("mAllowedNetworkTypes", mAllowedNetworkTypes);
560         pw.printPair("mAllowRoaming", mAllowRoaming);
561         pw.printPair("mAllowMetered", mAllowMetered);
562         pw.println();
563
564         pw.decreaseIndent();
565     }
566
567     /**
568      * Return time when this download will be ready for its next action, in
569      * milliseconds after given time.
570      *
571      * @return If {@code 0}, download is ready to proceed immediately. If
572      *         {@link Long#MAX_VALUE}, then download has no future actions.
573      */
574     public long nextActionMillis(long now) {
575         if (Downloads.Impl.isStatusCompleted(mStatus)) {
576             return Long.MAX_VALUE;
577         }
578         if (mStatus != Downloads.Impl.STATUS_WAITING_TO_RETRY) {
579             return 0;
580         }
581         long when = restartTime(now);
582         if (when <= now) {
583             return 0;
584         }
585         return when - now;
586     }
587
588     /**
589      * Returns whether a file should be scanned
590      */
591     public boolean shouldScanFile() {
592         return (mMediaScanned == 0)
593                 && (mDestination == Downloads.Impl.DESTINATION_EXTERNAL ||
594                         mDestination == Downloads.Impl.DESTINATION_FILE_URI ||
595                         mDestination == Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD)
596                 && Downloads.Impl.isStatusSuccess(mStatus);
597     }
598
599     void notifyPauseDueToSize(boolean isWifiRequired) {
600         Intent intent = new Intent(Intent.ACTION_VIEW);
601         intent.setData(getAllDownloadsUri());
602         intent.setClassName(SizeLimitActivity.class.getPackage().getName(),
603                 SizeLimitActivity.class.getName());
604         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
605         intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired);
606         mContext.startActivity(intent);
607     }
608
609     /**
610      * Query and return status of requested download.
611      */
612     public static int queryDownloadStatus(ContentResolver resolver, long id) {
613         final Cursor cursor = resolver.query(
614                 ContentUris.withAppendedId(Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI, id),
615                 new String[] { Downloads.Impl.COLUMN_STATUS }, null, null, null);
616         try {
617             if (cursor.moveToFirst()) {
618                 return cursor.getInt(0);
619             } else {
620                 // TODO: increase strictness of value returned for unknown
621                 // downloads; this is safe default for now.
622                 return Downloads.Impl.STATUS_PENDING;
623             }
624         } finally {
625             cursor.close();
626         }
627     }
628 }