diff options
author | Jeff Sharkey <jsharkey@android.com> | 2011-06-15 11:18:46 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2011-06-17 16:34:50 -0700 |
commit | 961024389b3782936a40a7d090d670290bb66c3c (patch) | |
tree | 7188814c93d784118a5efea428eb203418f88575 | |
parent | 1fec3e68161dce11cc6995c8c3de50d6ca37f2e7 (diff) | |
download | android_packages_providers_DownloadProvider-961024389b3782936a40a7d090d670290bb66c3c.tar.gz android_packages_providers_DownloadProvider-961024389b3782936a40a7d090d670290bb66c3c.tar.bz2 android_packages_providers_DownloadProvider-961024389b3782936a40a7d090d670290bb66c3c.zip |
Teach DownloadManager about network policy.
Now network access is determined by using getActiveNetworkInfoForUid()
which uses BLOCKED to indicate that network should be rejected for
the requesting UID. While download in progress, watch for any policy
changes that should trigger pause.
Also check NetworkInfo.isConnected() for correctness.
Change-Id: I1efa79823f15ecc3fa088a6719da1b770c64b255
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)); |