/*
* Copyright (C) 2010 mAPPn.Inc
*
* 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.lan.nicehair.common.download;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.CharArrayBuffer;
import android.database.Cursor;
import android.net.Uri;
import com.lan.nicehair.common.download.DownloadManager.Impl;
import com.lan.nicehair.utils.AppLog;
import com.lan.nicehair.utils.Utils;
/**
* Stores information about an individual download.
*/
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
private final String TAG="DownloadInfo";
/**
* 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;
/**
* 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";
// ID
public long mId;
// 下载链接
public String mUri;
// 提示
public String mHint;
// 文件名
public String mFileName;
// MIME TYPE
public String mMimeType;
// 存储路径
public int mDestination;
// 可见性
public int mVisibility;
// 下载控制(暂停、取消)
public int mControl;
// 下载状态
public int mStatus;
// 下载失败次数
public int mNumFailed;
// 重试时间
public int mRetryAfter;
// 重定向次数
public int mRedirectCount;
// 最后修改时间
public long mLastMod;
// 提醒包名
public String mPackage;
// 提醒类名
public String mClass;
// 用于提醒的额外信息
public String mExtras;
// 文件大小
public long mTotalBytes;
// 已经下载的大小
public long mCurrentBytes;
// 文件完整性ETAG
public String mETag;
// 是否删除此记录
public boolean mDeleted;
// 标题
public String mTitle;
// 描述信息
public String mDescription;
// 请求来源
public int mSource;
// MD5校验码
public String mMD5;
// 应用包名
public String mPackageName;
public int mFuzz;
public volatile boolean mHasActiveThread;
private Context mContext;
private DownloadInfo(Context context) {
mContext = context;
mFuzz = Helper.rnd.nextInt(1001);
}
public void sendIntentIfRequested() {
if (mPackage == null) {
return;
}
if (mClass == null) {
return;
}
// TODO
// Intent intent = new Intent(DownloadManager.ACTION_DOWNLOAD_COMPLETED);
// intent.setClassName(mPackage, mClass);
// if (mExtras != null) {
// intent.putExtra(DownloadManager.Impl.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());
// mContext.sendBroadcast(intent);
}
/**
* 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 == Impl.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 Impl.STATUS_PAUSED_BY_APP: // download is paused by app
case Impl.STATUS_PENDING: // download is explicit marked as ready to start
case Impl.STATUS_RUNNING: // download interrupted (process killed etc) while
// running, without a chance to update the database
return true;
case Impl.STATUS_WAITING_FOR_NETWORK:
case Impl.STATUS_QUEUED_FOR_WIFI:
return checkCanUseNetwork() == NETWORK_OK;
case Impl.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 (!Impl.isStatusCompleted(mStatus)) {
return false;
}
if (mVisibility == Impl.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 = Helper.getActiveNetworkType(mContext);
if (networkType == null) {
return NETWORK_NO_CONNECTION;
}
return NETWORK_OK;
}
/**
* @return a non-localized string appropriate for logging corresponding to one of the
* NETWORK_* constants.
*/
public String getLogMessageForNetworkError(int networkError) {
switch (networkError) {
case NETWORK_NO_CONNECTION:
return "no network connection available";
default:
return "unknown error with network connectivity";
}
}
/**
* 如果符合下载条件马上开始下载
*/
/*package*/ void startIfReady(long now) {
if (!isReadyToStart(now)) {
return;
}
AppLog.d(TAG,"Service spawning thread to handle download " + mId);
if (mHasActiveThread) {
throw new IllegalStateException("Multiple threads on same download");
}
if (mStatus != Impl.STATUS_RUNNING) {
mStatus = Impl.STATUS_RUNNING;
ContentValues values = new ContentValues();
values.put(Impl.COLUMN_STATUS, mStatus);
mContext.getContentResolver().update(getMyDownloadsUri(), values, null, null);
}
DownloadThread downloader = new DownloadThread(mContext, this);
mHasActiveThread = true;
downloader.start();
}
/**
* 判断存储位置处于内部
*/
public boolean isOnCache() {
return (mDestination == Impl.DESTINATION_CACHE_PARTITION
|| mDestination == Impl.DESTINATION_CACHE_PARTITION_PURGEABLE);
}
public Uri getMyDownloadsUri() {
return ContentUris.withAppendedId(DownloadManager.Impl.CONTENT_URI, mId);
}
/*
* 打印下载项目详细信息
*/
public void logVerboseInfo() {
AppLog.d(TAG,getVerboseInfo());
}
/*
* 获取下载项目详细信息
*/
public String getVerboseInfo() {
StringBuilder info = new StringBuilder();
info.append("ID : " + mId + "\n");
info.append("URI : " + ((mUri != null) ? "yes" : "no") + "\n");
info.append("HINT : " + mHint + "\n");
info.append("FILENAME: " + mFileName + "\n");
info.append("MIMETYPE: " + mMimeType + "\n");
info.append("DESTINAT: " + mDestination + "\n");
info.append("VISIBILI: " + mVisibility + "\n");
info.append("CONTROL : " + mControl + "\n");
info.append("STATUS : " + mStatus + "\n");
info.append("FAILED_C: " + mNumFailed + "\n");
info.append("RETRY_AF: " + mRetryAfter + "\n");
info.append("REDIRECT: " + mRedirectCount + "\n");
info.append("LAST_MOD: " + mLastMod + "\n");
info.append("PACKAGE : " + mPackage + "\n");
info.append("CLASS : " + mClass + "\n");
info.append("TOTAL : " + mTotalBytes + "\n");
info.append("CURRENT : " + mCurrentBytes + "\n");
info.append("ETAG : " + mETag + "\n");
info.append("DELETED : " + mDeleted + "\n");
return info.toString();
}
/**
* 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"
*/
/*package*/long nextAction(long now) {
if (Impl.isStatusCompleted(mStatus)) {
return -1;
}
if (mStatus != Impl.STATUS_WAITING_TO_RETRY) {
return 0;
}
long when = restartTime(now);
if (when <= now) {
return 0;
}
return when - now;
}
/**
* 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));
}
/**
* 持久化数据(DownloadInfo)的读取工具类
*/
public static class Reader {
private Cursor mCursor;
private CharArrayBuffer mOldChars;
private CharArrayBuffer mNewChars;
public Reader(Cursor cursor) {
mCursor = cursor;
}
public DownloadInfo newDownloadInfo(Context context) {
DownloadInfo info = new DownloadInfo(context);
updateFromDatabase(info);
return info;
}
public void updateFromDatabase(DownloadInfo info) {
info.mId = getLong(Impl._ID);
info.mUri = getString(info.mUri, Impl.COLUMN_URI);
info.mHint = getString(info.mHint, Impl.COLUMN_FILE_NAME_HINT);
info.mFileName = getString(info.mFileName, Impl.COLUMN_DATA);
info.mMimeType = getString(info.mMimeType, Impl.COLUMN_MIME_TYPE);
info.mDestination = getInt(Impl.COLUMN_DESTINATION);
info.mVisibility = getInt(Impl.COLUMN_VISIBILITY);
info.mStatus = getInt(Impl.COLUMN_STATUS);
info.mNumFailed = getInt(Impl.COLUMN_FAILED_CONNECTIONS);
int retryRedirect = getInt(Impl.COLUMN_RETRY_AFTER_REDIRECT_COUNT);
info.mRetryAfter = retryRedirect & 0xfffffff;
info.mRedirectCount = retryRedirect >> 28;
info.mLastMod = getLong(Impl.COLUMN_LAST_MODIFICATION);
info.mPackage = getString(info.mPackage, Impl.COLUMN_NOTIFICATION_PACKAGE);
info.mClass = getString(info.mClass, Impl.COLUMN_NOTIFICATION_CLASS);
info.mExtras = getString(info.mExtras, Impl.COLUMN_NOTIFICATION_EXTRAS);
info.mTotalBytes = getLong(Impl.COLUMN_TOTAL_BYTES);
info.mCurrentBytes = getLong(Impl.COLUMN_CURRENT_BYTES);
info.mETag = getString(info.mETag, Impl.COLUMN_ETAG);
info.mDeleted = getInt(Impl.COLUMN_DELETED) == 1;
info.mTitle = getString(info.mTitle, Impl.COLUMN_TITLE);
info.mDescription = getString(info.mDescription, Impl.COLUMN_DESCRIPTION);
info.mSource = getInt(Impl.COLUMN_SOURCE);
info.mPackageName = getString(info.mPackageName, Impl.COLUMN_PACKAGE_NAME);
info.mMD5 = getString(info.mPackageName, Impl.COLUMN_MD5);
synchronized (this) {
info.mControl = getInt(Impl.COLUMN_CONTROL);
}
}
/**
* 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));
}
}
/**
* 当Wi-Fi网络转换到手机网络时,并且还有批量下载任务,提醒用户可以暂停
*/
/*package*/ void notifyNetworkChanged() {
// TODO 去应用管理页面,提供一键暂停功能
}
}