• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.retaildemo;
18 
19 import android.app.DownloadManager;
20 import android.app.ProgressDialog;
21 import android.content.BroadcastReceiver;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.database.Cursor;
26 import android.net.ConnectivityManager;
27 import android.net.NetworkInfo;
28 import android.net.Uri;
29 import android.os.Handler;
30 import android.os.HandlerThread;
31 import android.os.Looper;
32 import android.os.Message;
33 import android.support.annotation.VisibleForTesting;
34 import android.util.Log;
35 import android.view.ContextThemeWrapper;
36 
37 import java.io.File;
38 import java.io.IOException;
39 import java.net.HttpURLConnection;
40 import java.net.URL;
41 
42 /**
43  * Downloads the video from the specified url. If the video is previously downloaded, then uses
44  * that but checks if there is a more recent version of the video available.
45  */
46 class DownloadVideoTask {
47     private static final String TAG = "DownloadVideoTask";
48     private static final boolean DEBUG = false;
49 
50     static final int MSG_CHECK_FOR_UPDATE = 1;
51     static final int MSG_DOWNLOAD_COMPLETE = 2;
52     static final int MSG_CLEANUP_DOWNLOAD_DIR = 3;
53 
54     private static final int CLEANUP_DELAY_MILLIS = 2 * 1000; // 2 seconds
55 
56     private final Injector mInjector;
57     private final Context mContext;
58     private final DownloadManager mDlm;
59     private final File mDownloadFile;
60     private final ResultListener mListener;
61 
62     private Handler mHandler;
63 
64     private ProgressDialog mProgressDialog;
65     private DownloadResultReceiver mDownloadReceiver;
66     private NetworkChangeReceiver mNetworkChangeReceiver;
67     private String mDownloadUrl;
68     private long mVideoDownloadId;
69     private long mVideoUpdateDownloadId;
70     private String mDownloadedPath;
71     private boolean mVideoAlreadySet;
72     private File mPreloadVideoFile;
73 
DownloadVideoTask(Context context, String downloadPath, File preloadVideoFile, ResultListener listener)74     public DownloadVideoTask(Context context, String downloadPath, File preloadVideoFile,
75             ResultListener listener) {
76         this(context, downloadPath, preloadVideoFile, listener, new Injector(context));
77     }
78 
79     @VisibleForTesting
DownloadVideoTask(Context context, String downloadPath, File preloadVideoFile, ResultListener listener, Injector injector)80     DownloadVideoTask(Context context, String downloadPath, File preloadVideoFile,
81             ResultListener listener, Injector injector) {
82         mInjector = injector;
83         mContext = context;
84         mDownloadFile = new File(downloadPath);
85         mListener = listener;
86         mPreloadVideoFile = preloadVideoFile;
87         mDlm = injector.getDownloadManager();
88         mDownloadUrl = injector.getDownloadUrl();
89     }
90 
run()91     public void run() {
92         mDownloadReceiver = new DownloadResultReceiver();
93         mContext.registerReceiver(mDownloadReceiver,
94                 new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
95 
96         mHandler = mInjector.getHandler(this);
97 
98         mVideoAlreadySet =
99                 mDownloadFile.exists() || mPreloadVideoFile.exists();
100         // If file already exists, no need to download it again.
101         if (mVideoAlreadySet) {
102             if (DEBUG) Log.d(TAG, "Video already exists at either " + mDownloadFile.getPath()
103                     + " or " + mPreloadVideoFile + ", checking for an update... ");
104             mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_FOR_UPDATE));
105         } else {
106             if (!isConnectedToNetwork()) {
107                 mListener.onError();
108                 mNetworkChangeReceiver = new NetworkChangeReceiver();
109                 mContext.registerReceiver(mNetworkChangeReceiver,
110                         new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
111                 return;
112             }
113             startDownload();
114         }
115     }
116 
startDownload()117     private void startDownload() {
118         final DownloadManager.Request request = createDownloadRequest();
119         mVideoDownloadId = mDlm.enqueue(request);
120         if (DEBUG) Log.d(TAG, "Started downloading the video at " + mDownloadUrl
121                 + " to " + mDownloadFile.getPath());
122         showProgressDialog();
123     }
124 
createDownloadRequest()125     private DownloadManager.Request createDownloadRequest() {
126         final DownloadManager.Request request = new DownloadManager.Request(
127                 Uri.parse(mDownloadUrl));
128         request.setDestinationUri(Uri.fromFile(mDownloadFile));
129         return request;
130     }
131 
132     private class DownloadResultReceiver extends BroadcastReceiver {
133         @Override
onReceive(Context context, Intent intent)134         public void onReceive(Context context, Intent intent) {
135             if (!DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
136                 return;
137             }
138 
139             final long id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0);
140             if (id == mVideoDownloadId) {
141                 final int status = checkDownloadsAndSetVideo(id);
142                 if (status == DownloadManager.STATUS_SUCCESSFUL ||
143                         status == DownloadManager.STATUS_FAILED) {
144                     if (mProgressDialog != null) {
145                         mProgressDialog.dismiss();
146                     }
147                     if (mDownloadReceiver != null) {
148                         mContext.unregisterReceiver(mDownloadReceiver);
149                         mDownloadReceiver = null;
150                     }
151                     if (status == DownloadManager.STATUS_FAILED) {
152                         mListener.onError();
153                     }
154                 }
155             } else if (id == mVideoUpdateDownloadId) {
156                 mHandler.sendMessage(mHandler.obtainMessage(MSG_DOWNLOAD_COMPLETE));
157             }
158         }
159     };
160 
161     final class ThreadHandler extends Handler {
ThreadHandler(Looper looper)162         public ThreadHandler(Looper looper) {
163             super(looper);
164         }
165 
166         @Override
handleMessage(Message msg)167         public void handleMessage(Message msg) {
168             switch (msg.what) {
169                 case MSG_CHECK_FOR_UPDATE:
170                     if (!isConnectedToNetwork()) {
171                         mNetworkChangeReceiver = new NetworkChangeReceiver();
172                         mContext.registerReceiver(mNetworkChangeReceiver,
173                                 new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
174                         return;
175                     }
176                     HttpURLConnection conn = null;
177                     try {
178                         conn = mInjector.openConnection(mDownloadUrl);
179                         final long lastModified = mDownloadFile.lastModified();
180                         conn.setIfModifiedSince(lastModified);
181                         conn.connect();
182                         if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_MODIFIED) {
183                             return;
184                         }
185                         final DownloadManager.Request request = createDownloadRequest();
186                         mVideoUpdateDownloadId = mDlm.enqueue(request);
187                         if (DEBUG) Log.d(TAG, "Started downloading the updated video at "
188                                 + mDownloadUrl);
189                     } catch (IOException e) {
190                         Log.e(TAG, "Error while checking for an updated video", e);
191                     } finally {
192                         if (conn != null) {
193                             conn.disconnect();
194                         }
195                     }
196                     break;
197                 case MSG_DOWNLOAD_COMPLETE:
198                     checkDownloadsAndSetVideo(mVideoUpdateDownloadId);
199                     break;
200                 case MSG_CLEANUP_DOWNLOAD_DIR:
201                     // If the video was downloaded to the same location as we needed, then
202                     // nothing else to do.
203                     if (mDownloadFile.getPath().equals(mDownloadedPath)) {
204                         return;
205                     }
206                     if (mDownloadFile.exists()) {
207                         mDownloadFile.delete();
208                     }
209                     if (new File(mDownloadedPath).renameTo(mDownloadFile)) {
210                         mListener.onFileDownloaded(mDownloadFile.getPath());
211                         final String downloadFileName = getFileBaseName(mDownloadFile.getName());
212                         // Delete other files in the directory
213                         for (File file : mDownloadFile.getParentFile().listFiles()) {
214                             if (getFileBaseName(file.getName()).startsWith(downloadFileName)
215                                     && !file.getPath().equals(mDownloadFile.getPath())) {
216                                 file.delete();
217                             }
218                         }
219                     }
220                     break;
221             }
222         }
223     }
224 
checkDownloadsAndSetVideo(long downloadId)225     private int checkDownloadsAndSetVideo(long downloadId) {
226         final DownloadManager.Query query =
227                 new DownloadManager.Query().setFilterById(downloadId);
228         Cursor cursor = mDlm.query(query);
229         try {
230             if (cursor != null & cursor.moveToFirst()) {
231                 final int columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
232                 final int status = cursor.getInt(columnIndex);
233                 if (status == DownloadManager.STATUS_SUCCESSFUL) {
234                     final String fileUri = cursor.getString(
235                             cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI));
236                     mDownloadedPath = Uri.parse(fileUri).getPath();
237                     if (DEBUG) Log.d(TAG, "Video successfully downloaded at " + mDownloadedPath);
238                     mListener.onFileDownloaded(mDownloadedPath);
239                     mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEANUP_DOWNLOAD_DIR),
240                             CLEANUP_DELAY_MILLIS);
241                 }
242                 return status;
243             }
244         } finally {
245             if (cursor != null) {
246                 cursor.close();
247             }
248             if (mNetworkChangeReceiver != null) {
249                 mContext.unregisterReceiver(mNetworkChangeReceiver);
250                 mNetworkChangeReceiver = null;
251             }
252         }
253         return -1;
254     }
255 
256     private class NetworkChangeReceiver extends BroadcastReceiver {
257         @Override
onReceive(Context context, Intent intent)258         public void onReceive(Context context, Intent intent) {
259             if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())
260                     && isConnectedToNetwork()) {
261                 if (mVideoAlreadySet) {
262                     mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_FOR_UPDATE));
263                 } else {
264                     startDownload();
265                 }
266             }
267         }
268     };
269 
showProgressDialog()270     private void showProgressDialog() {
271         mProgressDialog = mInjector.getProgressDialog();
272         mProgressDialog.show();
273     }
274 
isConnectedToNetwork()275     private boolean isConnectedToNetwork() {
276         ConnectivityManager cm = mInjector.getConnectivityManager();
277         NetworkInfo info = cm.getActiveNetworkInfo();
278         return info != null && info.isConnected();
279     }
280 
getFileBaseName(String fileName)281     private String getFileBaseName(String fileName) {
282         final int pos = fileName.lastIndexOf(".");
283         return pos > 0 ? fileName.substring(0, pos) : fileName;
284     }
285 
286     interface ResultListener {
onFileDownloaded(String downloadedFilePath)287         void onFileDownloaded(String downloadedFilePath);
onError()288         void onError();
289     }
290 
291     /**
292      * Unit test will subclass this to inject mocks.
293      */
294     @VisibleForTesting
295     static class Injector {
296         private final Context mContext;
297 
Injector(Context context)298         Injector(Context context) {
299             mContext = context;
300         }
301 
getDownloadManager()302         DownloadManager getDownloadManager() {
303             return (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE);
304         }
305 
getDownloadUrl()306         String getDownloadUrl() {
307             return mContext.getString(R.string.retail_demo_video_download_url);
308         }
309 
getConnectivityManager()310         ConnectivityManager getConnectivityManager() {
311             return (ConnectivityManager) mContext.getSystemService(
312                     Context.CONNECTIVITY_SERVICE);
313         }
314 
getHandler(DownloadVideoTask task)315         Handler getHandler(DownloadVideoTask task) {
316             // Initialize handler
317             HandlerThread thread = new HandlerThread(TAG);
318             thread.start();
319             return task.new ThreadHandler(thread.getLooper());
320         }
321 
getProgressDialog()322         ProgressDialog getProgressDialog() {
323             final ProgressDialog dialog = new ProgressDialog(
324                     new ContextThemeWrapper(mContext, android.R.style.Theme_Material_Light_Dialog));
325             dialog.setMessage(mContext.getString(R.string.downloading_video_msg));
326             dialog.setIndeterminate(false);
327             dialog.setCancelable(false);
328             dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
329             return dialog;
330         }
331 
openConnection(String downloadUri)332         HttpURLConnection openConnection(String downloadUri) throws IOException {
333             return (HttpURLConnection) new URL(downloadUri).openConnection();
334         }
335     }
336 }