/* * Copyright (C) 2008 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.konka.music.core.providers.downloads; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import android.content.ContentResolver; import android.content.ContentUris; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.CharArrayBuffer; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.Uri; import android.provider.BaseColumns; import android.util.Log; import android.util.Pair; import com.konka.music.core.providers.DownloadManager; /** * Stores information about an individual download. */ public class DownloadInfo { public static class Reader { private ContentResolver mResolver; private Cursor mCursor; private CharArrayBuffer mOldChars; private CharArrayBuffer mNewChars; public Reader(ContentResolver resolver, Cursor cursor) { mResolver = resolver; mCursor = cursor; } public DownloadInfo newDownloadInfo(Context context, SystemFacade systemFacade) { DownloadInfo info = new DownloadInfo(context, systemFacade); updateFromDatabase(info); readRequestHeaders(info); return info; } public void updateFromDatabase(DownloadInfo info) { info.mId = getLong(BaseColumns._ID); info.mUri = getString(info.mUri, Downloads.COLUMN_URI); info.mNoIntegrity = getInt(Downloads.COLUMN_NO_INTEGRITY) == 1; info.mHint = getString(info.mHint, Downloads.COLUMN_FILE_NAME_HINT); info.mFileName = getString(info.mFileName, Downloads._DATA); info.mMimeType = getString(info.mMimeType, Downloads.COLUMN_MIME_TYPE); info.mDestination = getInt(Downloads.COLUMN_DESTINATION); info.mVisibility = getInt(Downloads.COLUMN_VISIBILITY); info.mStatus = getInt(Downloads.COLUMN_STATUS); info.mNumFailed = getInt(Constants.FAILED_CONNECTIONS); int retryRedirect = getInt(Constants.RETRY_AFTER_X_REDIRECT_COUNT); info.mRetryAfter = retryRedirect & 0xfffffff; info.mLastMod = getLong(Downloads.COLUMN_LAST_MODIFICATION); info.mPackage = getString(info.mPackage, Downloads.COLUMN_NOTIFICATION_PACKAGE); info.mClass = getString(info.mClass, Downloads.COLUMN_NOTIFICATION_CLASS); info.mExtras = getString(info.mExtras, Downloads.COLUMN_NOTIFICATION_EXTRAS); info.mCookies = getString(info.mCookies, Downloads.COLUMN_COOKIE_DATA); info.mUserAgent = getString(info.mUserAgent, Downloads.COLUMN_USER_AGENT); info.mReferer = getString(info.mReferer, Downloads.COLUMN_REFERER); info.mTotalBytes = getLong(Downloads.COLUMN_TOTAL_BYTES); info.mCurrentBytes = getLong(Downloads.COLUMN_CURRENT_BYTES); info.mETag = getString(info.mETag, Constants.ETAG); info.mDeleted = getInt(Downloads.COLUMN_DELETED) == 1; info.mIsPublicApi = getInt(Downloads.COLUMN_IS_PUBLIC_API) != 0; info.mAllowedNetworkTypes = getInt(Downloads.COLUMN_ALLOWED_NETWORK_TYPES); info.mAllowRoaming = getInt(Downloads.COLUMN_ALLOW_ROAMING) != 0; info.mTitle = getString(info.mTitle, Downloads.COLUMN_TITLE); info.mDescription = getString(info.mDescription, Downloads.COLUMN_DESCRIPTION); info.mBypassRecommendedSizeLimit = getInt(Downloads.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT); synchronized (this) { info.mControl = getInt(Downloads.COLUMN_CONTROL); } } private void readRequestHeaders(DownloadInfo info) { info.mRequestHeaders.clear(); Uri headerUri = Uri.withAppendedPath(info.getAllDownloadsUri(), Downloads.RequestHeaders.URI_SEGMENT); Cursor cursor = mResolver.query(headerUri, null, null, null, null); try { int headerIndex = cursor.getColumnIndexOrThrow(Downloads.RequestHeaders.COLUMN_HEADER); int valueIndex = cursor.getColumnIndexOrThrow(Downloads.RequestHeaders.COLUMN_VALUE); for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { addHeader(info, cursor.getString(headerIndex), cursor.getString(valueIndex)); } } finally { cursor.close(); } if (info.mCookies != null) { addHeader(info, "Cookie", info.mCookies); } if (info.mReferer != null) { addHeader(info, "Referer", info.mReferer); } } private void addHeader(DownloadInfo info, String header, String value) { info.mRequestHeaders.add(Pair.create(header, value)); } /** * Returns a String that holds the current value of the column, optimizing for the case where the value hasn't changed. */ private String getString(String old, String column) { int index = mCursor.getColumnIndexOrThrow(column); if (old == null) { return mCursor.getString(index); } if (mNewChars == null) { mNewChars = new CharArrayBuffer(128); } mCursor.copyStringToBuffer(index, mNewChars); int length = mNewChars.sizeCopied; if (length != old.length()) { return new String(mNewChars.data, 0, length); } if (mOldChars == null || mOldChars.sizeCopied < length) { mOldChars = new CharArrayBuffer(length); } char[] oldArray = mOldChars.data; char[] newArray = mNewChars.data; old.getChars(0, length, oldArray, 0); for (int i = length - 1; i >= 0; --i) { if (oldArray[i] != newArray[i]) { return new String(newArray, 0, length); } } return old; } private Integer getInt(String column) { return mCursor.getInt(mCursor.getColumnIndexOrThrow(column)); } private Long getLong(String column) { return mCursor.getLong(mCursor.getColumnIndexOrThrow(column)); } } // 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; /** * There is no network connectivity. */ public static final int NETWORK_NO_CONNECTION = 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; /** * The current connection is roaming, and the download can't proceed over a roaming connection. */ public static final int NETWORK_CANNOT_USE_ROAMING = 5; /** * The app requesting the download specific that it can't use the current network connection. */ public static final int NETWORK_TYPE_DISALLOWED_BY_REQUESTOR = 6; /** * 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; public String mHint; public String mFileName; public String mMimeType; public int mDestination; public int mVisibility; public int mControl; public int mStatus; public int mNumFailed;// 重试失败次数 public int mRetryAfter; public long mLastMod; public String mPackage; public String mClass; public String mExtras; public String mCookies; public String mUserAgent; public String mReferer; public long mTotalBytes; public long mCurrentBytes; public String mETag; public boolean mDeleted; public boolean mIsPublicApi; public int mAllowedNetworkTypes; public boolean mAllowRoaming; public String mTitle; public String mDescription; public int mBypassRecommendedSizeLimit; public int mFuzz; // volatile public volatile boolean mHasActiveThread;// 线程是否是活的 private List<Pair<String, String>> mRequestHeaders = new ArrayList<Pair<String, String>>(); private SystemFacade mSystemFacade; private Context mContext; private DownloadInfo(Context context, SystemFacade systemFacade) { mContext = context; mSystemFacade = systemFacade; mFuzz = Helpers.sRandom.nextInt(1001); } public Collection<Pair<String, String>> getHeaders() { return Collections.unmodifiableList(mRequestHeaders); } public void sendIntentIfRequested() { if (mPackage == null) { return; } Intent intent; if (mIsPublicApi) { intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETE); intent.setPackage(mPackage); intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, mId); } else { // legacy behavior if (mClass == null) { return; } intent = new Intent(Downloads.ACTION_DOWNLOAD_COMPLETED); intent.setClassName(mPackage, mClass); if (mExtras != null) { intent.putExtra(Downloads.COLUMN_NOTIFICATION_EXTRAS, mExtras); } // We only send the content: URI, for security reasons. Otherwise, // malicious // applications would have an easier time spoofing download results // by // sending spoofed intents. intent.setData(getMyDownloadsUri()); } mSystemFacade.sendBroadcast(intent);// 发送广播 } /** * Returns the time when a download should be restarted. */ public long restartTime(long now) { if (mNumFailed == 0) { return now; } if (mRetryAfter > 0) { return mLastMod + mRetryAfter; } return mLastMod + Constants.RETRY_FIRST_DELAY * (1000 + mFuzz) * (1 << (mNumFailed - 1)); } /** * Returns whether this download (which the download manager hasn't seen yet) should be started. */ private boolean isReadyToStart(long now) { if (mHasActiveThread) { // already running return false; } if (mControl == Downloads.CONTROL_PAUSED) { // the download is paused, so it's not going to start return false; } switch (mStatus) { case 0: // status hasn't been initialized yet, this is a new download case Downloads.STATUS_PENDING: // download is explicit marked as ready // to start case Downloads.STATUS_RUNNING: // download interrupted (process killed // etc) while // running, without a chance to update // the database return true; case Downloads.STATUS_WAITING_FOR_NETWORK: case Downloads.STATUS_QUEUED_FOR_WIFI: return checkCanUseNetwork() == NETWORK_OK; case Downloads.STATUS_WAITING_TO_RETRY: // download was waiting for a delayed restart return restartTime(now) <= now; } return false; } /** * Returns whether this download has a visible notification after completion. */ public boolean hasCompletionNotification() { if (!Downloads.isStatusCompleted(mStatus)) { return false; } if (mVisibility == Downloads.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) { return true; } return false; } /** * 检测是否有可以使用的网络 Returns whether this download is allowed to use the network. * * @return one of the NETWORK_* constants */ public int checkCanUseNetwork() { Integer networkType = mSystemFacade.getActiveNetworkType(); if (networkType == null) { return NETWORK_NO_CONNECTION; } if (!isRoamingAllowed() && mSystemFacade.isNetworkRoaming()) { return NETWORK_CANNOT_USE_ROAMING; } return checkIsNetworkTypeAllowed(networkType); } private boolean isRoamingAllowed() { if (mIsPublicApi) { return mAllowRoaming; } else { // legacy behavior return true; } } /** * @return a non-localized string appropriate for logging corresponding to one of the NETWORK_* constants. */ public String getLogMessageForNetworkError(int networkError) { switch (networkError) { case NETWORK_RECOMMENDED_UNUSABLE_DUE_TO_SIZE: return "download size exceeds recommended limit for mobile network"; case NETWORK_UNUSABLE_DUE_TO_SIZE: return "download size exceeds limit for mobile network"; case NETWORK_NO_CONNECTION: return "no network connection available"; case NETWORK_CANNOT_USE_ROAMING: return "download cannot use the current network connection because it is roaming"; case NETWORK_TYPE_DISALLOWED_BY_REQUESTOR: return "download was requested to not use the current network type"; default: return "unknown error with network connectivity"; } } /** * 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 int checkIsNetworkTypeAllowed(int networkType) { if (mIsPublicApi) { int flag = translateNetworkTypeToApiFlag(networkType); if ((flag & mAllowedNetworkTypes) == 0) { return NETWORK_TYPE_DISALLOWED_BY_REQUESTOR; } } return checkSizeAllowedForNetwork(networkType); } /** * Translate a ConnectivityManager.TYPE_* constant to the corresponding DownloadManager.Request.NETWORK_* bit flag. */ private int translateNetworkTypeToApiFlag(int networkType) { switch (networkType) { case ConnectivityManager.TYPE_MOBILE: return DownloadManager.Request.NETWORK_MOBILE; case ConnectivityManager.TYPE_WIFI: return DownloadManager.Request.NETWORK_WIFI; default: return 0; } } /** * Check if the download's size prohibits it from running over the current network. * * @return one of the NETWORK_* constants */ private int checkSizeAllowedForNetwork(int networkType) { if (mTotalBytes <= 0) { return NETWORK_OK; // we don't know the size yet } if (networkType == ConnectivityManager.TYPE_WIFI) { return NETWORK_OK; // anything goes over wifi } Long maxBytesOverMobile = mSystemFacade.getMaxBytesOverMobile(); 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 NETWORK_OK; } /** * 这里启动下载线程 * * @param now */ void startIfReady(long now) { if (!isReadyToStart(now)) { return; } if (Constants.LOGV) { Log.v(Constants.TAG, "Service spawning thread to handle download " + mId); } if (mHasActiveThread) { throw new IllegalStateException("Multiple threads on same download"); } if (mStatus != Downloads.STATUS_RUNNING) { mStatus = Downloads.STATUS_RUNNING; ContentValues values = new ContentValues(); values.put(Downloads.COLUMN_STATUS, mStatus); mContext.getContentResolver().update(getAllDownloadsUri(), values, null, null);// 更新内容提供者 return; } DownloadThread downloader = new DownloadThread(mContext, mSystemFacade, this); mHasActiveThread = true; mSystemFacade.startThread(downloader,true); } public Uri getMyDownloadsUri() { return ContentUris.withAppendedId(Downloads.CONTENT_URI, mId); } public Uri getAllDownloadsUri() { return ContentUris.withAppendedId(Downloads.ALL_DOWNLOADS_CONTENT_URI, mId); } public void logVerboseInfo() { Log.v(Constants.TAG, "Service adding new entry"); Log.v(Constants.TAG, "ID : " + mId); Log.v(Constants.TAG, "URI : " + ((mUri != null) ? "yes" : "no")); Log.v(Constants.TAG, "NO_INTEG: " + mNoIntegrity); Log.v(Constants.TAG, "HINT : " + mHint); Log.v(Constants.TAG, "FILENAME: " + mFileName); Log.v(Constants.TAG, "MIMETYPE: " + mMimeType); Log.v(Constants.TAG, "DESTINAT: " + mDestination); Log.v(Constants.TAG, "VISIBILI: " + mVisibility); Log.v(Constants.TAG, "CONTROL : " + mControl); Log.v(Constants.TAG, "STATUS : " + mStatus); Log.v(Constants.TAG, "FAILED_C: " + mNumFailed); Log.v(Constants.TAG, "RETRY_AF: " + mRetryAfter); Log.v(Constants.TAG, "LAST_MOD: " + mLastMod); Log.v(Constants.TAG, "PACKAGE : " + mPackage); Log.v(Constants.TAG, "CLASS : " + mClass); Log.v(Constants.TAG, "COOKIES : " + ((mCookies != null) ? "yes" : "no")); Log.v(Constants.TAG, "AGENT : " + mUserAgent); Log.v(Constants.TAG, "REFERER : " + ((mReferer != null) ? "yes" : "no")); Log.v(Constants.TAG, "TOTAL : " + mTotalBytes); Log.v(Constants.TAG, "CURRENT : " + mCurrentBytes); Log.v(Constants.TAG, "ETAG : " + mETag); Log.v(Constants.TAG, "DELETED : " + mDeleted); } /** * Returns the amount of time (as measured from the "now" parameter) at which a download will be active. 0 = immediately - service should stick around to handle this download. -1 = never - service can go away without ever waking up. positive value - service must wake up in the future, as specified in ms from "now" */ long nextAction(long now) { if (Downloads.isStatusCompleted(mStatus)) { return -1; } if (mStatus != Downloads.STATUS_WAITING_TO_RETRY) { return 0; } long when = restartTime(now); if (when <= now) { return 0; } return when - now; } void notifyPauseDueToSize(boolean isWifiRequired) { // 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); } }