summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSteve Howard <showard@google.com>2010-09-17 16:45:58 -0700
committerSteve Howard <showard@google.com>2010-09-21 12:43:05 -0700
commitd319729622da1893e895f2e35f41d01ecdca3705 (patch)
tree02994abdf2725061b798cb32339b044592cbf581
parent78f433c68f14dfba605ceb0e5f3dc54243efd2b2 (diff)
downloadandroid_packages_providers_DownloadProvider-d319729622da1893e895f2e35f41d01ecdca3705.tar.gz
android_packages_providers_DownloadProvider-d319729622da1893e895f2e35f41d01ecdca3705.tar.bz2
android_packages_providers_DownloadProvider-d319729622da1893e895f2e35f41d01ecdca3705.zip
Implement dialogs for wifi required + recommended limits.
This change extends the original work to add a size limit over which wifi is required to download a file. First, this change adds a second size limit, over which wifi is recommended but not required. The user has the option to bypass this limit. Second, this change implements dialogs shown to the user when either limit is exceeded. These dialogs are shown by the background download manager service when a download is started and found to be over the limit (and wifi is not connected). I'm including one small fix to the unit tests needed from the previous change. Change-Id: Ia0f0acaa7b0d00e98355925c3446c0472048df10
-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));