diff options
author | Steve Howard <showard@google.com> | 2010-07-15 19:59:40 -0700 |
---|---|---|
committer | Steve Howard <showard@google.com> | 2010-07-19 11:04:38 -0700 |
commit | 071bd7acb3185f4f1e807855605c5e6018e9742f (patch) | |
tree | 85e1d36689bb632b20b74faaac9596d660a7a46d /src/com/android/providers | |
parent | 1aa26989047495ff58d3e2598d3f9549465cbb65 (diff) | |
download | android_packages_providers_DownloadProvider-071bd7acb3185f4f1e807855605c5e6018e9742f.tar.gz android_packages_providers_DownloadProvider-071bd7acb3185f4f1e807855605c5e6018e9742f.tar.bz2 android_packages_providers_DownloadProvider-071bd7acb3185f4f1e807855605c5e6018e9742f.zip |
Support for max download size that may go over mobile
This change introduces support for a maximum download size that may go
over a mobile connection. Downloads above this limit will wait for a
wifi connection.
To accomplish this, I moved a lot of the logic for checking
connectivity info into DownloadInfo itself. I then moved the code to
call these checks from DownloadService, where it would call the checks
before spawning a DownloadThread, into DownloadThread itself. This
makes it simpler to check connectivity after we get Content-Length
info. It also eliminates the risk of a race condition where
connectivity changes between the check and the actual request
execution.
I realize this change reduces efficiency, because we now call into
ConnectivityManager/TelephonyManager twice per DownloadThread, rather
than once per DownloadService "tick". I feel that it's OK since its a
small amount of computation running relatively infrequently. If we
feel that it's a serious concern, and that the efficiency issues
outweigh the race problem, I can go easily back to the old approach.
I've left out the code to actually fetch the limit. I think this will
come from system settings, but I want to double-check, so I'll put it
in a separate change.
Other changes:
* simplify SystemFacade's interface to get connectivity info - rather
than returning all connected types, just return the active type,
since that should be all we care about
* adding @LargeTest to PublicApiFunctionalTest
Change-Id: Id1faa2c45bf2dade9fe779440721a1d42cbdfcd1
Diffstat (limited to 'src/com/android/providers')
6 files changed, 114 insertions, 119 deletions
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java index 1ae10ce1..5cd50c92 100644 --- a/src/com/android/providers/downloads/DownloadInfo.java +++ b/src/com/android/providers/downloads/DownloadInfo.java @@ -16,12 +16,16 @@ package com.android.providers.downloads; -import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; +import android.net.ConnectivityManager; import android.net.Uri; import android.provider.Downloads; +import android.provider.Downloads.Impl; +import android.util.Log; import java.util.Collections; import java.util.HashMap; @@ -59,9 +63,15 @@ public class DownloadInfo { public int mFuzz; public volatile boolean mHasActiveThread; + private Map<String, String> mRequestHeaders = new HashMap<String, String>(); + private SystemFacade mSystemFacade; + private Context mContext; + + public DownloadInfo(Context context, SystemFacade systemFacade, Cursor cursor) { + mContext = context; + mSystemFacade = systemFacade; - public DownloadInfo(ContentResolver resolver, Cursor cursor) { int retryRedirect = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.RETRY_AFTER_X_REDIRECT_COUNT)); mId = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl._ID)); @@ -101,14 +111,14 @@ public class DownloadInfo { mMediaScanned = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1; mFuzz = Helpers.sRandom.nextInt(1001); - readRequestHeaders(resolver, mId); + readRequestHeaders(mId); } - private void readRequestHeaders(ContentResolver resolver, long downloadId) { + private void readRequestHeaders(long downloadId) { Uri headerUri = Downloads.Impl.CONTENT_URI.buildUpon() .appendPath(Long.toString(downloadId)) .appendPath(Downloads.Impl.RequestHeaders.URI_SEGMENT).build(); - Cursor cursor = resolver.query(headerUri, null, null, null, null); + Cursor cursor = mContext.getContentResolver().query(headerUri, null, null, null, null); try { int headerIndex = cursor.getColumnIndexOrThrow(Downloads.Impl.RequestHeaders.COLUMN_HEADER); @@ -133,7 +143,7 @@ public class DownloadInfo { return Collections.unmodifiableMap(mRequestHeaders); } - public void sendIntentIfRequested(Uri contentUri, Context context) { + public void sendIntentIfRequested(Uri contentUri) { if (mPackage != null && mClass != null) { Intent intent = new Intent(Downloads.Impl.ACTION_DOWNLOAD_COMPLETED); intent.setClassName(mPackage, mClass); @@ -144,7 +154,7 @@ public class DownloadInfo { // applications would have an easier time spoofing download results by // sending spoofed intents. intent.setData(contentUri); - context.sendBroadcast(intent); + mContext.sendBroadcast(intent); } } @@ -247,14 +257,57 @@ public class DownloadInfo { /** * Returns whether this download is allowed to use the network. */ - public boolean canUseNetwork(boolean available, boolean roaming) { - if (!available) { + public boolean canUseNetwork() { + Integer networkType = mSystemFacade.getActiveNetworkType(); + if (networkType == null) { return false; } - if (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING) { - return !roaming; - } else { - return true; + if (!isSizeAllowedForNetwork(networkType)) { + return false; + } + if (mDestination == Downloads.Impl.DESTINATION_CACHE_PARTITION_NOROAMING + && mSystemFacade.isNetworkRoaming()) { + return false; + } + return true; + } + + /** + * Check if the download's size prohibits it from running over the current network. + */ + private boolean isSizeAllowedForNetwork(int networkType) { + if (mTotalBytes <= 0) { + return true; // we don't know the size yet + } + if (networkType == ConnectivityManager.TYPE_WIFI) { + return true; // anything goes over wifi + } + Integer maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile(); + if (maxBytesOverMobile == null) { + return true; // no limit + } + return mTotalBytes <= maxBytesOverMobile; + } + + void startIfReady(long now) { + if (isReadyToStart(now)) { + if (Constants.LOGV) { + Log.v(Constants.TAG, "Service spawning thread to handle download " + mId); + } + if (mHasActiveThread) { + throw new IllegalStateException("Multiple threads on same download"); + } + if (mStatus != Impl.STATUS_RUNNING) { + mStatus = Impl.STATUS_RUNNING; + ContentValues values = new ContentValues(); + values.put(Impl.COLUMN_STATUS, mStatus); + mContext.getContentResolver().update( + ContentUris.withAppendedId(Impl.CONTENT_URI, mId), + values, null, null); + } + DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this); + mHasActiveThread = true; + downloader.start(); } } } diff --git a/src/com/android/providers/downloads/DownloadService.java b/src/com/android/providers/downloads/DownloadService.java index b5cb2d45..e474d4d7 100644 --- a/src/com/android/providers/downloads/DownloadService.java +++ b/src/com/android/providers/downloads/DownloadService.java @@ -310,8 +310,6 @@ public class DownloadService extends Service { } mPendingUpdate = false; } - boolean networkAvailable = Helpers.isNetworkAvailable(mSystemFacade); - boolean networkRoaming = Helpers.isNetworkRoaming(mSystemFacade); long now = mSystemFacade.currentTimeMillis(); Cursor cursor = getContentResolver().query(Downloads.Impl.CONTENT_URI, @@ -368,7 +366,7 @@ public class DownloadService extends Service { int id = cursor.getInt(idColumn); if (arrayPos == mDownloads.size()) { - insertDownload(cursor, arrayPos, networkAvailable, networkRoaming, now); + insertDownload(cursor, arrayPos, now); if (Constants.LOGVV) { Log.v(Constants.TAG, "Array update: appending " + id + " @ " + arrayPos); @@ -405,9 +403,7 @@ public class DownloadService extends Service { deleteDownload(arrayPos); // this advances in the array } else if (arrayId == id) { // This cursor row already exists in the stored array - updateDownload( - cursor, arrayPos, - networkAvailable, networkRoaming, now); + updateDownload(cursor, arrayPos, now); if (shouldScanFile(arrayPos) && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) { @@ -432,9 +428,7 @@ public class DownloadService extends Service { Log.v(Constants.TAG, "Array update: inserting " + id + " @ " + arrayPos); } - insertDownload( - cursor, arrayPos, - networkAvailable, networkRoaming, now); + insertDownload(cursor, arrayPos, now); if (shouldScanFile(arrayPos) && (!mediaScannerConnected() || !scanFile(cursor, arrayPos))) { @@ -551,10 +545,8 @@ public class DownloadService extends Service { * Keeps a local copy of the info about a download, and initiates the * download if appropriate. */ - private void insertDownload( - Cursor cursor, int arrayPos, - boolean networkAvailable, boolean networkRoaming, long now) { - DownloadInfo info = new DownloadInfo(getContentResolver(), cursor); + private void insertDownload(Cursor cursor, int arrayPos, long now) { + DownloadInfo info = new DownloadInfo(this, mSystemFacade, cursor); if (Constants.LOGVV) { Log.v(Constants.TAG, "Service adding new entry"); @@ -617,51 +609,18 @@ public class DownloadService extends Service { ContentValues values = new ContentValues(); values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_NOT_ACCEPTABLE); getContentResolver().update(uri, values, null, null); - info.sendIntentIfRequested(uri, this); + info.sendIntentIfRequested(uri); return; } } - if (info.canUseNetwork(networkAvailable, networkRoaming)) { - if (info.isReadyToStart(now)) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "Service spawning thread to handle new download " + - info.mId); - } - if (info.mHasActiveThread) { - throw new IllegalStateException("Multiple threads on same download on insert"); - } - if (info.mStatus != Downloads.Impl.STATUS_RUNNING) { - info.mStatus = Downloads.Impl.STATUS_RUNNING; - ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_STATUS, info.mStatus); - getContentResolver().update( - ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId), - values, null, null); - } - DownloadThread downloader = new DownloadThread(this, mSystemFacade, info); - info.mHasActiveThread = true; - downloader.start(); - } - } else { - if (info.mStatus == 0 - || info.mStatus == Downloads.Impl.STATUS_PENDING - || info.mStatus == Downloads.Impl.STATUS_RUNNING) { - info.mStatus = Downloads.Impl.STATUS_RUNNING_PAUSED; - Uri uri = ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId); - ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_STATUS, Downloads.Impl.STATUS_RUNNING_PAUSED); - getContentResolver().update(uri, values, null, null); - } - } + info.startIfReady(now); } /** * Updates the local copy of the info about a download. */ - private void updateDownload( - Cursor cursor, int arrayPos, - boolean networkAvailable, boolean networkRoaming, long now) { + private void updateDownload(Cursor cursor, int arrayPos, long now) { DownloadInfo info = (DownloadInfo) mDownloads.get(arrayPos); int statusColumn = cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_STATUS); int failedColumn = cursor.getColumnIndexOrThrow(Constants.FAILED_CONNECTIONS); @@ -715,26 +674,7 @@ public class DownloadService extends Service { info.mMediaScanned = cursor.getInt(cursor.getColumnIndexOrThrow(Constants.MEDIA_SCANNED)) == 1; - if (info.canUseNetwork(networkAvailable, networkRoaming)) { - if (info.isReadyToRestart(now)) { - if (Constants.LOGV) { - Log.v(Constants.TAG, "Service spawning thread to handle updated download " + - info.mId); - } - if (info.mHasActiveThread) { - throw new IllegalStateException("Multiple threads on same download on update"); - } - info.mStatus = Downloads.Impl.STATUS_RUNNING; - ContentValues values = new ContentValues(); - values.put(Downloads.Impl.COLUMN_STATUS, info.mStatus); - getContentResolver().update( - ContentUris.withAppendedId(Downloads.Impl.CONTENT_URI, info.mId), - values, null, null); - DownloadThread downloader = new DownloadThread(this, mSystemFacade, info); - info.mHasActiveThread = true; - downloader.start(); - } - } + info.startIfReady(now); } /** diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java index e7763e4e..d8271a2b 100644 --- a/src/com/android/providers/downloads/DownloadThread.java +++ b/src/com/android/providers/downloads/DownloadThread.java @@ -195,6 +195,12 @@ http_request_loop: request.addHeader("Range", "bytes=" + bytesSoFar + "-"); } + // check connectivity just before sending + if (!mInfo.canUseNetwork()) { + finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED; + break http_request_loop; + } + HttpResponse response; try { response = client.execute(request); @@ -437,8 +443,15 @@ http_request_loop: if (headerContentLength != null) { contentLength = Integer.parseInt(headerContentLength); } + mInfo.mTotalBytes = contentLength; values.put(Downloads.Impl.COLUMN_TOTAL_BYTES, contentLength); mContext.getContentResolver().update(contentUri, values, null, null); + // check connectivity again now that we know the total size + if (!mInfo.canUseNetwork()) { + finalStatus = Downloads.Impl.STATUS_RUNNING_PAUSED; + request.abort(); + break http_request_loop; + } } InputStream entityStream; @@ -772,7 +785,7 @@ http_request_loop: */ private void notifyThroughIntent() { Uri uri = Uri.parse(Downloads.Impl.CONTENT_URI + "/" + mInfo.mId); - mInfo.sendIntentIfRequested(uri, mContext); + mInfo.sendIntentIfRequested(uri); } /** diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java index 4a0f860b..0ad4edc0 100644 --- a/src/com/android/providers/downloads/Helpers.java +++ b/src/com/android/providers/downloads/Helpers.java @@ -23,14 +23,11 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.database.Cursor; import android.drm.mobile1.DrmRawContent; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; import android.net.Uri; import android.os.Environment; import android.os.StatFs; import android.os.SystemClock; import android.provider.Downloads; -import android.telephony.TelephonyManager; import android.util.Config; import android.util.Log; import android.webkit.MimeTypeMap; @@ -515,14 +512,7 @@ public class Helpers { * Returns whether the network is available */ public static boolean isNetworkAvailable(SystemFacade system) { - return !system.getConnectedNetworkTypes().isEmpty(); - } - - /** - * Returns whether the network is roaming - */ - public static boolean isNetworkRoaming(SystemFacade system) { - return system.isNetworkRoaming(); + return system.getActiveNetworkType() != null; } /** diff --git a/src/com/android/providers/downloads/RealSystemFacade.java b/src/com/android/providers/downloads/RealSystemFacade.java index 41ca6b6e..89cf3b1d 100644 --- a/src/com/android/providers/downloads/RealSystemFacade.java +++ b/src/com/android/providers/downloads/RealSystemFacade.java @@ -6,8 +6,6 @@ import android.net.NetworkInfo; import android.telephony.TelephonyManager; import android.util.Log; -import java.util.BitSet; - class RealSystemFacade implements SystemFacade { private Context mContext; @@ -19,30 +17,22 @@ class RealSystemFacade implements SystemFacade { return System.currentTimeMillis(); } - public BitSet getConnectedNetworkTypes() { - BitSet connectedTypes = new BitSet(); - + public Integer getActiveNetworkType() { ConnectivityManager connectivity = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivity == null) { Log.w(Constants.TAG, "couldn't get connectivity manager"); - return connectedTypes; + return null; } - NetworkInfo[] infos = connectivity.getAllNetworkInfo(); - if (infos != null) { - for (NetworkInfo info : infos) { - if (info.getState() == NetworkInfo.State.CONNECTED) { - connectedTypes.set(info.getType()); - } + NetworkInfo activeInfo = connectivity.getActiveNetworkInfo(); + if (activeInfo == null) { + if (Constants.LOGVV) { + Log.v(Constants.TAG, "network is not available"); } + return null; } - - if (Constants.LOGVV) { - boolean isConnected = !connectedTypes.isEmpty(); - Log.v(Constants.TAG, "network is " + (isConnected ? "" : "not ") + "available"); - } - return connectedTypes; + return activeInfo.getType(); } public boolean isNetworkRoaming() { @@ -56,10 +46,13 @@ class RealSystemFacade implements SystemFacade { NetworkInfo info = connectivity.getActiveNetworkInfo(); boolean isMobile = (info != null && info.getType() == ConnectivityManager.TYPE_MOBILE); boolean isRoaming = isMobile && TelephonyManager.getDefault().isNetworkRoaming(); - if (Constants.LOGVV) { - Log.v(Constants.TAG, "network is mobile: " + isMobile); - Log.v(Constants.TAG, "network is roaming: " + isRoaming); + if (Constants.LOGVV && isRoaming) { + Log.v(Constants.TAG, "network is roaming"); } - return isMobile && isRoaming; + return isRoaming; + } + + public Integer getMaxBytesOverMobile() { + return null; } } diff --git a/src/com/android/providers/downloads/SystemFacade.java b/src/com/android/providers/downloads/SystemFacade.java index e16e6a06..2addbf82 100644 --- a/src/com/android/providers/downloads/SystemFacade.java +++ b/src/com/android/providers/downloads/SystemFacade.java @@ -1,7 +1,6 @@ package com.android.providers.downloads; -import java.util.BitSet; interface SystemFacade { /** @@ -10,12 +9,19 @@ interface SystemFacade { public long currentTimeMillis(); /** - * @return Network types (as in ConnectivityManager.TYPE_*) of all connected networks. + * @return Network type (as in ConnectivityManager.TYPE_*) of currently active network, or null + * if there's no active connection. */ - public BitSet getConnectedNetworkTypes(); + public Integer getActiveNetworkType(); /** * @see android.telephony.TelephonyManager#isNetworkRoaming */ public boolean isNetworkRoaming(); + + /** + * @return maximum size, in bytes, of downloads that may go over a mobile connection; or null if + * there's no limit + */ + public Integer getMaxBytesOverMobile(); } |