summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AndroidManifest.xml2
-rw-r--r--res/values/strings.xml4
-rw-r--r--src/com/android/providers/downloads/DownloadInfo.java19
-rw-r--r--src/com/android/providers/downloads/DownloadThread.java86
-rw-r--r--src/com/android/providers/downloads/Helpers.java6
-rw-r--r--src/com/android/providers/downloads/RealSystemFacade.java13
-rw-r--r--src/com/android/providers/downloads/SystemFacade.java7
-rw-r--r--tests/src/com/android/providers/downloads/FakeSystemFacade.java9
-rw-r--r--tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java5
-rw-r--r--ui/src/com/android/providers/downloads/ui/DownloadAdapter.java10
10 files changed, 118 insertions, 43 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index d41702dc..b9c551e8 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -53,6 +53,8 @@
<uses-permission android:name="android.permission.INSTALL_DRM" />
<uses-permission android:name="android.permission.ACCESS_ALL_DOWNLOADS" />
<uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
+ <!-- TODO: replace with READ_NETWORK_POLICY permission when it exists -->
+ <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<application android:process="android.process.media"
android:label="@string/app_label">
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 9f410896..270a54ce 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -156,6 +156,10 @@
[CHAR LIMIT=24] -->
<string name="notification_need_wifi_for_size">Download size requires Wi-Fi</string>
+ <!-- Notification shown when a download has been paused because a user policy
+ has blocked network access to applications running in background. [CHAR LIMIT=24] -->
+ <string name="notification_paused_in_background">Paused in background</string>
+
<!-- Title for dialog when a download exceeds the carrier-specified maximum size of downloads
over the mobile network and Wi-Fi is required. The user has the choice to either queue the
download to start next time Wi-Fi is available or cancel the download altogether. [CHAR
diff --git a/src/com/android/providers/downloads/DownloadInfo.java b/src/com/android/providers/downloads/DownloadInfo.java
index 313386fe..00b10452 100644
--- a/src/com/android/providers/downloads/DownloadInfo.java
+++ b/src/com/android/providers/downloads/DownloadInfo.java
@@ -24,6 +24,8 @@ import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.NetworkInfo.DetailedState;
import android.net.Uri;
import android.os.Environment;
import android.provider.Downloads;
@@ -177,6 +179,11 @@ public class DownloadInfo {
public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6;
/**
+ * Current network is blocked for requesting application.
+ */
+ public static final int NETWORK_BLOCKED = 7;
+
+ /**
* 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.
*/
@@ -333,14 +340,17 @@ public class DownloadInfo {
* @return one of the NETWORK_* constants
*/
public int checkCanUseNetwork() {
- Integer networkType = mSystemFacade.getActiveNetworkType();
- if (networkType == null) {
+ final NetworkInfo info = mSystemFacade.getActiveNetworkInfo(mUid);
+ if (info == null) {
return NETWORK_NO_CONNECTION;
}
+ if (DetailedState.BLOCKED.equals(info.getDetailedState())) {
+ return NETWORK_BLOCKED;
+ }
if (!isRoamingAllowed() && mSystemFacade.isNetworkRoaming()) {
return NETWORK_CANNOT_USE_ROAMING;
}
- return checkIsNetworkTypeAllowed(networkType);
+ return checkIsNetworkTypeAllowed(info.getType());
}
private boolean isRoamingAllowed() {
@@ -372,6 +382,9 @@ public class DownloadInfo {
case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR:
return "download was requested to not use the current network type";
+ case NETWORK_BLOCKED:
+ return "network is blocked for requesting application";
+
default:
return "unknown error with network connectivity";
}
diff --git a/src/com/android/providers/downloads/DownloadThread.java b/src/com/android/providers/downloads/DownloadThread.java
index 7ddfe959..75d4ca32 100644
--- a/src/com/android/providers/downloads/DownloadThread.java
+++ b/src/com/android/providers/downloads/DownloadThread.java
@@ -16,14 +16,15 @@
package com.android.providers.downloads;
-import org.apache.http.conn.params.ConnRouteParams;
+import static android.Manifest.permission.MANAGE_NETWORK_POLICY;
import android.content.ContentValues;
import android.content.Context;
-import android.content.Intent;
-import android.net.http.AndroidHttpClient;
+import android.net.INetworkPolicyListener;
+import android.net.NetworkPolicyManager;
import android.net.Proxy;
import android.net.TrafficStats;
+import android.net.http.AndroidHttpClient;
import android.os.FileUtils;
import android.os.PowerManager;
import android.os.Process;
@@ -35,6 +36,7 @@ import android.util.Pair;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
+import org.apache.http.conn.params.ConnRouteParams;
import java.io.File;
import java.io.FileNotFoundException;
@@ -57,6 +59,8 @@ public class DownloadThread extends Thread {
private final StorageManager mStorageManager;
private DrmConvertSession mDrmConvertSession;
+ private volatile boolean mPolicyDirty;
+
public DownloadThread(Context context, SystemFacade systemFacade, DownloadInfo info,
StorageManager storageManager) {
mContext = context;
@@ -133,11 +137,16 @@ public class DownloadThread extends Thread {
int finalStatus = Downloads.Impl.STATUS_UNKNOWN_ERROR;
String errorMsg = null;
+ final NetworkPolicyManager netPolicy = NetworkPolicyManager.getSystemService(mContext);
+ final PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+
try {
- PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Constants.TAG);
wakeLock.acquire();
+ // while performing download, register for rules updates
+ netPolicy.registerListener(mPolicyListener);
+
if (Constants.LOGV) {
Log.v(Constants.TAG, "initiating download for " + mInfo.mUri);
}
@@ -203,6 +212,8 @@ public class DownloadThread extends Thread {
state.mNewUri, state.mMimeType, errorMsg);
DownloadHandler.getInstance().dequeueDownload(mInfo.mId);
+ netPolicy.unregisterListener(mPolicyListener);
+
if (wakeLock != null) {
wakeLock.release();
wakeLock = null;
@@ -242,6 +253,9 @@ public class DownloadThread extends Thread {
* Check if current connectivity is valid for this request.
*/
private void checkConnectivity() throws StopRequestException {
+ // checking connectivity will apply current policy
+ mPolicyDirty = false;
+
int networkUsable = mInfo.checkCanUseNetwork();
if (networkUsable != DownloadInfo.NETWORK_OK) {
int status = Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
@@ -251,6 +265,8 @@ public class DownloadThread extends Thread {
} else if (networkUsable == DownloadInfo.NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE) {
status = Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
mInfo.notifyPauseDueToSize(false);
+ } else if (networkUsable == DownloadInfo.NETWORK_BLOCKED) {
+ status = Downloads.Impl.STATUS_BLOCKED;
}
throw new StopRequestException(status,
mInfo.getLogMessageForNetworkError(networkUsable));
@@ -262,8 +278,9 @@ public class DownloadThread extends Thread {
* @param data buffer to use to read data
* @param entityStream stream for reading the HTTP response entity
*/
- private void transferData(State state, InnerState innerState, byte[] data,
- InputStream entityStream) throws StopRequestException {
+ private void transferData(
+ State state, InnerState innerState, byte[] data, InputStream entityStream)
+ throws StopRequestException {
for (;;) {
int bytesRead = readFromResponse(state, innerState, data, entityStream);
if (bytesRead == -1) { // success, end of stream already reached
@@ -364,12 +381,17 @@ public class DownloadThread extends Thread {
private void checkPausedOrCanceled(State state) throws StopRequestException {
synchronized (mInfo) {
if (mInfo.mControl == Downloads.Impl.CONTROL_PAUSED) {
- throw new StopRequestException(Downloads.Impl.STATUS_PAUSED_BY_APP,
- "download paused by owner");
+ throw new StopRequestException(
+ Downloads.Impl.STATUS_PAUSED_BY_APP, "download paused by owner");
+ }
+ if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
+ throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
}
}
- if (mInfo.mStatus == Downloads.Impl.STATUS_CANCELED) {
- throw new StopRequestException(Downloads.Impl.STATUS_CANCELED, "download canceled");
+
+ // if policy has been changed, trigger connectivity check
+ if (mPolicyDirty) {
+ checkConnectivity();
}
}
@@ -471,7 +493,7 @@ public class DownloadThread extends Thread {
try {
return entityStream.read(data);
} catch (IOException ex) {
- logNetworkState();
+ logNetworkState(mInfo.mUid);
ContentValues values = new ContentValues();
values.put(Downloads.Impl.COLUMN_CURRENT_BYTES, innerState.mBytesSoFar);
mContext.getContentResolver().update(mInfo.getAllDownloadsUri(), values, null, null);
@@ -496,16 +518,16 @@ public class DownloadThread extends Thread {
try {
return response.getEntity().getContent();
} catch (IOException ex) {
- logNetworkState();
+ logNetworkState(mInfo.mUid);
throw new StopRequestException(getFinalStatusForHttpError(state),
"while getting entity: " + ex.toString(), ex);
}
}
- private void logNetworkState() {
+ private void logNetworkState(int uid) {
if (Constants.LOGX) {
Log.i(Constants.TAG,
- "Net " + (Helpers.isNetworkAvailable(mSystemFacade) ? "Up" : "Down"));
+ "Net " + (Helpers.isNetworkAvailable(mSystemFacade, uid) ? "Up" : "Down"));
}
}
@@ -766,7 +788,7 @@ public class DownloadThread extends Thread {
throw new StopRequestException(Downloads.Impl.STATUS_HTTP_DATA_ERROR,
"while trying to execute request: " + ex.toString(), ex);
} catch (IOException ex) {
- logNetworkState();
+ logNetworkState(mInfo.mUid);
throw new StopRequestException(getFinalStatusForHttpError(state),
"while trying to execute request: " + ex.toString(), ex);
}
@@ -775,10 +797,15 @@ public class DownloadThread extends Thread {
private int getFinalStatusForHttpError(State state) {
int networkUsable = mInfo.checkCanUseNetwork();
if (networkUsable != DownloadInfo.NETWORK_OK) {
- return (networkUsable == DownloadInfo.NETWORK_UNUSABLE_DUE_TO_SIZE ||
- networkUsable == DownloadInfo.NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE)
- ? Downloads.Impl.STATUS_QUEUED_FOR_WIFI
- : Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
+ switch (networkUsable) {
+ case DownloadInfo.NETWORK_UNUSABLE_DUE_TO_SIZE:
+ case DownloadInfo.NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE:
+ return Downloads.Impl.STATUS_QUEUED_FOR_WIFI;
+ case DownloadInfo.NETWORK_BLOCKED:
+ return Downloads.Impl.STATUS_BLOCKED;
+ default:
+ return Downloads.Impl.STATUS_WAITING_FOR_NETWORK;
+ }
} else if (mInfo.mNumFailed < Constants.MAX_RETRIES) {
state.mCountRetry = true;
return Downloads.Impl.STATUS_WAITING_TO_RETRY;
@@ -938,4 +965,25 @@ public class DownloadThread extends Thread {
return null;
}
}
+
+ private INetworkPolicyListener mPolicyListener = new INetworkPolicyListener.Stub() {
+ @Override
+ public void onUidRulesChanged(int uid, int uidRules) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, Constants.TAG);
+
+ if (uid == mInfo.mUid) {
+ mPolicyDirty = true;
+ }
+ }
+
+ @Override
+ public void onMeteredIfacesChanged(String[] meteredIfaces) {
+ // only someone like NPMS should only be calling us
+ mContext.enforceCallingOrSelfPermission(MANAGE_NETWORK_POLICY, Constants.TAG);
+
+ mPolicyDirty = true;
+ }
+ };
+
}
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 543b652d..cc7311de 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Environment;
import android.os.SystemClock;
@@ -356,8 +357,9 @@ public class Helpers {
/**
* Returns whether the network is available
*/
- public static boolean isNetworkAvailable(SystemFacade system) {
- return system.getActiveNetworkType() != null;
+ public static boolean isNetworkAvailable(SystemFacade system, int uid) {
+ final NetworkInfo info = system.getActiveNetworkInfo(uid);
+ return info != null && info.isConnected();
}
/**
diff --git a/src/com/android/providers/downloads/RealSystemFacade.java b/src/com/android/providers/downloads/RealSystemFacade.java
index 71dac5fe..f2423d47 100644
--- a/src/com/android/providers/downloads/RealSystemFacade.java
+++ b/src/com/android/providers/downloads/RealSystemFacade.java
@@ -27,7 +27,7 @@ class RealSystemFacade implements SystemFacade {
return System.currentTimeMillis();
}
- public Integer getActiveNetworkType() {
+ public NetworkInfo getActiveNetworkInfo(int uid) {
ConnectivityManager connectivity =
(ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (connectivity == null) {
@@ -35,14 +35,11 @@ class RealSystemFacade implements SystemFacade {
return null;
}
- NetworkInfo activeInfo = connectivity.getActiveNetworkInfo();
- if (activeInfo == null) {
- if (Constants.LOGVV) {
- Log.v(Constants.TAG, "network is not available");
- }
- return null;
+ final NetworkInfo activeInfo = connectivity.getActiveNetworkInfoForUid(uid);
+ if (activeInfo == null && Constants.LOGVV) {
+ Log.v(Constants.TAG, "network is not available");
}
- return activeInfo.getType();
+ return activeInfo;
}
public boolean isNetworkRoaming() {
diff --git a/src/com/android/providers/downloads/SystemFacade.java b/src/com/android/providers/downloads/SystemFacade.java
index ed0d3306..a1e4098c 100644
--- a/src/com/android/providers/downloads/SystemFacade.java
+++ b/src/com/android/providers/downloads/SystemFacade.java
@@ -4,6 +4,7 @@ package com.android.providers.downloads;
import android.app.Notification;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.NetworkInfo;
interface SystemFacade {
@@ -13,10 +14,10 @@ interface SystemFacade {
public long currentTimeMillis();
/**
- * @return Network type (as in ConnectivityManager.TYPE_*) of currently active network, or null
- * if there's no active connection.
+ * @return Currently active network, or null if there's no active
+ * connection.
*/
- public Integer getActiveNetworkType();
+ public NetworkInfo getActiveNetworkInfo(int uid);
/**
* @see android.telephony.TelephonyManager#isNetworkRoaming
diff --git a/tests/src/com/android/providers/downloads/FakeSystemFacade.java b/tests/src/com/android/providers/downloads/FakeSystemFacade.java
index 9620ffc3..fbaf6366 100644
--- a/tests/src/com/android/providers/downloads/FakeSystemFacade.java
+++ b/tests/src/com/android/providers/downloads/FakeSystemFacade.java
@@ -4,6 +4,7 @@ import android.app.Notification;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
import android.test.AssertionFailedError;
import java.util.ArrayList;
@@ -36,8 +37,12 @@ public class FakeSystemFacade implements SystemFacade {
return mTimeMillis;
}
- public Integer getActiveNetworkType() {
- return mActiveNetworkType;
+ public NetworkInfo getActiveNetworkInfo(int uid) {
+ if (mActiveNetworkType == null) {
+ return null;
+ } else {
+ return new NetworkInfo(mActiveNetworkType, 0, null, null);
+ }
}
public boolean isNetworkRoaming() {
diff --git a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
index 64c19530..f2a26f12 100644
--- a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
+++ b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java
@@ -441,11 +441,12 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest {
}
public void testBasicConnectivityChanges() throws Exception {
+ // without connectivity, download immediately pauses
+ mSystemFacade.mActiveNetworkType = null;
+
enqueueResponse(HTTP_OK, FILE_CONTENT);
Download download = enqueueRequest(getRequest());
- // without connectivity, download immediately pauses
- mSystemFacade.mActiveNetworkType = null;
download.runUntilStatus(DownloadManager.STATUS_PAUSED);
// connecting should start the download
diff --git a/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java b/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java
index 33f9531d..d09cb18f 100644
--- a/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java
+++ b/ui/src/com/android/providers/downloads/ui/DownloadAdapter.java
@@ -155,10 +155,12 @@ public class DownloadAdapter extends CursorAdapter {
return R.string.download_running;
case DownloadManager.STATUS_PAUSED:
- if (mCursor.getInt(mReasonColumnId) == DownloadManager.PAUSED_QUEUED_FOR_WIFI) {
- return R.string.download_queued;
- } else {
- return R.string.download_running;
+ final int reason = mCursor.getInt(mReasonColumnId);
+ switch (reason) {
+ case DownloadManager.PAUSED_QUEUED_FOR_WIFI:
+ return R.string.download_queued;
+ default:
+ return R.string.download_running;
}
}
throw new IllegalStateException("Unknown status: " + mCursor.getInt(mStatusColumnId));