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
.OwnCloudVersion
;
43 import eu
.alefzero
.webdav
.OnDatatransferProgressListener
;
44 import eu
.alefzero
.webdav
.WebdavEntry
;
45 import eu
.alefzero
.webdav
.WebdavUtils
;
47 import com
.owncloud
.android
.network
.OwnCloudClientUtils
;
49 import android
.accounts
.Account
;
50 import android
.accounts
.AccountManager
;
51 import android
.app
.Notification
;
52 import android
.app
.NotificationManager
;
53 import android
.app
.PendingIntent
;
54 import android
.app
.Service
;
55 import android
.content
.Intent
;
56 import android
.os
.Binder
;
57 import android
.os
.Handler
;
58 import android
.os
.HandlerThread
;
59 import android
.os
.IBinder
;
60 import android
.os
.Looper
;
61 import android
.os
.Message
;
62 import android
.os
.Process
;
63 import android
.util
.Log
;
64 import android
.webkit
.MimeTypeMap
;
65 import android
.widget
.RemoteViews
;
67 import com
.owncloud
.android
.R
;
68 import eu
.alefzero
.webdav
.WebdavClient
;
70 public class FileUploader
extends Service
implements OnDatatransferProgressListener
{
72 public static final String UPLOAD_FINISH_MESSAGE
= "UPLOAD_FINISH";
73 public static final String EXTRA_UPLOAD_RESULT
= "RESULT";
74 public static final String EXTRA_REMOTE_PATH
= "REMOTE_PATH";
75 public static final String EXTRA_OLD_REMOTE_PATH
= "OLD_REMOTE_PATH";
76 public static final String EXTRA_FILE_PATH
= "FILE_PATH";
77 public static final String ACCOUNT_NAME
= "ACCOUNT_NAME";
79 public static final String KEY_FILE
= "FILE";
80 public static final String KEY_LOCAL_FILE
= "LOCAL_FILE";
81 public static final String KEY_REMOTE_FILE
= "REMOTE_FILE";
82 public static final String KEY_MIME_TYPE
= "MIME_TYPE";
84 public static final String KEY_ACCOUNT
= "ACCOUNT";
86 public static final String KEY_UPLOAD_TYPE
= "UPLOAD_TYPE";
87 public static final String KEY_FORCE_OVERWRITE
= "KEY_FORCE_OVERWRITE";
88 public static final String KEY_INSTANT_UPLOAD
= "INSTANT_UPLOAD";
90 public static final int UPLOAD_SINGLE_FILE
= 0;
91 public static final int UPLOAD_MULTIPLE_FILES
= 1;
93 private static final String TAG
= FileUploader
.class.getSimpleName();
95 private Looper mServiceLooper
;
96 private ServiceHandler mServiceHandler
;
97 private IBinder mBinder
;
98 private WebdavClient mUploadClient
= null
;
99 private Account mLastAccount
= null
;
100 private FileDataStorageManager mStorageManager
;
102 private ConcurrentMap
<String
, UploadFileOperation
> mPendingUploads
= new ConcurrentHashMap
<String
, UploadFileOperation
>();
103 private UploadFileOperation mCurrentUpload
= null
;
105 private NotificationManager mNotificationManager
;
106 private Notification mNotification
;
107 private int mLastPercent
;
108 private RemoteViews mDefaultNotificationContentView
;
112 * Builds a key for mPendingUploads from the account and file to upload
114 * @param account Account where the file to download is stored
115 * @param file File to download
117 private String
buildRemoteName(Account account
, OCFile file
) {
118 return account
.name
+ file
.getRemotePath();
121 private String
buildRemoteName(Account account
, String remotePath
) {
122 return account
.name
+ remotePath
;
127 * Checks if an ownCloud server version should support chunked uploads.
129 * @param version OwnCloud version instance corresponding to an ownCloud server.
130 * @return 'True' if the ownCloud server with version supports chunked uploads.
132 private static boolean chunkedUploadIsSupported(OwnCloudVersion version
) {
133 return (version
!= null
&& version
.compareTo(OwnCloudVersion
.owncloud_v4_5
) >= 0);
139 * Service initialization
142 public void onCreate() {
144 mNotificationManager
= (NotificationManager
) getSystemService(NOTIFICATION_SERVICE
);
145 HandlerThread thread
= new HandlerThread("FileUploaderThread",
146 Process
.THREAD_PRIORITY_BACKGROUND
);
148 mServiceLooper
= thread
.getLooper();
149 mServiceHandler
= new ServiceHandler(mServiceLooper
, this);
150 mBinder
= new FileUploaderBinder();
155 * Entry point to add one or several files to the queue of uploads.
157 * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working
158 * although the caller activity goes away.
161 public int onStartCommand(Intent intent
, int flags
, int startId
) {
162 if (!intent
.hasExtra(KEY_ACCOUNT
) || !intent
.hasExtra(KEY_UPLOAD_TYPE
) || !(intent
.hasExtra(KEY_LOCAL_FILE
) || intent
.hasExtra(KEY_FILE
))) {
163 Log
.e(TAG
, "Not enough information provided in intent");
164 return Service
.START_NOT_STICKY
;
166 int uploadType
= intent
.getIntExtra(KEY_UPLOAD_TYPE
, -1);
167 if (uploadType
== -1) {
168 Log
.e(TAG
, "Incorrect upload type provided");
169 return Service
.START_NOT_STICKY
;
171 Account account
= intent
.getParcelableExtra(KEY_ACCOUNT
);
173 String
[] localPaths
= null
, remotePaths
= null
, mimeTypes
= null
;
174 OCFile
[] files
= null
;
175 if (uploadType
== UPLOAD_SINGLE_FILE
) {
177 if (intent
.hasExtra(KEY_FILE
)) {
178 files
= new OCFile
[] {intent
.getParcelableExtra(KEY_FILE
) };
181 localPaths
= new String
[] { intent
.getStringExtra(KEY_LOCAL_FILE
) };
182 remotePaths
= new String
[] { intent
.getStringExtra(KEY_REMOTE_FILE
) };
183 mimeTypes
= new String
[] { intent
.getStringExtra(KEY_MIME_TYPE
) };
186 } else { // mUploadType == UPLOAD_MULTIPLE_FILES
188 if (intent
.hasExtra(KEY_FILE
)) {
189 files
= (OCFile
[]) intent
.getParcelableArrayExtra(KEY_FILE
); // TODO will this casting work fine?
192 localPaths
= intent
.getStringArrayExtra(KEY_LOCAL_FILE
);
193 remotePaths
= intent
.getStringArrayExtra(KEY_REMOTE_FILE
);
194 mimeTypes
= intent
.getStringArrayExtra(KEY_MIME_TYPE
);
198 FileDataStorageManager storageManager
= new FileDataStorageManager(account
, getContentResolver());
200 boolean forceOverwrite
= intent
.getBooleanExtra(KEY_FORCE_OVERWRITE
, false
);
201 boolean isInstant
= intent
.getBooleanExtra(KEY_INSTANT_UPLOAD
, false
);
202 boolean fixed
= false
;
204 fixed
= checkAndFixInstantUploadDirectory(storageManager
); // MUST be done BEFORE calling obtainNewOCFileToUpload
207 if (intent
.hasExtra(KEY_FILE
) && files
== null
) {
208 Log
.e(TAG
, "Incorrect array for OCFiles provided in upload intent");
209 return Service
.START_NOT_STICKY
;
211 } else if (!intent
.hasExtra(KEY_FILE
)) {
212 if (localPaths
== null
) {
213 Log
.e(TAG
, "Incorrect array for local paths provided in upload intent");
214 return Service
.START_NOT_STICKY
;
216 if (remotePaths
== null
) {
217 Log
.e(TAG
, "Incorrect array for remote paths provided in upload intent");
218 return Service
.START_NOT_STICKY
;
220 if (localPaths
.length
!= remotePaths
.length
) {
221 Log
.e(TAG
, "Different number of remote paths and local paths!");
222 return Service
.START_NOT_STICKY
;
225 files
= new OCFile
[localPaths
.length
];
226 for (int i
=0; i
< localPaths
.length
; i
++) {
227 files
[i
] = obtainNewOCFileToUpload(remotePaths
[i
], localPaths
[i
], ((mimeTypes
!=null
)?mimeTypes
[i
]:(String
)null
), storageManager
);
231 OwnCloudVersion ocv
= new OwnCloudVersion(AccountManager
.get(this).getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
));
232 boolean chunked
= FileUploader
.chunkedUploadIsSupported(ocv
);
233 AbstractList
<String
> requestedUploads
= new Vector
<String
>();
234 String uploadKey
= null
;
235 UploadFileOperation newUpload
= null
;
237 for (int i
=0; i
< files
.length
; i
++) {
238 uploadKey
= buildRemoteName(account
, files
[i
].getRemotePath());
240 newUpload
= new ChunkedUploadFileOperation(account
, files
[i
], isInstant
, forceOverwrite
, false
);
242 newUpload
= new UploadFileOperation(account
, files
[i
], isInstant
, forceOverwrite
, false
);
245 newUpload
.setRemoteFolderToBeCreated();
247 mPendingUploads
.putIfAbsent(uploadKey
, newUpload
);
248 newUpload
.addDatatransferProgressListener(this);
249 requestedUploads
.add(uploadKey
);
252 } catch (IllegalArgumentException e
) {
253 Log
.e(TAG
, "Not enough information provided in intent: " + e
.getMessage());
254 return START_NOT_STICKY
;
256 } catch (IllegalStateException e
) {
257 Log
.e(TAG
, "Bad information provided in intent: " + e
.getMessage());
258 return START_NOT_STICKY
;
260 } catch (Exception e
) {
261 Log
.e(TAG
, "Unexpected exception while processing upload intent", e
);
262 return START_NOT_STICKY
;
266 if (requestedUploads
.size() > 0) {
267 Message msg
= mServiceHandler
.obtainMessage();
269 msg
.obj
= requestedUploads
;
270 mServiceHandler
.sendMessage(msg
);
273 return Service
.START_NOT_STICKY
;
278 * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the addition of new files.
280 * Implemented to perform cancellation, pause and resume of existing uploads.
283 public IBinder
onBind(Intent arg0
) {
288 * Binder to let client components to perform operations on the queue of uploads.
290 * It provides by itself the available operations.
292 public class FileUploaderBinder
extends Binder
{
295 * Cancels a pending or current upload of a remote file.
297 * @param account Owncloud account where the remote file will be stored.
298 * @param file A file in the queue of pending uploads
300 public void cancel(Account account
, OCFile file
) {
301 UploadFileOperation upload
= null
;
302 synchronized (mPendingUploads
) {
303 upload
= mPendingUploads
.remove(buildRemoteName(account
, file
));
305 if (upload
!= null
) {
312 * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it
314 * If 'file' is a directory, returns 'true' if some of its descendant files is downloading or waiting to download.
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 String targetKey
= buildRemoteName(account
, file
);
321 synchronized (mPendingUploads
) {
322 if (file
.isDirectory()) {
323 // this can be slow if there are many downloads :(
324 Iterator
<String
> it
= mPendingUploads
.keySet().iterator();
325 boolean found
= false
;
326 while (it
.hasNext() && !found
) {
327 found
= it
.next().startsWith(targetKey
);
331 return (mPendingUploads
.containsKey(targetKey
));
341 * Upload worker. Performs the pending uploads in the order they were requested.
343 * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
345 private static class ServiceHandler
extends Handler
{
346 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
347 FileUploader mService
;
348 public ServiceHandler(Looper looper
, FileUploader service
) {
351 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
356 public void handleMessage(Message msg
) {
357 @SuppressWarnings("unchecked")
358 AbstractList
<String
> requestedUploads
= (AbstractList
<String
>) msg
.obj
;
359 if (msg
.obj
!= null
) {
360 Iterator
<String
> it
= requestedUploads
.iterator();
361 while (it
.hasNext()) {
362 mService
.uploadFile(it
.next());
365 mService
.stopSelf(msg
.arg1
);
373 * Core upload method: sends the file(s) to upload
375 * @param uploadKey Key to access the upload to perform, contained in mPendingUploads
377 public void uploadFile(String uploadKey
) {
379 synchronized(mPendingUploads
) {
380 mCurrentUpload
= mPendingUploads
.get(uploadKey
);
383 if (mCurrentUpload
!= null
) {
385 notifyUploadStart(mCurrentUpload
);
388 /// prepare client object to send requests to the ownCloud server
389 if (mUploadClient
== null
|| !mLastAccount
.equals(mCurrentUpload
.getAccount())) {
390 mLastAccount
= mCurrentUpload
.getAccount();
391 mStorageManager
= new FileDataStorageManager(mLastAccount
, getContentResolver());
392 mUploadClient
= OwnCloudClientUtils
.createOwnCloudClient(mLastAccount
, getApplicationContext());
395 /// create remote folder for instant uploads
396 if (mCurrentUpload
.isRemoteFolderToBeCreated()) {
397 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
401 /// perform the upload
402 RemoteOperationResult uploadResult
= null
;
404 uploadResult
= mCurrentUpload
.execute(mUploadClient
);
405 if (uploadResult
.isSuccess()) {
410 synchronized(mPendingUploads
) {
411 mPendingUploads
.remove(uploadKey
);
416 notifyUploadResult(uploadResult
, mCurrentUpload
);
418 sendFinalBroadcast(mCurrentUpload
, uploadResult
);
425 * Saves a OC File after a successful upload.
427 * A PROPFIND is necessary to keep the props in the local database synchronized with the server,
428 * specially the modification time and Etag (where available)
430 * TODO refactor this ugly thing
432 private void saveUploadedFile() {
433 OCFile file
= mCurrentUpload
.getFile();
434 long syncDate
= System
.currentTimeMillis();
435 file
.setLastSyncDateForData(syncDate
);
437 /// new PROPFIND to keep data consistent with server in theory, should return the same we already have
438 PropFindMethod propfind
= null
;
439 RemoteOperationResult result
= null
;
441 propfind
= new PropFindMethod(mUploadClient
.getBaseUri() + WebdavUtils
.encodePath(mCurrentUpload
.getRemotePath()));
442 int status
= mUploadClient
.executeMethod(propfind
);
443 boolean isMultiStatus
= (status
== HttpStatus
.SC_MULTI_STATUS
);
445 MultiStatus resp
= propfind
.getResponseBodyAsMultiStatus();
446 WebdavEntry we
= new WebdavEntry(resp
.getResponses()[0],
447 mUploadClient
.getBaseUri().getPath());
448 updateOCFile(file
, we
);
449 file
.setLastSyncDateForProperties(syncDate
);
452 mUploadClient
.exhaustResponse(propfind
.getResponseBodyAsStream());
455 result
= new RemoteOperationResult(isMultiStatus
, status
);
456 Log
.i(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage());
458 } catch (Exception e
) {
459 result
= new RemoteOperationResult(e
);
460 Log
.e(TAG
, "Update: synchronizing properties for uploaded " + mCurrentUpload
.getRemotePath() + ": " + result
.getLogMessage(), e
);
463 if (propfind
!= null
)
464 propfind
.releaseConnection();
467 /// maybe this would be better as part of UploadFileOperation... or maybe all this method
468 if (mCurrentUpload
.wasRenamed()) {
469 OCFile oldFile
= mCurrentUpload
.getOldFile();
470 if (oldFile
.fileExists()) {
471 // the upload was the result of a conflict resolved with 'Keep both' by the user
472 /*File localFile = new File(file.getStoragePath());
473 File newLocalFile = new File(FileStorageUtils.getDefaultSavePathFor(mCurrentUpload.getAccount().name, file));
474 boolean renameSuccessed = localFile.renameTo(newLocalFile);
475 if (renameSuccessed) {
476 file.setStoragePath(newLocalFile.getAbsolutePath());
480 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'");
481 file.setStoragePath(null);
483 // - local file will be kept there as 'trash' until is download (and overwritten) again from the server;
484 // - user will see as 'not down' a file that was just upload
486 // - no loss of data happened
487 // - 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
489 if (oldFile
.isDown()) {
490 File oldLocalFile
= new File(oldFile
.getStoragePath());
491 oldLocalFile
.delete(); // the RemoteFileOperation copied and renamed it! // TODO launch the 'Keep both' option with mMove set 'ture'
493 oldFile
.setStoragePath(null
);
494 mStorageManager
.saveFile(oldFile
);
496 } // else: it was just an automatic renaming due to a name coincidence; nothing else is needed, the storagePath is right in the instance returned by mCurrentUpload.getFile()
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
);