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