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_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"; // TODO remove this as a possible input argument ; use KEY_FILE everywhere
82 public static final String KEY_REMOTE_FILE
= "REMOTE_FILE"; // TODO remove this as a possible input argument ; use KEY_FILE everywhere
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 if (intent
.hasExtra(KEY_FILE
) && files
== null
) {
202 Log
.e(TAG
, "Incorrect array for OCFiles provided in upload intent");
203 return Service
.START_NOT_STICKY
;
205 } else if (!intent
.hasExtra(KEY_FILE
)) {
206 if (localPaths
== null
) {
207 Log
.e(TAG
, "Incorrect array for local paths provided in upload intent");
208 return Service
.START_NOT_STICKY
;
210 if (remotePaths
== null
) {
211 Log
.e(TAG
, "Incorrect array for remote paths provided in upload intent");
212 return Service
.START_NOT_STICKY
;
214 if (localPaths
.length
!= remotePaths
.length
) {
215 Log
.e(TAG
, "Different number of remote paths and local paths!");
216 return Service
.START_NOT_STICKY
;
219 files
= new OCFile
[localPaths
.length
];
220 for (int i
=0; i
< localPaths
.length
; i
++) {
221 files
[i
] = obtainNewOCFileToUpload(remotePaths
[i
], localPaths
[i
], ((mimeTypes
!=null
)?mimeTypes
[i
]:(String
)null
), storageManager
);
225 boolean isInstant
= intent
.getBooleanExtra(KEY_INSTANT_UPLOAD
, false
);
226 boolean forceOverwrite
= intent
.getBooleanExtra(KEY_FORCE_OVERWRITE
, false
);
228 OwnCloudVersion ocv
= new OwnCloudVersion(AccountManager
.get(this).getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
));
229 boolean chunked
= FileUploader
.chunkedUploadIsSupported(ocv
);
230 AbstractList
<String
> requestedUploads
= new Vector
<String
>();
231 String uploadKey
= null
;
232 UploadFileOperation newUpload
= null
;
233 boolean fixed
= false
;
235 fixed
= checkAndFixInstantUploadDirectory(storageManager
);
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 * @param account Owncloud account where the remote file will be stored.
316 * @param file A file that could be in the queue of pending uploads
318 public boolean isUploading(Account account
, OCFile file
) {
319 synchronized (mPendingUploads
) {
320 return (mPendingUploads
.containsKey(buildRemoteName(account
, file
)));
329 * Upload worker. Performs the pending uploads in the order they were requested.
331 * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
333 private static class ServiceHandler
extends Handler
{
334 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
335 FileUploader mService
;
336 public ServiceHandler(Looper looper
, FileUploader service
) {
339 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
344 public void handleMessage(Message msg
) {
345 @SuppressWarnings("unchecked")
346 AbstractList
<String
> requestedUploads
= (AbstractList
<String
>) msg
.obj
;
347 if (msg
.obj
!= null
) {
348 Iterator
<String
> it
= requestedUploads
.iterator();
349 while (it
.hasNext()) {
350 mService
.uploadFile(it
.next());
353 mService
.stopSelf(msg
.arg1
);
361 * Core upload method: sends the file(s) to upload
363 * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
365 public void uploadFile(String uploadKey
) {
367 synchronized(mPendingUploads
) {
368 mCurrentUpload
= mPendingUploads
.get(uploadKey
);
371 if (mCurrentUpload
!= null
) {
373 notifyUploadStart(mCurrentUpload
);
376 /// prepare client object to send requests to the ownCloud server
377 if (mUploadClient
== null
|| !mLastAccount
.equals(mCurrentUpload
.getAccount())) {
378 mLastAccount
= mCurrentUpload
.getAccount();
379 mStorageManager
= new FileDataStorageManager(mLastAccount
, getContentResolver());
380 mUploadClient
= OwnCloudClientUtils
.createOwnCloudClient(mLastAccount
, getApplicationContext());
383 /// create remote folder for instant uploads
384 if (mCurrentUpload
.isRemoteFolderToBeCreated()) {
385 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
389 /// perform the upload
390 RemoteOperationResult uploadResult
= null
;
392 uploadResult
= mCurrentUpload
.execute(mUploadClient
);
393 if (uploadResult
.isSuccess()) {
398 synchronized(mPendingUploads
) {
399 mPendingUploads
.remove(uploadKey
);
404 notifyUploadResult(uploadResult
, mCurrentUpload
);
406 sendFinalBroadcast(mCurrentUpload
, uploadResult
);
413 * Saves a OC File after a successful upload.
415 * A PROPFIND is necessary to keep the props in the local database synchronized with the server,
416 * specially the modification time and Etag (where available)
418 * TODO refactor this ugly thing
420 private void saveUploadedFile() {
421 OCFile file
= mCurrentUpload
.getFile();
422 long syncDate
= System
.currentTimeMillis();
423 file
.setLastSyncDateForData(syncDate
);
425 /// new PROPFIND to keep data consistent with server in theory, should return the same we already have
426 PropFindMethod propfind
= null
;
427 RemoteOperationResult result
= null
;
429 propfind
= new PropFindMethod(mUploadClient
.getBaseUri() + WebdavUtils
.encodePath(mCurrentUpload
.getRemotePath()));
430 int status
= mUploadClient
.executeMethod(propfind
);
431 boolean isMultiStatus
= (status
== HttpStatus
.SC_MULTI_STATUS
);
433 MultiStatus resp
= propfind
.getResponseBodyAsMultiStatus();
434 WebdavEntry we
= new WebdavEntry(resp
.getResponses()[0],
435 mUploadClient
.getBaseUri().getPath());
436 updateOCFile(file
, we
);
437 file
.setLastSyncDateForProperties(syncDate
);
440 mUploadClient
.exhaustResponse(propfind
.getResponseBodyAsStream());
443 result
= new RemoteOperationResult(isMultiStatus
, status
);
444 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage());
446 } catch (Exception e
) {
447 result
= new RemoteOperationResult(e
);
448 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage(), e
);
451 if (propfind
!= null
)
452 propfind
.releaseConnection();
456 if (mCurrentUpload
.wasRenamed()) {
457 OCFile oldFile
= mCurrentUpload
.getOldFile();
458 if (!oldFile
.fileExists()) {
459 // just a name coincidence
460 file
.setStoragePath(oldFile
.getStoragePath());
463 // conflict resolved with 'Keep both' by the user
464 File localFile
= new File(oldFile
.getStoragePath());
465 File newLocalFile
= new File(FileStorageUtils
.getDefaultSavePathFor(mCurrentUpload
.getAccount().name
, file
));
466 boolean renameSuccessed
= localFile
.renameTo(newLocalFile
);
467 if (renameSuccessed
) {
468 file
.setStoragePath(newLocalFile
.getAbsolutePath());
472 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'");
473 file
.setStoragePath(null
);
475 // - local file will be kept there as 'trash' until is download (and overwritten) again from the server;
476 // - user will see as 'not down' a file that was just upload
478 // - no loss of data happened
479 // - 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
481 oldFile
.setStoragePath(null
);
482 mStorageManager
.saveFile(oldFile
);
486 mStorageManager
.saveFile(file
);
490 private void updateOCFile(OCFile file
, WebdavEntry we
) {
491 file
.setCreationTimestamp(we
.createTimestamp());
492 file
.setFileLength(we
.contentLength());
493 file
.setMimetype(we
.contentType());
494 file
.setModificationTimestamp(we
.modifiedTimesamp());
495 // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available
499 private boolean checkAndFixInstantUploadDirectory(FileDataStorageManager storageManager
) {
500 OCFile instantUploadDir
= storageManager
.getFileByPath(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);
501 if (instantUploadDir
== null
) {
502 // first instant upload in the account, or never account not synchronized after the remote InstantUpload folder was created
503 OCFile newDir
= new OCFile(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);
504 newDir
.setMimetype("DIR");
505 newDir
.setParentId(storageManager
.getFileByPath(OCFile
.PATH_SEPARATOR
).getFileId());
506 storageManager
.saveFile(newDir
);
513 private OCFile
obtainNewOCFileToUpload(String remotePath
, String localPath
, String mimeType
, FileDataStorageManager storageManager
) {
514 OCFile newFile
= new OCFile(remotePath
);
515 newFile
.setStoragePath(localPath
);
516 newFile
.setLastSyncDateForProperties(0);
517 newFile
.setLastSyncDateForData(0);
520 if (localPath
!= null
&& localPath
.length() > 0) {
521 File localFile
= new File(localPath
);
522 newFile
.setFileLength(localFile
.length());
523 } // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created
526 if (mimeType
== null
|| mimeType
.length() <= 0) {
528 mimeType
= MimeTypeMap
.getSingleton()
529 .getMimeTypeFromExtension(
530 remotePath
.substring(remotePath
.lastIndexOf('.') + 1));
531 } catch (IndexOutOfBoundsException e
) {
532 Log
.e(TAG
, "Trying to find out MIME type of a file without extension: " + remotePath
);
535 if (mimeType
== null
) {
536 mimeType
= "application/octet-stream";
538 newFile
.setMimetype(mimeType
);
541 String parentPath
= new File(remotePath
).getParent();
542 parentPath
= parentPath
.endsWith("/")?parentPath
:parentPath
+"/" ;
543 OCFile parentDir
= storageManager
.getFileByPath(parentPath
);
544 if (parentDir
== null
) {
545 throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath
);
547 long parentDirId
= parentDir
.getFileId();
548 newFile
.setParentId(parentDirId
);
554 * Creates a status notification to show the upload progress
556 * @param upload Upload operation starting.
558 private void notifyUploadStart(UploadFileOperation upload
) {
559 /// create status notification with a progress bar
561 mNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_in_progress_ticker
), System
.currentTimeMillis());
562 mNotification
.flags
|= Notification
.FLAG_ONGOING_EVENT
;
563 mDefaultNotificationContentView
= mNotification
.contentView
;
564 mNotification
.contentView
= new RemoteViews(getApplicationContext().getPackageName(), R
.layout
.progressbar_layout
);
565 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, 0, false
);
566 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.uploader_upload_in_progress_content
), 0, new File(upload
.getStoragePath()).getName()));
567 mNotification
.contentView
.setImageViewResource(R
.id
.status_icon
, R
.drawable
.icon
);
569 /// includes a pending intent in the notification showing the details view of the file
570 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
571 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
572 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
573 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
574 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
576 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
581 * Callback method to update the progress bar in the status notification
584 public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, long totalToTransfer
, String fileName
) {
585 int percent
= (int)(100.0*((double)totalTransferredSoFar
)/((double)totalToTransfer
));
586 if (percent
!= mLastPercent
) {
587 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, percent
, false
);
588 String text
= String
.format(getString(R
.string
.uploader_upload_in_progress_content
), percent
, fileName
);
589 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, text
);
590 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
592 mLastPercent
= percent
;
597 * Callback method to update the progress bar in the status notification (old version)
600 public void onTransferProgress(long progressRate
) {
601 // NOTHING TO DO HERE ANYMORE
606 * Updates the status notification with the result of an upload operation.
608 * @param uploadResult Result of the upload operation.
609 * @param upload Finished upload operation
611 private void notifyUploadResult(RemoteOperationResult uploadResult
, UploadFileOperation upload
) {
612 if (uploadResult
.isCancelled()) {
613 /// cancelled operation -> silent removal of progress notification
614 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
616 } else if (uploadResult
.isSuccess()) {
617 /// success -> silent update of progress notification to success message
618 mNotification
.flags ^
= Notification
.FLAG_ONGOING_EVENT
; // remove the ongoing flag
619 mNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
620 mNotification
.contentView
= mDefaultNotificationContentView
;
622 /// includes a pending intent in the notification showing the details view of the file
623 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
624 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
625 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
626 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
627 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
629 mNotification
.setLatestEventInfo( getApplicationContext(),
630 getString(R
.string
.uploader_upload_succeeded_ticker
),
631 String
.format(getString(R
.string
.uploader_upload_succeeded_content_single
), (new File(upload
.getStoragePath())).getName()),
632 mNotification
.contentIntent
);
634 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
636 /* Notification about multiple uploads: pending of update
637 mNotification.setLatestEventInfo( getApplicationContext(),
638 getString(R.string.uploader_upload_succeeded_ticker),
639 String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),
640 mNotification.contentIntent);
644 /// fail -> explicit failure notification
645 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
646 Notification finalNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_failed_ticker
), System
.currentTimeMillis());
647 finalNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
648 // TODO put something smart in the contentIntent below
649 finalNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), new Intent(), 0);
650 finalNotification
.setLatestEventInfo( getApplicationContext(),
651 getString(R
.string
.uploader_upload_failed_ticker
),
652 String
.format(getString(R
.string
.uploader_upload_failed_content_single
), (new File(upload
.getStoragePath())).getName()),
653 finalNotification
.contentIntent
);
655 mNotificationManager
.notify(R
.string
.uploader_upload_failed_ticker
, finalNotification
);
657 /* Notification about multiple uploads failure: pending of update
658 finalNotification.setLatestEventInfo( getApplicationContext(),
659 getString(R.string.uploader_upload_failed_ticker),
660 String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),
661 finalNotification.contentIntent);
669 * Sends a broadcast in order to the interested activities can update their view
671 * @param upload Finished upload operation
672 * @param uploadResult Result of the upload operation
674 private void sendFinalBroadcast(UploadFileOperation upload
, RemoteOperationResult uploadResult
) {
675 Intent end
= new Intent(UPLOAD_FINISH_MESSAGE
);
676 end
.putExtra(EXTRA_REMOTE_PATH
, upload
.getRemotePath()); // real remote path, after possible automatic renaming
677 end
.putExtra(EXTRA_FILE_PATH
, upload
.getStoragePath());
678 end
.putExtra(ACCOUNT_NAME
, upload
.getAccount().name
);
679 end
.putExtra(EXTRA_UPLOAD_RESULT
, uploadResult
.isSuccess());
680 end
.putExtra(EXTRA_PARENT_DIR_ID
, upload
.getFile().getParentId());