Browse Source

Merge pull request #1307 from owncloud/share_with_password_and_expiration_time

Share via link with password and expiration time
David A. Velasco 9 years ago
parent
commit
385f3b7a1c
27 changed files with 1350 additions and 325 deletions
  1. 1 1
      owncloud-android-library
  2. 137 13
      res/layout/share_file_layout.xml
  3. 1 10
      res/menu/file_actions_menu.xml
  4. 11 4
      res/values/strings.xml
  5. 72 22
      src/com/owncloud/android/datamodel/FileDataStorageManager.java
  6. 6 21
      src/com/owncloud/android/files/FileMenuFilter.java
  7. 121 26
      src/com/owncloud/android/files/FileOperationsHelper.java
  8. 21 8
      src/com/owncloud/android/operations/CreateShareViaLinkOperation.java
  9. 5 0
      src/com/owncloud/android/operations/GetSharesForFileOperation.java
  10. 1 1
      src/com/owncloud/android/operations/RefreshFolderOperation.java
  11. 2 2
      src/com/owncloud/android/operations/UnshareOperation.java
  12. 152 0
      src/com/owncloud/android/operations/UpdateShareViaLinkOperation.java
  13. 37 13
      src/com/owncloud/android/services/OperationsService.java
  14. 44 28
      src/com/owncloud/android/ui/activity/FileActivity.java
  15. 1 25
      src/com/owncloud/android/ui/activity/FileDisplayActivity.java
  16. 43 13
      src/com/owncloud/android/ui/activity/ShareActivity.java
  17. 138 0
      src/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
  18. 6 24
      src/com/owncloud/android/ui/dialog/ShareLinkToDialog.java
  19. 18 18
      src/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java
  20. 0 9
      src/com/owncloud/android/ui/fragment/FileDetailFragment.java
  21. 0 8
      src/com/owncloud/android/ui/fragment/OCFileListFragment.java
  22. 510 14
      src/com/owncloud/android/ui/fragment/ShareFileFragment.java
  23. 1 38
      src/com/owncloud/android/ui/preview/PreviewImageActivity.java
  24. 0 8
      src/com/owncloud/android/ui/preview/PreviewImageFragment.java
  25. 0 10
      src/com/owncloud/android/ui/preview/PreviewMediaFragment.java
  26. 0 8
      src/com/owncloud/android/ui/preview/PreviewTextFragment.java
  27. 22 1
      src/com/owncloud/android/utils/ErrorMessageAdapter.java

+ 1 - 1
owncloud-android-library

@@ -1 +1 @@
-Subproject commit b09969d078b3a790b01c8d61a7298a37439a9f24
+Subproject commit 9e5c44ddb58970f1bbdf6723145a47379bdbccba

+ 137 - 13
res/layout/share_file_layout.xml

@@ -15,17 +15,19 @@
   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context="com.owncloud.android.ui.fragment.ShareFileFragment">
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            xmlns:tools="http://schemas.android.com/tools"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            tools:context="com.owncloud.android.ui.fragment.ShareFileFragment"
+            android:id="@+id/shareScroll">
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
+        android:layout_height="wrap_content"
         android:background="@color/background_material_light"
-        android:orientation="vertical">
+        android:orientation="vertical"
+        >
 
         <RelativeLayout
             android:id="@+id/shareHeaderContainer"
@@ -51,10 +53,12 @@
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:text="@string/placeholder_filename"
-                android:textSize="16dip"
+                android:textSize="16sp"
                 android:layout_gravity="center_vertical"
                 android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
                 android:layout_marginRight="8dp"
+                android:layout_marginEnd="8dp"
                 android:layout_toRightOf="@+id/shareFileIcon"
                 android:layout_toEndOf="@+id/shareFileIcon"
                 android:singleLine="true"
@@ -64,7 +68,7 @@
             <TextView
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
-                android:textSize="12dip"
+                android:textSize="12sp"
                 android:text="@string/placeholder_filesize"
                 android:id="@+id/shareFileSize"
                 android:layout_below="@+id/shareFileName"
@@ -72,6 +76,7 @@
                 android:layout_toEndOf="@+id/shareFileIcon"
                 android:layout_marginTop="4dp"
                 android:layout_marginLeft="4dp"
+                android:layout_marginStart="4dp"
                 android:layout_marginBottom="12dp"
                 android:layout_gravity="center_vertical"/>
 
@@ -80,10 +85,10 @@
         <TextView
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:textSize="16dip"
+            android:textSize="16sp"
             android:text="@string/share_with_user_section_title"
             android:id="@+id/shareWithUsersSectionTitle"
-            android:layout_gravity="left"
+            android:layout_gravity="start"
             android:padding="8dp"
             android:background="@color/actionbar_start_color"
             android:textColor="@color/white"/>
@@ -101,7 +106,7 @@
             android:layout_height="wrap_content"
             android:id="@+id/shareNoUsers"
             android:text="@string/share_no_users"
-            android:textSize="12dip"
+            android:textSize="12sp"
             android:padding="12dp" />
 
         <android.support.v7.widget.AppCompatButton
@@ -113,5 +118,124 @@
             android:text="@string/share_add_user_or_group"
             android:contentDescription="shareAddUserButton"/>
 
+        <Switch
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:textSize="16sp"
+            android:text="@string/share_via_link_section_title"
+            android:id="@+id/shareViaLinkSectionSwitch"
+            android:layout_gravity="start"
+            android:padding="8dp"
+            android:background="@color/actionbar_start_color"
+            android:textColor="@color/white"/>
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/shareViaLinkExpirationSection"
+            >
+
+            <Switch
+                android:id="@+id/shareViaLinkExpirationSwitch"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_alignParentEnd="true"
+                android:layout_centerInParent="true"
+                android:padding="8dp"
+                />
+
+            <TextView
+                android:id="@+id/shareViaLinkExpirationLabel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_toLeftOf="@id/shareViaLinkExpirationSwitch"
+                android:layout_toStartOf="@id/shareViaLinkExpirationSwitch"
+                android:paddingTop="8dp"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:text="@string/share_via_link_expiration_date_label"
+                android:textSize="16sp"
+                />
+
+            <TextView
+                android:id="@+id/shareViaLinkExpirationValue"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_toLeftOf="@id/shareViaLinkExpirationSwitch"
+                android:layout_toStartOf="@id/shareViaLinkExpirationSwitch"
+                android:layout_below="@id/shareViaLinkExpirationLabel"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:paddingBottom="8dp"
+                android:textSize="12sp"
+                />
+
+        </RelativeLayout>
+
+        <RelativeLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:id="@+id/shareViaLinkPasswordSection"
+            >
+
+            <Switch
+                android:id="@+id/shareViaLinkPasswordSwitch"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentRight="true"
+                android:layout_alignParentEnd="true"
+                android:layout_centerInParent="true"
+                android:padding="8dp"
+            />
+
+            <TextView
+                android:id="@+id/shareViaLinkPasswordLabel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_toLeftOf="@id/shareViaLinkPasswordSwitch"
+                android:layout_toStartOf="@id/shareViaLinkPasswordSwitch"
+                android:paddingTop="8dp"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:text="@string/share_via_link_password_label"
+                android:textSize="16sp"
+                />
+
+            <TextView
+                android:id="@+id/shareViaLinkPasswordValue"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentLeft="true"
+                android:layout_alignParentStart="true"
+                android:layout_toLeftOf="@id/shareViaLinkPasswordSwitch"
+                android:layout_toStartOf="@id/shareViaLinkPasswordSwitch"
+                android:layout_below="@id/shareViaLinkPasswordLabel"
+                android:paddingLeft="8dp"
+                android:paddingRight="8dp"
+                android:paddingBottom="8dp"
+                android:text="@string/share_via_link_password_title"
+                android:textSize="12sp"
+                android:visibility="invisible"
+                />
+
+        </RelativeLayout>
+
+        <android.support.v7.widget.AppCompatButton
+            android:id="@+id/shareViaLinkGetLinkButton"
+            style="@style/ownCloud.Button"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_horizontal"
+            android:text="@string/share_get_public_link_button"
+            android:contentDescription="shareGetLinkButton"/>
+
     </LinearLayout>
-</FrameLayout>
+
+</ScrollView>

+ 1 - 10
res/menu/file_actions_menu.xml

@@ -20,18 +20,9 @@
 
     <item
         android:id="@+id/action_share_file"
-        android:title="@string/action_share_file"
+        android:title="@string/action_share"
         android:icon="@android:drawable/ic_menu_share"
         android:orderInCategory="1" />
-    <item
-        android:id="@+id/action_unshare_file"
-        android:title="@string/action_unshare_file"
-        android:icon="@android:drawable/ic_menu_share"
-        android:orderInCategory="1" />
-    <item
-        android:id="@+id/action_share_with_users"
-        android:title="@string/action_share_with_users"
-        android:orderInCategory="1" />
 
     <item
         android:id="@+id/action_open_file_with"

+ 11 - 4
res/values/strings.xml

@@ -85,9 +85,7 @@
     <string name="filedetails_sync_file">Synchronize</string>
     <string name="filedetails_renamed_in_upload_msg">File was renamed to %1$s during upload</string>
     <string name="list_layout">List Layout</string>
-    <string name="action_share_file">Share link</string>
-    <string name="action_unshare_file">Unshare link</string>
-    <string name="action_share_with_users">Share with users</string>
+    <string name="action_share">Share</string>
     <string name="common_yes">Yes</string>
     <string name="common_no">No</string>
     <string name="common_ok">OK</string>
@@ -288,6 +286,8 @@
 	<string name="share_link_file_error">An error occurred while trying to share this file or folder</string>
 	<string name="unshare_link_file_no_exist">Unable to unshare. Please check whether the file exists</string>
 	<string name="unshare_link_file_error">An error occurred while trying to unshare this file or folder</string>
+    <string name="update_link_file_no_exist">Unable to update. Please check whether the file exists </string>
+    <string name="update_link_file_error">An error occurred while trying to update the shared link</string>
     <string name="share_link_password_title">Enter a password</string>
     <string name="share_link_empty_password">You must enter a password</string>
 
@@ -309,6 +309,7 @@
     <string name="forbidden_permissions_delete">to delete this file</string>
     <string name="share_link_forbidden_permissions">to share this file</string>
     <string name="unshare_link_forbidden_permissions">to unshare this file</string>
+    <string name="update_link_forbidden_permissions">to update this shared link</string>
     <string name="forbidden_permissions_create">to create the file</string>
     <string name="uploader_upload_forbidden_permissions">to upload in this folder</string>
     <string name="downloader_download_file_not_found">The file is no longer available on the server</string>
@@ -370,9 +371,15 @@
     <string name="file_list__footer__files_and_folders">%1$d files, %2$d folders</string>
 
     <string name="share_dialog_title">Sharing</string>
-    <string name="share_with_user_section_title">Share with Users and Groups</string>
+    <string name="share_with_user_section_title">Share with users and groups</string>
     <string name="share_no_users">No data shared with users yet</string>
     <string name="share_add_user_or_group">Add User or Group</string>
+    <string name="share_via_link_section_title">Share link</string>
+    <string name="share_via_link_expiration_date_label">Set expiration date</string>
+    <string name="share_via_link_password_label">Password protect</string>
+    <string name="share_via_link_password_title">Secured</string>
+    <string name="share_get_public_link_button">Get link</string>
+
     <string name="share_search">Search</string>
 
     <string name="search_users_and_groups_hint">Search users and groups</string>

+ 72 - 22
src/com/owncloud/android/datamodel/FileDataStorageManager.java

@@ -20,16 +20,6 @@
 
 package com.owncloud.android.datamodel;
 
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-import java.util.Vector;
-
 import android.accounts.Account;
 import android.content.ContentProviderClient;
 import android.content.ContentProviderOperation;
@@ -47,18 +37,26 @@ import android.provider.MediaStore;
 import com.owncloud.android.MainApp;
 import com.owncloud.android.db.ProviderMeta.ProviderTableMeta;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.lib.resources.files.FileUtils;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.lib.resources.status.CapabilityBooleanType;
 import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.utils.FileStorageUtils;
 
+import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.Vector;
 
 public class FileDataStorageManager {
 
@@ -935,20 +933,20 @@ public class FileDataStorageManager {
         );
         cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0);
         cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId());
-        cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared());
+        cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getRemoteId());
         cv.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, mAccount.name);
 
-        if (shareExists(share.getIdRemoteShared())) {// for renamed files; no more delete and create
+        if (shareExists(share.getRemoteId())) {// for renamed files; no more delete and create
             overriden = true;
             if (getContentResolver() != null) {
                 getContentResolver().update(ProviderTableMeta.CONTENT_URI_SHARE, cv,
                         ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?",
-                        new String[]{String.valueOf(share.getIdRemoteShared())});
+                        new String[]{String.valueOf(share.getRemoteId())});
             } else {
                 try {
                     getContentProviderClient().update(ProviderTableMeta.CONTENT_URI_SHARE,
                             cv, ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?",
-                            new String[]{String.valueOf(share.getIdRemoteShared())});
+                            new String[]{String.valueOf(share.getRemoteId())});
                 } catch (RemoteException e) {
                     Log_OC.e(TAG,
                             "Fail to insert insert file to database "
@@ -981,16 +979,42 @@ public class FileDataStorageManager {
     }
 
 
+    /**
+     * Get first share bound to a file with a known path and given {@link ShareType}.
+     *
+     * @param path          Path of the file.
+     * @param type          Type of the share to get
+     * @param shareWith     Target of the share. Ignored in type is {@link ShareType#PUBLIC_LINK}
+     * @return              First {@OCShare} instance found in DB bound to the file in 'path'
+     */
     public OCShare getFirstShareByPathAndType(String path, ShareType type, String shareWith) {
         Cursor c = null;
+        if (shareWith == null) {
+            shareWith = "";
+        }
 
         String selection = ProviderTableMeta.OCSHARES_PATH + "=? AND "
                 + ProviderTableMeta.OCSHARES_SHARE_TYPE + "=? AND "
-                + ProviderTableMeta.OCSHARES_SHARE_WITH + "=? AND "
                 + ProviderTableMeta.OCSHARES_ACCOUNT_OWNER + "=?" ;
+        if (!ShareType.PUBLIC_LINK.equals(type)) {
+            selection += " AND " + ProviderTableMeta.OCSHARES_SHARE_WITH + "=?";
+        }
 
-        String [] selectionArgs =  new String[]{path, Integer.toString(type.getValue()),
-                shareWith, mAccount.name};
+        String [] selectionArgs;
+        if (ShareType.PUBLIC_LINK.equals(type)) {
+            selectionArgs = new String[]{
+                    path,
+                    Integer.toString(type.getValue()),
+                    mAccount.name
+            };
+        } else {
+            selectionArgs = new String[]{
+                    path,
+                    Integer.toString(type.getValue()),
+                    mAccount.name,
+                    shareWith
+            };
+        }
 
         if (getContentResolver() != null) {
             c = getContentResolver().query(
@@ -1190,16 +1214,16 @@ public class FileDataStorageManager {
                 );
                 cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0);
                 cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId());
-                cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared());
+                cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getRemoteId());
                 cv.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, mAccount.name);
 
-                if (shareExists(share.getIdRemoteShared())) {
+                if (shareExists(share.getRemoteId())) {
                     // updating an existing file
                     operations.add(
                             ContentProviderOperation.newUpdate(ProviderTableMeta.CONTENT_URI_SHARE).
                                     withValues(cv).
                                     withSelection(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED + "=?",
-                                            new String[]{String.valueOf(share.getIdRemoteShared())})
+                                            new String[]{String.valueOf(share.getRemoteId())})
                                     .build());
                 } else {
                     // adding a new file
@@ -1403,6 +1427,30 @@ public class FileDataStorageManager {
 //        updateSharedFiles(sharedFiles);
     }
 
+    public void removeSharesForFile(String remotePath) {
+        resetShareFlagInAFile(remotePath);
+        ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();
+        operations = prepareRemoveSharesInFile(remotePath, operations);
+        // apply operations in batch
+        if (operations.size() > 0) {
+            Log_OC.d(TAG, "Sending " + operations.size() + " operations to FileContentProvider");
+            try {
+                if (getContentResolver() != null) {
+                    getContentResolver().applyBatch(MainApp.getAuthority(), operations);
+
+                } else {
+                    getContentProviderClient().applyBatch(operations);
+                }
+
+            } catch (OperationApplicationException e) {
+                Log_OC.e(TAG, "Exception in batch of operations " + e.getMessage());
+
+            } catch (RemoteException e) {
+                Log_OC.e(TAG, "Exception in batch of operations  " + e.getMessage());
+            }
+        }
+    }
+
 
     public void saveSharesInFolder(ArrayList<OCShare> shares, OCFile folder) {
         resetShareFlagsInFolder(folder);
@@ -1463,7 +1511,7 @@ public class FileDataStorageManager {
                 );
                 cv.put(ProviderTableMeta.OCSHARES_IS_DIRECTORY, share.isFolder() ? 1 : 0);
                 cv.put(ProviderTableMeta.OCSHARES_USER_ID, share.getUserId());
-                cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getIdRemoteShared());
+                cv.put(ProviderTableMeta.OCSHARES_ID_REMOTE_SHARED, share.getRemoteId());
                 cv.put(ProviderTableMeta.OCSHARES_ACCOUNT_OWNER, mAccount.name);
 
                 // adding a new share resource
@@ -1860,6 +1908,8 @@ public class FileDataStorageManager {
 
         if (c.moveToFirst()) {
             capability = createCapabilityInstance(c);
+        } else {
+            capability = new OCCapability();    // return default with all UNKNOWN
         }
         c.close();
         return capability;

+ 6 - 21
src/com/owncloud/android/files/FileMenuFilter.java

@@ -184,34 +184,19 @@ public class FileMenuFilter {
         }
 
         // SHARE FILE
-        // TODO add check on SHARE available on server side?
         boolean shareAllowed = (mContext != null  &&
                 mContext.getString(R.string.share_feature).equalsIgnoreCase("on"));
-        if (!shareAllowed || mFile == null) {
-            toHide.add(R.id.action_share_file);
-        } else {
-            toShow.add(R.id.action_share_file);
-        }
-
-        // UNSHARE FILE
-        // TODO add check on SHARE available on server side?
-        if ( !shareAllowed || (mFile == null || !mFile.isSharedViaLink())) {
-            toHide.add(R.id.action_unshare_file);
-        } else {
-            toShow.add(R.id.action_unshare_file);
-        }
-
-        // SHARE FILE, with Users
         OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name);
         boolean shareApiEnabled  = capability != null &&
-                (capability.getFilesSharingApiEnabled().isTrue() || capability.getFilesSharingApiEnabled().isUnknown());
-        if (!shareAllowed ||  mFile == null || !shareApiEnabled ) {
-            toHide.add(R.id.action_share_with_users);
+                (capability.getFilesSharingApiEnabled().isTrue() ||
+                        capability.getFilesSharingApiEnabled().isUnknown()
+                );
+        if (!shareAllowed ||  mFile == null || !shareApiEnabled) {
+            toHide.add(R.id.action_share_file);
         } else {
-            toShow.add(R.id.action_share_with_users);
+            toShow.add(R.id.action_share_file);
         }
 
-
         // SEE DETAILS
         if (mFile == null || mFile.isFolder()) {
             toHide.add(R.id.action_see_details);

+ 121 - 26
src/com/owncloud/android/files/FileOperationsHelper.java

@@ -46,8 +46,8 @@ import com.owncloud.android.services.observer.FileObserverService;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.activity.ShareActivity;
 import com.owncloud.android.ui.dialog.ShareLinkToDialog;
+import com.owncloud.android.ui.dialog.SharePasswordDialogFragment;
 
-import org.apache.http.protocol.HTTP;
 
 import java.util.List;
 
@@ -139,19 +139,32 @@ public class FileOperationsHelper {
                 .show();
     }
 
-    public void shareFileWithLink(OCFile file) {
 
+    /**
+     * Helper method to share a file via a public link. Starts a request to do it in {@link OperationsService}
+     *
+     * @param file          The file to share.
+     * @param password      Optional password to protect the public share.
+     */
+    public void shareFileViaLink(OCFile file, String password) {
         if (isSharedSupported()) {
             if (file != null) {
-                String link = "https://fake.url";
-                Intent intent = createShareWithLinkIntent(link);
-                String[] packagesToExclude = new String[]{mFileActivity.getPackageName()};
-                DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intent,
-                        packagesToExclude, file);
-                chooserDialog.show(mFileActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
+                mFileActivity.showLoadingDialog(
+                        mFileActivity.getApplicationContext().
+                                getString(R.string.wait_a_moment)
+                );
+                Intent service = new Intent(mFileActivity, OperationsService.class);
+                service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK);
+                service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+                if (password != null && password.length() > 0) {
+                    service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password);
+                }
+                service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+                mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
 
             } else {
                 Log_OC.wtf(TAG, "Trying to share a NULL OCFile");
+                // TODO user-level error?
             }
 
         } else {
@@ -164,6 +177,30 @@ public class FileOperationsHelper {
         }
     }
 
+    public void getFileWithLink(OCFile file){
+        if (isSharedSupported()) {
+            if (file != null) {
+                mFileActivity.showLoadingDialog(mFileActivity.getApplicationContext().
+                        getString(R.string.wait_a_moment));
+
+                Intent service = new Intent(mFileActivity, OperationsService.class);
+                service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK);
+                service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+                service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+                mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
+
+            } else {
+                Log_OC.wtf(TAG, "Trying to share a NULL OCFile");
+            }
+        } else {
+            // Show a Message
+            Toast t = Toast.makeText(
+                    mFileActivity, mFileActivity.getString(R.string.share_link_no_support_share_api),
+                    Toast.LENGTH_LONG
+            );
+            t.show();
+        }
+    }
 
     public void shareFileWithLinkToApp(OCFile file, String password, Intent sendIntent) {
         
@@ -175,7 +212,7 @@ public class FileOperationsHelper {
             service.setAction(OperationsService.ACTION_CREATE_SHARE_VIA_LINK);
             service.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
             service.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
-            service.putExtra(OperationsService.EXTRA_PASSWORD_SHARE, password);
+            service.putExtra(OperationsService.EXTRA_SHARE_PASSWORD, password);
             service.putExtra(OperationsService.EXTRA_SEND_INTENT, sendIntent);
             mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(service);
             
@@ -184,17 +221,8 @@ public class FileOperationsHelper {
         }
     }
 
-
-    private Intent createShareWithLinkIntent(String link) {
-        Intent intentToShareLink = new Intent(Intent.ACTION_SEND);
-        intentToShareLink.putExtra(Intent.EXTRA_TEXT, link);
-        intentToShareLink.setType(HTTP.PLAIN_TEXT_TYPE);
-        return intentToShareLink;
-    }
-
-
     /**
-     * Helper method to share a file with a know sharee. Starts a request to do it in {@link OperationsService}
+     * Helper method to share a file with a known sharee. Starts a request to do it in {@link OperationsService}
      *
      * @param file          The file to share.
      * @param shareeName    Name (user name or group name) of the target sharee.
@@ -232,7 +260,13 @@ public class FileOperationsHelper {
     }
 
 
-    public void unshareFileWithLink(OCFile file) {
+    /**
+     * Helper method to unshare a file publicly shared via link.
+     * Starts a request to do it in {@link OperationsService}
+     *
+     * @param file      The file to unshare.
+     */
+    public void unshareFileViaLink(OCFile file) {
 
         // Unshare the file: Create the intent
         Intent unshareService = new Intent(mFileActivity, OperationsService.class);
@@ -242,7 +276,7 @@ public class FileOperationsHelper {
         unshareService.putExtra(OperationsService.EXTRA_SHARE_TYPE, ShareType.PUBLIC_LINK);
         unshareService.putExtra(OperationsService.EXTRA_SHARE_WITH, "");
 
-        unshareFile(unshareService);
+        queueShareIntent(unshareService);
     }
 
     public void unshareFileWithUserOrGroup(OCFile file, ShareType shareType, String userOrGroup){
@@ -255,15 +289,15 @@ public class FileOperationsHelper {
         unshareService.putExtra(OperationsService.EXTRA_SHARE_TYPE, shareType);
         unshareService.putExtra(OperationsService.EXTRA_SHARE_WITH, userOrGroup);
 
-        unshareFile(unshareService);
+        queueShareIntent(unshareService);
     }
 
 
-    private void unshareFile(Intent unshareService){
+    private void queueShareIntent(Intent shareIntent){
         if (isSharedSupported()) {
             // Unshare the file
             mWaitingForOpId = mFileActivity.getOperationsServiceBinder().
-                    queueNewOperation(unshareService);
+                    queueNewOperation(shareIntent);
 
             mFileActivity.showLoadingDialog(mFileActivity.getApplicationContext().
                     getString(R.string.wait_a_moment));
@@ -292,6 +326,67 @@ public class FileOperationsHelper {
     }
 
 
+    /**
+     * Starts a dialog that requests a password to the user to protect a share link.
+     *
+     * @param   file            File which public share will be protected by the requested password
+     * @param   createShare     When 'true', the request for password will be followed by the creation of a new
+     *                          public link; when 'false', a public share is assumed to exist, and the password
+     *                          is bound to it.
+     */
+    public void requestPasswordForShareViaLink(OCFile file, boolean createShare) {
+        SharePasswordDialogFragment dialog =
+                SharePasswordDialogFragment.newInstance(file, createShare);
+        dialog.show(
+                mFileActivity.getSupportFragmentManager(),
+                SharePasswordDialogFragment.PASSWORD_FRAGMENT
+        );
+    }
+
+    /**
+     * Updates a public share on a file to set its password.
+     * Starts a request to do it in {@link OperationsService}
+     *
+     * @param file          File which public share will be protected with a password.
+     * @param password      Password to set for the public link; null or empty string to clear
+     *                      the current password
+     */
+    public void setPasswordToShareViaLink(OCFile file, String password) {
+        // Set password updating share
+        Intent updateShareIntent = new Intent(mFileActivity, OperationsService.class);
+        updateShareIntent.setAction(OperationsService.ACTION_UPDATE_SHARE);
+        updateShareIntent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+        updateShareIntent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+        updateShareIntent.putExtra(
+                OperationsService.EXTRA_SHARE_PASSWORD,
+                (password == null) ? "" : password
+        );
+
+        queueShareIntent(updateShareIntent);
+    }
+
+
+    /**
+     * Updates a public share on a file to set its expiration date.
+     * Starts a request to do it in {@link OperationsService}
+     *
+     * @param file                      File which public share will be constrained with an expiration date.
+     * @param expirationTimeInMillis    Expiration date to set. A negative value clears the current expiration
+     *                                  date, leaving the link unrestricted. Zero makes no change.
+     */
+    public void setExpirationDateToShareViaLink(OCFile file, long expirationTimeInMillis) {
+        Intent updateShareIntent = new Intent(mFileActivity, OperationsService.class);
+        updateShareIntent.setAction(OperationsService.ACTION_UPDATE_SHARE);
+        updateShareIntent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount());
+        updateShareIntent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath());
+        updateShareIntent.putExtra(
+                OperationsService.EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS,
+                expirationTimeInMillis
+        );
+        queueShareIntent(updateShareIntent);
+    }
+
+
     /**
      * @return 'True' if the server supports the Search Users API
      */
@@ -315,8 +410,7 @@ public class FileOperationsHelper {
 
             // Show dialog, without the own app
             String[] packagesToExclude = new String[]{mFileActivity.getPackageName()};
-            DialogFragment chooserDialog = ShareLinkToDialog.newInstance(sendIntent,
-                    packagesToExclude, file);
+            DialogFragment chooserDialog = ShareLinkToDialog.newInstance(sendIntent, packagesToExclude);
             chooserDialog.show(mFileActivity.getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
 
         } else {
@@ -492,4 +586,5 @@ public class FileOperationsHelper {
         }
         return false;
     }
+
 }

+ 21 - 8
src/com/owncloud/android/operations/CreateShareViaLinkOperation.java

@@ -30,7 +30,6 @@ import android.content.Context;
 import android.content.Intent;
 
 import com.owncloud.android.R;
-import com.owncloud.android.datamodel.FileDataStorageManager;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.OwnCloudClient;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
@@ -42,9 +41,9 @@ import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
 import com.owncloud.android.operations.common.SyncOperation;
 
-public class CreateShareViaLinkOperation extends SyncOperation {
+import java.util.ArrayList;
 
-    protected FileDataStorageManager mStorageManager;
+public class CreateShareViaLinkOperation extends SyncOperation {
 
     private String mPath;
     private String mPassword;
@@ -76,10 +75,21 @@ public class CreateShareViaLinkOperation extends SyncOperation {
         // Check if the share link already exists
         RemoteOperation operation = new GetRemoteSharesForFileOperation(mPath, false, false);
         RemoteOperationResult result = operation.execute(client);
-        // TODO - fix this check; if the user already shared the file with users or group, a share via link will not be created
 
-        if (!result.isSuccess() || result.getData().size() <= 0) {
-            operation = new CreateRemoteShareOperation(
+        boolean shareByLink = false;
+        // Check if the file is shared by link
+        if (result.isSuccess() && result.getData().size() > 0){
+            ArrayList<Object> shares = result.getData();
+            for(Object object: shares){
+                if (((OCShare) object).getShareType() == ShareType.PUBLIC_LINK){
+                    shareByLink = true;
+                    break;
+                }
+            }
+        }
+
+        if (!result.isSuccess() || !shareByLink) {
+            CreateRemoteShareOperation createOp = new CreateRemoteShareOperation(
                     mPath,
                     ShareType.PUBLIC_LINK,
                     "",
@@ -87,7 +97,8 @@ public class CreateShareViaLinkOperation extends SyncOperation {
                     mPassword,
                     OCShare.DEFAULT_PERMISSION
             );
-            result = operation.execute(client);
+            createOp.setGetShareDetails(true);
+            result = createOp.execute(client);
         }
         
         if (result.isSuccess()) {
@@ -148,10 +159,12 @@ public class CreateShareViaLinkOperation extends SyncOperation {
         // Update OCFile with data from share: ShareByLink  and publicLink
         OCFile file = getStorageManager().getFileByPath(mPath);
         if (file!=null) {
-            mSendIntent.putExtra(Intent.EXTRA_TEXT, share.getShareLink());
             file.setPublicLink(share.getShareLink());
             file.setShareViaLink(true);
             getStorageManager().saveFile(file);
+            if (mSendIntent != null) {
+                mSendIntent.putExtra(Intent.EXTRA_TEXT, share.getShareLink());
+            }
         }
     }
 

+ 5 - 0
src/com/owncloud/android/operations/GetSharesForFileOperation.java

@@ -72,6 +72,11 @@ public class GetSharesForFileOperation extends SyncOperation {
             }
 
             getStorageManager().saveSharesDB(shares);
+
+        } else if (result.getCode() == RemoteOperationResult.ResultCode.SHARE_NOT_FOUND) {
+            // no share on the file - remove local shares
+            getStorageManager().removeSharesForFile(mPath);
+
         }
 
         return result;

+ 1 - 1
src/com/owncloud/android/operations/RefreshFolderOperation.java

@@ -246,7 +246,7 @@ public class RefreshFolderOperation extends RemoteOperation {
         GetCapabilitiesOperarion getCapabilities = new GetCapabilitiesOperarion();
         RemoteOperationResult  result = getCapabilities.execute(mStorageManager,mContext);
         if (!result.isSuccess()){
-            Log_OC.d(TAG, "Update Capabilities unsuccessfully");
+            Log_OC.w(TAG, "Update Capabilities unsuccessfully");
         }
     }
 

+ 2 - 2
src/com/owncloud/android/operations/UnshareOperation.java

@@ -69,11 +69,11 @@ public class UnshareOperation extends SyncOperation {
         if (share != null) {
             OCFile file = getStorageManager().getFileByPath(mRemotePath);
             RemoveRemoteShareOperation operation =
-                    new RemoveRemoteShareOperation((int) share.getIdRemoteShared());
+                    new RemoveRemoteShareOperation((int) share.getRemoteId());
             result = operation.execute(client);
 
             if (result.isSuccess()) {
-                Log_OC.d(TAG, "Share id = " + share.getIdRemoteShared() + " deleted");
+                Log_OC.d(TAG, "Share id = " + share.getRemoteId() + " deleted");
 
                 if (mShareType == ShareType.PUBLIC_LINK) {
                     file.setShareViaLink(false);

+ 152 - 0
src/com/owncloud/android/operations/UpdateShareViaLinkOperation.java

@@ -0,0 +1,152 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.operations;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.operations.RemoteOperation;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.lib.resources.shares.GetRemoteShareOperation;
+import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.lib.resources.shares.UpdateRemoteShareOperation;
+import com.owncloud.android.operations.common.SyncOperation;
+
+import java.util.Calendar;
+
+
+/**
+ * Updates an existing public share for a given file
+ */
+
+public class UpdateShareViaLinkOperation extends SyncOperation {
+
+    private String mPath;
+    private String mPassword;
+    private long mExpirationDateInMillis;
+
+    /**
+     * Constructor
+     *
+     * @param path          Full path of the file/folder being shared. Mandatory argument
+     */
+    public UpdateShareViaLinkOperation(String path) {
+
+        mPath = path;
+        mPassword = null;
+        mExpirationDateInMillis = 0;
+    }
+
+
+    /**
+     * Set password to update in public link.
+     *
+     * @param password      Password to set to the public link.
+     *                      Empty string clears the current password.
+     *                      Null results in no update applied to the password.
+     */
+    public void setPassword(String password) {
+        mPassword = password;
+    }
+
+
+    /**
+     * Set expiration date to update in Share resource.
+     *
+     * @param expirationDateInMillis    Expiration date to set to the public link.
+     *                                  A negative value clears the current expiration date.
+     *                                  Zero value (start-of-epoch) results in no update done on
+     *                                  the expiration date.
+     */
+    public void setExpirationDate(long expirationDateInMillis) {
+        mExpirationDateInMillis = expirationDateInMillis;
+    }
+
+
+    @Override
+    protected RemoteOperationResult run(OwnCloudClient client) {
+
+        OCShare publicShare = getStorageManager().getFirstShareByPathAndType(
+                mPath,
+                ShareType.PUBLIC_LINK,
+                ""
+        );
+
+        if (publicShare == null) {
+            // TODO try to get remote share before failing?
+            return new RemoteOperationResult(
+                    RemoteOperationResult.ResultCode.SHARE_NOT_FOUND
+            );
+        }
+
+        // Update remote share with password
+        UpdateRemoteShareOperation udpateOp = new UpdateRemoteShareOperation(
+            publicShare.getRemoteId()
+        );
+        udpateOp.setPassword(mPassword);
+        udpateOp.setExpirationDate(mExpirationDateInMillis);
+        RemoteOperationResult result = udpateOp.execute(client);
+
+        if (result.isSuccess()) {
+            // Retrieve updated share / save directly with password? -> no; the password is not be saved
+            RemoteOperation getShareOp = new GetRemoteShareOperation(publicShare.getRemoteId());
+            result = getShareOp.execute(client);
+            if (result.isSuccess()) {
+                OCShare share = (OCShare) result.getData().get(0);
+                updateData(share);
+            }
+        }
+
+        return result;
+    }
+
+    public String getPath() {
+        return mPath;
+    }
+
+    public String getPassword() {
+        return mPassword;
+    }
+
+    private void updateData(OCShare share) {
+        // Update DB with the response
+        share.setPath(mPath);
+        if (mPath.endsWith(FileUtils.PATH_SEPARATOR)) {
+            share.setIsFolder(true);
+        } else {
+            share.setIsFolder(false);
+        }
+
+        getStorageManager().saveShare(share);   // TODO info about having a password? ask to Gonzalo
+
+        // Update OCFile with data from share: ShareByLink  and publicLink
+        // TODO check & remove if not needed
+        OCFile file = getStorageManager().getFileByPath(mPath);
+        if (file != null) {
+            file.setPublicLink(share.getShareLink());
+            file.setShareViaLink(true);
+            getStorageManager().saveFile(file);
+        }
+    }
+
+}
+

+ 37 - 13
src/com/owncloud/android/services/OperationsService.java

@@ -64,9 +64,11 @@ import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareOperation;
+import com.owncloud.android.operations.UpdateShareViaLinkOperation;
 import com.owncloud.android.operations.common.SyncOperation;
 
 import java.io.IOException;
+import java.util.Calendar;
 import java.util.Iterator;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
@@ -88,15 +90,19 @@ public class OperationsService extends Service {
     public static final String EXTRA_RESULT = "RESULT";
     public static final String EXTRA_NEW_PARENT_PATH = "NEW_PARENT_PATH";
     public static final String EXTRA_FILE = "FILE";
-    public static final String EXTRA_PASSWORD_SHARE = "PASSWORD_SHARE";
+    public static final String EXTRA_SHARE_PASSWORD = "SHARE_PASSWORD";
     public static final String EXTRA_SHARE_TYPE = "SHARE_TYPE";
     public static final String EXTRA_SHARE_WITH = "SHARE_WITH";
+    public static final String EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS = "SHARE_EXPIRATION_YEAR";
+    public static final String EXTRA_SHARE_EXPIRATION_MONTH_OF_YEAR = "SHARE_EXPIRATION_MONTH_OF_YEAR";
+    public static final String EXTRA_SHARE_EXPIRATION_DAY_OF_MONTH = "SHARE_EXPIRATION_DAY_OF_MONTH";
 
     public static final String EXTRA_COOKIE = "COOKIE";
 
     public static final String ACTION_CREATE_SHARE_VIA_LINK = "CREATE_SHARE_VIA_LINK";
     public static final String ACTION_CREATE_SHARE_WITH_SHAREE = "CREATE_SHARE_WITH_SHAREE";
     public static final String ACTION_UNSHARE = "UNSHARE";
+    public static final String ACTION_UPDATE_SHARE = "UPDATE_SHARE";
     public static final String ACTION_GET_SERVER_INFO = "GET_SERVER_INFO";
     public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN = "OAUTH2_GET_ACCESS_TOKEN";
     public static final String ACTION_GET_USER_NAME = "GET_USER_NAME";
@@ -553,7 +559,7 @@ public class OperationsService extends Service {
                 String action = operationIntent.getAction();
                 if (action.equals(ACTION_CREATE_SHARE_VIA_LINK)) {  // Create public share via link
                     String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                    String password = operationIntent.getStringExtra(EXTRA_PASSWORD_SHARE);
+                    String password = operationIntent.getStringExtra(EXTRA_SHARE_PASSWORD);
                     Intent sendIntent = operationIntent.getParcelableExtra(EXTRA_SEND_INTENT);
                     if (remotePath.length() > 0) {
                         operation = new CreateShareViaLinkOperation(
@@ -563,17 +569,35 @@ public class OperationsService extends Service {
                         );
                     }
 
-                } else if (action.equals(ACTION_CREATE_SHARE_WITH_SHAREE)) {  // Create private share with user or group
-                        String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
-                        String shareeName = operationIntent.getStringExtra(EXTRA_SHARE_WITH);
-                        ShareType shareType = (ShareType) operationIntent.getSerializableExtra(EXTRA_SHARE_TYPE);
-                        if (remotePath.length() > 0) {
-                            operation = new CreateShareWithShareeOperation(
-                                    remotePath,
-                                    shareeName,
-                                    shareType
-                            );
-                        }
+                } else if (ACTION_UPDATE_SHARE.equals(action)) {
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    if (remotePath.length() > 0) {
+                        operation = new UpdateShareViaLinkOperation(remotePath);
+
+                        String password = operationIntent.getStringExtra(EXTRA_SHARE_PASSWORD);
+                        ((UpdateShareViaLinkOperation)operation).setPassword(password);
+
+                        long expirationDate = operationIntent.getLongExtra(
+                                EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS,
+                                0
+                        );
+                        ((UpdateShareViaLinkOperation)operation).setExpirationDate(
+                                expirationDate
+                        );
+                    }
+
+                } else if (action.equals(ACTION_CREATE_SHARE_WITH_SHAREE)) {
+                    // Create private share with user or group
+                    String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);
+                    String shareeName = operationIntent.getStringExtra(EXTRA_SHARE_WITH);
+                    ShareType shareType = (ShareType) operationIntent.getSerializableExtra(EXTRA_SHARE_TYPE);
+                    if (remotePath.length() > 0) {
+                        operation = new CreateShareWithShareeOperation(
+                                remotePath,
+                                shareeName,
+                                shareType
+                        );
+                    }
 
                 } else if (action.equals(ACTION_UNSHARE)) {  // Unshare file
                     String remotePath = operationIntent.getStringExtra(EXTRA_REMOTE_PATH);

+ 44 - 28
src/com/owncloud/android/ui/activity/FileActivity.java

@@ -67,12 +67,14 @@ import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.operations.CreateShareViaLinkOperation;
 import com.owncloud.android.operations.CreateShareWithShareeOperation;
 import com.owncloud.android.operations.GetSharesForFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareOperation;
+import com.owncloud.android.operations.UpdateShareViaLinkOperation;
 import com.owncloud.android.services.OperationsService;
 import com.owncloud.android.services.OperationsService.OperationsServiceBinder;
 import com.owncloud.android.ui.NavigationDrawerItem;
@@ -93,8 +95,6 @@ public class FileActivity extends AppCompatActivity
 
     public static final String EXTRA_FILE = "com.owncloud.android.ui.activity.FILE";
     public static final String EXTRA_ACCOUNT = "com.owncloud.android.ui.activity.ACCOUNT";
-    public static final String EXTRA_WAITING_TO_PREVIEW =
-            "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW";
     public static final String EXTRA_FROM_NOTIFICATION =
             "com.owncloud.android.ui.activity.FROM_NOTIFICATION";
 
@@ -113,9 +113,13 @@ public class FileActivity extends AppCompatActivity
     /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located.*/
     private Account mAccount;
 
-    /** Main {@link OCFile} handled by the activity.*/
+    /** Capabilites of the server where {@link #mAccount} lives */
+     private OCCapability mCapabilities;
+
+     /** Main {@link OCFile} handled by the activity.*/
     private OCFile mFile;
 
+
     /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud
      * {@link Account} */
     private boolean mRedirectingToSetupAccount = false;
@@ -141,12 +145,12 @@ public class FileActivity extends AppCompatActivity
 
     private OperationsServiceBinder mOperationsServiceBinder = null;
 
+    private boolean mResumed = false;
+
     protected FileDownloaderBinder mDownloaderBinder = null;
     protected FileUploaderBinder mUploaderBinder = null;
     private ServiceConnection mDownloadServiceConnection, mUploadServiceConnection = null;
 
-    private boolean mTryShareAgain = false;
-
     // Navigation Drawer
     protected DrawerLayout mDrawerLayout;
     protected ActionBarDrawerToggle mDrawerToggle;
@@ -161,6 +165,7 @@ public class FileActivity extends AppCompatActivity
     protected NavigationDrawerListAdapter mNavigationDrawerAdapter = null;
 
 
+
     // TODO re-enable when "Accounts" is available in Navigation Drawer
 //    protected boolean mShowAccounts = false;
 
@@ -183,7 +188,6 @@ public class FileActivity extends AppCompatActivity
             mFileOperationsHelper.setOpIdWaitingFor(
                     savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID, Long.MAX_VALUE)
                     );
-            mTryShareAgain = savedInstanceState.getBoolean(KEY_TRY_SHARE_AGAIN);
             if (getSupportActionBar() != null) {
                 getSupportActionBar().setTitle(savedInstanceState.getString(KEY_ACTION_BAR_TITLE));
             }
@@ -255,7 +259,7 @@ public class FileActivity extends AppCompatActivity
     @Override
     protected void onResume() {
         super.onResume();
-
+        mResumed = true;
         if (mOperationsServiceBinder != null) {
             doOnResumeAndBound();
         }
@@ -266,7 +270,7 @@ public class FileActivity extends AppCompatActivity
         if (mOperationsServiceBinder != null) {
             mOperationsServiceBinder.removeOperationListener(this);
         }
-
+        mResumed = false;
         super.onPause();
     }
 
@@ -554,7 +558,6 @@ public class FileActivity extends AppCompatActivity
         outState.putParcelable(FileActivity.EXTRA_FILE, mFile);
         outState.putBoolean(FileActivity.EXTRA_FROM_NOTIFICATION, mFromNotification);
         outState.putLong(KEY_WAITING_FOR_OP_ID, mFileOperationsHelper.getOpIdWaitingFor());
-        outState.putBoolean(KEY_TRY_SHARE_AGAIN, mTryShareAgain);
         if(getSupportActionBar() != null && getSupportActionBar().getTitle() != null) {
             // Null check in case the actionbar is used in ActionBar.NAVIGATION_MODE_LIST
             // since it doesn't have a title then
@@ -598,6 +601,18 @@ public class FileActivity extends AppCompatActivity
         mAccount = account;
     }
 
+
+    /**
+     * Getter for the capabilities of the server where the current OC account lives.
+     *
+     * @return  Capabilities of the server where the current OC account lives. Null if the account is not
+     *          set yet.
+     */
+    public OCCapability getCapabilities() {
+        return mCapabilities;
+    }
+
+
     /**
      * @return Value of mFromNotification: True if the Activity is launched by a notification
      */
@@ -612,14 +627,6 @@ public class FileActivity extends AppCompatActivity
         return mRedirectingToSetupAccount;
     }
 
-    public boolean isTryShareAgain(){
-        return mTryShareAgain;
-    }
-
-    public void setTryShareAgain(boolean tryShareAgain) {
-       mTryShareAgain = tryShareAgain;
-    }
-
     public OperationsServiceBinder getOperationsServiceBinder() {
         return mOperationsServiceBinder;
     }
@@ -676,6 +683,7 @@ public class FileActivity extends AppCompatActivity
     protected void onAccountSet(boolean stateWasRecovered) {
         if (getAccount() != null) {
             mStorageManager = new FileDataStorageManager(getAccount(), getContentResolver());
+            mCapabilities = mStorageManager.getCapability(mAccount.name);
 
         } else {
             Log_OC.wtf(TAG, "onAccountChanged was called with NULL account associated!");
@@ -730,12 +738,12 @@ public class FileActivity extends AppCompatActivity
                         Toast.LENGTH_LONG);
                 t.show();
             }
-            mTryShareAgain = false;
 
         } else if (operation == null ||
                 operation instanceof CreateShareWithShareeOperation ||
                 operation instanceof UnshareOperation ||
-                operation instanceof SynchronizeFolderOperation
+                operation instanceof SynchronizeFolderOperation ||
+                operation instanceof UpdateShareViaLinkOperation
                 ) {
             if (result.isSuccess()) {
                 updateFileFromDB();
@@ -754,10 +762,10 @@ public class FileActivity extends AppCompatActivity
             onSynchronizeFileOperationFinish((SynchronizeFileOperation) operation, result);
 
         } else if (operation instanceof GetSharesForFileOperation) {
-            if (result.isSuccess()) {
+            if (result.isSuccess() || result.getCode() == ResultCode.SHARE_NOT_FOUND) {
                 updateFileFromDB();
 
-            } else if (result.getCode() != ResultCode.SHARE_NOT_FOUND) {
+            } else {
                 Toast t = Toast.makeText(this,
                         ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
                         Toast.LENGTH_LONG);
@@ -781,25 +789,31 @@ public class FileActivity extends AppCompatActivity
     private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
                                                      RemoteOperationResult result) {
         if (result.isSuccess()) {
-            mTryShareAgain = false;
             updateFileFromDB();
 
             Intent sendIntent = operation.getSendIntentWithSubject(this);
-            startActivity(sendIntent);
+            if (sendIntent != null) {
+                startActivity(sendIntent);
+            }
+
         } else {
             // Detect Failure (403) --> needs Password
             if (result.getCode() == ResultCode.SHARE_FORBIDDEN) {
-                if (!isTryShareAgain()) {
+                String password = operation.getPassword();
+                if ((password == null || password.length() == 0) &&
+                    getCapabilities().getFilesSharingPublicEnabled().isUnknown())
+                    {
+                    // Was tried without password, but not sure that it's optional. Try with password.
+                    // Try with password before giving up.
+                    // See also ShareFileFragment#OnShareViaLinkListener
                     SharePasswordDialogFragment dialog =
-                            SharePasswordDialogFragment.newInstance(new OCFile(operation.getPath()),
-                                    operation.getSendIntent());
+                            SharePasswordDialogFragment.newInstance(new OCFile(operation.getPath()), true);
                     dialog.show(getSupportFragmentManager(), DIALOG_SHARE_PASSWORD);
                 } else {
                     Toast t = Toast.makeText(this,
                         ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
                         Toast.LENGTH_LONG);
                     t.show();
-                    mTryShareAgain = false;
                 }
             } else {
                 Toast t = Toast.makeText(this,
@@ -891,7 +905,9 @@ public class FileActivity extends AppCompatActivity
                 /*if (!mOperationsServiceBinder.isPerformingBlockingOperation()) {
                     dismissLoadingDialog();
                 }*/
-                doOnResumeAndBound();
+                if (mResumed) {
+                    doOnResumeAndBound();
+                }
 
             } else {
                 return;

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

@@ -26,7 +26,6 @@ import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AuthenticatorException;
 import android.annotation.TargetApi;
-import android.support.v7.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -50,6 +49,7 @@ import android.support.v4.app.FragmentManager;
 import android.support.v4.app.FragmentTransaction;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.view.GravityCompat;
+import android.support.v7.app.AlertDialog;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -79,14 +79,11 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCo
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.operations.CopyFileOperation;
 import com.owncloud.android.operations.CreateFolderOperation;
-import com.owncloud.android.operations.CreateShareViaLinkOperation;
-import com.owncloud.android.operations.CreateShareWithShareeOperation;
 import com.owncloud.android.operations.MoveFileOperation;
 import com.owncloud.android.operations.RefreshFolderOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.operations.UnshareOperation;
 import com.owncloud.android.services.observer.FileObserverService;
 import com.owncloud.android.syncadapter.FileSyncAdapter;
 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
@@ -1353,15 +1350,6 @@ public class FileDisplayActivity extends HookActivity
         } else if (operation instanceof CreateFolderOperation) {
             onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
 
-        } else if (operation instanceof CreateShareViaLinkOperation ||
-                    operation instanceof CreateShareWithShareeOperation ) {
-
-            refreshShowDetails();
-            refreshListOfFilesFragment();
-
-        } else if (operation instanceof UnshareOperation) {
-            onUnshareLinkOperationFinish((UnshareOperation) operation, result);
-
         } else if (operation instanceof MoveFileOperation) {
             onMoveFileOperationFinish((MoveFileOperation) operation, result);
 
@@ -1371,18 +1359,6 @@ public class FileDisplayActivity extends HookActivity
 
     }
 
-    private void onUnshareLinkOperationFinish(UnshareOperation operation,
-                                              RemoteOperationResult result) {
-        if (result.isSuccess()) {
-            refreshShowDetails();
-            refreshListOfFilesFragment();
-
-        } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) {
-            cleanSecondFragment();
-            refreshListOfFilesFragment();
-        }
-    }
-
     private void refreshShowDetails() {
         FileFragment details = getSecondFragment();
         if (details != null) {

+ 43 - 13
src/com/owncloud/android/ui/activity/ShareActivity.java

@@ -25,11 +25,14 @@ import android.app.SearchManager;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentTransaction;
 
 import com.owncloud.android.R;
 import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.operations.CreateShareViaLinkOperation;
+import com.owncloud.android.operations.GetSharesForFileOperation;
 import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
 
 import com.owncloud.android.lib.common.operations.RemoteOperation;
@@ -37,10 +40,13 @@ import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.resources.shares.OCShare;
 import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.ui.dialog.ShareLinkToDialog;
 import com.owncloud.android.ui.fragment.SearchShareesFragment;
 import com.owncloud.android.ui.fragment.ShareFileFragment;
 import com.owncloud.android.utils.GetShareWithUsersAsyncTask;
 
+import org.apache.http.protocol.HTTP;
+
 
 /**
  * Activity for sharing files
@@ -55,6 +61,8 @@ public class ShareActivity extends FileActivity
     private static final String TAG_SHARE_FRAGMENT = "SHARE_FRAGMENT";
     private static final String TAG_SEARCH_FRAGMENT = "SEARCH_USER_AND_GROUPS_FRAGMENT";
 
+    /** Tag for dialog */
+    private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -78,7 +86,7 @@ public class ShareActivity extends FileActivity
 
         // Load data into the list
         Log_OC.d(TAG, "Refreshing lists on account set");
-        refreshUsersInLists();
+        refreshSharesFromStorageManager();
 
         // Request for a refresh of the data through the server (starts an Async Task)
         refreshUsersOrGroupsListFromServer();
@@ -153,26 +161,48 @@ public class ShareActivity extends FileActivity
     public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
         super.onRemoteOperationFinish(operation, result);
 
-        if (result.isSuccess()) {
-            Log_OC.d(TAG, "Refreshing lists on successful sync");
-            refreshUsersInLists();
+        if (result.isSuccess() ||
+            (operation instanceof GetSharesForFileOperation &&
+                result.getCode() == RemoteOperationResult.ResultCode.SHARE_NOT_FOUND
+            )
+        ) {
+            Log_OC.d(TAG, "Refreshing view on successful operation or finished refresh");
+            refreshSharesFromStorageManager();
+        }
+
+        if (operation instanceof CreateShareViaLinkOperation) {
+            // Send link to the app
+            String link = ((OCShare) (result.getData().get(0))).getShareLink();
+            Log_OC.d(TAG, "Share link = " + link);
+
+            Intent intentToShareLink = new Intent(Intent.ACTION_SEND);
+            intentToShareLink.putExtra(Intent.EXTRA_TEXT, link);
+            intentToShareLink.setType(HTTP.PLAIN_TEXT_TYPE);
+            String[] packagesToExclude = new String[]{getPackageName()};
+            DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude);
+            chooserDialog.show(getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
         }
 
     }
 
-    private void refreshUsersInLists() {
+
+    /**
+     * Updates the view, reading data from {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     */
+    private void refreshSharesFromStorageManager() {
+
         ShareFileFragment shareFileFragment = getShareFileFragment();
-        if (shareFileFragment != null) {          // only if added to the view hierarchy!!
-            if (shareFileFragment.isAdded()) {
-                shareFileFragment.refreshUsersOrGroupsListFromDB();
-            }
+        if (shareFileFragment != null
+                && shareFileFragment.isAdded()) {   // only if added to the view hierarchy!!
+            shareFileFragment.refreshCapabilitiesFromDB();
+            shareFileFragment.refreshUsersOrGroupsListFromDB();
+            shareFileFragment.refreshPublicShareFromDB();
         }
 
         SearchShareesFragment searchShareesFragment = getSearchFragment();
-        if (searchShareesFragment != null) {
-            if (searchShareesFragment.isAdded()) {  // only if added to the view hierarchy!!
-                searchShareesFragment.refreshUsersOrGroupsListFromDB();
-            }
+        if (searchShareesFragment != null &&
+                searchShareesFragment.isAdded()) {  // only if added to the view hierarchy!!
+            searchShareesFragment.refreshUsersOrGroupsListFromDB();
         }
     }
 

+ 138 - 0
src/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java

@@ -0,0 +1,138 @@
+/**
+ *   ownCloud Android client application
+ *
+ *   @author David A. Velasco
+ *   Copyright (C) 2015 ownCloud Inc.
+ *
+ *   This program is free software: you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License version 2,
+ *   as published by the Free Software Foundation.
+ *
+ *   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.ui.dialog;
+
+
+import android.app.DatePickerDialog;
+import android.app.Dialog;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.text.format.DateUtils;
+import android.widget.DatePicker;
+import android.widget.Toast;
+
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.ui.activity.FileActivity;
+
+import java.util.Calendar;
+import java.util.Date;
+
+/**
+ *  Dialog requesting a date after today.
+ */
+public class ExpirationDatePickerDialogFragment
+        extends DialogFragment
+        implements DatePickerDialog.OnDateSetListener {
+
+    /** Tag for FragmentsManager */
+    public static final String DATE_PICKER_DIALOG = "DATE_PICKER_DIALOG";
+
+    /** Parameter constant for {@link OCFile} instance to set the expiration date */
+    private static final String ARG_FILE = "FILE";
+
+    /** Parameter constant for date chosen initially */
+    private static final String ARG_CHOSEN_DATE_IN_MILLIS = "CHOSEN_DATE_IN_MILLIS";
+
+    /** File to bind an expiration date */
+    private OCFile mFile;
+
+    /**
+     *  Factory method to create new instances
+     *
+     *  @param file                 File to bind an expiration date
+     *  @param chosenDateInMillis   Date chosen when the dialog appears
+     *  @return                     New dialog instance
+     */
+    public static ExpirationDatePickerDialogFragment newInstance(OCFile file, long chosenDateInMillis) {
+        Bundle arguments = new Bundle();
+        arguments.putParcelable(ARG_FILE, file);
+        arguments.putLong(ARG_CHOSEN_DATE_IN_MILLIS, chosenDateInMillis);
+
+        ExpirationDatePickerDialogFragment dialog = new ExpirationDatePickerDialogFragment();
+        dialog.setArguments(arguments);
+        return dialog;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return      A new dialog to let the user choose an expiration date that will be bound to a share link.
+     */
+    @Override
+    public Dialog onCreateDialog(Bundle savedInstanceState) {
+        // Load arguments
+        mFile = getArguments().getParcelable(ARG_FILE);
+
+        // Chosen date received as an argument must be later than tomorrow ; default to tomorrow in other case
+        final Calendar chosenDate = Calendar.getInstance();
+        long tomorrowInMillis = chosenDate.getTimeInMillis() + DateUtils.DAY_IN_MILLIS;
+        long chosenDateInMillis = getArguments().getLong(ARG_CHOSEN_DATE_IN_MILLIS);
+        if (chosenDateInMillis > tomorrowInMillis) {
+            chosenDate.setTimeInMillis(chosenDateInMillis);
+        } else {
+            chosenDate.setTimeInMillis(tomorrowInMillis);
+        }
+
+        // Create a new instance of DatePickerDialog
+        DatePickerDialog dialog = new DatePickerDialog(
+                getActivity(),
+                this,
+                chosenDate.get(Calendar.YEAR),
+                chosenDate.get(Calendar.MONTH),
+                chosenDate.get(Calendar.DAY_OF_MONTH)
+        );
+
+        // Prevent days in the past may be chosen
+        DatePicker picker = dialog.getDatePicker();
+        picker.setMinDate(tomorrowInMillis - 1000);
+
+        // Enforce spinners view; ignored by MD-based theme in Android >=5, but calendar is REALLY buggy
+        // in Android < 5, so let's be sure it never appears (in tablets both spinners and calendar are
+        // shown by default)
+        picker.setCalendarViewShown(false);
+
+        return dialog;
+    }
+
+    /**
+     * Called when the user choses an expiration date.
+     *
+     * @param view              View instance where the date was chosen
+     * @param year              Year of the date chosen.
+     * @param monthOfYear       Month of the date chosen [0, 11]
+     * @param dayOfMonth        Day of the date chosen
+     */
+    @Override
+    public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth) {
+
+        Calendar chosenDate = Calendar.getInstance();
+        chosenDate.set(Calendar.YEAR, year);
+        chosenDate.set(Calendar.MONTH, monthOfYear);
+        chosenDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
+        long chosenDateInMillis = chosenDate.getTimeInMillis();
+
+        ((FileActivity)getActivity()).getFileOperationsHelper().setExpirationDateToShareViaLink(
+                mFile,
+                chosenDateInMillis
+        );
+    }
+}

+ 6 - 24
src/com/owncloud/android/ui/dialog/ShareLinkToDialog.java

@@ -44,11 +44,8 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import com.owncloud.android.R;
-import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.ui.activity.ComponentsGetter;
 import com.owncloud.android.ui.activity.CopyToClipboardActivity;
-import com.owncloud.android.ui.activity.FileActivity;
 
 /**
  * Dialog showing a list activities able to resolve a given Intent, 
@@ -61,20 +58,15 @@ public class ShareLinkToDialog  extends DialogFragment {
             ".ARG_INTENT";
     private final static String ARG_PACKAGES_TO_EXCLUDE =  ShareLinkToDialog.class.getSimpleName() +
             ".ARG_PACKAGES_TO_EXCLUDE";
-    private final static String ARG_FILE_TO_SHARE = ShareLinkToDialog.class.getSimpleName() +
-            ".FILE_TO_SHARE";
-    
+
     private ActivityAdapter mAdapter;
-    private OCFile mFile;
     private Intent mIntent;
     
-    public static ShareLinkToDialog newInstance(Intent intent, String[] packagesToExclude,
-                                                OCFile fileToShare) {
+    public static ShareLinkToDialog newInstance(Intent intent, String[] packagesToExclude) {
         ShareLinkToDialog f = new ShareLinkToDialog();
         Bundle args = new Bundle();
         args.putParcelable(ARG_INTENT, intent);
         args.putStringArray(ARG_PACKAGES_TO_EXCLUDE, packagesToExclude);
-        args.putParcelable(ARG_FILE_TO_SHARE, fileToShare);
         f.setArguments(args);
         return f;
     }
@@ -90,8 +82,7 @@ public class ShareLinkToDialog  extends DialogFragment {
         String[] packagesToExclude = getArguments().getStringArray(ARG_PACKAGES_TO_EXCLUDE);
         List<String> packagesToExcludeList = Arrays.asList(packagesToExclude != null ?
                 packagesToExclude : new String[0]);
-        mFile = getArguments().getParcelable(ARG_FILE_TO_SHARE);
-        
+
         PackageManager pm= getActivity().getPackageManager();
         List<ResolveInfo> activities = pm.queryIntentActivities(mIntent,
                 PackageManager.MATCH_DEFAULT_ONLY);
@@ -142,19 +133,10 @@ public class ShareLinkToDialog  extends DialogFragment {
                             ComponentName name=new ComponentName(
                                     actInfo.applicationInfo.packageName, 
                                     actInfo.name);
-                            mIntent.setComponent(name);                               
-
-                            if (sendAction) {
-                                dialog.dismiss();    // explicitly added for Android 2.x devices
-
-                                // Send the file
-                                ((FileActivity)getActivity()).startActivity(mIntent);
+                            mIntent.setComponent(name);
 
-                            } else {
-                                // Create a new share resource
-                                ((ComponentsGetter)getActivity()).getFileOperationsHelper()
-                                    .shareFileWithLinkToApp(mFile, "", mIntent);
-                            }
+                            // Send the file
+                            getActivity().startActivity(mIntent);
                         }
         })
         .create();

+ 18 - 18
src/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java

@@ -21,7 +21,6 @@ package com.owncloud.android.ui.dialog;
 import android.support.v7.app.AlertDialog;
 import android.app.Dialog;
 import android.content.DialogInterface;
-import android.content.Intent;
 import android.os.Bundle;
 import android.support.v4.app.DialogFragment;
 import android.view.LayoutInflater;
@@ -45,25 +44,26 @@ public class SharePasswordDialogFragment extends DialogFragment
         implements DialogInterface.OnClickListener {
 
     private static final String ARG_FILE = "FILE";
-    private static final String ARG_SEND_INTENT = "SEND_INTENT";
+    private static final String ARG_CREATE_SHARE = "CREATE_SHARE";
 
     public static final String PASSWORD_FRAGMENT = "PASSWORD_FRAGMENT";
 
     private OCFile mFile;
-    private Intent mSendIntent;
+    private boolean mCreateShare;
 
     /**
      * Public factory method to create new SharePasswordDialogFragment instances.
      *
-     * @param file
-     * @param sendIntent
-     * @return              Dialog ready to show.
+     * @param   file            OCFile bound to the public share that which password will be set or updated
+     * @param   createShare     When 'true', the public share will be created; when 'false', will be assumed
+     *                          that the public share already exists, and its state will be directly updated.
+     * @return                  Dialog ready to show.
      */
-    public static SharePasswordDialogFragment newInstance(OCFile file, Intent sendIntent) {
+    public static SharePasswordDialogFragment newInstance(OCFile file, boolean createShare) {
         SharePasswordDialogFragment frag = new SharePasswordDialogFragment();
         Bundle args = new Bundle();
         args.putParcelable(ARG_FILE, file);
-        args.putParcelable(ARG_SEND_INTENT, sendIntent);
+        args.putBoolean(ARG_CREATE_SHARE, createShare);
         frag.setArguments(args);
         return frag;
     }
@@ -71,7 +71,7 @@ public class SharePasswordDialogFragment extends DialogFragment
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         mFile = getArguments().getParcelable(ARG_FILE);
-        mSendIntent = getArguments().getParcelable(ARG_SEND_INTENT);
+        mCreateShare = getArguments().getBoolean(ARG_CREATE_SHARE, false);
 
         // Inflate the layout for the dialog
         LayoutInflater inflater = getActivity().getLayoutInflater();
@@ -97,9 +97,6 @@ public class SharePasswordDialogFragment extends DialogFragment
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (which == AlertDialog.BUTTON_POSITIVE) {
-            // Enable the flag "Share again"
-            ((FileActivity) getActivity()).setTryShareAgain(true);
-
             String password =
                     ((TextView)(getDialog().findViewById(R.id.share_password)))
                         .getText().toString();
@@ -112,13 +109,16 @@ public class SharePasswordDialogFragment extends DialogFragment
                 return;
             }
 
-            // Share the file
-            ((FileActivity)getActivity()).getFileOperationsHelper()
-                                    .shareFileWithLinkToApp(mFile, password, mSendIntent);
+            if (mCreateShare) {
+                // Share the file
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        shareFileViaLink(mFile, password);
 
-        } else {
-            // Disable the flag "Share again"
-            ((FileActivity) getActivity()).setTryShareAgain(false);
+            } else {
+                // updat existing link
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        setPasswordToShareViaLink(mFile, password);
+            }
         }
     }
 }

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

@@ -227,18 +227,9 @@ public class FileDetailFragment extends FileFragment implements OnClickListener
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_share_file: {
-                mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
-                return true;
-            }
-            case R.id.action_share_with_users: {
                 mContainerActivity.getFileOperationsHelper().showShareFile(getFile());
                 return true;
             }
-            case R.id.action_unshare_file: {
-                mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
-                return true;
-            }
-
             case R.id.action_open_file_with: {
                 mContainerActivity.getFileOperationsHelper().openFile(getFile());
                 return true;

+ 0 - 8
src/com/owncloud/android/ui/fragment/OCFileListFragment.java

@@ -344,10 +344,6 @@ public class OCFileListFragment extends ExtendedListFragment
         mTargetFile = (OCFile) mAdapter.getItem(filePosition);
         switch (menuId) {
             case R.id.action_share_file: {
-                mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
-                return true;
-            }
-            case R.id.action_share_with_users: {
                 mContainerActivity.getFileOperationsHelper().showShareFile(mTargetFile);
                 return true;
             }
@@ -355,10 +351,6 @@ public class OCFileListFragment extends ExtendedListFragment
                 mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
                 return true;
             }
-            case R.id.action_unshare_file: {
-                mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
-                return true;
-            }
             case R.id.action_rename_file: {
                 RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
                 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);

+ 510 - 14
src/com/owncloud/android/ui/fragment/ShareFileFragment.java

@@ -26,12 +26,17 @@ import android.app.Activity;
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.support.v4.app.Fragment;
+import android.support.v7.widget.AppCompatButton;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.Button;
+import android.widget.CompoundButton;
 import android.widget.ImageView;
+import android.widget.ListAdapter;
 import android.widget.ListView;
+import android.widget.ScrollView;
+import android.widget.Switch;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -41,16 +46,22 @@ import com.owncloud.android.datamodel.OCFile;
 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
 import com.owncloud.android.lib.common.utils.Log_OC;
 import com.owncloud.android.lib.resources.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
+import com.owncloud.android.lib.resources.status.OCCapability;
 import com.owncloud.android.ui.activity.FileActivity;
-import com.owncloud.android.ui.activity.ShareActivity;
 import com.owncloud.android.ui.adapter.ShareUserListAdapter;
+import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
 import com.owncloud.android.utils.DisplayUtils;
 import com.owncloud.android.utils.MimetypeIconUtil;
 
+import java.text.SimpleDateFormat;
+
 import java.util.ArrayList;
+import java.util.Date;
 
 /**
- * Fragment for Sharing a file with sharees (users or groups)
+ * Fragment for Sharing a file with sharees (users or groups) or creating
+ * a public link.
  *
  * A simple {@link Fragment} subclass.
  *
@@ -66,19 +77,48 @@ public class ShareFileFragment extends Fragment
 
     private static final String TAG = ShareFileFragment.class.getSimpleName();
 
-    // the fragment initialization parameters
+    /** The fragment initialization parameters */
     private static final String ARG_FILE = "FILE";
     private static final String ARG_ACCOUNT = "ACCOUNT";
 
-    // Parameters
+//    /** Tag for dialog */
+//    private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
+
+    /** File to share, received as a parameter in construction time */
     private OCFile mFile;
+
+    /** OC account holding the file to share, received as a parameter in construction time */
     private Account mAccount;
 
-    // other members
-    private ArrayList<OCShare> mShares;
-    private ShareUserListAdapter mUserGroupsAdapter = null;
+    /** Reference to parent listener */
     private OnShareFragmentInteractionListener mListener;
 
+    /** List of private shares bound to the file */
+    private ArrayList<OCShare> mPrivateShares;
+
+    /** Capabilities of the server */
+    private OCCapability mCapabilities;
+
+    /** Adapter to show private shares */
+    private ShareUserListAdapter mUserGroupsAdapter = null;
+
+    /** Public share bound to the file */
+    private OCShare mPublicShare;
+
+    /** Listener for changes on switch to share / unshare publicly */
+    private CompoundButton.OnCheckedChangeListener mOnShareViaLinkSwitchCheckedChangeListener;
+
+    /**
+     * Listener for user actions to set, update or clear password on public link
+     */
+    private OnPasswordInteractionListener mOnPasswordInteractionListener = null;
+
+    /**
+     * Listener for user actions to set, update or clear expiration date on public link
+     */
+    private OnExpirationDateInteractionListener mOnExpirationDateInteractionListener = null;
+
+
     /**
      * Public factory method to create new ShareFileFragment instances.
      *
@@ -105,18 +145,22 @@ public class ShareFileFragment extends Fragment
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        Log_OC.d(TAG, "onCreate");
         if (getArguments() != null) {
             mFile = getArguments().getParcelable(ARG_FILE);
             mAccount = getArguments().getParcelable(ARG_ACCOUNT);
         }
     }
 
+
     /**
      * {@inheritDoc}
      */
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
                              Bundle savedInstanceState) {
+        Log_OC.d(TAG, "onCreateView");
+
         // Inflate the layout for this fragment
         View view = inflater.inflate(R.layout.share_file_layout, container, false);
 
@@ -149,7 +193,7 @@ public class ShareFileFragment extends Fragment
         addUserGroupButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                 boolean shareWithUsersEnable = AccountUtils.hasSearchUsersSupport(mAccount);
+                boolean shareWithUsersEnable = AccountUtils.hasSearchUsersSupport(mAccount);
                 if (shareWithUsersEnable) {
                     // Show Search Fragment
                     mListener.showSearchUsersAndGroups();
@@ -160,15 +204,253 @@ public class ShareFileFragment extends Fragment
             }
         });
 
+        // Switch to create public share
+        mOnShareViaLinkSwitchCheckedChangeListener = new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
+            }
+        };
+
+        // Set listener for user actions on switch for sharing/unsharing via link
+        initShareViaLinkListener(view);
+
+        // Set listener for user actions on expiration date
+        initExpirationListener(view);
+
+        // Set listener for user actions on password
+        initPasswordListener(view);
+
         return view;
     }
 
+
+    /**
+     * Binds listener for user actions to create or delete a public share
+     * to the views receiving the user events.
+     *
+     * @param shareView     Root view in the fragment.
+     */
+    private void initShareViaLinkListener(View shareView) {
+        mOnShareViaLinkSwitchCheckedChangeListener = new OnShareViaLinkListener();
+        Switch shareViaLinkSwitch = (Switch) shareView.findViewById(R.id.shareViaLinkSectionSwitch);
+        shareViaLinkSwitch.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
+    }
+
+    /**
+     * Listener for user actions that create or delete a public share.
+     */
+    private class OnShareViaLinkListener
+            implements CompoundButton.OnCheckedChangeListener {
+
+        /**
+         * Called by R.id.shareViaLinkSectionSwitch to create or delete a public link.
+         *
+         * @param switchView    {@link Switch} toggled by the user, R.id.shareViaLinkSectionSwitch
+         * @param isChecked     New switch state.
+         */
+        @Override
+        public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
+            if (!isResumed()) {
+                // very important, setCheched(...) is called automatically during
+                // Fragment recreation on device rotations
+                return;
+            }
+            if (isChecked) {
+                if (mCapabilities != null &&
+                        mCapabilities.getFilesSharingPublicPasswordEnforced().isTrue()) {
+                    // password enforced by server, request to the user before trying to create
+                    ((FileActivity) getActivity()).getFileOperationsHelper().
+                            requestPasswordForShareViaLink(mFile, true);
+
+                } else {
+                    // create without password if not enforced by server or we don't know if enforced;
+                    ((FileActivity) getActivity()).getFileOperationsHelper().
+                            shareFileViaLink(mFile, null);
+
+                    // FileActivtiy#onCreateShareViaLinkOperationFinish still handles the guess of enforcement
+                    // for server in versions previous to OwnCloudVersion#MINIMUM_VERSION_CAPABILITIES_API
+                }
+
+            } else {
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        unshareFileViaLink(mFile);
+            }
+
+            // undo the toggle to grant the view will be correct if any intermediate dialog is cancelled or
+            // the create/delete operation fails
+            switchView.setOnCheckedChangeListener(null);
+            switchView.toggle();
+            switchView.setOnCheckedChangeListener(mOnShareViaLinkSwitchCheckedChangeListener);
+        }
+    }
+
+
+    /**
+     * Binds listener for user actions that start any update on a expiration date
+     * for the public link to the views receiving the user events.
+     *
+     * @param shareView     Root view in the fragment.
+     */
+    private void initExpirationListener(View shareView) {
+        mOnExpirationDateInteractionListener = new OnExpirationDateInteractionListener();
+
+        ((Switch) shareView.findViewById(R.id.shareViaLinkExpirationSwitch)).
+                setOnCheckedChangeListener(mOnExpirationDateInteractionListener);
+
+        shareView.findViewById(R.id.shareViaLinkExpirationLabel).
+                setOnClickListener(mOnExpirationDateInteractionListener);
+
+        shareView.findViewById(R.id.shareViaLinkExpirationValue).
+                setOnClickListener(mOnExpirationDateInteractionListener);
+    }
+
+    /**
+     * Listener for user actions that start any update on the expiration date for the public link.
+     */
+    private class OnExpirationDateInteractionListener
+            implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
+
+        /**
+         * Called by R.id.shareViaLinkExpirationSwitch to set or clear the expiration date.
+         *
+         * @param switchView    {@link Switch} toggled by the user, R.id.shareViaLinkExpirationSwitch
+         * @param isChecked     New switch state.
+         */
+        @Override
+        public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
+            if (!isResumed()) {
+                // very important, setCheched(...) is called automatically during
+                // Fragment recreation on device rotations
+                return;
+            }
+            if (isChecked) {
+                ExpirationDatePickerDialogFragment dialog =
+                        ExpirationDatePickerDialogFragment.newInstance(mFile, -1);
+                dialog.show(
+                        getActivity().getSupportFragmentManager(),
+                        ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
+                );
+
+            } else {
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        setExpirationDateToShareViaLink(mFile, -1);
+            }
+
+            // undo the toggle to grant the view will be correct if the dialog is cancelled
+            switchView.setOnCheckedChangeListener(null);
+            switchView.toggle();
+            switchView.setOnCheckedChangeListener(mOnExpirationDateInteractionListener);
+        }
+
+        /**
+         * Called by R.id.shareViaLinkExpirationLabel or R.id.shareViaLinkExpirationValue
+         * to change the current expiration date.
+         *
+         * @param expirationView      Label or value view touched by the user.
+         */
+        @Override
+        public void onClick(View expirationView) {
+            if (mPublicShare != null && mPublicShare.getExpirationDate() > 0) {
+                long chosenDateInMillis = -1;
+                if (mPublicShare != null) {
+                    chosenDateInMillis = mPublicShare.getExpirationDate();
+                }
+                ExpirationDatePickerDialogFragment dialog =
+                        ExpirationDatePickerDialogFragment.newInstance(
+                                mFile,
+                                chosenDateInMillis
+                        );
+                dialog.show(
+                        getActivity().getSupportFragmentManager(),
+                        ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
+                );
+            }
+        }
+    }
+
+
+    /**
+     * Binds listener for user actions that start any update on a password for the public link
+     * to the views receiving the user events.
+     *
+     * @param shareView     Root view in the fragment.
+     */
+    private void initPasswordListener(View shareView) {
+        mOnPasswordInteractionListener = new OnPasswordInteractionListener();
+
+        ((Switch) shareView.findViewById(R.id.shareViaLinkPasswordSwitch)).
+                setOnCheckedChangeListener(mOnPasswordInteractionListener);
+
+        shareView.findViewById(R.id.shareViaLinkPasswordLabel).
+                setOnClickListener(mOnPasswordInteractionListener);
+
+        shareView.findViewById(R.id.shareViaLinkPasswordValue).
+                setOnClickListener(mOnPasswordInteractionListener);
+    }
+
+
+    /**
+     * Listener for user actions that start any update on a password for the public link.
+     */
+    private class OnPasswordInteractionListener
+            implements CompoundButton.OnCheckedChangeListener, View.OnClickListener {
+
+        /**
+         * Called by R.id.shareViaLinkPasswordSwitch to set or clear the password.
+         *
+         * @param switchView    {@link Switch} toggled by the user, R.id.shareViaLinkPasswordSwitch
+         * @param isChecked     New switch state.
+         */
+        @Override
+        public void onCheckedChanged(CompoundButton switchView, boolean isChecked) {
+            if (!isResumed()) {
+                // very important, setCheched(...) is called automatically during
+                // Fragment recreation on device rotations
+                return;
+            }
+            if (isChecked) {
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        requestPasswordForShareViaLink(mFile, false);
+            } else {
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        setPasswordToShareViaLink(mFile, "");   // "" clears
+            }
+
+            // undo the toggle to grant the view will be correct if the dialog is cancelled
+            switchView.setOnCheckedChangeListener(null);
+            switchView.toggle();
+            switchView.setOnCheckedChangeListener(mOnPasswordInteractionListener);
+        }
+
+        /**
+         * Called by R.id.shareViaLinkPasswordLabel or R.id.shareViaLinkPasswordValue
+         * to change the current password.
+         *
+         * @param passwordView      Label or value view touched by the user.
+         */
+        @Override
+        public void onClick(View passwordView) {
+            if (mPublicShare != null && mPublicShare.isPasswordProtected()) {
+                ((FileActivity) getActivity()).getFileOperationsHelper().
+                        requestPasswordForShareViaLink(mFile, false);
+            }
+        }
+    }
+
+
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
+        Log_OC.d(TAG, "onActivityCreated");
+
+        // Load known capabilities of the server from DB
+        refreshCapabilitiesFromDB();
 
-        // Load data into the list
+        // Load data into the list of private shares
         refreshUsersOrGroupsListFromDB();
+
+        // Load data of public share, if exists
+        refreshPublicShareFromDB();
     }
 
     @Override
@@ -188,8 +470,23 @@ public class ShareFileFragment extends Fragment
         mListener = null;
     }
 
+
+    /**
+     * Get known server capabilities from DB
+     *
+     * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     * instance ready to use. If not ready, does nothing.
+     */
+    public void refreshCapabilitiesFromDB() {
+        if (((FileActivity)mListener).getStorageManager() != null) {
+            mCapabilities = ((FileActivity)mListener).getStorageManager().
+                    getCapability(mAccount.name);
+        }
+    }
+
+
     /**
-     * Get users and groups from the DB to fill in the "share with" list
+     * Get users and groups from the DB to fill in the "share with" list.
      *
      * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
      * instance ready to use. If not ready, does nothing.
@@ -197,7 +494,7 @@ public class ShareFileFragment extends Fragment
     public void refreshUsersOrGroupsListFromDB (){
         if (((FileActivity) mListener).getStorageManager() != null) {
             // Get Users and Groups
-            mShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
+            mPrivateShares = ((FileActivity) mListener).getStorageManager().getSharesWithForAFile(
                     mFile.getRemotePath(),
                     mAccount.name
             );
@@ -213,7 +510,7 @@ public class ShareFileFragment extends Fragment
         mUserGroupsAdapter = new ShareUserListAdapter(
                 getActivity(),
                 R.layout.share_user_item,
-                mShares,
+                mPrivateShares,
                 this
         );
 
@@ -221,15 +518,19 @@ public class ShareFileFragment extends Fragment
         TextView noShares = (TextView) getView().findViewById(R.id.shareNoUsers);
         ListView usersList = (ListView) getView().findViewById(R.id.shareUsersList);
 
-        if (mShares.size() > 0) {
+        if (mPrivateShares.size() > 0) {
             noShares.setVisibility(View.GONE);
             usersList.setVisibility(View.VISIBLE);
             usersList.setAdapter(mUserGroupsAdapter);
-
+            setListViewHeightBasedOnChildren(usersList);
         } else {
             noShares.setVisibility(View.VISIBLE);
             usersList.setVisibility(View.GONE);
         }
+
+        // Set Scroll to initial position
+        ScrollView scrollView = (ScrollView) getView().findViewById(R.id.shareScroll);
+        scrollView.scrollTo(0, 0);
     }
 
     @Override
@@ -240,6 +541,201 @@ public class ShareFileFragment extends Fragment
     }
 
 
+
+    /**
+     * Get public link from the DB to fill in the "Share link" section in the UI.
+     *
+     * Takes into account server capabilities before reading database.
+     *
+     * Depends on the parent Activity provides a {@link com.owncloud.android.datamodel.FileDataStorageManager}
+     * instance ready to use. If not ready, does nothing.
+     */
+    public void refreshPublicShareFromDB() {
+        if (isPublicShareDisabled()) {
+            hidePublicShare();
+
+        } else if (((FileActivity) mListener).getStorageManager() != null) {
+            // Get public share
+            mPublicShare = ((FileActivity) mListener).getStorageManager().getFirstShareByPathAndType(
+                    mFile.getRemotePath(),
+                    ShareType.PUBLIC_LINK,
+                    ""
+            );
+
+            // Update public share section
+            updatePublicShareSection();
+        }
+    }
+
+    /**
+     * @return  'True' when public share is disabled in the server
+     */
+    private boolean isPublicShareDisabled() {
+        return (mCapabilities != null &&
+                mCapabilities.getFilesSharingPublicEnabled().isFalse()
+        );
+    }
+
+    /**
+     * Updates in the UI the section about public share with the information in the current
+     * public share bound to mFile, if any
+     */
+    private void updatePublicShareSection() {
+        if (mPublicShare != null && ShareType.PUBLIC_LINK.equals(mPublicShare.getShareType())) {
+            /// public share bound -> expand section
+            Switch shareViaLinkSwitch = getShareViaLinkSwitch();
+            if (!shareViaLinkSwitch.isChecked()) {
+                // set null listener before setChecked() to prevent infinite loop of calls
+                shareViaLinkSwitch.setOnCheckedChangeListener(null);
+                shareViaLinkSwitch.setChecked(true);
+                shareViaLinkSwitch.setOnCheckedChangeListener(
+                        mOnShareViaLinkSwitchCheckedChangeListener
+                );
+            }
+            getExpirationDateSection().setVisibility(View.VISIBLE);
+            getPasswordSection().setVisibility(View.VISIBLE);
+            // GetLink button
+            AppCompatButton getLinkButton = getGetLinkButton();
+            getLinkButton.setVisibility(View.VISIBLE);
+            getLinkButton.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    //GetLink from the server and show ShareLinkToDialog
+                    ((FileActivity) getActivity()).getFileOperationsHelper().
+                            getFileWithLink(mFile);
+
+                }
+            });
+
+            /// update state of expiration date switch and message depending on expiration date
+            Switch expirationDateSwitch = getExpirationDateSwitch();
+            // set null listener before setChecked() to prevent infinite loop of calls
+            expirationDateSwitch.setOnCheckedChangeListener(null);
+            long expirationDate = mPublicShare.getExpirationDate();
+            if (expirationDate > 0) {
+                if (!expirationDateSwitch.isChecked()) {
+                    expirationDateSwitch.toggle();
+                }
+                String formattedDate =
+                        SimpleDateFormat.getDateInstance().format(
+                                new Date(expirationDate)
+                        );
+                getExpirationDateValue().setText(formattedDate);
+            } else {
+                if (expirationDateSwitch.isChecked()) {
+                    expirationDateSwitch.toggle();
+                }
+                getExpirationDateValue().setText(R.string.empty);
+            }
+            // recover listener
+            expirationDateSwitch.setOnCheckedChangeListener(
+                    mOnExpirationDateInteractionListener
+            );
+
+            /// update state of password switch and message depending on password protection
+            Switch passwordSwitch = getPasswordSwitch();
+            // set null listener before setChecked() to prevent infinite loop of calls
+            passwordSwitch.setOnCheckedChangeListener(null);
+            if (mPublicShare.isPasswordProtected()) {
+                if (!passwordSwitch.isChecked()) {
+                    passwordSwitch.toggle();
+                }
+                getPasswordValue().setVisibility(View.VISIBLE);
+            } else {
+                if (passwordSwitch.isChecked()) {
+                    passwordSwitch.toggle();
+                }
+                getPasswordValue().setVisibility(View.INVISIBLE);
+            }
+            // recover listener
+            passwordSwitch.setOnCheckedChangeListener(
+                    mOnPasswordInteractionListener
+            );
+
+
+        } else {
+            /// no public share -> collapse section
+            Switch shareViaLinkSwitch = getShareViaLinkSwitch();
+            if (shareViaLinkSwitch.isChecked()) {
+                shareViaLinkSwitch.setOnCheckedChangeListener(null);
+                getShareViaLinkSwitch().setChecked(false);
+                shareViaLinkSwitch.setOnCheckedChangeListener(
+                        mOnShareViaLinkSwitchCheckedChangeListener
+                );
+            }
+            getExpirationDateSection().setVisibility(View.GONE);
+            getPasswordSection().setVisibility(View.GONE);
+            getGetLinkButton().setVisibility(View.GONE);
+        }
+    }
+
+
+    /// BEWARE: next methods will failed with NullPointerException if called before onCreateView() finishes
+
+    private Switch getShareViaLinkSwitch() {
+        return (Switch) getView().findViewById(R.id.shareViaLinkSectionSwitch);
+    }
+
+    private View getExpirationDateSection() {
+        return getView().findViewById(R.id.shareViaLinkExpirationSection);
+    }
+
+    private Switch getExpirationDateSwitch() {
+        return (Switch) getView().findViewById(R.id.shareViaLinkExpirationSwitch);
+    }
+
+    private TextView getExpirationDateValue() {
+        return (TextView) getView().findViewById(R.id.shareViaLinkExpirationValue);
+    }
+
+    private View getPasswordSection() {
+        return getView().findViewById(R.id.shareViaLinkPasswordSection);
+    }
+
+    private Switch getPasswordSwitch() {
+        return (Switch) getView().findViewById(R.id.shareViaLinkPasswordSwitch);
+    }
+
+    private TextView getPasswordValue() {
+        return (TextView) getView().findViewById(R.id.shareViaLinkPasswordValue);
+    }
+
+    private AppCompatButton getGetLinkButton() {
+        return (AppCompatButton) getView().findViewById(R.id.shareViaLinkGetLinkButton);
+    }
+
+    /**
+     * Hides all the UI elements related to public share
+     */
+    private void hidePublicShare() {
+        getShareViaLinkSwitch().setVisibility(View.GONE);
+        getExpirationDateSection().setVisibility(View.GONE);
+        getPasswordSection().setVisibility(View.GONE);
+        getGetLinkButton().setVisibility(View.GONE);
+    }
+
+    public static void setListViewHeightBasedOnChildren(ListView listView) {
+        ListAdapter listAdapter = listView.getAdapter();
+        if (listAdapter == null) {
+            return;
+        }
+        int desiredWidth = View.MeasureSpec.makeMeasureSpec(listView.getWidth(), View.MeasureSpec.AT_MOST);
+        int totalHeight = 0;
+        View view = null;
+        for (int i = 0; i < listAdapter.getCount(); i++) {
+            view = listAdapter.getView(i, view, listView);
+            if (i == 0) {
+                view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, ViewGroup.LayoutParams.WRAP_CONTENT));
+            }
+            view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
+            totalHeight += view.getMeasuredHeight();
+        }
+        ViewGroup.LayoutParams params = listView.getLayoutParams();
+        params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
+        listView.setLayoutParams(params);
+        listView.requestLayout();
+    }
+
     /**
      * This interface must be implemented by activities that contain this
      * fragment to allow an interaction in this fragment to be communicated

+ 1 - 38
src/com/owncloud/android/ui/preview/PreviewImageActivity.java

@@ -51,16 +51,11 @@ import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
 import com.owncloud.android.lib.common.utils.Log_OC;
-import com.owncloud.android.operations.CreateShareViaLinkOperation;
-import com.owncloud.android.operations.CreateShareWithShareeOperation;
 import com.owncloud.android.operations.RemoveFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
-import com.owncloud.android.operations.UnshareOperation;
 import com.owncloud.android.ui.activity.FileActivity;
 import com.owncloud.android.ui.activity.FileDisplayActivity;
-import com.owncloud.android.ui.activity.ShareActivity;
 import com.owncloud.android.ui.fragment.FileFragment;
 
 
@@ -230,14 +225,7 @@ public class PreviewImageActivity extends FileActivity implements
     public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
         super.onRemoteOperationFinish(operation, result);
         
-        if (operation instanceof CreateShareViaLinkOperation ||
-                operation instanceof CreateShareWithShareeOperation) {
-            onCreateShareOperationFinish(result);
-
-        } else if (operation instanceof UnshareOperation) {
-            onUnshareLinkOperationFinish((UnshareOperation) operation, result);
-            
-        } else if (operation instanceof RemoveFileOperation) {
+        if (operation instanceof RemoveFileOperation) {
             finish();
         } else if (operation instanceof SynchronizeFileOperation) {
             onSynchronizeFileOperationFinish((SynchronizeFileOperation) operation, result);
@@ -245,31 +233,6 @@ public class PreviewImageActivity extends FileActivity implements
         }
     }
     
-    
-    private void onUnshareLinkOperationFinish(UnshareOperation operation,
-                                              RemoteOperationResult result) {
-        if (result.isSuccess()) {
-            OCFile file = getStorageManager().getFileByPath(getFile().getRemotePath());
-            if (file != null) {
-                setFile(file);
-            }
-            invalidateOptionsMenu();
-        } else if  (result.getCode() == ResultCode.SHARE_NOT_FOUND) {
-            backToDisplayActivity();
-        }
-            
-    }
-    
-    private void onCreateShareOperationFinish(RemoteOperationResult result) {
-        if (result.isSuccess()) {
-            OCFile file = getStorageManager().getFileByPath(getFile().getRemotePath());
-            if (file != null) {
-                setFile(file);
-            }
-            invalidateOptionsMenu();
-        }
-    }
-
     private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation,
                                                   RemoteOperationResult result) {
         if (result.isSuccess()) {

+ 0 - 8
src/com/owncloud/android/ui/preview/PreviewImageFragment.java

@@ -286,17 +286,9 @@ public class PreviewImageFragment extends FileFragment {
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_share_file: {
-                mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
-                return true;
-            }
-            case R.id.action_share_with_users: {
                 mContainerActivity.getFileOperationsHelper().showShareFile(getFile());
                 return true;
             }
-            case R.id.action_unshare_file: {
-                mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
-                return true;
-            }
             case R.id.action_open_file_with: {
                 openFile();
                 return true;

+ 0 - 10
src/com/owncloud/android/ui/preview/PreviewMediaFragment.java

@@ -349,19 +349,9 @@ public class PreviewMediaFragment extends FileFragment implements
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_share_file: {
-                stopPreview(false);
-                mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
-                return true;
-            }
-            case R.id.action_share_with_users: {
                 seeShareFile();
                 return true;
             }
-            case R.id.action_unshare_file: {
-                stopPreview(false);
-                mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
-                return true;
-            }
             case R.id.action_open_file_with: {
                 openFile();
                 return true;

+ 0 - 8
src/com/owncloud/android/ui/preview/PreviewTextFragment.java

@@ -299,17 +299,9 @@ public class PreviewTextFragment extends FileFragment {
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case R.id.action_share_file: {
-                mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
-                return true;
-            }
-            case R.id.action_share_with_users: {
                 mContainerActivity.getFileOperationsHelper().showShareFile(getFile());
                 return true;
             }
-            case R.id.action_unshare_file: {
-                mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
-                return true;
-            }
             case R.id.action_open_file_with: {
                 openFile();
                 return true;

+ 22 - 1
src/com/owncloud/android/utils/ErrorMessageAdapter.java

@@ -27,6 +27,7 @@ import com.owncloud.android.R;
 import com.owncloud.android.lib.common.operations.RemoteOperation;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.resources.shares.UpdateRemoteShareOperation;
 import com.owncloud.android.operations.CopyFileOperation;
 import com.owncloud.android.operations.CreateFolderOperation;
 import com.owncloud.android.operations.CreateShareViaLinkOperation;
@@ -38,6 +39,7 @@ import com.owncloud.android.operations.RenameFileOperation;
 import com.owncloud.android.operations.SynchronizeFileOperation;
 import com.owncloud.android.operations.SynchronizeFolderOperation;
 import com.owncloud.android.operations.UnshareOperation;
+import com.owncloud.android.operations.UpdateShareViaLinkOperation;
 import com.owncloud.android.operations.UploadFileOperation;
 
 import org.apache.commons.httpclient.ConnectTimeoutException;
@@ -189,7 +191,7 @@ public class ErrorMessageAdapter {
             if (result.getData() != null && result.getData().size() > 0) {
                 message = (String) result.getData().get(0);     // share API sends its own error messages
 
-            } else  if (result.getCode() == ResultCode.SHARE_NOT_FOUND)  {
+            } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) {
                 message = res.getString(R.string.unshare_link_file_no_exist);
 
             } else if (result.getCode() == ResultCode.SHARE_FORBIDDEN) {
@@ -201,6 +203,25 @@ public class ErrorMessageAdapter {
                 // Show a Message, operation finished without success
                 message = res.getString(R.string.unshare_link_file_error);
             }
+
+        } else if (operation instanceof UpdateShareViaLinkOperation) {
+
+            if (result.getData() != null && result.getData().size() > 0) {
+                message = (String) result.getData().get(0);     // share API sends its own error messages
+
+            } else if (result.getCode() == ResultCode.SHARE_NOT_FOUND) {
+                message = res.getString(R.string.update_link_file_no_exist);
+
+            } else if (result.getCode() == ResultCode.SHARE_FORBIDDEN) {
+                // Error --> No permissions
+                message = String.format(res.getString(R.string.forbidden_permissions),
+                        res.getString(R.string.update_link_forbidden_permissions));
+
+            } else {    // Generic error
+                // Show a Message, operation finished without success
+                message = res.getString(R.string.update_link_file_error);
+            }
+
         } else if (operation instanceof MoveFileOperation) {
 
             if (result.getCode() == ResultCode.FILE_NOT_FOUND) {