af327a56841c0858e0b6ce21a79a54228b0e8037
[pub/Android/ownCloud.git] / src / eu / alefzero / owncloud / files / services / FileDownloader.java
1 package eu.alefzero.owncloud.files.services;
2
3 import java.io.File;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.Map;
7
8 import android.accounts.Account;
9 import android.accounts.AccountManager;
10 import android.app.Notification;
11 import android.app.NotificationManager;
12 import android.app.PendingIntent;
13 import android.app.Service;
14 import android.content.ContentValues;
15 import android.content.Intent;
16 import android.os.Environment;
17 import android.os.Handler;
18 import android.os.HandlerThread;
19 import android.os.IBinder;
20 import android.os.Looper;
21 import android.os.Message;
22 import android.os.Process;
23 import android.util.Log;
24 import android.widget.RemoteViews;
25 import eu.alefzero.owncloud.R;
26 import eu.alefzero.owncloud.authenticator.AccountAuthenticator;
27 import eu.alefzero.owncloud.db.ProviderMeta.ProviderTableMeta;
28 import eu.alefzero.owncloud.files.interfaces.OnDatatransferProgressListener;
29 import eu.alefzero.webdav.WebdavClient;
30
31 public class FileDownloader extends Service implements OnDatatransferProgressListener {
32 public static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
33 public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
34 public static final String EXTRA_ACCOUNT = "ACCOUNT";
35 public static final String EXTRA_FILE_PATH = "FILE_PATH";
36 public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
37 public static final String EXTRA_FILE_SIZE = "FILE_SIZE";
38 public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
39
40 private static final String TAG = "FileDownloader";
41
42 private NotificationManager mNotificationMngr;
43 private Looper mServiceLooper;
44 private ServiceHandler mServiceHandler;
45 private Account mAccount;
46 private String mFilePath;
47 private String mRemotePath;
48 private int mLastPercent;
49 private long mTotalDownloadSize;
50 private long mCurrentDownloadSize;
51 private Notification mNotification;
52
53 /**
54 * Static map with the files being download and the path to the temporal file were are download
55 */
56 private static Map<String, String> mDownloadsInProgress = Collections.synchronizedMap(new HashMap<String, String>());
57
58 /**
59 * Returns True when the file referred by 'remotePath' in the ownCloud account 'account' is downloading
60 */
61 public static boolean isDownloading(Account account, String remotePath) {
62 return (mDownloadsInProgress.get(buildRemoteName(account.name, remotePath)) != null);
63 }
64
65 /**
66 * Builds a key for mDownloadsInProgress from the accountName and remotePath
67 */
68 private static String buildRemoteName(String accountName, String remotePath) {
69 return accountName + remotePath;
70 }
71
72
73 private final class ServiceHandler extends Handler {
74 public ServiceHandler(Looper looper) {
75 super(looper);
76 }
77
78 @Override
79 public void handleMessage(Message msg) {
80 downloadFile();
81 stopSelf(msg.arg1);
82 }
83 }
84
85 public static final String getSavePath() {
86 File sdCard = Environment.getExternalStorageDirectory();
87 return sdCard.getAbsolutePath() + "/owncloud/";
88 }
89
90 public static final String getTemporalPath() {
91 File sdCard = Environment.getExternalStorageDirectory();
92 return sdCard.getAbsolutePath() + "/owncloud.tmp/";
93 }
94
95 @Override
96 public void onCreate() {
97 super.onCreate();
98 mNotificationMngr = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
99 HandlerThread thread = new HandlerThread("FileDownladerThread",
100 Process.THREAD_PRIORITY_BACKGROUND);
101 thread.start();
102 mServiceLooper = thread.getLooper();
103 mServiceHandler = new ServiceHandler(mServiceLooper);
104 }
105
106 @Override
107 public IBinder onBind(Intent arg0) {
108 return null;
109 }
110
111 @Override
112 public int onStartCommand(Intent intent, int flags, int startId) {
113 if ( !intent.hasExtra(EXTRA_ACCOUNT) ||
114 !intent.hasExtra(EXTRA_FILE_PATH) ||
115 !intent.hasExtra(EXTRA_REMOTE_PATH)
116 ) {
117 Log.e(TAG, "Not enough information provided in intent");
118 return START_NOT_STICKY;
119 }
120 mAccount = intent.getParcelableExtra(EXTRA_ACCOUNT);
121 mFilePath = intent.getStringExtra(EXTRA_FILE_PATH);
122 mRemotePath = intent.getStringExtra(EXTRA_REMOTE_PATH);
123 mTotalDownloadSize = intent.getLongExtra(EXTRA_FILE_SIZE, -1);
124 mCurrentDownloadSize = mLastPercent = 0;
125
126 Message msg = mServiceHandler.obtainMessage();
127 msg.arg1 = startId;
128 mServiceHandler.sendMessage(msg);
129
130 return START_NOT_STICKY;
131 }
132
133 /**
134 * Core download method: requests the file to download and stores it.
135 */
136 private void downloadFile() {
137 boolean downloadResult = false;
138
139 /// prepare client object to send the request to the ownCloud server
140 AccountManager am = (AccountManager) getSystemService(ACCOUNT_SERVICE);
141 WebdavClient wdc = new WebdavClient(mAccount, getApplicationContext());
142 String username = mAccount.name.split("@")[0];
143 String password = null;
144 try {
145 password = am.blockingGetAuthToken(mAccount,
146 AccountAuthenticator.AUTH_TOKEN_TYPE, true);
147 } catch (Exception e) {
148 Log.e(TAG, "Access to account credentials failed", e);
149 sendFinalBroadcast(downloadResult, null);
150 return;
151 }
152 wdc.setCredentials(username, password);
153 wdc.allowSelfsignedCertificates();
154 wdc.setDataTransferProgressListener(this);
155
156
157 /// download will be in a temporal file
158 File tmpFile = new File(getTemporalPath() + mAccount.name + mFilePath);
159
160 /// create status notification to show the download progress
161 mNotification = new Notification(R.drawable.icon, getString(R.string.downloader_download_in_progress_ticker), System.currentTimeMillis());
162 mNotification.flags |= Notification.FLAG_ONGOING_EVENT;
163 mNotification.contentView = new RemoteViews(getApplicationContext().getPackageName(), R.layout.progressbar_layout);
164 mNotification.contentView.setProgressBar(R.id.status_progress, 100, 0, mTotalDownloadSize == -1);
165 mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.downloader_download_in_progress_content), 0, tmpFile.getName()));
166 mNotification.contentView.setImageViewResource(R.id.status_icon, R.drawable.icon);
167 // TODO put something smart in the contentIntent below
168 mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
169 mNotificationMngr.notify(R.string.downloader_download_in_progress_ticker, mNotification);
170
171
172 /// perform the download
173 tmpFile.getParentFile().mkdirs();
174 mDownloadsInProgress.put(buildRemoteName(mAccount.name, mRemotePath), tmpFile.getAbsolutePath());
175 File newFile = null;
176 try {
177 if (wdc.downloadFile(mRemotePath, tmpFile)) {
178 newFile = new File(getSavePath() + mAccount.name + mFilePath);
179 newFile.getParentFile().mkdirs();
180 boolean moved = tmpFile.renameTo(newFile);
181
182 if (moved) {
183 ContentValues cv = new ContentValues();
184 cv.put(ProviderTableMeta.FILE_STORAGE_PATH, newFile.getAbsolutePath());
185 getContentResolver().update(
186 ProviderTableMeta.CONTENT_URI,
187 cv,
188 ProviderTableMeta.FILE_NAME + "=? AND "
189 + ProviderTableMeta.FILE_ACCOUNT_OWNER + "=?",
190 new String[] {
191 mFilePath.substring(mFilePath.lastIndexOf('/') + 1),
192 mAccount.name });
193 downloadResult = true;
194 }
195 }
196 } finally {
197 mDownloadsInProgress.remove(buildRemoteName(mAccount.name, mRemotePath));
198 }
199
200
201 /// notify result
202 mNotificationMngr.cancel(R.string.downloader_download_in_progress_ticker);
203 int tickerId = (downloadResult) ? R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker;
204 int contentId = (downloadResult) ? R.string.downloader_download_succeeded_content : R.string.downloader_download_failed_content;
205 Notification finalNotification = new Notification(R.drawable.icon, getString(tickerId), System.currentTimeMillis());
206 finalNotification.flags |= Notification.FLAG_AUTO_CANCEL;
207 // TODO put something smart in the contentIntent below
208 finalNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, new Intent(), PendingIntent.FLAG_UPDATE_CURRENT);
209 finalNotification.setLatestEventInfo(getApplicationContext(), getString(tickerId), String.format(getString(contentId), tmpFile.getName()), finalNotification.contentIntent);
210 mNotificationMngr.notify(tickerId, finalNotification);
211
212 sendFinalBroadcast(downloadResult, (downloadResult)?newFile.getAbsolutePath():null);
213 }
214
215 /**
216 * Callback method to update the progress bar in the status notification.
217 */
218 @Override
219 public void transferProgress(long progressRate) {
220 mCurrentDownloadSize += progressRate;
221 int percent = (int)(100.0*((double)mCurrentDownloadSize)/((double)mTotalDownloadSize));
222 if (percent != mLastPercent) {
223 mNotification.contentView.setProgressBar(R.id.status_progress, 100, (int)(100*mCurrentDownloadSize/mTotalDownloadSize), mTotalDownloadSize == -1);
224 mNotification.contentView.setTextViewText(R.id.status_text, String.format(getString(R.string.downloader_download_in_progress_content), percent, new File(mFilePath).getName()));
225 mNotificationMngr.notify(R.string.downloader_download_in_progress_ticker, mNotification);
226 }
227
228 mLastPercent = percent;
229 }
230
231
232 /**
233 * Sends a broadcast in order to the interested activities can update their view
234 *
235 * @param downloadResult 'True' if the download was successful
236 * @param newFilePath Absolute path to the download file
237 */
238 private void sendFinalBroadcast(boolean downloadResult, String newFilePath) {
239 Intent end = new Intent(DOWNLOAD_FINISH_MESSAGE);
240 end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult);
241 end.putExtra(ACCOUNT_NAME, mAccount.name);
242 end.putExtra(EXTRA_REMOTE_PATH, mRemotePath);
243 if (downloadResult) {
244 end.putExtra(EXTRA_FILE_PATH, newFilePath);
245 }
246 sendBroadcast(end);
247 }
248
249 }