/* * Copyright (c) 2015 LingoChamp 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.liulishuo.filedownloader; import android.app.Application; import android.app.Notification; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.os.IBinder; import com.liulishuo.filedownloader.download.CustomComponentHolder; import com.liulishuo.filedownloader.event.DownloadServiceConnectChangedEvent; import com.liulishuo.filedownloader.model.FileDownloadStatus; import com.liulishuo.filedownloader.model.FileDownloadTaskAtom; import com.liulishuo.filedownloader.services.DownloadMgrInitialParams; import com.liulishuo.filedownloader.util.FileDownloadHelper; import com.liulishuo.filedownloader.util.FileDownloadLog; import com.liulishuo.filedownloader.util.FileDownloadProperties; import com.liulishuo.filedownloader.util.FileDownloadUtils; import java.io.File; import java.util.List; /** * The basic entrance for FileDownloader. * * @see com.liulishuo.filedownloader.services.FileDownloadService The service for FileDownloader. * @see FileDownloadProperties */ @SuppressWarnings("WeakerAccess") public class FileDownloader { /** * You can invoke this method anytime before you using the FileDownloader. *
* If you want to register your own customize components please using * {@link #setupOnApplicationOnCreate(Application)} on the {@link Application#onCreate()} * instead. * * @param context the context of Application or Activity etc.. */ public static void setup(Context context) { FileDownloadHelper.holdContext(context.getApplicationContext()); } /** * Using this method to setup the FileDownloader only you want to register your own customize * components for Filedownloader, otherwise just using {@link #setup(Context)} instead. *
* Please invoke this method on the {@link Application#onCreate()} because of the customize * components must be assigned before FileDownloader is running. * * Such as: * * class MyApplication extends Application { * ... * public void onCreate() { * ... * FileDownloader.setupOnApplicationOnCreate(this) * .idGenerator(new MyIdGenerator()) * .database(new MyDatabase()) * ... * .commit(); * ... * } * ... * } * @param application the application. * @return the customize components maker. */ public static DownloadMgrInitialParams.InitCustomMaker setupOnApplicationOnCreate( Application application) { final Context context = application.getApplicationContext(); FileDownloadHelper.holdContext(context); DownloadMgrInitialParams.InitCustomMaker customMaker = new DownloadMgrInitialParams.InitCustomMaker(); CustomComponentHolder.getImpl().setInitCustomMaker(customMaker); return customMaker; } /** * @deprecated please use {@link #setup(Context)} instead. */ public static void init(final Context context) { if (context == null) { throw new IllegalArgumentException("the provided context must not be null!"); } setup(context); } /** * @deprecated please using {@link #setupOnApplicationOnCreate(Application)} instead. */ public static void init(final Context context, final DownloadMgrInitialParams.InitCustomMaker maker) { if (FileDownloadLog.NEED_LOG) { FileDownloadLog.d(FileDownloader.class, "init Downloader with params: %s %s", context, maker); } if (context == null) { throw new IllegalArgumentException("the provided context must not be null!"); } FileDownloadHelper.holdContext(context.getApplicationContext()); CustomComponentHolder.getImpl().setInitCustomMaker(maker); } private static final class HolderClass { private static final FileDownloader INSTANCE = new FileDownloader(); } public static FileDownloader getImpl() { return HolderClass.INSTANCE; } /** * For avoiding missing screen frames. * * This mechanism is used for avoid methods in {@link FileDownloadListener} is invoked too * frequent in result the system missing screen frames in the main thread. ** We wrap the message package which size is {@link FileDownloadMessageStation#SUB_PACKAGE_SIZE} * and post the package to the main thread with the interval: * {@link FileDownloadMessageStation#INTERVAL} milliseconds. *
* The default interval is 10ms, if {@code intervalMillisecond} equal to or less than 0, each * callback in {@link FileDownloadListener} will be posted to the main thread immediately. * * @param intervalMillisecond The time interval between posting two message packages. * @see #enableAvoidDropFrame() * @see #disableAvoidDropFrame() * @see #setGlobalHandleSubPackageSize(int) */ public static void setGlobalPost2UIInterval(final int intervalMillisecond) { FileDownloadMessageStation.INTERVAL = intervalMillisecond; } /** * For avoiding missing screen frames. * * This mechanism is used for avoid methods in {@link FileDownloadListener} is invoked too * frequent in result the system missing screen frames in the main thread. ** We wrap the message package which size is {@link FileDownloadMessageStation#SUB_PACKAGE_SIZE} * and post the package to the main thread with the interval: * {@link FileDownloadMessageStation#INTERVAL} milliseconds. *
* The default count of message for a message package is 5.
*
* @param packageSize The count of message for a message package.
* @see #setGlobalPost2UIInterval(int)
*/
public static void setGlobalHandleSubPackageSize(final int packageSize) {
if (packageSize <= 0) {
throw new IllegalArgumentException("sub package size must more than 0");
}
FileDownloadMessageStation.SUB_PACKAGE_SIZE = packageSize;
}
/**
* Avoid missing screen frames, this leads to all callbacks in {@link FileDownloadListener} do
* not be invoked at once when it has already achieved to ensure callbacks don't be too frequent
*
* @see #isEnabledAvoidDropFrame()
* @see #setGlobalPost2UIInterval(int)
*/
public static void enableAvoidDropFrame() {
setGlobalPost2UIInterval(FileDownloadMessageStation.DEFAULT_INTERVAL);
}
/**
* Disable avoiding missing screen frames, let all callbacks in {@link FileDownloadListener}
* can be invoked at once when it achieve.
*
* @see #isEnabledAvoidDropFrame()
* @see #setGlobalPost2UIInterval(int)
*/
public static void disableAvoidDropFrame() {
setGlobalPost2UIInterval(-1);
}
/**
* @return {@code true} if enabled the function of avoiding missing screen frames.
* @see #enableAvoidDropFrame()
* @see #disableAvoidDropFrame()
* @see #setGlobalPost2UIInterval(int)
*/
public static boolean isEnabledAvoidDropFrame() {
return FileDownloadMessageStation.isIntervalValid();
}
/**
* Create a download task.
*/
public BaseDownloadTask create(final String url) {
return new DownloadTask(url);
}
/**
* Start the download queue by the same listener.
*
* @param listener Used to assemble tasks which is bound by the same {@code listener}
* @param isSerial Whether start tasks one by one rather than parallel.
* @return {@code true} if start tasks successfully.
*/
public boolean start(final FileDownloadListener listener, final boolean isSerial) {
if (listener == null) {
FileDownloadLog.w(this, "Tasks with the listener can't start, because the listener "
+ "provided is null: [null, %B]", isSerial);
return false;
}
return isSerial
? getQueuesHandler().startQueueSerial(listener)
: getQueuesHandler().startQueueParallel(listener);
}
/**
* Pause the download queue by the same {@code listener}.
*
* @param listener the listener.
* @see #pause(int)
*/
public void pause(final FileDownloadListener listener) {
FileDownloadTaskLauncher.getImpl().expire(listener);
final List
* If there are tasks with the {@code id} in downloading, will be paused first;
* If delete the data with the {@code id} in the filedownloader database successfully, will try
* to delete its intermediate downloading file and downloaded file.
*
* @param id the download {@code id}.
* @param targetFilePath the target path.
* @return {@code true} if the data with the {@code id} in filedownloader database was deleted,
* and tasks with the {@code id} was paused; {@code false} otherwise.
*/
public boolean clear(final int id, final String targetFilePath) {
pause(id);
if (FileDownloadServiceProxy.getImpl().clearTaskData(id)) {
// delete the task data in the filedownloader database successfully or no data with the
// id in filedownloader database.
final File intermediateFile = new File(FileDownloadUtils.getTempPath(targetFilePath));
if (intermediateFile.exists()) {
//noinspection ResultOfMethodCallIgnored
intermediateFile.delete();
}
final File targetFile = new File(targetFilePath);
if (targetFile.exists()) {
//noinspection ResultOfMethodCallIgnored
targetFile.delete();
}
return true;
}
return false;
}
/**
* Clear all data in the filedownloader database.
*
* Note: Normally, YOU NO NEED to clearAllTaskData manually, because the
* FileDownloader will maintain those data to ensure only if the data available for resuming
* can be kept automatically.
*
* @see #clear(int, String)
*/
public void clearAllTaskData() {
pauseAll();
FileDownloadServiceProxy.getImpl().clearAllTaskData();
}
/**
* Get downloaded bytes so far by the downloadId.
*/
public long getSoFar(final int downloadId) {
BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(downloadId);
if (task == null) {
return FileDownloadServiceProxy.getImpl().getSofar(downloadId);
}
return task.getOrigin().getLargeFileSoFarBytes();
}
/**
* Get the total bytes of the target file for the task with the {code id}.
*/
public long getTotal(final int id) {
BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(id);
if (task == null) {
return FileDownloadServiceProxy.getImpl().getTotal(id);
}
return task.getOrigin().getLargeFileTotalBytes();
}
/**
* @param id The downloadId.
* @return The downloading status without cover the completed status (if completed you will
* receive
* {@link FileDownloadStatus#INVALID_STATUS} ).
* @see #getStatus(String, String)
* @see #getStatus(int, String)
*/
public byte getStatusIgnoreCompleted(final int id) {
return getStatus(id, null);
}
/**
* @param url The downloading URL.
* @param path The downloading file's path.
* @return The downloading status.
* @see #getStatus(int, String)
* @see #getStatusIgnoreCompleted(int)
*/
public byte getStatus(final String url, final String path) {
return getStatus(FileDownloadUtils.generateId(url, path), path);
}
/**
* @param id The downloadId.
* @param path The target file path.
* @return the downloading status.
* @see FileDownloadStatus
* @see #getStatus(String, String)
* @see #getStatusIgnoreCompleted(int)
*/
public byte getStatus(final int id, final String path) {
byte status;
BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(id);
if (task == null) {
status = FileDownloadServiceProxy.getImpl().getStatus(id);
} else {
status = task.getOrigin().getStatus();
}
if (path != null && status == FileDownloadStatus.INVALID_STATUS) {
if (FileDownloadUtils.isFilenameConverted(FileDownloadHelper.getAppContext())
&& new File(path).exists()) {
status = FileDownloadStatus.completed;
}
}
return status;
}
/**
* Find the running task by {@code url} and default path, and replace its listener with
* the new one {@code listener}.
*
* @return The target task's DownloadId, if not exist target task, and replace failed, will be 0
* @see #replaceListener(int, FileDownloadListener)
* @see #replaceListener(String, String, FileDownloadListener)
*/
public int replaceListener(String url, FileDownloadListener listener) {
return replaceListener(url, FileDownloadUtils.getDefaultSaveFilePath(url), listener);
}
/**
* Find the running task by {@code url} and {@code path}, and replace its listener with
* the new one {@code listener}.
*
* @return The target task's DownloadId, if not exist target task, and replace failed, will be 0
* @see #replaceListener(String, FileDownloadListener)
* @see #replaceListener(int, FileDownloadListener)
*/
public int replaceListener(String url, String path, FileDownloadListener listener) {
return replaceListener(FileDownloadUtils.generateId(url, path), listener);
}
/**
* Find the running task by {@code id}, and replace its listener width the new one
* {@code listener}.
*
* @return The target task's DownloadId, if not exist target task, and replace failed, will be 0
* @see #replaceListener(String, FileDownloadListener)
* @see #replaceListener(String, String, FileDownloadListener)
*/
public int replaceListener(int id, FileDownloadListener listener) {
final BaseDownloadTask.IRunningTask task = FileDownloadList.getImpl().get(id);
if (task == null) {
return 0;
}
task.getOrigin().setListener(listener);
return task.getOrigin().getId();
}
/**
* Start and bind the FileDownloader service.
*
* Tips: The FileDownloader service will start and bind automatically when any
* task is request to start.
*
* @see #bindService(Runnable)
* @see #isServiceConnected()
* @see #addServiceConnectListener(FileDownloadConnectListener)
*/
public void bindService() {
if (!isServiceConnected()) {
FileDownloadServiceProxy.getImpl()
.bindStartByContext(FileDownloadHelper.getAppContext());
}
}
/**
* Start and bind the FileDownloader service and run {@code runnable} as soon as the binding is
* successful.
*
* Tips: The FileDownloader service will start and bind automatically when any
* task is request to start.
*
* @param runnable the command will be executed as soon as the FileDownloader Service is
* successfully bound.
* @see #isServiceConnected()
* @see #bindService()
* @see #addServiceConnectListener(FileDownloadConnectListener)
*/
public void bindService(final Runnable runnable) {
if (isServiceConnected()) {
runnable.run();
} else {
FileDownloadServiceProxy.getImpl().
bindStartByContext(FileDownloadHelper.getAppContext(), runnable);
}
}
/**
* Unbind and stop the downloader service.
*/
public void unBindService() {
if (isServiceConnected()) {
FileDownloadServiceProxy.getImpl().unbindByContext(FileDownloadHelper.getAppContext());
}
}
/**
* Unbind and stop the downloader service when there is no task running in the FileDownloader.
*
* @return {@code true} if unbind and stop the downloader service successfully, {@code false}
* there are some tasks running in the FileDownloader.
*/
public boolean unBindServiceIfIdle() {
// check idle
if (!isServiceConnected()) {
return false;
}
if (FileDownloadList.getImpl().isEmpty()
&& FileDownloadServiceProxy.getImpl().isIdle()) {
unBindService();
return true;
}
return false;
}
/**
* @return {@code true} if the downloader service has been started and connected.
*/
public boolean isServiceConnected() {
return FileDownloadServiceProxy.getImpl().isConnected();
}
/**
* Add the listener for listening when the status of connection with the downloader service is
* changed.
*
* @param listener The downloader service connection listener.
* @see #removeServiceConnectListener(FileDownloadConnectListener)
*/
public void addServiceConnectListener(final FileDownloadConnectListener listener) {
FileDownloadEventPool.getImpl().addListener(DownloadServiceConnectChangedEvent.ID,
listener);
}
/**
* Remove the listener for listening when the status of connection with the downloader service
* is changed.
*
* @param listener The downloader service connection listener.
* @see #addServiceConnectListener(FileDownloadConnectListener)
*/
public void removeServiceConnectListener(final FileDownloadConnectListener listener) {
FileDownloadEventPool.getImpl().removeListener(DownloadServiceConnectChangedEvent.ID,
listener);
}
/**
* Start the {@code notification} with the {@code id}. This will let the downloader service
* change to a foreground service.
*
* In foreground status, will save the FileDownloader alive, even user kill the application
* from recent apps.
*
*
* If the FileDownloader service has been started and connected, the request you invoke in
* {@link FileDownloadLine} will be executed immediately.
*
* Note: FileDownloader can not block the main thread, because the system is
* also call-backs the {@link ServiceConnection#onServiceConnected(ComponentName, IBinder)}
* method in the main thread.
*
* Tips: The FileDownloader service will start and bind automatically when any
* task is request to start.
*
* @see FileDownloadLine
* @see #bindService(Runnable)
*/
public FileDownloadLine insureServiceBind() {
return new FileDownloadLine();
}
/**
* If the FileDownloader service is not started and connected will return {@code false}
* immediately, and meanwhile FileDownloader will try to start FileDownloader service and try to
* bind with it, and after it is bound successfully the request you invoke in
* {@link FileDownloadLineAsync} will be executed automatically.
*
* If the FileDownloader service has been started and connected, the request you invoke in
* {@link FileDownloadLineAsync} will be executed immediately.
*
* @see FileDownloadLineAsync
* @see #bindService(Runnable)
*/
public FileDownloadLineAsync insureServiceBindAsync() {
return new FileDownloadLineAsync();
}
private static final Object INIT_QUEUES_HANDLER_LOCK = new Object();
private IQueuesHandler mQueuesHandler;
IQueuesHandler getQueuesHandler() {
if (mQueuesHandler == null) {
synchronized (INIT_QUEUES_HANDLER_LOCK) {
if (mQueuesHandler == null) {
mQueuesHandler = new QueuesHandler();
}
}
}
return mQueuesHandler;
}
private static final Object INIT_LOST_CONNECTED_HANDLER_LOCK = new Object();
private ILostServiceConnectedHandler mLostConnectedHandler;
ILostServiceConnectedHandler getLostConnectedHandler() {
if (mLostConnectedHandler == null) {
synchronized (INIT_LOST_CONNECTED_HANDLER_LOCK) {
if (mLostConnectedHandler == null) {
mLostConnectedHandler = new LostServiceConnectedHandler();
addServiceConnectListener((FileDownloadConnectListener) mLostConnectedHandler);
}
}
}
return mLostConnectedHandler;
}
}