Эх сурвалжийг харах

Added progress bar for file transfers in details view (untested)

David A. Velasco 12 жил өмнө
parent
commit
24dd513682

+ 222 - 212
res/layout/file_details_fragment.xml

@@ -16,215 +16,225 @@
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    android:background="@color/owncloud_white" >
-
-    <ScrollView
-        android:id="@+id/fdScrollView"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent" >
-
-        <RelativeLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content" >
-
-            <RelativeLayout
-                android:id="@+id/fdFileHeaderContainer"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_marginLeft="16dp"
-                android:layout_marginTop="4dp" >
-
-                <ImageView
-                    android:id="@+id/fdIcon"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:src="@drawable/file" />
-
-                <TextView
-                    android:id="@+id/fdFilename"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_centerVertical="true"
-                    android:layout_toRightOf="@+id/fdIcon"
-                    android:text="file.name"
-                    android:textAppearance="?android:attr/textAppearanceLarge" />
-            </RelativeLayout>
-
-            <RelativeLayout
-                android:id="@+id/fdDetailsContainer"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_below="@id/fdFileHeaderContainer" >
-
-                <RelativeLayout
-                    android:id="@+id/fdLabelContainer"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentLeft="true"
-                    android:layout_alignParentTop="true"
-                    android:layout_marginLeft="16dp" >
-
-                    <TextView
-                        android:id="@+id/fdTypeLabel"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="24dp"
-                        android:text="@string/filedetails_type"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-
-                    <TextView
-                        android:id="@+id/fdSizeLabel"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_below="@+id/fdTypeLabel"
-                        android:layout_marginTop="12dp"
-                        android:text="@string/filedetails_size"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-
-                    <TextView
-                        android:id="@+id/fdCreatedLabel"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_below="@+id/fdSizeLabel"
-                        android:layout_marginTop="12dp"
-                        android:text="@string/filedetails_created"
-                        android:visibility="gone"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-                    
-                    <TextView
-                        android:id="@+id/fdModifiedLabel"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_below="@+id/fdCreatedLabel"
-                        android:layout_marginTop="12dp"
-                        android:text="@string/filedetails_modified"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-                </RelativeLayout>
-
-                <RelativeLayout
-                    android:id="@+id/fdValueContainer"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_alignParentTop="true"
-                    android:layout_marginLeft="12dp"
-                    android:layout_toRightOf="@+id/fdLabelContainer" >
-
-                    <TextView
-                        android:id="@+id/fdType"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="24dp"
-                        android:text="JPG Image"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-
-                    <TextView
-                        android:id="@+id/fdSize"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_below="@+id/fdType"
-                        android:layout_marginTop="12dp"
-                        android:text="389 KB"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-
-                    <TextView
-                        android:id="@+id/fdCreated"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_below="@+id/fdSize"
-                        android:layout_marginTop="12dp"
-                        android:visibility="gone"
-                        android:text="2012/05/18 12:23 PM"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-
-                    <TextView
-                        android:id="@+id/fdModified"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_below="@+id/fdCreated"
-                        android:layout_marginTop="12dp"
-                        android:text="2012/05/19 02:56 PM"
-                        android:textAppearance="?android:attr/textAppearanceMedium" />
-                </RelativeLayout>
-
-            </RelativeLayout>
-
-            <RelativeLayout
-                android:id="@+id/fdPreviewAndDL"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_below="@+id/fdDetailsContainer"
-                android:gravity="center_horizontal" >
-
-                <CheckBox
-                    android:id="@+id/fdKeepInSync"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:layout_centerHorizontal="true"
-                    android:text="@string/fd_keep_in_sync" />
-
-                <LinearLayout
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:layout_below="@id/fdKeepInSync"
-                    android:orientation="vertical" >
-
-                    <LinearLayout
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:gravity="center_horizontal" >
-
-                        <Button
-                            android:id="@+id/fdRemoveBtn"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="12dp"
-                            android:text="@string/common_remove" />
-
-                        <Button
-                            android:id="@+id/fdOpenBtn"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="12dp"
-                            android:text="@string/filedetails_open" />
-
-                    </LinearLayout>
-
-                    <LinearLayout
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"
-                        android:gravity="center_horizontal">
-
-                        <Button
-                            android:id="@+id/fdDownloadBtn"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="12dp"
-                            android:text="@string/filedetails_download" />
-
-                        <Button
-                            android:id="@+id/fdRenameBtn"
-                            android:layout_width="wrap_content"
-                            android:layout_height="wrap_content"
-                            android:layout_marginTop="12dp"
-                            android:text="@string/common_rename" />
-
-                    </LinearLayout>
-<!-- 
-                    <Button
-                        android:id="@+id/fdShareBtn"
-                        android:layout_width="wrap_content"
-                        android:layout_height="wrap_content"
-                        android:layout_marginTop="12dp"
-                        android:text="@string/common_share" />
- -->
-
-                </LinearLayout>
-            </RelativeLayout>
-
-        </RelativeLayout>
-    </ScrollView>
-
-</RelativeLayout>
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+	android:id="@+id/fdScrollView"
+	android:layout_width="fill_parent"
+	android:layout_height="fill_parent" >
+
+	<RelativeLayout
+		android:layout_width="match_parent"
+		android:layout_height="wrap_content" >
+
+		<RelativeLayout
+			android:id="@+id/fdFileHeaderContainer"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_marginLeft="16dp"
+			android:layout_marginTop="4dp" >
+	
+			<ImageView
+				android:id="@+id/fdIcon"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:src="@drawable/file" />
+				
+			<TextView
+			    android:id="@+id/fdFilename"
+			    android:layout_width="wrap_content"
+			    android:layout_height="wrap_content"
+			    android:layout_centerVertical="true"
+			    android:layout_toRightOf="@+id/fdIcon"
+			    android:text="@string/placeholder_filename"
+			    android:textAppearance="?android:attr/textAppearanceLarge" />
+			
+		</RelativeLayout>
+	
+		<RelativeLayout
+			android:id="@+id/fdDetailsContainer"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_below="@id/fdFileHeaderContainer" >
+		
+			<RelativeLayout
+				android:id="@+id/fdLabelContainer"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_alignParentLeft="true"
+				android:layout_alignParentTop="true"
+				android:layout_marginLeft="16dp" >
+				
+				<TextView
+					android:id="@+id/fdTypeLabel"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_marginTop="24dp"
+					android:text="@string/filedetails_type"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+					
+				<TextView
+					android:id="@+id/fdSizeLabel"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_below="@+id/fdTypeLabel"
+					android:layout_marginTop="12dp"
+					android:text="@string/filedetails_size"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+					
+				<TextView
+					android:id="@+id/fdCreatedLabel"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_below="@+id/fdSizeLabel"
+					android:layout_marginTop="12dp"
+					android:text="@string/filedetails_created"
+					android:visibility="gone"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+					  
+				<TextView
+					android:id="@+id/fdModifiedLabel"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_below="@+id/fdCreatedLabel"
+					android:layout_marginTop="12dp"
+					android:text="@string/filedetails_modified"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+			</RelativeLayout>
+	
+			<RelativeLayout
+				android:id="@+id/fdValueContainer"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_alignParentTop="true"
+				android:layout_marginLeft="12dp"
+				android:layout_toRightOf="@+id/fdLabelContainer" >
+				
+				<TextView
+					android:id="@+id/fdType"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_marginTop="24dp"
+					android:text="@string/placeholder_filetype"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+					
+				<TextView
+					android:id="@+id/fdSize"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_below="@+id/fdType"
+					android:layout_marginTop="12dp"
+					android:text="@string/placeholder_filesize"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+					
+				<TextView
+					android:id="@+id/fdCreated"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_below="@+id/fdSize"
+					android:layout_marginTop="12dp"
+					android:visibility="gone"
+					android:text="@string/placeholder_timestamp"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+					
+				<TextView
+					android:id="@+id/fdModified"
+					android:layout_width="wrap_content"
+					android:layout_height="wrap_content"
+					android:layout_below="@+id/fdCreated"
+					android:layout_marginTop="12dp"
+					android:text="@string/placeholder_timestamp"
+					android:textAppearance="?android:attr/textAppearanceMedium" />
+		
+			</RelativeLayout>
+	
+		</RelativeLayout>
+	
+		<RelativeLayout
+			android:id="@+id/fdProgressAndControl"
+			android:layout_width="match_parent"
+			android:layout_height="wrap_content"
+			android:layout_below="@+id/fdDetailsContainer"
+			android:gravity="center_horizontal" 
+			android:layout_margin="16dp"
+			>
+			
+			<CheckBox
+				android:id="@+id/fdKeepInSync"
+				android:layout_width="wrap_content"
+				android:layout_height="wrap_content"
+				android:layout_centerHorizontal="true"
+				android:text="@string/fd_keep_in_sync" />
+
+			<LinearLayout
+				android:layout_width="match_parent"
+				android:layout_height="wrap_content"
+				android:layout_below="@id/fdKeepInSync"
+				android:orientation="vertical" >
+				
+    			<TextView 
+    			    android:id="@+id/fdProgressText" 
+    			    android:layout_width="match_parent"
+      				android:layout_height="wrap_content" 
+      				android:text="@string/downloader_download_in_progress_ticker"
+      				/>
+    			
+    			<ProgressBar android:id="@+id/fdProgressBar"
+					android:layout_width="match_parent" 
+					android:layout_height="wrap_content"
+					android:progressDrawable="@android:drawable/progress_horizontal"
+					android:indeterminate="false" 
+					android:indeterminateOnly="false" 
+					/>
+				    								
+			    <LinearLayout
+					android:layout_width="match_parent"
+					android:layout_height="wrap_content"
+					android:gravity="center_horizontal"
+					android:layout_marginTop="12dp"
+					>
+				    				
+					<Button
+						android:id="@+id/fdDownloadBtn"
+						android:layout_width="0dp"
+						android:layout_height="wrap_content"
+						android:layout_weight="1"
+						android:text="@string/filedetails_download" />
+						
+					<Button
+						android:id="@+id/fdOpenBtn"
+						android:layout_width="0dp"
+						android:layout_height="wrap_content"
+						android:layout_weight="1"
+						android:text="@string/filedetails_open" />
+						
+				</LinearLayout>
+		
+				<LinearLayout
+					android:layout_width="match_parent"
+					android:layout_height="wrap_content"
+					android:gravity="center_horizontal"
+					android:layout_marginTop="12dp"
+					>
+					
+					<Button
+						android:id="@+id/fdRenameBtn"
+						android:layout_width="0dp"
+						android:layout_height="wrap_content"
+						android:layout_weight="1"
+						android:text="@string/common_rename" />
+						
+				    <Button
+						android:id="@+id/fdRemoveBtn"
+						android:layout_width="0dp"
+						android:layout_height="wrap_content"
+						android:layout_weight="1"
+						android:text="@string/common_remove" />
+						
+				</LinearLayout>
+	
+			</LinearLayout>
+			
+		</RelativeLayout>
+
+	</RelativeLayout>
+	
+</ScrollView>

+ 5 - 1
res/values/strings.xml

@@ -259,7 +259,11 @@
 	<string name="ssl_validator_label_signature">Signature:</string>
 	<string name="ssl_validator_label_signature_algorithm">Algorithm:</string>
 			
-    <string name="text_placeholder">This is a placeholder</string>
+    <string name="placeholder_sentence">This is a placeholder</string>
+    <string name="placeholder_filename">placeholder.txt</string>
+    <string name="placeholder_filetype">PNG Image</string>
+    <string name="placeholder_filesize">389 KB</string>
+    <string name="placeholder_timestamp">2012/05/18 12:23 PM</string>
     
     <string name="instant_upload_on_wifi">Upload pictures via WiFi only</string>
 	<string name="instant_upload_path">/InstantUpload</string>

+ 50 - 0
src/com/owncloud/android/files/services/FileDownloader.java

@@ -49,6 +49,7 @@ import android.os.Looper;
 import android.os.Message;
 import android.os.Process;
 import android.util.Log;
+import android.widget.ProgressBar;
 import android.widget.RemoteViews;
 
 import com.owncloud.android.R;
@@ -214,6 +215,55 @@ public class FileDownloader extends Service implements OnDatatransferProgressLis
                 }
             }
         }
+
+        
+        /**
+         * Adds a listener interested in the progress of the download for a concrete file.
+         * 
+         * @param listener      Object to notify about progress of transfer.    
+         * @param account       ownCloud account holding the file of interest.
+         * @param file          {@link OCfile} of interest for listener. 
+         */
+        public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+            if (account == null || file == null) return;
+            String targetKey = buildRemoteName(account, file);
+            DownloadFileOperation target = null;
+            synchronized (mPendingDownloads) {
+                if (!file.isDirectory()) {
+                    target = mPendingDownloads.get(targetKey);
+                } else {
+                    // nothing to do for directories, right now
+                }
+            }
+            if (target != null) {
+                target.addDatatransferProgressListener(listener);
+            }
+        }
+        
+        
+        /**
+         * Removes a listener interested in the progress of the download for a concrete file.
+         * 
+         * @param listener      Object to notify about progress of transfer.    
+         * @param account       ownCloud account holding the file of interest.
+         * @param file          {@link OCfile} of interest for listener. 
+         */
+        public void removeDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+            if (account == null || file == null) return;
+            String targetKey = buildRemoteName(account, file);
+            DownloadFileOperation target = null;
+            synchronized (mPendingDownloads) {
+                if (!file.isDirectory()) {
+                    target = mPendingDownloads.get(targetKey);
+                } else {
+                    // nothing to do for directories, right now
+                }
+            }
+            if (target != null) {
+                target.removeDatatransferProgressListener(listener);
+            }
+        }
+        
     }
     
     

+ 27 - 0
src/com/owncloud/android/files/services/FileUploader.java

@@ -34,6 +34,7 @@ import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.InstantUploadBroadcastReceiver;
 import com.owncloud.android.operations.ChunkedUploadFileOperation;
+import com.owncloud.android.operations.DownloadFileOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.UploadFileOperation;
 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
@@ -340,6 +341,32 @@ public class FileUploader extends Service implements OnDatatransferProgressListe
                 }
             }
         }
+
+
+        /**
+         * Adds a listener interested in the progress of the download for a concrete file.
+         * 
+         * @param listener      Object to notify about progress of transfer.    
+         * @param account       ownCloud account holding the file of interest.
+         * @param file          {@link OCfile} of interest for listener. 
+         */
+        public void addDatatransferProgressListener (OnDatatransferProgressListener listener, Account account, OCFile file) {
+            if (account == null || file == null) return;
+            String targetKey = buildRemoteName(account, file);
+            UploadFileOperation target = null;
+            synchronized (mPendingUploads) {
+                if (!file.isDirectory()) {
+                    target = mPendingUploads.get(targetKey);
+                } else {
+                    // nothing to do for directories, right now
+                }
+            }
+            if (target != null) {
+                target.addDatatransferProgressListener(listener);
+            }
+        }
+        
+        
     }
     
     

+ 33 - 0
src/com/owncloud/android/network/ProgressiveDataTransferer.java

@@ -0,0 +1,33 @@
+/* ownCloud Android client application
+ *   Copyright (C) 2012-2013 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation, either version 3 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+package com.owncloud.android.network;
+
+import java.util.Collection;
+
+import eu.alefzero.webdav.OnDatatransferProgressListener;
+
+public interface ProgressiveDataTransferer {
+
+    public void addDatatransferProgressListener (OnDatatransferProgressListener listener);
+    
+    public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners);
+
+    public void removeDatatransferProgressListener(OnDatatransferProgressListener listener);
+
+}

+ 5 - 4
src/com/owncloud/android/operations/ChunkedUploadFileOperation.java

@@ -28,6 +28,7 @@ import org.apache.commons.httpclient.HttpException;
 import org.apache.commons.httpclient.methods.PutMethod;
 
 import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.network.ProgressiveDataTransferer;
 
 import android.accounts.Account;
 import android.util.Log;
@@ -61,16 +62,16 @@ public class ChunkedUploadFileOperation extends UploadFileOperation {
             File file = new File(getStoragePath());
             raf = new RandomAccessFile(file, "r");
             channel = raf.getChannel();
-            ChunkFromFileChannelRequestEntity entity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file);
-            entity.addOnDatatransferProgressListeners(getDataTransferListeners());
+            mEntity = new ChunkFromFileChannelRequestEntity(channel, getMimeType(), CHUNK_SIZE, file);
+            ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(getDataTransferListeners());
             long offset = 0;
             String uriPrefix = client.getBaseUri() + WebdavUtils.encodePath(getRemotePath()) + "-chunking-" + Math.abs((new Random()).nextInt(9000)+1000) + "-" ;
             long chunkCount = (long) Math.ceil((double)file.length() / CHUNK_SIZE);
             for (int chunkIndex = 0; chunkIndex < chunkCount ; chunkIndex++, offset += CHUNK_SIZE) {
                 mPutMethod = new PutMethod(uriPrefix + chunkCount + "-" + chunkIndex);
                 mPutMethod.addRequestHeader(OC_CHUNKED_HEADER, OC_CHUNKED_HEADER);
-                entity.setOffset(offset);
-                mPutMethod.setRequestEntity(entity);
+                ((ChunkFromFileChannelRequestEntity)mEntity).setOffset(offset);
+                mPutMethod.setRequestEntity(mEntity);
                 status = client.executeMethod(mPutMethod);
                 client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
                 Log.d(TAG, "Upload of " + getStoragePath() + " to " + getRemotePath() + ", chunk index " + chunkIndex + ", count " + chunkCount + ", HTTP result status " + status);

+ 15 - 4
src/com/owncloud/android/operations/DownloadFileOperation.java

@@ -123,9 +123,17 @@ public class DownloadFileOperation extends RemoteOperation {
     
     
     public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
-        mDataTransferListeners.add(listener);
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.add(listener);
+        }
     }
     
+    public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.remove(listener);
+        }
+    }
+
     @Override
     protected RemoteOperationResult run(WebdavClient client) {
         RemoteOperationResult result = null;
@@ -190,9 +198,11 @@ public class DownloadFileOperation extends RemoteOperation {
                     }
                     fos.write(bytes, 0, readResult);
                     transferred += readResult;
-                    it = mDataTransferListeners.iterator();
-                    while (it.hasNext()) {
-                        it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName());
+                    synchronized (mDataTransferListeners) {
+                        it = mDataTransferListeners.iterator();
+                        while (it.hasNext()) {
+                            it.next().onTransferProgress(readResult, transferred, mFile.getFileLength(), targetFile.getName());
+                        }
                     }
                 }
                 savedFile = true;
@@ -221,4 +231,5 @@ public class DownloadFileOperation extends RemoteOperation {
         mCancellationRequested.set(true);   // atomic set; there is no need of synchronizing it
     }
 
+
 }

+ 24 - 4
src/com/owncloud/android/operations/UploadFileOperation.java

@@ -30,10 +30,12 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.commons.httpclient.HttpException;
 import org.apache.commons.httpclient.methods.PutMethod;
+import org.apache.commons.httpclient.methods.RequestEntity;
 import org.apache.http.HttpStatus;
 
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.files.services.FileUploader;
+import com.owncloud.android.network.ProgressiveDataTransferer;
 import com.owncloud.android.operations.RemoteOperation;
 import com.owncloud.android.operations.RemoteOperationResult;
 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
@@ -70,6 +72,8 @@ public class UploadFileOperation extends RemoteOperation {
     private Set<OnDatatransferProgressListener> mDataTransferListeners = new HashSet<OnDatatransferProgressListener>();
     private final AtomicBoolean mCancellationRequested = new AtomicBoolean(false);
 
+    protected RequestEntity mEntity = null;
+
     
     public UploadFileOperation( Account account,
                                 OCFile file,
@@ -152,7 +156,21 @@ public class UploadFileOperation extends RemoteOperation {
     }
     
     public void addDatatransferProgressListener (OnDatatransferProgressListener listener) {
-        mDataTransferListeners.add(listener);
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.add(listener);
+        }
+        if (mEntity != null) {
+            ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListener(listener);
+        }
+    }
+    
+    public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.remove(listener);
+        }
+        if (mEntity != null) {
+            ((ProgressiveDataTransferer)mEntity).removeDatatransferProgressListener(listener);
+        }
     }
     
     @Override
@@ -331,9 +349,11 @@ public class UploadFileOperation extends RemoteOperation {
         int status = -1;
         try {
             File f = new File(mFile.getStoragePath());
-            FileRequestEntity entity = new FileRequestEntity(f, getMimeType());
-            entity.addOnDatatransferProgressListeners(mDataTransferListeners);
-            mPutMethod.setRequestEntity(entity);
+            mEntity  = new FileRequestEntity(f, getMimeType());
+            synchronized (mDataTransferListeners) {
+                ((ProgressiveDataTransferer)mEntity).addDatatransferProgressListeners(mDataTransferListeners);
+            }
+            mPutMethod.setRequestEntity(mEntity);
             status = client.executeMethod(mPutMethod);
             client.exhaustResponse(mPutMethod.getResponseBodyAsStream());
             

+ 86 - 9
src/com/owncloud/android/ui/activity/FileDetailActivity.java

@@ -17,6 +17,8 @@
  */
 package com.owncloud.android.ui.activity;
 
+import java.lang.ref.WeakReference;
+
 import android.accounts.Account;
 import android.app.Dialog;
 import android.app.ProgressDialog;
@@ -30,6 +32,7 @@ import android.os.IBinder;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentTransaction;
 import android.util.Log;
+import android.widget.ProgressBar;
 
 import com.actionbarsherlock.app.ActionBar;
 import com.actionbarsherlock.app.SherlockFragmentActivity;
@@ -40,18 +43,18 @@ import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
 import com.owncloud.android.files.services.FileUploader;
 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.ui.fragment.FileDetailFragment;
-import com.owncloud.android.ui.fragment.FileFragment;
 import com.owncloud.android.ui.fragment.FilePreviewFragment;
 
-import com.owncloud.android.AccountUtils;
 import com.owncloud.android.R;
 
+import eu.alefzero.webdav.OnDatatransferProgressListener;
+
 /**
  * This activity displays the details of a file like its name, its size and so
  * on.
  * 
  * @author Bartek Przybylski
- * 
+ * @author David A. Velasco
  */
 public class FileDetailActivity extends SherlockFragmentActivity implements FileDetailFragment.ContainerActivity {
     
@@ -62,11 +65,16 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
     public static final String EXTRA_MODE = "MODE";
     public static final int MODE_DETAILS = 0;
     public static final int MODE_PREVIEW = 1;
+
+    private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";
     
     private boolean mConfigurationChangedToLandscape = false;
     private FileDownloaderBinder mDownloaderBinder = null;
     private ServiceConnection mDownloadConnection, mUploadConnection = null;
     private FileUploaderBinder mUploaderBinder = null;
+    private boolean mWaitingToPreview;
+
+    public ProgressListener mProgressListener;
     
 
     @Override
@@ -91,7 +99,10 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
             actionBar.setDisplayHomeAsUpEnabled(true);
 
             if (savedInstanceState == null) {
+                mWaitingToPreview = false;
                 createChildFragment();
+            } else {
+                mWaitingToPreview = savedInstanceState.getBoolean(KEY_WAITING_TO_PREVIEW);
             }
             
         }  else {
@@ -100,8 +111,11 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         
         
     }
-    
-    
+
+    /**
+     * Creates the proper fragment depending upon the state of the handled {@link OCFile} and
+     * the requested {@link Intent}.
+     */
     private void createChildFragment() {
         OCFile file = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE);
         Account account = getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT);
@@ -109,7 +123,13 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         
         Fragment newFragment = null;
         if (FilePreviewFragment.canBePreviewed(file) && mode == MODE_PREVIEW) {
-            newFragment = new FilePreviewFragment(file, account);
+            if (file.isDown()) {
+                newFragment = new FilePreviewFragment(file, account);
+            
+            } else {
+                newFragment = new FileDetailFragment(file, account);
+                mWaitingToPreview = true;
+            }
             
         } else {
             newFragment = new FileDetailFragment(file, account);
@@ -120,24 +140,48 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
     }
     
 
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putBoolean(KEY_WAITING_TO_PREVIEW, mWaitingToPreview);
+    }
+
 
     /** Defines callbacks for service binding, passed to bindService() */
     private class DetailsServiceConnection implements ServiceConnection {
 
         @Override
         public void onServiceConnected(ComponentName component, IBinder service) {
+            Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
+            FileDetailFragment detailsFragment = (FileDetailFragment) fragment;
+                
             if (component.equals(new ComponentName(FileDetailActivity.this, FileDownloader.class))) {
                 Log.d(TAG, "Download service connected");
                 mDownloaderBinder = (FileDownloaderBinder) service;
+                if (detailsFragment != null) {
+                    mProgressListener = new ProgressListener(detailsFragment.getProgressBar());
+                    mDownloaderBinder.addDatatransferProgressListener(
+                            mProgressListener, 
+                            (Account) getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT), 
+                            (OCFile) getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE)
+                            );
+                }
             } else if (component.equals(new ComponentName(FileDetailActivity.this, FileUploader.class))) {
                 Log.d(TAG, "Upload service connected");
                 mUploaderBinder = (FileUploaderBinder) service;
+                if (detailsFragment != null) {
+                    mProgressListener = new ProgressListener(detailsFragment.getProgressBar());
+                    mUploaderBinder.addDatatransferProgressListener(
+                            mProgressListener, 
+                            (Account) getIntent().getParcelableExtra(FileDetailFragment.EXTRA_ACCOUNT), 
+                            (OCFile) getIntent().getParcelableExtra(FileDetailFragment.EXTRA_FILE)
+                            );
+                }
             } else {
                 return;
             }
-            Fragment fragment = getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG);
-            if (fragment != null && fragment instanceof FileDetailFragment) {
-                ((FileDetailFragment) fragment).updateFileDetails(false);   // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
+            
+            if (detailsFragment != null) {
+                detailsFragment.updateFileDetails(false);   // let the fragment gets the mDownloadBinder through getDownloadBinder() (see FileDetailFragment#updateFileDetais())
             }
         }
 
@@ -153,6 +197,39 @@ public class FileDetailActivity extends SherlockFragmentActivity implements File
         }
     };    
     
+    
+    /**
+     * Helper class responsible for updating the progress bar shown for file uploading or downloading  
+     * 
+     * @author David A. Velasco
+     */
+    private class ProgressListener implements OnDatatransferProgressListener {
+        int mLastPercent = 0;
+        WeakReference<ProgressBar> mProgressBar = null;
+        
+        ProgressListener(ProgressBar progressBar) {
+            mProgressBar = new WeakReference<ProgressBar>(progressBar);
+        }
+        
+        @Override
+        public void onTransferProgress(long progressRate) {
+            // old method, nothing here
+        };
+
+        @Override
+        public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) {
+            int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
+            if (percent != mLastPercent) {
+                ProgressBar pb = mProgressBar.get();
+                if (pb != null) {
+                    pb.setProgress(percent);
+                }
+            }
+            mLastPercent = percent;
+        }
+
+    };
+    
 
     @Override
     public void onDestroy() {

+ 2 - 2
src/com/owncloud/android/ui/activity/FileDisplayActivity.java

@@ -293,7 +293,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
         if (mDualPane && getSupportFragmentManager().findFragmentByTag(FileDetailFragment.FTAG) == null) {
             FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
             if (mCurrentFile != null) {
-                if (FilePreviewFragment.canBePreviewed(mCurrentFile)) {
+                if (mCurrentFile.isDown() && FilePreviewFragment.canBePreviewed(mCurrentFile)) {
                     transaction.replace(R.id.file_details_container, new FilePreviewFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
                 } else {
                     transaction.replace(R.id.file_details_container, new FileDetailFragment(mCurrentFile, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
@@ -1027,7 +1027,7 @@ public class FileDisplayActivity extends SherlockFragmentActivity implements
         if (mDualPane) {
             // buttons in the details view are problematic when trying to reuse an existing fragment; create always a new one solves some of them, BUT no all; downloads are 'dangerous'
             FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
-            if (FilePreviewFragment.canBePreviewed(file)) {
+            if (file != null && file.isDown() && FilePreviewFragment.canBePreviewed(file)) {
                 transaction.replace(R.id.file_details_container, new FilePreviewFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);
             } else {
                 transaction.replace(R.id.file_details_container, new FileDetailFragment(file, AccountUtils.getCurrentOwnCloudAccount(this)), FileDetailFragment.FTAG);

+ 1 - 1
src/com/owncloud/android/ui/dialog/IndeterminateProgressDialog.java

@@ -43,7 +43,7 @@ public class IndeterminateProgressDialog extends SherlockDialogFragment {
         dialog.setIndeterminate(true);
         
         /// set message
-        int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.text_placeholder);
+        int messageId = getArguments().getInt(ARG_MESSAGE_ID, R.string.placeholder_sentence);
         dialog.setMessage(getString(messageId));
         
         /// set cancellation behavior

+ 37 - 0
src/com/owncloud/android/ui/fragment/FileDetailFragment.java

@@ -18,6 +18,7 @@
 package com.owncloud.android.ui.fragment;
 
 import java.io.File;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -65,10 +66,12 @@ import android.view.View.OnClickListener;
 import android.view.View.OnTouchListener;
 import android.view.ViewGroup;
 import android.webkit.MimeTypeMap;
+import android.webkit.WebView.FindListener;
 import android.widget.Button;
 import android.widget.CheckBox;
 import android.widget.ImageView;
 import android.widget.MediaController;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
 import android.widget.VideoView;
@@ -105,6 +108,8 @@ import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
 import com.owncloud.android.utils.OwnCloudVersion;
 
 import com.owncloud.android.R;
+
+import eu.alefzero.webdav.OnDatatransferProgressListener;
 import eu.alefzero.webdav.WebdavClient;
 import eu.alefzero.webdav.WebdavUtils;
 
@@ -731,9 +736,23 @@ public class FileDetailFragment extends SherlockFragment implements
             ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(false);
             ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(false);
             getView().findViewById(R.id.fdKeepInSync).setEnabled(false);
+            
+            // show the progress bar for the transfer
+            ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
+            progressBar.setVisibility(View.VISIBLE);
+            TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
+            progressText.setVisibility(View.VISIBLE);
+            FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
+            FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
+            if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
+                progressText.setText(R.string.downloader_download_in_progress_ticker);
+            } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {
+                progressText.setText(R.string.uploader_upload_in_progress_ticker);
+            }
         }
     }
     
+
     /**
      * Enables or disables buttons for a file locally available 
      */
@@ -746,6 +765,10 @@ public class FileDetailFragment extends SherlockFragment implements
             ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
             ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);
             getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
+            
+            // hides the progress bar
+            ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
+            progressBar.setVisibility(View.GONE);
         }
     }
 
@@ -761,6 +784,10 @@ public class FileDetailFragment extends SherlockFragment implements
             ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
             ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);
             getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
+            
+            // hides the progress bar
+            ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
+            progressBar.setVisibility(View.GONE);
         }
     }
     
@@ -1074,4 +1101,14 @@ public class FileDetailFragment extends SherlockFragment implements
     }
 
 
+    public ProgressBar getProgressBar() {
+        View v = getView();
+        if (v != null) {
+            return (ProgressBar) v.findViewById(R.id.fdProgressBar);
+        } else {
+            return null;
+        }
+    }
+
+
 }

+ 1 - 2
src/com/owncloud/android/ui/fragment/FilePreviewFragment.java

@@ -1039,8 +1039,7 @@ public class FilePreviewFragment extends SherlockFragment implements
      * @return          'True' if the file can be handled by the fragment.
      */
     public static boolean canBePreviewed(OCFile file) {
-        return (file != null && file.isDown() && 
-                (file.isAudio() || file.isVideo() || file.isImage()));
+        return (file != null && (file.isAudio() || file.isVideo() || file.isImage()));
     }
 
     /**

+ 23 - 10
src/eu/alefzero/webdav/ChunkFromFileChannelRequestEntity.java

@@ -30,6 +30,8 @@ import java.util.Set;
 
 import org.apache.commons.httpclient.methods.RequestEntity;
 
+import com.owncloud.android.network.ProgressiveDataTransferer;
+
 import eu.alefzero.webdav.OnDatatransferProgressListener;
 
 import android.util.Log;
@@ -40,7 +42,7 @@ import android.util.Log;
  * 
  * @author David A. Velasco
  */
-public class ChunkFromFileChannelRequestEntity implements RequestEntity {
+public class ChunkFromFileChannelRequestEntity implements RequestEntity, ProgressiveDataTransferer {
 
     private static final String TAG = ChunkFromFileChannelRequestEntity.class.getSimpleName();
     
@@ -90,16 +92,25 @@ public class ChunkFromFileChannelRequestEntity implements RequestEntity {
         return true;
     }
     
-    public void addOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
-        mDataTransferListeners.add(listener);
+    @Override
+    public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.add(listener);
+        }
     }
     
-    public void addOnDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
-        mDataTransferListeners.addAll(listeners);
+    @Override
+    public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.addAll(listeners);
+        }
     }
     
-    public void removeOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
-        mDataTransferListeners.remove(listener);
+    @Override
+    public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.remove(listener);
+        }
     }
     
     
@@ -116,9 +127,11 @@ public class ChunkFromFileChannelRequestEntity implements RequestEntity {
                 out.write(mBuffer.array(), 0, readCount);
                 mBuffer.clear();
                 mTransferred += readCount;
-                it = mDataTransferListeners.iterator();
-                while (it.hasNext()) {
-                    it.next().onTransferProgress(readCount, mTransferred, size, mFile.getName());
+                synchronized (mDataTransferListeners) {
+                    it = mDataTransferListeners.iterator();
+                    while (it.hasNext()) {
+                        it.next().onTransferProgress(readCount, mTransferred, size, mFile.getName());
+                    }
                 }
             }
             

+ 24 - 11
src/eu/alefzero/webdav/FileRequestEntity.java

@@ -31,6 +31,8 @@ import java.util.Set;
 
 import org.apache.commons.httpclient.methods.RequestEntity;
 
+import com.owncloud.android.network.ProgressiveDataTransferer;
+
 import eu.alefzero.webdav.OnDatatransferProgressListener;
 
 import android.util.Log;
@@ -40,7 +42,7 @@ import android.util.Log;
  * A RequestEntity that represents a File.
  * 
  */
-public class FileRequestEntity implements RequestEntity {
+public class FileRequestEntity implements RequestEntity, ProgressiveDataTransferer {
 
     final File mFile;
     final String mContentType;
@@ -69,17 +71,26 @@ public class FileRequestEntity implements RequestEntity {
     public boolean isRepeatable() {
         return true;
     }
-    
-    public void addOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
-        mDataTransferListeners.add(listener);
+
+    @Override
+    public void addDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.add(listener);
+        }
     }
     
-    public void addOnDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
-        mDataTransferListeners.addAll(listeners);
+    @Override
+    public void addDatatransferProgressListeners(Collection<OnDatatransferProgressListener> listeners) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.addAll(listeners);
+        }
     }
     
-    public void removeOnDatatransferProgressListener(OnDatatransferProgressListener listener) {
-        mDataTransferListeners.remove(listener);
+    @Override
+    public void removeDatatransferProgressListener(OnDatatransferProgressListener listener) {
+        synchronized (mDataTransferListeners) {
+            mDataTransferListeners.remove(listener);
+        }
     }
     
     
@@ -102,9 +113,11 @@ public class FileRequestEntity implements RequestEntity {
                 out.write(tmp.array(), 0, readResult);
                 tmp.clear();
                 transferred += readResult;
-                it = mDataTransferListeners.iterator();
-                while (it.hasNext()) {
-                    it.next().onTransferProgress(readResult, transferred, size, mFile.getName());
+                synchronized (mDataTransferListeners) {
+                    it = mDataTransferListeners.iterator();
+                    while (it.hasNext()) {
+                        it.next().onTransferProgress(readResult, transferred, size, mFile.getName());
+                    }
                 }
             }
             

+ 1 - 1
src/eu/alefzero/webdav/WebdavClient.java

@@ -165,7 +165,7 @@ public class WebdavClient extends HttpClient {
         try {
             File f = new File(localFile);
             FileRequestEntity entity = new FileRequestEntity(f, contentType);
-            entity.addOnDatatransferProgressListener(mDataTransferListener);
+            entity.addDatatransferProgressListener(mDataTransferListener);
             put.setRequestEntity(entity);
             status = executeMethod(put);