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 if (intent
.hasExtra(KEY_FILE
) && files
== null
) {
203 Log
.e(TAG
, "Incorrect array for OCFiles provided in upload intent");
204 return Service
.START_NOT_STICKY
;
206 } else if (!intent
.hasExtra(KEY_FILE
)) {
207 if (localPaths
== null
) {
208 Log
.e(TAG
, "Incorrect array for local paths provided in upload intent");
209 return Service
.START_NOT_STICKY
;
211 if (remotePaths
== null
) {
212 Log
.e(TAG
, "Incorrect array for remote paths provided in upload intent");
213 return Service
.START_NOT_STICKY
;
215 if (localPaths
.length
!= remotePaths
.length
) {
216 Log
.e(TAG
, "Different number of remote paths and local paths!");
217 return Service
.START_NOT_STICKY
;
220 files
= new OCFile
[localPaths
.length
];
221 for (int i
=0; i
< localPaths
.length
; i
++) {
222 files
[i
] = obtainNewOCFileToUpload(remotePaths
[i
], localPaths
[i
], ((mimeTypes
!=null
)?mimeTypes
[i
]:(String
)null
), storageManager
);
226 boolean isInstant
= intent
.getBooleanExtra(KEY_INSTANT_UPLOAD
, false
);
227 boolean forceOverwrite
= intent
.getBooleanExtra(KEY_FORCE_OVERWRITE
, false
);
229 OwnCloudVersion ocv
= new OwnCloudVersion(AccountManager
.get(this).getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
));
230 boolean chunked
= FileUploader
.chunkedUploadIsSupported(ocv
);
231 AbstractList
<String
> requestedUploads
= new Vector
<String
>();
232 String uploadKey
= null
;
233 UploadFileOperation newUpload
= null
;
234 boolean fixed
= false
;
236 fixed
= checkAndFixInstantUploadDirectory(storageManager
);
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 } // don't worry about not assigning size, the problems with localPath are checked when the UploadFileOperation instance is created
527 if (mimeType
== null
|| mimeType
.length() <= 0) {
529 mimeType
= MimeTypeMap
.getSingleton()
530 .getMimeTypeFromExtension(
531 remotePath
.substring(remotePath
.lastIndexOf('.') + 1));
532 } catch (IndexOutOfBoundsException e
) {
533 Log
.e(TAG
, "Trying to find out MIME type of a file without extension: " + remotePath
);
536 if (mimeType
== null
) {
537 mimeType
= "application/octet-stream";
539 newFile
.setMimetype(mimeType
);
542 String parentPath
= new File(remotePath
).getParent();
543 parentPath
= parentPath
.endsWith("/")?parentPath
:parentPath
+"/" ;
544 OCFile parentDir
= storageManager
.getFileByPath(parentPath
);
545 if (parentDir
== null
) {
546 throw new IllegalStateException("Can not upload a file to a non existing remote location: " + parentPath
);
548 long parentDirId
= parentDir
.getFileId();
549 newFile
.setParentId(parentDirId
);
555 * Creates a status notification to show the upload progress
557 * @param upload Upload operation starting.
559 private void notifyUploadStart(UploadFileOperation upload
) {
560 /// create status notification with a progress bar
562 mNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_in_progress_ticker
), System
.currentTimeMillis());
563 mNotification
.flags
|= Notification
.FLAG_ONGOING_EVENT
;
564 mDefaultNotificationContentView
= mNotification
.contentView
;
565 mNotification
.contentView
= new RemoteViews(getApplicationContext().getPackageName(), R
.layout
.progressbar_layout
);
566 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, 0, false
);
567 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.uploader_upload_in_progress_content
), 0, new File(upload
.getStoragePath()).getName()));
568 mNotification
.contentView
.setImageViewResource(R
.id
.status_icon
, R
.drawable
.icon
);
570 /// includes a pending intent in the notification showing the details view of the file
571 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
572 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
573 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
574 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
575 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
577 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
582 * Callback method to update the progress bar in the status notification
585 public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, long totalToTransfer
, String fileName
) {
586 int percent
= (int)(100.0*((double)totalTransferredSoFar
)/((double)totalToTransfer
));
587 if (percent
!= mLastPercent
) {
588 mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, percent
, false
);
589 String text
= String
.format(getString(R
.string
.uploader_upload_in_progress_content
), percent
, fileName
);
590 mNotification
.contentView
.setTextViewText(R
.id
.status_text
, text
);
591 mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);
593 mLastPercent
= percent
;
598 * Callback method to update the progress bar in the status notification (old version)
601 public void onTransferProgress(long progressRate
) {
602 // NOTHING TO DO HERE ANYMORE
607 * Updates the status notification with the result of an upload operation.
609 * @param uploadResult Result of the upload operation.
610 * @param upload Finished upload operation
612 private void notifyUploadResult(RemoteOperationResult uploadResult
, UploadFileOperation upload
) {
613 if (uploadResult
.isCancelled()) {
614 /// cancelled operation -> silent removal of progress notification
615 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
617 } else if (uploadResult
.isSuccess()) {
618 /// success -> silent update of progress notification to success message
619 mNotification
.flags ^
= Notification
.FLAG_ONGOING_EVENT
; // remove the ongoing flag
620 mNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
621 mNotification
.contentView
= mDefaultNotificationContentView
;
623 /// includes a pending intent in the notification showing the details view of the file
624 Intent showDetailsIntent
= new Intent(this, FileDetailActivity
.class);
625 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_FILE
, upload
.getFile());
626 showDetailsIntent
.putExtra(FileDetailFragment
.EXTRA_ACCOUNT
, upload
.getAccount());
627 showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
);
628 mNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), showDetailsIntent
, 0);
630 mNotification
.setLatestEventInfo( getApplicationContext(),
631 getString(R
.string
.uploader_upload_succeeded_ticker
),
632 String
.format(getString(R
.string
.uploader_upload_succeeded_content_single
), (new File(upload
.getStoragePath())).getName()),
633 mNotification
.contentIntent
);
635 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
637 /* Notification about multiple uploads: pending of update
638 mNotification.setLatestEventInfo( getApplicationContext(),
639 getString(R.string.uploader_upload_succeeded_ticker),
640 String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),
641 mNotification.contentIntent);
645 /// fail -> explicit failure notification
646 mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
);
647 Notification finalNotification
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_failed_ticker
), System
.currentTimeMillis());
648 finalNotification
.flags
|= Notification
.FLAG_AUTO_CANCEL
;
649 // TODO put something smart in the contentIntent below
650 finalNotification
.contentIntent
= PendingIntent
.getActivity(getApplicationContext(), (int)System
.currentTimeMillis(), new Intent(), 0);
651 finalNotification
.setLatestEventInfo( getApplicationContext(),
652 getString(R
.string
.uploader_upload_failed_ticker
),
653 String
.format(getString(R
.string
.uploader_upload_failed_content_single
), (new File(upload
.getStoragePath())).getName()),
654 finalNotification
.contentIntent
);
656 mNotificationManager
.notify(R
.string
.uploader_upload_failed_ticker
, finalNotification
);
658 /* Notification about multiple uploads failure: pending of update
659 finalNotification.setLatestEventInfo( getApplicationContext(),
660 getString(R.string.uploader_upload_failed_ticker),
661 String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),
662 finalNotification.contentIntent);
670 * Sends a broadcast in order to the interested activities can update their view
672 * @param upload Finished upload operation
673 * @param uploadResult Result of the upload operation
675 private void sendFinalBroadcast(UploadFileOperation upload
, RemoteOperationResult uploadResult
) {
676 Intent end
= new Intent(UPLOAD_FINISH_MESSAGE
);
677 end
.putExtra(EXTRA_REMOTE_PATH
, upload
.getRemotePath()); // real remote path, after possible automatic renaming
678 if (upload
.wasRenamed()) {
679 end
.putExtra(EXTRA_OLD_REMOTE_PATH
, upload
.getOldFile().getRemotePath());
681 end
.putExtra(EXTRA_FILE_PATH
, upload
.getStoragePath());
682 end
.putExtra(ACCOUNT_NAME
, upload
.getAccount().name
);
683 end
.putExtra(EXTRA_UPLOAD_RESULT
, uploadResult
.isSuccess());
684 end
.putExtra(EXTRA_PARENT_DIR_ID
, upload
.getFile().getParentId());