blob: 9170b1ec21a70ebee5d41134e35b0c3ffb00c02b [file] [log] [blame]
/*
* Copyright (C) 2016 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.android.retaildemo;
import android.app.DownloadManager;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
import android.view.ContextThemeWrapper;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* Downloads the video from the specified url. If the video is previously downloaded, then uses
* that but checks if there is a more recent version of the video available.
*/
class DownloadVideoTask {
private static final String TAG = "DownloadVideoTask";
private static final boolean DEBUG = false;
static final int MSG_CHECK_FOR_UPDATE = 1;
static final int MSG_DOWNLOAD_COMPLETE = 2;
static final int MSG_CLEANUP_DOWNLOAD_DIR = 3;
private static final int CLEANUP_DELAY_MILLIS = 2 * 1000; // 2 seconds
private final Injector mInjector;
private final Context mContext;
private final DownloadManager mDlm;
private final File mDownloadFile;
private final ResultListener mListener;
private Handler mHandler;
private ProgressDialog mProgressDialog;
private DownloadResultReceiver mDownloadReceiver;
private NetworkChangeReceiver mNetworkChangeReceiver;
private String mDownloadUrl;
private long mVideoDownloadId;
private long mVideoUpdateDownloadId;
private String mDownloadedPath;
private boolean mVideoAlreadySet;
private File mPreloadVideoFile;
public DownloadVideoTask(Context context, String downloadPath, File preloadVideoFile,
ResultListener listener) {
this(context, downloadPath, preloadVideoFile, listener, new Injector(context));
}
@VisibleForTesting
DownloadVideoTask(Context context, String downloadPath, File preloadVideoFile,
ResultListener listener, Injector injector) {
mInjector = injector;
mContext = context;
mDownloadFile = new File(downloadPath);
mListener = listener;
mPreloadVideoFile = preloadVideoFile;
mDlm = injector.getDownloadManager();
mDownloadUrl = injector.getDownloadUrl();
}
public void run() {
mDownloadReceiver = new DownloadResultReceiver();
mContext.registerReceiver(mDownloadReceiver,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
mHandler = mInjector.getHandler(this);
mVideoAlreadySet =
mDownloadFile.exists() || mPreloadVideoFile.exists();
// If file already exists, no need to download it again.
if (mVideoAlreadySet) {
if (DEBUG) Log.d(TAG, "Video already exists at either " + mDownloadFile.getPath()
+ " or " + mPreloadVideoFile + ", checking for an update... ");
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_FOR_UPDATE));
} else {
if (!isConnectedToNetwork()) {
mListener.onError();
mNetworkChangeReceiver = new NetworkChangeReceiver();
mContext.registerReceiver(mNetworkChangeReceiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
return;
}
startDownload();
}
}
private void startDownload() {
final DownloadManager.Request request = createDownloadRequest();
mVideoDownloadId = mDlm.enqueue(request);
if (DEBUG) Log.d(TAG, "Started downloading the video at " + mDownloadUrl
+ " to " + mDownloadFile.getPath());
showProgressDialog();
}
private DownloadManager.Request createDownloadRequest() {
final DownloadManager.Request request = new DownloadManager.Request(
Uri.parse(mDownloadUrl));
request.setDestinationUri(Uri.fromFile(mDownloadFile));
return request;
}
private class DownloadResultReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (!DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
return;
}
final long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
if (id == mVideoDownloadId) {
final int status = checkDownloadsAndSetVideo(id);
if (status == DownloadManager.STATUS_SUCCESSFUL ||
status == DownloadManager.STATUS_FAILED) {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
}
if (mDownloadReceiver != null) {
mContext.unregisterReceiver(mDownloadReceiver);
mDownloadReceiver = null;
}
if (status == DownloadManager.STATUS_FAILED) {
mListener.onError();
}
}
} else if (id == mVideoUpdateDownloadId) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_DOWNLOAD_COMPLETE));
}
}
};
final class ThreadHandler extends Handler {
public ThreadHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CHECK_FOR_UPDATE:
if (!isConnectedToNetwork()) {
mNetworkChangeReceiver = new NetworkChangeReceiver();
mContext.registerReceiver(mNetworkChangeReceiver,
new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
return;
}
HttpURLConnection conn = null;
try {
conn = mInjector.openConnection(mDownloadUrl);
final long lastModified = mDownloadFile.lastModified();
conn.setIfModifiedSince(lastModified);
conn.connect();
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
return;
}
final DownloadManager.Request request = createDownloadRequest();
mVideoUpdateDownloadId = mDlm.enqueue(request);
if (DEBUG) Log.d(TAG, "Started downloading the updated video at "
+ mDownloadUrl);
} catch (IOException e) {
Log.e(TAG, "Error while checking for an updated video", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
break;
case MSG_DOWNLOAD_COMPLETE:
checkDownloadsAndSetVideo(mVideoUpdateDownloadId);
break;
case MSG_CLEANUP_DOWNLOAD_DIR:
// If the video was downloaded to the same location as we needed, then
// nothing else to do.
if (mDownloadFile.getPath().equals(mDownloadedPath)) {
return;
}
if (mDownloadFile.exists()) {
mDownloadFile.delete();
}
if (new File(mDownloadedPath).renameTo(mDownloadFile)) {
mListener.onFileDownloaded(mDownloadFile.getPath());
final String downloadFileName = getFileBaseName(mDownloadFile.getName());
// Delete other files in the directory
for (File file : mDownloadFile.getParentFile().listFiles()) {
if (getFileBaseName(file.getName()).startsWith(downloadFileName)
&& !file.getPath().equals(mDownloadFile.getPath())) {
file.delete();
}
}
}
break;
}
}
}
private int checkDownloadsAndSetVideo(long downloadId) {
final DownloadManager.Query query =
new DownloadManager.Query().setFilterById(downloadId);
Cursor cursor = mDlm.query(query);
try {
if (cursor != null & cursor.moveToFirst()) {
final int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
final int status = cursor.getInt(columnIndex);
if (status == DownloadManager.STATUS_SUCCESSFUL) {
final String fileUri = cursor.getString(
cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
mDownloadedPath = Uri.parse(fileUri).getPath();
if (DEBUG) Log.d(TAG, "Video successfully downloaded at " + mDownloadedPath);
mListener.onFileDownloaded(mDownloadedPath);
mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEANUP_DOWNLOAD_DIR),
CLEANUP_DELAY_MILLIS);
}
return status;
}
} finally {
if (cursor != null) {
cursor.close();
}
if (mNetworkChangeReceiver != null) {
mContext.unregisterReceiver(mNetworkChangeReceiver);
mNetworkChangeReceiver = null;
}
}
return -1;
}
private class NetworkChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())
&& isConnectedToNetwork()) {
if (mVideoAlreadySet) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_FOR_UPDATE));
} else {
startDownload();
}
}
}
};
private void showProgressDialog() {
mProgressDialog = mInjector.getProgressDialog();
mProgressDialog.show();
}
private boolean isConnectedToNetwork() {
ConnectivityManager cm = mInjector.getConnectivityManager();
NetworkInfo info = cm.getActiveNetworkInfo();
return info != null && info.isConnected();
}
private String getFileBaseName(String fileName) {
final int pos = fileName.lastIndexOf(".");
return pos > 0 ? fileName.substring(0, pos) : fileName;
}
interface ResultListener {
void onFileDownloaded(String downloadedFilePath);
void onError();
}
/**
* Unit test will subclass this to inject mocks.
*/
@VisibleForTesting
static class Injector {
private final Context mContext;
Injector(Context context) {
mContext = context;
}
DownloadManager getDownloadManager() {
return (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
}
String getDownloadUrl() {
return mContext.getString(R.string.retail_demo_video_download_url);
}
ConnectivityManager getConnectivityManager() {
return (ConnectivityManager) mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
}
Handler getHandler(DownloadVideoTask task) {
// Initialize handler
HandlerThread thread = new HandlerThread(TAG);
thread.start();
return task.new ThreadHandler(thread.getLooper());
}
ProgressDialog getProgressDialog() {
final ProgressDialog dialog = new ProgressDialog(
new ContextThemeWrapper(mContext, android.R.style.Theme_Material_Light_Dialog));
dialog.setMessage(mContext.getString(R.string.downloading_video_msg));
dialog.setIndeterminate(false);
dialog.setCancelable(false);
dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
return dialog;
}
HttpURLConnection openConnection(String downloadUri) throws IOException {
return (HttpURLConnection) new URL(downloadUri).openConnection();
}
}
}