diff options
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)); |