1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com
.owncloud
.android
.files
.services
;
22 import java
.util
.AbstractList
;
23 import java
.util
.Iterator
;
24 import java
.util
.Vector
;
25 import java
.util
.concurrent
.ConcurrentHashMap
;
26 import java
.util
.concurrent
.ConcurrentMap
;
28 import org
.apache
.http
.HttpStatus
;
29 import org
.apache
.jackrabbit
.webdav
.MultiStatus
;
30 import org
.apache
.jackrabbit
.webdav
.client
.methods
.PropFindMethod
;
32 import com
.owncloud
.android
.authenticator
.AccountAuthenticator
;
33 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
;
34 import com
.owncloud
.android
.datamodel
.OCFile
;
35 import com
.owncloud
.android
.files
.InstantUploadBroadcastReceiver
;
36 import com
.owncloud
.android
.operations
.ChunkedUploadFileOperation
;
37 import com
.owncloud
.android
.operations
.RemoteOperationResult
;
38 import com
.owncloud
.android
.operations
.UploadFileOperation
;
39 import com
.owncloud
.android
.ui
.activity
.FileDetailActivity
;
40 import com
.owncloud
.android
.ui
.fragment
.FileDetailFragment
;
41 import com
.owncloud
.android
.utils
.FileStorageUtils
;
42 import com
.owncloud
.android
.utils
.OwnCloudVersion
;
44 import eu
.alefzero
.webdav
.OnDatatransferProgressListener
;
45 import eu
.alefzero
.webdav
.WebdavEntry
;
46 import eu
.alefzero
.webdav
.WebdavUtils
;
48 import com
.owncloud
.android
.network
.OwnCloudClientUtils
;
50 import android
.accounts
.Account
;
51 import android
.accounts
.AccountManager
;
52 import android
.app
.Notification
;
53 import android
.app
.NotificationManager
;
54 import android
.app
.PendingIntent
;
55 import android
.app
.Service
;
56 import android
.content
.Intent
;
57 import android
.os
.Binder
;
58 import android
.os
.Handler
;
59 import android
.os
.HandlerThread
;
60 import android
.os
.IBinder
;
61 import android
.os
.Looper
;
62 import android
.os
.Message
;
63 import android
.os
.Process
;
64 import android
.util
.Log
;
65 import android
.webkit
.MimeTypeMap
;
66 import android
.widget
.RemoteViews
;
68 import com
.owncloud
.android
.R
;
69 import eu
.alefzero
.webdav
.WebdavClient
;
71 public class FileUploader
extends Service
implements OnDatatransferProgressListener
{
73 public static final String UPLOAD_FINISH_MESSAGE
= "UPLOAD_FINISH";
74 public static final String EXTRA_UPLOAD_RESULT
= "RESULT";
75 public static final String EXTRA_REMOTE_PATH
= "REMOTE_PATH";
76 public static final String EXTRA_OLD_REMOTE_PATH
= "OLD_REMOTE_PATH";
77 public static final String EXTRA_FILE_PATH
= "FILE_PATH";
78 public static final String ACCOUNT_NAME
= "ACCOUNT_NAME";
80 public static final String KEY_FILE
= "FILE";
81 public static final String KEY_LOCAL_FILE
= "LOCAL_FILE";
82 public static final String KEY_REMOTE_FILE
= "REMOTE_FILE";
83 public static final String KEY_MIME_TYPE
= "MIME_TYPE";
85 public static final String KEY_ACCOUNT
= "ACCOUNT";
87 public static final String KEY_UPLOAD_TYPE
= "UPLOAD_TYPE";
88 public static final String KEY_FORCE_OVERWRITE
= "KEY_FORCE_OVERWRITE";
89 public static final String KEY_INSTANT_UPLOAD
= "INSTANT_UPLOAD";
91 public static final int UPLOAD_SINGLE_FILE
= 0;
92 public static final int UPLOAD_MULTIPLE_FILES
= 1;
94 private static final String TAG
= FileUploader
.class.getSimpleName();
96 private Looper mServiceLooper
;
97 private ServiceHandler mServiceHandler
;
98 private IBinder mBinder
;
99 private WebdavClient mUploadClient
= null
;
100 private Account mLastAccount
= null
;
101 private FileDataStorageManager mStorageManager
;
103 private ConcurrentMap
<String
, UploadFileOperation
> mPendingUploads
= new ConcurrentHashMap
<String
, UploadFileOperation
>();
104 private UploadFileOperation mCurrentUpload
= null
;
106 private NotificationManager mNotificationManager
;
107 private Notification mNotification
;
108 private int mLastPercent
;
109 private RemoteViews mDefaultNotificationContentView
;
113 * Builds a key for mPendingUploads from the account and file to upload
115 * @param account Account where the file to download is stored
116 * @param file File to download
118 private String
buildRemoteName(Account account
, OCFile file
) {
119 return account
.name
+ file
.getRemotePath();
122 private String
buildRemoteName(Account account
, String remotePath
) {
123 return account
.name
+ remotePath
;
128 * Checks if an ownCloud server version should support chunked uploads.
130 * @param version OwnCloud version instance corresponding to an ownCloud server.
131 * @return 'True' if the ownCloud server with version supports chunked uploads.
133 private static boolean chunkedUploadIsSupported(OwnCloudVersion version
) {
134 return (version
!= null
&& version
.compareTo(OwnCloudVersion
.owncloud_v4_5
) >= 0);
140 * Service initialization
143 public void onCreate() {
145 mNotificationManager
= (NotificationManager
) getSystemService(NOTIFICATION_SERVICE
);
146 HandlerThread thread
= new HandlerThread("FileUploaderThread",
147 Process
.THREAD_PRIORITY_BACKGROUND
);
149 mServiceLooper
= thread
.getLooper();
150 mServiceHandler
= new ServiceHandler(mServiceLooper
, this);
151 mBinder
= new FileUploaderBinder();
156 * Entry point to add one or several files to the queue of uploads.
158 * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working
159 * although the caller activity goes away.
162 public int onStartCommand(Intent intent
, int flags
, int startId
) {
163 if (!intent
.hasExtra(KEY_ACCOUNT
) || !intent
.hasExtra(KEY_UPLOAD_TYPE
) || !(intent
.hasExtra(KEY_LOCAL_FILE
) || intent
.hasExtra(KEY_FILE
))) {
164 Log
.e(TAG
, "Not enough information provided in intent");
165 return Service
.START_NOT_STICKY
;
167 int uploadType
= intent
.getIntExtra(KEY_UPLOAD_TYPE
, -1);
168 if (uploadType
== -1) {
169 Log
.e(TAG
, "Incorrect upload type provided");
170 return Service
.START_NOT_STICKY
;
172 Account account
= intent
.getParcelableExtra(KEY_ACCOUNT
);
174 String
[] localPaths
= null
, remotePaths
= null
, mimeTypes
= null
;
175 OCFile
[] files
= null
;
176 if (uploadType
== UPLOAD_SINGLE_FILE
) {
178 if (intent
.hasExtra(KEY_FILE
)) {
179 files
= new OCFile
[] {intent
.getParcelableExtra(KEY_FILE
) };
182 localPaths
= new String
[] { intent
.getStringExtra(KEY_LOCAL_FILE
) };
183 remotePaths
= new String
[] { intent
.getStringExtra(KEY_REMOTE_FILE
) };
184 mimeTypes
= new String
[] { intent
.getStringExtra(KEY_MIME_TYPE
) };
187 } else { // mUploadType == UPLOAD_MULTIPLE_FILES
189 if (intent
.hasExtra(KEY_FILE
)) {
190 files
= (OCFile
[]) intent
.getParcelableArrayExtra(KEY_FILE
); // TODO will this casting work fine?
193 localPaths
= intent
.getStringArrayExtra(KEY_LOCAL_FILE
);
194 remotePaths
= intent
.getStringArrayExtra(KEY_REMOTE_FILE
);
195 mimeTypes
= intent
.getStringArrayExtra(KEY_MIME_TYPE
);
199 FileDataStorageManager storageManager
= new FileDataStorageManager(account
, getContentResolver());
201 boolean forceOverwrite
= intent
.getBooleanExtra(KEY_FORCE_OVERWRITE
, false
);
202 boolean isInstant
= intent
.getBooleanExtra(KEY_INSTANT_UPLOAD
, false
);
203 boolean fixed
= false
;
205 fixed
= checkAndFixInstantUploadDirectory(storageManager
); // MUST be done BEFORE calling obtainNewOCFileToUpload
208 if (intent
.hasExtra(KEY_FILE
) && files
== null
) {
209 Log
.e(TAG
, "Incorrect array for OCFiles provided in upload intent");
210 return Service
.START_NOT_STICKY
;
212 } else if (!intent
.hasExtra(KEY_FILE
)) {
213 if (localPaths
== null
) {
214 Log
.e(TAG
, "Incorrect array for local paths provided in upload intent");
215 return Service
.START_NOT_STICKY
;
217 if (remotePaths
== null
) {
218 Log
.e(TAG
, "Incorrect array for remote paths provided in upload intent");
219 return Service
.START_NOT_STICKY
;
221 if (localPaths
.length
!= remotePaths
.length
) {
222 Log
.e(TAG
, "Different number of remote paths and local paths!");
223 return Service
.START_NOT_STICKY
;
226 files
= new OCFile
[localPaths
.length
];
227 for (int i
=0; i
< localPaths
.length
; i
++) {
228 files
[i
] = obtainNewOCFileToUpload(remotePaths
[i
], localPaths
[i
], ((mimeTypes
!=null
)?mimeTypes
[i
]:(String
)null
), storageManager
);
232 OwnCloudVersion ocv
= new OwnCloudVersion(AccountManager
.get(this).getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
));
233 boolean chunked
= FileUploader
.chunkedUploadIsSupported(ocv
);
234 AbstractList
<String
> requestedUploads
= new Vector
<String
>();
235 String uploadKey
= null
;
236 UploadFileOperation newUpload
= null
;
238 for (int i
=0; i
< files
.length
; i
++) {
239 uploadKey
= buildRemoteName(account
, files
[i
].getRemotePath());
241 newUpload
= new ChunkedUploadFileOperation(account
, files
[i
], isInstant
, forceOverwrite
);
243 newUpload
= new UploadFileOperation(account
, files
[i
], isInstant
, forceOverwrite
);
246 newUpload
.setRemoteFolderToBeCreated();
248 mPendingUploads
.putIfAbsent(uploadKey
, newUpload
);
249 newUpload
.addDatatransferProgressListener(this);
250 requestedUploads
.add(uploadKey
);
253 } catch (IllegalArgumentException e
) {
254 Log
.e(TAG
, "Not enough information provided in intent: " + e
.getMessage());
255 return START_NOT_STICKY
;
257 } catch (IllegalStateException e
) {
258 Log
.e(TAG
, "Bad information provided in intent: " + e
.getMessage());
259 return START_NOT_STICKY
;
261 } catch (Exception e
) {
262 Log
.e(TAG
, "Unexpected exception while processing upload intent", e
);
263 return START_NOT_STICKY
;
267 if (requestedUploads
.size() > 0) {
268 Message msg
= mServiceHandler
.obtainMessage();
270 msg
.obj
= requestedUploads
;
271 mServiceHandler
.sendMessage(msg
);
274 return Service
.START_NOT_STICKY
;
279 * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the addition of new files.
281 * Implemented to perform cancellation, pause and resume of existing uploads.
284 public IBinder
onBind(Intent arg0
) {
289 * Binder to let client components to perform operations on the queue of uploads.
291 * It provides by itself the available operations.
293 public class FileUploaderBinder
extends Binder
{
296 * Cancels a pending or current upload of a remote file.
298 * @param account Owncloud account where the remote file will be stored.
299 * @param file A file in the queue of pending uploads
301 public void cancel(Account account
, OCFile file
) {
302 UploadFileOperation upload
= null
;
303 synchronized (mPendingUploads
) {
304 upload
= mPendingUploads
.remove(buildRemoteName(account
, file
));
306 if (upload
!= null
) {
313 * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it
315 * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download.
317 * @param account Owncloud account where the remote file will be stored.
318 * @param file A file that could be in the queue of pending uploads
320 public boolean isUploading(Account account
, OCFile file
) {
321 String targetKey
= buildRemoteName(account
, file
);
322 synchronized (mPendingUploads
) {
323 if (file
.isDirectory()) {
324 // this can be slow if there are many downloads :(
325 Iterator
<String
> it
= mPendingUploads
.keySet().iterator();
326 boolean found
= false
;
327 while (it
.hasNext() && !found
) {
328 found
= it
.next().startsWith(targetKey
);
332 return (mPendingUploads
.containsKey(targetKey
));
342 * Upload worker. Performs the pending uploads in the order they were requested.
344 * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
346 private static class ServiceHandler
extends Handler
{
347 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
348 FileUploader mService
;
349 public ServiceHandler(Looper looper
, FileUploader service
) {
352 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
357 public void handleMessage(Message msg
) {
358 @SuppressWarnings("unchecked")
359 AbstractList
<String
> requestedUploads
= (AbstractList
<String
>) msg
.obj
;
360 if (msg
.obj
!= null
) {
361 Iterator
<String
> it
= requestedUploads
.iterator();
362 while (it
.hasNext()) {
363 mService
.uploadFile(it
.next());
366 mService
.stopSelf(msg
.arg1
);
374 * Core upload method: sends the file(s) to upload
376 * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
378 public void uploadFile(String uploadKey
) {
380 synchronized(mPendingUploads
) {
381 mCurrentUpload
= mPendingUploads
.get(uploadKey
);
384 if (mCurrentUpload
!= null
) {
386 notifyUploadStart(mCurrentUpload
);
389 /// prepare client object to send requests to the ownCloud server
390 if (mUploadClient
== null
|| !mLastAccount
.equals(mCurrentUpload
.getAccount())) {
391 mLastAccount
= mCurrentUpload
.getAccount();
392 mStorageManager
= new FileDataStorageManager(mLastAccount
, getContentResolver());
393 mUploadClient
= OwnCloudClientUtils
.createOwnCloudClient(mLastAccount
, getApplicationContext());
396 /// create remote folder for instant uploads
397 if (mCurrentUpload
.isRemoteFolderToBeCreated()) {
398 mUploadClient
.createDirectory(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
); // ignoring result; fail could just mean that it already exists, but local database is not synchronized; the upload will be tried anyway
402 /// perform the upload
403 RemoteOperationResult uploadResult
= null
;
405 uploadResult
= mCurrentUpload
.execute(mUploadClient
);
406 if (uploadResult
.isSuccess()) {
411 synchronized(mPendingUploads
) {
412 mPendingUploads
.remove(uploadKey
);
417 notifyUploadResult(uploadResult
, mCurrentUpload
);
419 sendFinalBroadcast(mCurrentUpload
, uploadResult
);
426 * Saves a OC File after a successful upload.
428 * A PROPFIND is necessary to keep the props in the local database synchronized with the server,
429 * specially the modification time and Etag (where available)
431 * TODO refactor this ugly thing
433 private void saveUploadedFile() {
434 OCFile file
= mCurrentUpload
.getFile();
435 long syncDate
= System
.currentTimeMillis();
436 file
.setLastSyncDateForData(syncDate
);
438 /// new PROPFIND to keep data consistent with server in theory, should return the same we already have
439 PropFindMethod propfind
= null
;
440 RemoteOperationResult result
= null
;
442 propfind
= new PropFindMethod(mUploadClient
.getBaseUri() + WebdavUtils
.encodePath(mCurrentUpload
.getRemotePath()));
443 int status
= mUploadClient
.executeMethod(propfind
);
444 boolean isMultiStatus
= (status
== HttpStatus
.SC_MULTI_STATUS
);
446 MultiStatus resp
= propfind
.getResponseBodyAsMultiStatus();
447 WebdavEntry we
= new WebdavEntry(resp
.getResponses()[0],
448 mUploadClient
.getBaseUri().getPath());
449 updateOCFile(file
, we
);
450 file
.setLastSyncDateForProperties(syncDate
);
453 mUploadClient
.exhaustResponse(propfind
.getResponseBodyAsStream());
456 result
= new RemoteOperationResult(isMultiStatus
, status
);
457 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage());
459 } catch (Exception e
) {
460 result
= new RemoteOperationResult(e
);
461 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage(), e
);
464 if (propfind
!= null
)
465 propfind
.releaseConnection();
469 if (mCurrentUpload
.wasRenamed()) {
470 OCFile oldFile
= mCurrentUpload
.getOldFile();
471 if (!oldFile
.fileExists()) {
472 // just a name coincidence
473 file
.setStoragePath(oldFile
.getStoragePath());
476 // conflict resolved with 'Keep both' by the user
477 File localFile
= new File(oldFile
.getStoragePath());
478 File newLocalFile
= new File(FileStorageUtils
.getDefaultSavePathFor(mCurrentUpload
.getAccount().name
, file
));
479 boolean renameSuccessed
= localFile
.renameTo(newLocalFile
);
480 if (renameSuccessed
) {
481 file
.setStoragePath(newLocalFile
.getAbsolutePath());
485 Log
.d(TAG
, "DAMN IT: local rename failed after uploading a file with a new name already existing both in the remote account and the local database (should be due to a conflict solved with 'keep both'");
486 file
.setStoragePath(null
);
488 // - local file will be kept there as 'trash' until is download (and overwritten) again from the server;
489 // - user will see as 'not down' a file that was just upload
491 // - no loss of data happened
492 // - when the user downloads again the renamed and original file from the server, local file names and contents will be correctly synchronized with names and contents in server
494 oldFile
.setStoragePath(null
);
495 mStorageManager
.saveFile(oldFile
);
499 mStorageManager
.saveFile(file
);
503 private void updateOCFile(OCFile file
, WebdavEntry we
) {
504 file
.setCreationTimestamp(we
.createTimestamp());
505 file
.setFileLength(we
.contentLength());
506 file
.setMimetype(we
.contentType());
507 file
.setModificationTimestamp(we
.modifiedTimesamp());
508 // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available
512 private boolean checkAndFixInstantUploadDirectory(FileDataStorageManager storageManager
) {
513 OCFile instantUploadDir
= storageManager
.getFileByPath(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);
514 if (instantUploadDir
== null
) {
515 // first instant upload in the account, or never account not synchronized after the remote InstantUpload folder was created
516 OCFile newDir
= new OCFile(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);
517 newDir
.setMimetype("DIR");
518 newDir
.setParentId(storageManager
.getFileByPath(OCFile
.PATH_SEPARATOR
).getFileId());
519 storageManager
.saveFile(newDir
);
526 private OCFile
obtainNewOCFileToUpload(String remotePath
, String localPath
, String mimeType
, FileDataStorageManager storageManager
) {
527 OCFile newFile
= new OCFile(remotePath
);
528 newFile
.setStoragePath(localPath
);
529 newFile
.setLastSyncDateForProperties(0);
530 newFile
.setLastSyncDateForData(0);
533 if (localPath
!= null
&& localPath
.length() > 0) {
534 File localFile
= new File(localPath
);
535 newFile
.setFileLength(localFile
.length());
536 newFile
.setLastSyncDateForData(localFile
.lastModified());
537 } // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created
540 if (mimeType
== null
|| mimeType
.length() <= 0) {
542 mimeType
= MimeTypeMap
.getSingleton()
543 .getMimeTypeFromExtension(
544 remotePath
.substring(remotePath
.lastIndexOf('.') + 1));
545 } catch (IndexOutOfBoundsException e
) {
546 Log
.e(TAG
, "Trying to find out MIME type of a file without extension: " + remotePath
);
549 if (mimeType
== null
) {
550 mimeType
= "application/octet-stream";
552 newFile
.setMimetype(mimeType
);
555 String parentPath
= new File(remotePath
).getParent();
556 parentPath
= parentPath
.endsWith(OCFile
.PATH_SEPARATOR
) ? parentPath
: parentPath
+ OCFile
.PATH_SEPARATOR
;
557 OCFile parentDir
= storageManager
.getFileByPath(parentPath
);
558 if (parentDir
== null
) {
559 throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath
);
561 long parentDirId
= parentDir
.getFileId();
562 newFile
.setParentId(parentDirId
);
568 * Creates a status notification to show the upload progress
570 * @param upload Upload operation starting.
572 private void notifyUploadStart(UploadFileOperation upload
) {
573 /// create status notification with a progress bar
575 mNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_in_progress_ticker
), System
.currentTimeMillis());
576 mNotification
.flags
|= Notification
.FLAG_ONGOING_EVENT
;
577 mDefaultNotificationContentView
= mNotification
.contentView
;
578 mNotification
.contentView
= new RemoteViews(getApplicationContext().getPackageName(), R
.layout
.progressbar_layout
);
579 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, 0, false
);
580 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.uploader_upload_in_progress_content
), 0, new File(upload
.getStoragePath()).getName()));
581 mNotification
.contentView
.setImageViewResource(R
.id
.status_icon
, R
.drawable
.icon
);
583 /// includes a pending intent in the notification showing the details view of the file
584 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
585 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
586 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
587 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
588 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
590 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
595 * Callback method to update the progress bar in the status notification
598 public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, long totalToTransfer
, String fileName
) {
599 int percent
= (int)(100.0*((double)totalTransferredSoFar
)/((double)totalToTransfer
));
600 if (percent
!= mLastPercent
) {
601 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, percent
, false
);
602 String text
= String
.format(getString(R
.string
.uploader_upload_in_progress_content
), percent
, fileName
);
603 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, text
);
604 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
606 mLastPercent
= percent
;
611 * Callback method to update the progress bar in the status notification (old version)
614 public void onTransferProgress(long progressRate
) {
615 // NOTHING TO DO HERE ANYMORE
620 * Updates the status notification with the result of an upload operation.
622 * @param uploadResult Result of the upload operation.
623 * @param upload Finished upload operation
625 private void notifyUploadResult(RemoteOperationResult uploadResult
, UploadFileOperation upload
) {
626 if (uploadResult
.isCancelled()) {
627 /// cancelled operation -> silent removal of progress notification
628 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
630 } else if (uploadResult
.isSuccess()) {
631 /// success -> silent update of progress notification to success message
632 mNotification
.flags ^
= Notification
.FLAG_ONGOING_EVENT
; // remove the ongoing flag
633 mNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
634 mNotification
.contentView
= mDefaultNotificationContentView
;
636 /// includes a pending intent in the notification showing the details view of the file
637 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
638 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
639 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
640 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
641 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
643 mNotification
.setLatestEventInfo( getApplicationContext(),
644 getString(R
.string
.uploader_upload_succeeded_ticker
),
645 String
.format(getString(R
.string
.uploader_upload_succeeded_content_single
), (new File(upload
.getStoragePath())).getName()),
646 mNotification
.contentIntent
);
648 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
); // NOT AN ERROR; uploader_upload_in_progress_ticker is the target, not a new notification
650 /* Notification about multiple uploads: pending of update
651 mNotification.setLatestEventInfo( getApplicationContext(),
652 getString(R.string.uploader_upload_succeeded_ticker),
653 String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),
654 mNotification.contentIntent);
658 /// fail -> explicit failure notification
659 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
660 Notification finalNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_failed_ticker
), System
.currentTimeMillis());
661 finalNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
662 // TODO put something smart in the contentIntent below
663 finalNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), new Intent(), 0);
664 finalNotification
.setLatestEventInfo( getApplicationContext(),
665 getString(R
.string
.uploader_upload_failed_ticker
),
666 String
.format(getString(R
.string
.uploader_upload_failed_content_single
), (new File(upload
.getStoragePath())).getName()),
667 finalNotification
.contentIntent
);
669 mNotificationManager
.notify(R
.string
.uploader_upload_failed_ticker
, finalNotification
);
671 /* Notification about multiple uploads failure: pending of update
672 finalNotification.setLatestEventInfo( getApplicationContext(),
673 getString(R.string.uploader_upload_failed_ticker),
674 String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),
675 finalNotification.contentIntent);
683 * Sends a broadcast in order to the interested activities can update their view
685 * @param upload Finished upload operation
686 * @param uploadResult Result of the upload operation
688 private void sendFinalBroadcast(UploadFileOperation upload
, RemoteOperationResult uploadResult
) {
689 Intent end
= new Intent(UPLOAD_FINISH_MESSAGE
);
690 end
.putExtra(EXTRA_REMOTE_PATH
, upload
.getRemotePath()); // real remote path, after possible automatic renaming
691 if (upload
.wasRenamed()) {
692 end
.putExtra(EXTRA_OLD_REMOTE_PATH
, upload
.getOldFile().getRemotePath());
694 end
.putExtra(EXTRA_FILE_PATH
, upload
.getStoragePath());
695 end
.putExtra(ACCOUNT_NAME
, upload
.getAccount().name
);
696 end
.putExtra(EXTRA_UPLOAD_RESULT
, uploadResult
.isSuccess());
697 sendStickyBroadcast(end
);