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_PARENT_DIR_ID
= "PARENT_DIR_ID";
75 public static final String EXTRA_UPLOAD_RESULT
= "RESULT";
76 public static final String EXTRA_REMOTE_PATH
= "REMOTE_PATH";
77 public static final String EXTRA_OLD_REMOTE_PATH
= "OLD_REMOTE_PATH";
78 public static final String EXTRA_FILE_PATH
= "FILE_PATH";
79 public static final String ACCOUNT_NAME
= "ACCOUNT_NAME";
81 public static final String KEY_FILE
= "FILE";
82 public static final String KEY_LOCAL_FILE
= "LOCAL_FILE"; // TODO remove this as a possible input argument ; use KEY_FILE everywhere
83 public static final String KEY_REMOTE_FILE
= "REMOTE_FILE"; // TODO remove this as a possible input argument ; use KEY_FILE everywhere
84 public static final String KEY_MIME_TYPE
= "MIME_TYPE";
86 public static final String KEY_ACCOUNT
= "ACCOUNT";
88 public static final String KEY_UPLOAD_TYPE
= "UPLOAD_TYPE";
89 public static final String KEY_FORCE_OVERWRITE
= "KEY_FORCE_OVERWRITE";
90 public static final String KEY_INSTANT_UPLOAD
= "INSTANT_UPLOAD";
92 public static final int UPLOAD_SINGLE_FILE
= 0;
93 public static final int UPLOAD_MULTIPLE_FILES
= 1;
95 private static final String TAG
= FileUploader
.class.getSimpleName();
97 private Looper mServiceLooper
;
98 private ServiceHandler mServiceHandler
;
99 private IBinder mBinder
;
100 private WebdavClient mUploadClient
= null
;
101 private Account mLastAccount
= null
;
102 private FileDataStorageManager mStorageManager
;
104 private ConcurrentMap
<String
, UploadFileOperation
> mPendingUploads
= new ConcurrentHashMap
<String
, UploadFileOperation
>();
105 private UploadFileOperation mCurrentUpload
= null
;
107 private NotificationManager mNotificationManager
;
108 private Notification mNotification
;
109 private int mLastPercent
;
110 private RemoteViews mDefaultNotificationContentView
;
114 * Builds a key for mPendingUploads from the account and file to upload
116 * @param account Account where the file to download is stored
117 * @param file File to download
119 private String
buildRemoteName(Account account
, OCFile file
) {
120 return account
.name
+ file
.getRemotePath();
123 private String
buildRemoteName(Account account
, String remotePath
) {
124 return account
.name
+ remotePath
;
129 * Checks if an ownCloud server version should support chunked uploads.
131 * @param version OwnCloud version instance corresponding to an ownCloud server.
132 * @return 'True' if the ownCloud server with version supports chunked uploads.
134 private static boolean chunkedUploadIsSupported(OwnCloudVersion version
) {
135 return (version
!= null
&& version
.compareTo(OwnCloudVersion
.owncloud_v4_5
) >= 0);
141 * Service initialization
144 public void onCreate() {
146 mNotificationManager
= (NotificationManager
) getSystemService(NOTIFICATION_SERVICE
);
147 HandlerThread thread
= new HandlerThread("FileUploaderThread",
148 Process
.THREAD_PRIORITY_BACKGROUND
);
150 mServiceLooper
= thread
.getLooper();
151 mServiceHandler
= new ServiceHandler(mServiceLooper
, this);
152 mBinder
= new FileUploaderBinder();
157 * Entry point to add one or several files to the queue of uploads.
159 * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working
160 * although the caller activity goes away.
163 public int onStartCommand(Intent intent
, int flags
, int startId
) {
164 if (!intent
.hasExtra(KEY_ACCOUNT
) || !intent
.hasExtra(KEY_UPLOAD_TYPE
) || !(intent
.hasExtra(KEY_LOCAL_FILE
) || intent
.hasExtra(KEY_FILE
))) {
165 Log
.e(TAG
, "Not enough information provided in intent");
166 return Service
.START_NOT_STICKY
;
168 int uploadType
= intent
.getIntExtra(KEY_UPLOAD_TYPE
, -1);
169 if (uploadType
== -1) {
170 Log
.e(TAG
, "Incorrect upload type provided");
171 return Service
.START_NOT_STICKY
;
173 Account account
= intent
.getParcelableExtra(KEY_ACCOUNT
);
175 String
[] localPaths
= null
, remotePaths
= null
, mimeTypes
= null
;
176 OCFile
[] files
= null
;
177 if (uploadType
== UPLOAD_SINGLE_FILE
) {
179 if (intent
.hasExtra(KEY_FILE
)) {
180 files
= new OCFile
[] {intent
.getParcelableExtra(KEY_FILE
) };
183 localPaths
= new String
[] { intent
.getStringExtra(KEY_LOCAL_FILE
) };
184 remotePaths
= new String
[] { intent
.getStringExtra(KEY_REMOTE_FILE
) };
185 mimeTypes
= new String
[] { intent
.getStringExtra(KEY_MIME_TYPE
) };
188 } else { // mUploadType == UPLOAD_MULTIPLE_FILES
190 if (intent
.hasExtra(KEY_FILE
)) {
191 files
= (OCFile
[]) intent
.getParcelableArrayExtra(KEY_FILE
); // TODO will this casting work fine?
194 localPaths
= intent
.getStringArrayExtra(KEY_LOCAL_FILE
);
195 remotePaths
= intent
.getStringArrayExtra(KEY_REMOTE_FILE
);
196 mimeTypes
= intent
.getStringArrayExtra(KEY_MIME_TYPE
);
200 FileDataStorageManager storageManager
= new FileDataStorageManager(account
, getContentResolver());
202 boolean forceOverwrite
= intent
.getBooleanExtra(KEY_FORCE_OVERWRITE
, false
);
203 boolean isInstant
= intent
.getBooleanExtra(KEY_INSTANT_UPLOAD
, false
);
204 boolean fixed
= false
;
206 fixed
= checkAndFixInstantUploadDirectory(storageManager
); // MUST be done BEFORE calling obtainNewOCFileToUpload
209 if (intent
.hasExtra(KEY_FILE
) && files
== null
) {
210 Log
.e(TAG
, "Incorrect array for OCFiles provided in upload intent");
211 return Service
.START_NOT_STICKY
;
213 } else if (!intent
.hasExtra(KEY_FILE
)) {
214 if (localPaths
== null
) {
215 Log
.e(TAG
, "Incorrect array for local paths provided in upload intent");
216 return Service
.START_NOT_STICKY
;
218 if (remotePaths
== null
) {
219 Log
.e(TAG
, "Incorrect array for remote paths provided in upload intent");
220 return Service
.START_NOT_STICKY
;
222 if (localPaths
.length
!= remotePaths
.length
) {
223 Log
.e(TAG
, "Different number of remote paths and local paths!");
224 return Service
.START_NOT_STICKY
;
227 files
= new OCFile
[localPaths
.length
];
228 for (int i
=0; i
< localPaths
.length
; i
++) {
229 files
[i
] = obtainNewOCFileToUpload(remotePaths
[i
], localPaths
[i
], ((mimeTypes
!=null
)?mimeTypes
[i
]:(String
)null
), storageManager
);
233 OwnCloudVersion ocv
= new OwnCloudVersion(AccountManager
.get(this).getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
));
234 boolean chunked
= FileUploader
.chunkedUploadIsSupported(ocv
);
235 AbstractList
<String
> requestedUploads
= new Vector
<String
>();
236 String uploadKey
= null
;
237 UploadFileOperation newUpload
= null
;
239 for (int i
=0; i
< files
.length
; i
++) {
240 uploadKey
= buildRemoteName(account
, files
[i
].getRemotePath());
242 newUpload
= new ChunkedUploadFileOperation(account
, files
[i
], isInstant
, forceOverwrite
);
244 newUpload
= new UploadFileOperation(account
, files
[i
], isInstant
, forceOverwrite
);
247 newUpload
.setRemoteFolderToBeCreated();
249 mPendingUploads
.putIfAbsent(uploadKey
, newUpload
);
250 newUpload
.addDatatransferProgressListener(this);
251 requestedUploads
.add(uploadKey
);
254 } catch (IllegalArgumentException e
) {
255 Log
.e(TAG
, "Not enough information provided in intent: " + e
.getMessage());
256 return START_NOT_STICKY
;
258 } catch (IllegalStateException e
) {
259 Log
.e(TAG
, "Bad information provided in intent: " + e
.getMessage());
260 return START_NOT_STICKY
;
262 } catch (Exception e
) {
263 Log
.e(TAG
, "Unexpected exception while processing upload intent", e
);
264 return START_NOT_STICKY
;
268 if (requestedUploads
.size() > 0) {
269 Message msg
= mServiceHandler
.obtainMessage();
271 msg
.obj
= requestedUploads
;
272 mServiceHandler
.sendMessage(msg
);
275 return Service
.START_NOT_STICKY
;
280 * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the addition of new files.
282 * Implemented to perform cancellation, pause and resume of existing uploads.
285 public IBinder
onBind(Intent arg0
) {
290 * Binder to let client components to perform operations on the queue of uploads.
292 * It provides by itself the available operations.
294 public class FileUploaderBinder
extends Binder
{
297 * Cancels a pending or current upload of a remote file.
299 * @param account Owncloud account where the remote file will be stored.
300 * @param file A file in the queue of pending uploads
302 public void cancel(Account account
, OCFile file
) {
303 UploadFileOperation upload
= null
;
304 synchronized (mPendingUploads
) {
305 upload
= mPendingUploads
.remove(buildRemoteName(account
, file
));
307 if (upload
!= null
) {
314 * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it
316 * @param account Owncloud account where the remote file will be stored.
317 * @param file A file that could be in the queue of pending uploads
319 public boolean isUploading(Account account
, OCFile file
) {
320 synchronized (mPendingUploads
) {
321 return (mPendingUploads
.containsKey(buildRemoteName(account
, file
)));
330 * Upload worker. Performs the pending uploads in the order they were requested.
332 * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
334 private static class ServiceHandler
extends Handler
{
335 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
336 FileUploader mService
;
337 public ServiceHandler(Looper looper
, FileUploader service
) {
340 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
345 public void handleMessage(Message msg
) {
346 @SuppressWarnings("unchecked")
347 AbstractList
<String
> requestedUploads
= (AbstractList
<String
>) msg
.obj
;
348 if (msg
.obj
!= null
) {
349 Iterator
<String
> it
= requestedUploads
.iterator();
350 while (it
.hasNext()) {
351 mService
.uploadFile(it
.next());
354 mService
.stopSelf(msg
.arg1
);
362 * Core upload method: sends the file(s) to upload
364 * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
366 public void uploadFile(String uploadKey
) {
368 synchronized(mPendingUploads
) {
369 mCurrentUpload
= mPendingUploads
.get(uploadKey
);
372 if (mCurrentUpload
!= null
) {
374 notifyUploadStart(mCurrentUpload
);
377 /// prepare client object to send requests to the ownCloud server
378 if (mUploadClient
== null
|| !mLastAccount
.equals(mCurrentUpload
.getAccount())) {
379 mLastAccount
= mCurrentUpload
.getAccount();
380 mStorageManager
= new FileDataStorageManager(mLastAccount
, getContentResolver());
381 mUploadClient
= OwnCloudClientUtils
.createOwnCloudClient(mLastAccount
, getApplicationContext());
384 /// create remote folder for instant uploads
385 if (mCurrentUpload
.isRemoteFolderToBeCreated()) {
386 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
390 /// perform the upload
391 RemoteOperationResult uploadResult
= null
;
393 uploadResult
= mCurrentUpload
.execute(mUploadClient
);
394 if (uploadResult
.isSuccess()) {
399 synchronized(mPendingUploads
) {
400 mPendingUploads
.remove(uploadKey
);
405 notifyUploadResult(uploadResult
, mCurrentUpload
);
407 sendFinalBroadcast(mCurrentUpload
, uploadResult
);
414 * Saves a OC File after a successful upload.
416 * A PROPFIND is necessary to keep the props in the local database synchronized with the server,
417 * specially the modification time and Etag (where available)
419 * TODO refactor this ugly thing
421 private void saveUploadedFile() {
422 OCFile file
= mCurrentUpload
.getFile();
423 long syncDate
= System
.currentTimeMillis();
424 file
.setLastSyncDateForData(syncDate
);
426 /// new PROPFIND to keep data consistent with server in theory, should return the same we already have
427 PropFindMethod propfind
= null
;
428 RemoteOperationResult result
= null
;
430 propfind
= new PropFindMethod(mUploadClient
.getBaseUri() + WebdavUtils
.encodePath(mCurrentUpload
.getRemotePath()));
431 int status
= mUploadClient
.executeMethod(propfind
);
432 boolean isMultiStatus
= (status
== HttpStatus
.SC_MULTI_STATUS
);
434 MultiStatus resp
= propfind
.getResponseBodyAsMultiStatus();
435 WebdavEntry we
= new WebdavEntry(resp
.getResponses()[0],
436 mUploadClient
.getBaseUri().getPath());
437 updateOCFile(file
, we
);
438 file
.setLastSyncDateForProperties(syncDate
);
441 mUploadClient
.exhaustResponse(propfind
.getResponseBodyAsStream());
444 result
= new RemoteOperationResult(isMultiStatus
, status
);
445 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage());
447 } catch (Exception e
) {
448 result
= new RemoteOperationResult(e
);
449 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage(), e
);
452 if (propfind
!= null
)
453 propfind
.releaseConnection();
457 if (mCurrentUpload
.wasRenamed()) {
458 OCFile oldFile
= mCurrentUpload
.getOldFile();
459 if (!oldFile
.fileExists()) {
460 // just a name coincidence
461 file
.setStoragePath(oldFile
.getStoragePath());
464 // conflict resolved with 'Keep both' by the user
465 File localFile
= new File(oldFile
.getStoragePath());
466 File newLocalFile
= new File(FileStorageUtils
.getDefaultSavePathFor(mCurrentUpload
.getAccount().name
, file
));
467 boolean renameSuccessed
= localFile
.renameTo(newLocalFile
);
468 if (renameSuccessed
) {
469 file
.setStoragePath(newLocalFile
.getAbsolutePath());
473 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'");
474 file
.setStoragePath(null
);
476 // - local file will be kept there as 'trash' until is download (and overwritten) again from the server;
477 // - user will see as 'not down' a file that was just upload
479 // - no loss of data happened
480 // - 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
482 oldFile
.setStoragePath(null
);
483 mStorageManager
.saveFile(oldFile
);
487 mStorageManager
.saveFile(file
);
491 private void updateOCFile(OCFile file
, WebdavEntry we
) {
492 file
.setCreationTimestamp(we
.createTimestamp());
493 file
.setFileLength(we
.contentLength());
494 file
.setMimetype(we
.contentType());
495 file
.setModificationTimestamp(we
.modifiedTimesamp());
496 // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available
500 private boolean checkAndFixInstantUploadDirectory(FileDataStorageManager storageManager
) {
501 OCFile instantUploadDir
= storageManager
.getFileByPath(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);
502 if (instantUploadDir
== null
) {
503 // first instant upload in the account, or never account not synchronized after the remote InstantUpload folder was created
504 OCFile newDir
= new OCFile(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);
505 newDir
.setMimetype("DIR");
506 newDir
.setParentId(storageManager
.getFileByPath(OCFile
.PATH_SEPARATOR
).getFileId());
507 storageManager
.saveFile(newDir
);
514 private OCFile
obtainNewOCFileToUpload(String remotePath
, String localPath
, String mimeType
, FileDataStorageManager storageManager
) {
515 OCFile newFile
= new OCFile(remotePath
);
516 newFile
.setStoragePath(localPath
);
517 newFile
.setLastSyncDateForProperties(0);
518 newFile
.setLastSyncDateForData(0);
521 if (localPath
!= null
&& localPath
.length() > 0) {
522 File localFile
= new File(localPath
);
523 newFile
.setFileLength(localFile
.length());
524 newFile
.setLastSyncDateForData(localFile
.lastModified());
525 } // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created
528 if (mimeType
== null
|| mimeType
.length() <= 0) {
530 mimeType
= MimeTypeMap
.getSingleton()
531 .getMimeTypeFromExtension(
532 remotePath
.substring(remotePath
.lastIndexOf('.') + 1));
533 } catch (IndexOutOfBoundsException e
) {
534 Log
.e(TAG
, "Trying to find out MIME type of a file without extension: " + remotePath
);
537 if (mimeType
== null
) {
538 mimeType
= "application/octet-stream";
540 newFile
.setMimetype(mimeType
);
543 String parentPath
= new File(remotePath
).getParent();
544 parentPath
= parentPath
.endsWith("/")?parentPath
:parentPath
+"/" ;
545 OCFile parentDir
= storageManager
.getFileByPath(parentPath
);
546 if (parentDir
== null
) {
547 throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath
);
549 long parentDirId
= parentDir
.getFileId();
550 newFile
.setParentId(parentDirId
);
556 * Creates a status notification to show the upload progress
558 * @param upload Upload operation starting.
560 private void notifyUploadStart(UploadFileOperation upload
) {
561 /// create status notification with a progress bar
563 mNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_in_progress_ticker
), System
.currentTimeMillis());
564 mNotification
.flags
|= Notification
.FLAG_ONGOING_EVENT
;
565 mDefaultNotificationContentView
= mNotification
.contentView
;
566 mNotification
.contentView
= new RemoteViews(getApplicationContext().getPackageName(), R
.layout
.progressbar_layout
);
567 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, 0, false
);
568 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.uploader_upload_in_progress_content
), 0, new File(upload
.getStoragePath()).getName()));
569 mNotification
.contentView
.setImageViewResource(R
.id
.status_icon
, R
.drawable
.icon
);
571 /// includes a pending intent in the notification showing the details view of the file
572 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
573 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
574 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
575 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
576 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
578 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
583 * Callback method to update the progress bar in the status notification
586 public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, long totalToTransfer
, String fileName
) {
587 int percent
= (int)(100.0*((double)totalTransferredSoFar
)/((double)totalToTransfer
));
588 if (percent
!= mLastPercent
) {
589 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, percent
, false
);
590 String text
= String
.format(getString(R
.string
.uploader_upload_in_progress_content
), percent
, fileName
);
591 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, text
);
592 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
594 mLastPercent
= percent
;
599 * Callback method to update the progress bar in the status notification (old version)
602 public void onTransferProgress(long progressRate
) {
603 // NOTHING TO DO HERE ANYMORE
608 * Updates the status notification with the result of an upload operation.
610 * @param uploadResult Result of the upload operation.
611 * @param upload Finished upload operation
613 private void notifyUploadResult(RemoteOperationResult uploadResult
, UploadFileOperation upload
) {
614 if (uploadResult
.isCancelled()) {
615 /// cancelled operation -> silent removal of progress notification
616 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
618 } else if (uploadResult
.isSuccess()) {
619 /// success -> silent update of progress notification to success message
620 mNotification
.flags ^
= Notification
.FLAG_ONGOING_EVENT
; // remove the ongoing flag
621 mNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
622 mNotification
.contentView
= mDefaultNotificationContentView
;
624 /// includes a pending intent in the notification showing the details view of the file
625 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
626 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
627 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
628 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
629 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
631 mNotification
.setLatestEventInfo( getApplicationContext(),
632 getString(R
.string
.uploader_upload_succeeded_ticker
),
633 String
.format(getString(R
.string
.uploader_upload_succeeded_content_single
), (new File(upload
.getStoragePath())).getName()),
634 mNotification
.contentIntent
);
636 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
638 /* Notification about multiple uploads: pending of update
639 mNotification.setLatestEventInfo( getApplicationContext(),
640 getString(R.string.uploader_upload_succeeded_ticker),
641 String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),
642 mNotification.contentIntent);
646 /// fail -> explicit failure notification
647 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
648 Notification finalNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_failed_ticker
), System
.currentTimeMillis());
649 finalNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
650 // TODO put something smart in the contentIntent below
651 finalNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), new Intent(), 0);
652 finalNotification
.setLatestEventInfo( getApplicationContext(),
653 getString(R
.string
.uploader_upload_failed_ticker
),
654 String
.format(getString(R
.string
.uploader_upload_failed_content_single
), (new File(upload
.getStoragePath())).getName()),
655 finalNotification
.contentIntent
);
657 mNotificationManager
.notify(R
.string
.uploader_upload_failed_ticker
, finalNotification
);
659 /* Notification about multiple uploads failure: pending of update
660 finalNotification.setLatestEventInfo( getApplicationContext(),
661 getString(R.string.uploader_upload_failed_ticker),
662 String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),
663 finalNotification.contentIntent);
671 * Sends a broadcast in order to the interested activities can update their view
673 * @param upload Finished upload operation
674 * @param uploadResult Result of the upload operation
676 private void sendFinalBroadcast(UploadFileOperation upload
, RemoteOperationResult uploadResult
) {
677 Intent end
= new Intent(UPLOAD_FINISH_MESSAGE
);
678 end
.putExtra(EXTRA_REMOTE_PATH
, upload
.getRemotePath()); // real remote path, after possible automatic renaming
679 if (upload
.wasRenamed()) {
680 end
.putExtra(EXTRA_OLD_REMOTE_PATH
, upload
.getOldFile().getRemotePath());
682 end
.putExtra(EXTRA_FILE_PATH
, upload
.getStoragePath());
683 end
.putExtra(ACCOUNT_NAME
, upload
.getAccount().name
);
684 end
.putExtra(EXTRA_UPLOAD_RESULT
, uploadResult
.isSuccess());
685 end
.putExtra(EXTRA_PARENT_DIR_ID
, upload
.getFile().getParentId());
686 sendStickyBroadcast(end
);