summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml3
-rw-r--r--res/values/strings.xml45
-rw-r--r--src/com/android/providers/downloads/DownloadInfo.java92
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java11
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java8
-rw-r--r--src/com/android/providers/downloads/RealSystemFacade.java10
-rw-r--r--src/com/android/providers/downloads/SizeLimitActivity.java137
-rw-r--r--src/com/android/providers/downloads/SystemFacade.java7
-rw-r--r--tests/src/com/android/providers/downloads/FakeSystemFacade.java5
-rw-r--r--tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java1
10 files changed, 293 insertions, 26 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 6108ac23..f7001ff2 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -76,5 +76,8 @@
<action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
</intent-filter>
</receiver>
+
+ <activity android:name=".SizeLimitActivity"
+ android:launchMode="singleTask" />
</application>
</manifest>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1623fbe2..543c95c7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -154,5 +154,50 @@
[CHAR LIMIT=24] -->
<string name="notification_need_wifi_for_size">Need wifi due to size</string>
+ <!-- Title for dialog when a download exceeds the carrier-specified maximum size of downloads
+ over the mobile network and WiFi is required. The user has the choice to either queue the
+ download to start next time WiFi is available or cancel the download altogether. [CHAR
+ LIMIT=50] -->
+ <string name="wifi_required_title">Download too large for operator network</string>
+
+ <!-- Text for dialog when a download exceeds the carrier-specified maximum size of downloads
+ over the mobile network and WiFi is required. The user has the choice to either queue the
+ download to start next time WiFi is available or cancel the download altogether. [CHAR
+ LIMIT=200] -->
+ <string name="wifi_required_body">You must use WiFi to complete this <xliff:g id="size"
+ example="12.3KB">%s</xliff:g> download.\n\nClick \"<xliff:g id="queue_text"
+ example="Queue">%s</xliff:g>\" below to begin this download the next time you are connected
+ to a WiFi network.</string>
+
+ <!-- Title for dialog when a download exceeds the carrier-specified recommended maximum size of
+ downloads over the mobile network, and the user may choose to start the download over mobile
+ anyway or to queue for download to start next time a WiFi connection is available [CHAR
+ LIMIT=50] -->
+ <string name="wifi_recommended_title">Queue for download later?</string>
+
+ <!-- Text for dialog when a download exceeds the carrier-specified recommended maximum size of
+ downloads over the mobile network, and the user may choose to start the download over mobile
+ anyway or to queue for download to start next time a WiFi connection is available [CHAR
+ LIMIT=200] -->
+ <string name="wifi_recommended_body">Starting this <xliff:g id="size" example="12.3KB">%s
+ </xliff:g> download now may shorten your battery life and/or result in excessive usage of
+ your mobile data connection, which can lead to charges by your mobile operator depending on
+ your data plan.\n\nClick \"<xliff:g id="queue_text" example="Queue">%s</xliff:g>\" below to
+ begin this download the next time you are connected to a WiFi network.</string>
+
+
+ <!-- Text for button to queue a download to start next time WiFi is available [CHAR LIMIT=25]
+ -->
+ <string name="button_queue_for_wifi">Queue</string>
+
+ <!-- Text for button to cancel a download because it's too large to proceed over the mobile
+ network and the user does not want to queue it for WiFi [CHAR LIMIT=25] -->
+ <string name="button_cancel_download">Cancel</string>
+
+ <!-- Text for button to start a download over the mobile connection now, even though it's over
+ the carrier-specified recommended maximum size for downloads over the mobile connection
+ [CHAR LIMIT=25] -->
+ <string name="button_start_now">Start now</string>
+
</resources>
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java
index eb9ac4bd..467af836 100644
--- a/src/com/android/providers/downloads/DownloadInfo.java
+++ b/src/com/android/providers/downloads/DownloadInfo.java
@@ -87,6 +87,8 @@ public class DownloadInfo {
info.mAllowRoaming = getInt(Downloads.Impl.COLUMN_ALLOW_ROAMING) != 0;
info.mTitle = getString(info.mTitle, Downloads.Impl.COLUMN_TITLE);
info.mDescription = getString(info.mDescription, Downloads.Impl.COLUMN_DESCRIPTION);
+ info.mBypassRecommendedSizeLimit =
+ getInt(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
synchronized (this) {
info.mControl = getInt(Downloads.Impl.COLUMN_CONTROL);
@@ -159,6 +161,37 @@ public class DownloadInfo {
}
}
+ // the following NETWORK_* constants are used to indicates specfic reasons for disallowing a
+ // download from using a network, since specific causes can require special handling
+
+ /**
+ * The network is usable for the given download.
+ */
+ public static final int NETWORK_OK = 1;
+
+ /**
+ * The network is unusuable for some unspecified reason.
+ */
+ public static final int NETWORK_UNUSABLE_GENERIC = 2;
+
+ /**
+ * The download exceeds the maximum size for this network.
+ */
+ public static final int NETWORK_UNUSABLE_DUE_TO_SIZE = 3;
+
+ /**
+ * The download exceeds the recommended maximum size for this network, the user must confirm for
+ * this download to proceed without WiFi.
+ */
+ public static final int NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE = 4;
+
+ /**
+ * For intents used to notify the user that a download exceeds a size threshold, if this extra
+ * is true, WiFi is required for this download size; otherwise, it is only recommended.
+ */
+ public static final String EXTRA_IS_WIFI_REQUIRED = "isWifiRequired";
+
+
public long mId;
public String mUri;
public boolean mNoIntegrity;
@@ -188,6 +221,7 @@ public class DownloadInfo {
public boolean mAllowRoaming;
public String mTitle;
public String mDescription;
+ public int mBypassRecommendedSizeLimit;
public String mPausedReason;
public int mFuzz;
@@ -307,7 +341,7 @@ public class DownloadInfo {
if (mStatus == Downloads.Impl.STATUS_RUNNING_PAUSED) {
if (mNumFailed == 0) {
// download is waiting for network connectivity to return before it can resume
- return canUseNetwork();
+ return checkCanUseNetwork() == NETWORK_OK;
}
if (restartTime() < now) {
// download was waiting for a delayed restart, and the delay has expired
@@ -333,19 +367,17 @@ public class DownloadInfo {
/**
* Returns whether this download is allowed to use the network.
+ * @return one of the NETWORK_* constants
*/
- public boolean canUseNetwork() {
+ public int checkCanUseNetwork() {
Integer networkType = mSystemFacade.getActiveNetworkType();
if (networkType == null) {
- return false;
- }
- if (!isNetworkTypeAllowed(networkType)) {
- return false;
+ return NETWORK_UNUSABLE_GENERIC;
}
if (!isRoamingAllowed() && mSystemFacade.isNetworkRoaming()) {
- return false;
+ return NETWORK_UNUSABLE_GENERIC;
}
- return true;
+ return checkIsNetworkTypeAllowed(networkType);
}
private boolean isRoamingAllowed() {
@@ -359,20 +391,16 @@ public class DownloadInfo {
/**
* Check if this download can proceed over the given network type.
* @param networkType a constant from ConnectivityManager.TYPE_*.
+ * @return one of the NETWORK_* constants
*/
- private boolean isNetworkTypeAllowed(int networkType) {
+ private int checkIsNetworkTypeAllowed(int networkType) {
if (mIsPublicApi) {
int flag = translateNetworkTypeToApiFlag(networkType);
if ((flag & mAllowedNetworkTypes) == 0) {
- return false;
+ return NETWORK_UNUSABLE_GENERIC;
}
}
- if (!isSizeAllowedForNetwork(networkType)) {
- mPausedReason = mContext.getResources().getString(
- R.string.notification_need_wifi_for_size);
- return false;
- }
- return true;
+ return checkSizeAllowedForNetwork(networkType);
}
/**
@@ -397,19 +425,27 @@ public class DownloadInfo {
/**
* Check if the download's size prohibits it from running over the current network.
+ * @return one of the NETWORK_* constants
*/
- private boolean isSizeAllowedForNetwork(int networkType) {
+ private int checkSizeAllowedForNetwork(int networkType) {
if (mTotalBytes <= 0) {
- return true; // we don't know the size yet
+ return NETWORK_OK; // we don't know the size yet
}
if (networkType == ConnectivityManager.TYPE_WIFI) {
- return true; // anything goes over wifi
+ return NETWORK_OK; // anything goes over wifi
}
Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile();
- if (maxBytesOverMobile == null) {
- return true; // no limit
+ if (maxBytesOverMobile != null && mTotalBytes > maxBytesOverMobile) {
+ return NETWORK_UNUSABLE_DUE_TO_SIZE;
+ }
+ if (mBypassRecommendedSizeLimit == 0) {
+ Long recommendedMaxBytesOverMobile = mSystemFacade.getRecommendedMaxBytesOverMobile();
+ if (recommendedMaxBytesOverMobile != null
+ && mTotalBytes > recommendedMaxBytesOverMobile) {
+ return NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE;
+ }
}
- return mTotalBytes <= maxBytesOverMobile;
+ return NETWORK_OK;
}
void start(long now) {
@@ -505,4 +541,16 @@ public class DownloadInfo {
&& Downloads.Impl.isStatusSuccess(mStatus)
&& !DrmRawContent.DRM_MIMETYPE_MESSAGE_STRING.equalsIgnoreCase(mMimeType);
}
+
+ void notifyPauseDueToSize(boolean isWifiRequired) {
+ mPausedReason = mContext.getResources().getString(
+ R.string.notification_need_wifi_for_size);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(getAllDownloadsUri());
+ intent.setClassName(SizeLimitActivity.class.getPackage().getName(),
+ SizeLimitActivity.class.getName());
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra(EXTRA_IS_WIFI_REQUIRED, isWifiRequired);
+ mContext.startActivity(intent);
+ }
}
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index 102c611d..26065015 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -57,7 +57,7 @@ public final class DownloadProvider extends ContentProvider {
/** Database filename */
private static final String DB_NAME = "downloads.db";
/** Current database version */
- private static final int DB_VERSION = 103;
+ private static final int DB_VERSION = 104;
/** Name of table in the database */
private static final String DB_TABLE = "downloads";
@@ -223,6 +223,11 @@ public final class DownloadProvider extends ContentProvider {
makeCacheDownloadsInvisible(db);
break;
+ case 104:
+ addColumn(db, DB_TABLE, Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT,
+ "INTEGER NOT NULL DEFAULT 0");
+ break;
+
default:
throw new IllegalStateException("Don't know how to upgrade to " + version);
}
@@ -839,7 +844,9 @@ public final class DownloadProvider extends ContentProvider {
Integer status = values.getAsInteger(Downloads.Impl.COLUMN_STATUS);
boolean isRestart = status != null && status == Downloads.Impl.STATUS_PENDING;
- if (isRestart) {
+ boolean isUserBypassingSizeLimit =
+ values.containsKey(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
+ if (isRestart || isUserBypassingSizeLimit) {
startService = true;
}
}
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 57007f49..79778b0c 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -240,7 +240,13 @@ public class DownloadThread extends Thread {
* Check if current connectivity is valid for this request.
*/
private void checkConnectivity(State state) throws StopRequest {
- if (!mInfo.canUseNetwork()) {
+ int networkUsable = mInfo.checkCanUseNetwork();
+ if (networkUsable != DownloadInfo.NETWORK_OK) {
+ if (networkUsable == DownloadInfo.NETWORK_UNUSABLE_DUE_TO_SIZE) {
+ mInfo.notifyPauseDueToSize(true);
+ } else if (networkUsable == DownloadInfo.NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE) {
+ mInfo.notifyPauseDueToSize(false);
+ }
throw new StopRequest(Downloads.Impl.STATUS_RUNNING_PAUSED);
}
}
diff --git a/src/com/android/providers/downloads/RealSystemFacade.java b/src/com/android/providers/downloads/RealSystemFacade.java
index 421fc2be..ce86f739 100644
--- a/src/com/android/providers/downloads/RealSystemFacade.java
+++ b/src/com/android/providers/downloads/RealSystemFacade.java
@@ -71,6 +71,16 @@ class RealSystemFacade implements SystemFacade {
}
@Override
+ public Long getRecommendedMaxBytesOverMobile() {
+ try {
+ return Settings.Secure.getLong(mContext.getContentResolver(),
+ Settings.Secure.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+ } catch (SettingNotFoundException exc) {
+ return null;
+ }
+ }
+
+ @Override
public void sendBroadcast(Intent intent) {
mContext.sendBroadcast(intent);
}
diff --git a/src/com/android/providers/downloads/SizeLimitActivity.java b/src/com/android/providers/downloads/SizeLimitActivity.java
new file mode 100644
index 00000000..53e70de7
--- /dev/null
+++ b/src/com/android/providers/downloads/SizeLimitActivity.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.providers.downloads;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.Downloads;
+import android.text.format.Formatter;
+import android.util.Log;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+/**
+ * Activity to show dialogs to the user when a download exceeds a limit on download sizes for
+ * mobile networks. This activity gets started by the background download service when a download's
+ * size is discovered to be exceeded one of these thresholds.
+ */
+public class SizeLimitActivity extends Activity
+ implements DialogInterface.OnCancelListener, DialogInterface.OnClickListener {
+ private Dialog mDialog;
+ private Queue<Intent> mDownloadsToShow = new LinkedList<Intent>();
+ private Uri mCurrentUri;
+ private Intent mCurrentIntent;
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ setIntent(intent);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = getIntent();
+ if (intent != null) {
+ mDownloadsToShow.add(intent);
+ setIntent(null);
+ showNextDialog();
+ }
+ if (mDialog != null && !mDialog.isShowing()) {
+ mDialog.show();
+ }
+ }
+
+ private void showNextDialog() {
+ if (mDialog != null) {
+ return;
+ }
+
+ if (mDownloadsToShow.isEmpty()) {
+ finish();
+ return;
+ }
+
+ mCurrentIntent = mDownloadsToShow.poll();
+ mCurrentUri = mCurrentIntent.getData();
+ Cursor cursor = getContentResolver().query(mCurrentUri, null, null, null, null);
+ try {
+ if (!cursor.moveToFirst()) {
+ Log.e(Constants.TAG, "Empty cursor for URI " + mCurrentUri);
+ dialogClosed();
+ return;
+ }
+ showDialog(cursor);
+ } finally {
+ cursor.close();
+ }
+ }
+
+ private void showDialog(Cursor cursor) {
+ int size = cursor.getInt(cursor.getColumnIndexOrThrow(Downloads.Impl.COLUMN_TOTAL_BYTES));
+ String sizeString = Formatter.formatFileSize(this, size);
+ String queueText = getString(R.string.button_queue_for_wifi);
+ boolean isWifiRequired =
+ mCurrentIntent.getExtras().getBoolean(DownloadInfo.EXTRA_IS_WIFI_REQUIRED);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ if (isWifiRequired) {
+ builder.setTitle(R.string.wifi_required_title)
+ .setMessage(getString(R.string.wifi_required_body, sizeString, queueText))
+ .setPositiveButton(R.string.button_queue_for_wifi, this)
+ .setNegativeButton(R.string.button_cancel_download, this);
+ } else {
+ builder.setTitle(R.string.wifi_recommended_title)
+ .setMessage(getString(R.string.wifi_recommended_body, sizeString, queueText))
+ .setPositiveButton(R.string.button_start_now, this)
+ .setNegativeButton(R.string.button_queue_for_wifi, this);
+ }
+ mDialog = builder.setOnCancelListener(this).show();
+ }
+
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ dialogClosed();
+ }
+
+ private void dialogClosed() {
+ mDialog = null;
+ mCurrentUri = null;
+ showNextDialog();
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ boolean isRequired =
+ mCurrentIntent.getExtras().getBoolean(DownloadInfo.EXTRA_IS_WIFI_REQUIRED);
+ if (isRequired && which == AlertDialog.BUTTON_NEGATIVE) {
+ getContentResolver().delete(mCurrentUri, null, null);
+ } else if (!isRequired && which == AlertDialog.BUTTON_POSITIVE) {
+ ContentValues values = new ContentValues();
+ values.put(Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT, true);
+ getContentResolver().update(mCurrentUri, values , null, null);
+ }
+ dialogClosed();
+ }
+}
diff --git a/src/com/android/providers/downloads/SystemFacade.java b/src/com/android/providers/downloads/SystemFacade.java
index 50624c3a..ed0d3306 100644
--- a/src/com/android/providers/downloads/SystemFacade.java
+++ b/src/com/android/providers/downloads/SystemFacade.java
@@ -30,6 +30,13 @@ interface SystemFacade {
public Long getMaxBytesOverMobile();
/**
+ * @return recommended maximum size, in bytes, of downloads that may go over a mobile
+ * connection; or null if there's no recommended limit. The user will have the option to bypass
+ * this limit.
+ */
+ public Long getRecommendedMaxBytesOverMobile();
+
+ /**
* Send a broadcast intent.
*/
public void sendBroadcast(Intent intent);
diff --git a/tests/src/com/android/providers/downloads/FakeSystemFacade.java b/tests/src/com/android/providers/downloads/FakeSystemFacade.java
index d80bd4ad..5263015c 100644
--- a/tests/src/com/android/providers/downloads/FakeSystemFacade.java
+++ b/tests/src/com/android/providers/downloads/FakeSystemFacade.java
@@ -18,6 +18,7 @@ public class FakeSystemFacade implements SystemFacade {
Integer mActiveNetworkType = ConnectivityManager.TYPE_WIFI;
boolean mIsRoaming = false;
Long mMaxBytesOverMobile = null;
+ Long mRecommendedMaxBytesOverMobile = null;
List<Intent> mBroadcastsSent = new ArrayList<Intent>();
Map<Long,Notification> mActiveNotifications = new HashMap<Long,Notification>();
List<Notification> mCanceledNotifications = new ArrayList<Notification>();
@@ -43,6 +44,10 @@ public class FakeSystemFacade implements SystemFacade {
return mMaxBytesOverMobile ;
}
+ public Long getRecommendedMaxBytesOverMobile() {
+ return mRecommendedMaxBytesOverMobile ;
+ }
+
@Override
public void sendBroadcast(Intent intent) {
mBroadcastsSent.add(intent);
diff --git a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
index 554cc1ea..6c81bc65 100644
--- a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
+++ b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
@@ -536,7 +536,6 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest {
public void testEmptyFields() throws Exception {
Download download = enqueueRequest(getRequest());
- assertNull(download.getStringField(DownloadManager.COLUMN_LOCAL_URI));
assertEquals("", download.getStringField(DownloadManager.COLUMN_TITLE));
assertEquals("", download.getStringField(DownloadManager.COLUMN_DESCRIPTION));
assertNull(download.getStringField(DownloadManager.COLUMN_MEDIA_TYPE));