diff --git a/drawable_resources/alert-octagon.svg b/drawable_resources/alert-octagon.svg
new file mode 100644
index 000000000000..47476ee27919
--- /dev/null
+++ b/drawable_resources/alert-octagon.svg
@@ -0,0 +1 @@
+
diff --git a/scripts/lint/lint-results.txt b/scripts/lint/lint-results.txt
index 4d1eaf39a016..eaacc19c5e75 100644
--- a/scripts/lint/lint-results.txt
+++ b/scripts/lint/lint-results.txt
@@ -1,2 +1,2 @@
DO NOT TOUCH; GENERATED BY DRONE
- Lint Report: 104 warnings
+ Lint Report: 103 warnings
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 74bddddb6acc..a197673da878 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -80,6 +80,7 @@
tools:ignore="UnusedAttribute">
@@ -88,6 +89,11 @@
+
+
+
+
diff --git a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
index fc6005590ee7..3291a4ce12ca 100644
--- a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
+++ b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java
@@ -22,7 +22,6 @@
package com.owncloud.android.datamodel;
import android.accounts.Account;
-import android.accounts.AccountManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -773,12 +772,14 @@ public static class AvatarGenerationTask extends AsyncTask(avatarGenerationListener);
mCallContext = callContext;
if (storageManager == null) {
@@ -787,6 +788,9 @@ public AvatarGenerationTask(AvatarGenerationListener avatarGenerationListener, O
mAccount = account;
mResources = resources;
mAvatarRadius = avatarRadius;
+ mUserId = userId;
+ mServerName = serverName;
+ mContext = context;
}
@SuppressFBWarnings("Dm")
@@ -796,20 +800,17 @@ protected Drawable doInBackground(String... params) {
try {
if (mAccount != null) {
- OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount,
- MainApp.getAppContext());
- mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
- getClientFor(ocAccount, MainApp.getAppContext());
+ OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext);
+ mClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, mContext);
}
- mUsername = params[0];
thumbnail = doAvatarInBackground();
} catch(OutOfMemoryError oome) {
Log_OC.e(TAG, "Out of memory");
} catch(Throwable t){
// the app should never break due to a problem with avatars
- Log_OC.e(TAG, "Generation of avatar for " + mUsername + " failed", t);
+ Log_OC.e(TAG, "Generation of avatar for " + mUserId + " failed", t);
}
return thumbnail;
@@ -820,7 +821,7 @@ protected void onPostExecute(Drawable drawable) {
AvatarGenerationListener listener = mAvatarGenerationListener.get();
AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(mCallContext);
- if (this == avatarWorkerTask && listener.shouldCallGeneratedCallback(mUsername, mCallContext)) {
+ if (this == avatarWorkerTask && listener.shouldCallGeneratedCallback(mUserId, mCallContext)) {
listener.avatarGenerated(drawable, mCallContext);
}
}
@@ -830,7 +831,7 @@ protected void onPostExecute(Drawable drawable) {
* Converts size of file icon from dp to pixel
* @return int
*/
- private int getAvatarDimension(){
+ private int getAvatarDimension() {
// Converts dp to pixel
Resources r = MainApp.getAppContext().getResources();
return Math.round(r.getDimension(R.dimen.file_avatar_size));
@@ -839,13 +840,14 @@ private int getAvatarDimension(){
private @Nullable
Drawable doAvatarInBackground() {
Bitmap avatar = null;
- String username = mUsername;
- ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(
- MainApp.getAppContext().getContentResolver());
+ String accountName = mUserId + "@" + mServerName;
+
+ ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider(mContext.getContentResolver());
+
+ String eTag = arbitraryDataProvider.getValue(accountName, ThumbnailsCacheManager.AVATAR);
+ String avatarKey = "a_" + mUserId + "_" + mServerName + "_" + eTag;
- String eTag = arbitraryDataProvider.getValue(mAccount, AVATAR);
- final String imageKey = "a_" + username + "_" + eTag;
int px = getAvatarDimension();
// Download avatar from server
@@ -854,18 +856,13 @@ Drawable doAvatarInBackground() {
if (serverOCVersion.supportsRemoteThumbnails()) {
GetMethod get = null;
try {
- String userId = AccountManager.get(MainApp.getAppContext()).getUserData(mAccount,
- com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID);
-
- if (TextUtils.isEmpty(userId)) {
- userId = AccountUtils.getAccountUsername(username);
- }
-
- String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(userId) + "/" + px;
+ String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(mUserId) + "/" + px;
Log_OC.d("Avatar", "URI: " + uri);
get = new GetMethod(uri);
- if (!eTag.isEmpty()) {
+ // only use eTag if available and corresponding avatar is still there
+ // (might be deleted from cache)
+ if (!eTag.isEmpty() && getBitmapFromDiskCache(avatarKey) != null) {
get.setRequestHeader("If-None-Match", eTag);
}
@@ -877,18 +874,19 @@ Drawable doAvatarInBackground() {
// new avatar
InputStream inputStream = get.getResponseBodyAsStream();
+ String newETag = null;
if (get.getResponseHeader(ETAG) != null) {
- eTag = get.getResponseHeader(ETAG).getValue().replace("\"", "");
- arbitraryDataProvider.storeOrUpdateKeyValue(mAccount.name, AVATAR, eTag);
+ newETag = get.getResponseHeader(ETAG).getValue().replace("\"", "");
+ arbitraryDataProvider.storeOrUpdateKeyValue(accountName, AVATAR, newETag);
}
Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
avatar = ThumbnailUtils.extractThumbnail(bitmap, px, px);
// Add avatar to cache
- if (avatar != null) {
+ if (avatar != null && !TextUtils.isEmpty(newETag)) {
avatar = handlePNG(avatar, px, px);
- String newImageKey = "a_" + username + "_" + eTag;
+ String newImageKey = "a_" + mUserId + "_" + mServerName + "_" + newETag;
addBitmapToCache(newImageKey, avatar);
} else {
return TextDrawable.createAvatar(mAccount.name, mAvatarRadius);
@@ -897,7 +895,7 @@ Drawable doAvatarInBackground() {
case HttpStatus.SC_NOT_MODIFIED:
// old avatar
- avatar = getBitmapFromDiskCache(imageKey);
+ avatar = getBitmapFromDiskCache(avatarKey);
mClient.exhaustResponse(get.getResponseBodyAsStream());
break;
@@ -965,7 +963,7 @@ public static boolean cancelPotentialAvatarWork(Object file, ImageView imageView
final AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(imageView);
if (avatarWorkerTask != null) {
- final Object usernameData = avatarWorkerTask.mUsername;
+ final Object usernameData = avatarWorkerTask.mUserId;
// If usernameData is not yet set or it differs from the new data
if (usernameData == null || !usernameData.equals(file)) {
// Cancel previous task
@@ -984,7 +982,7 @@ public static boolean cancelPotentialAvatarWork(Object file, MenuItem menuItem)
final AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(menuItem);
if (avatarWorkerTask != null) {
- final Object usernameData = avatarWorkerTask.mUsername;
+ final Object usernameData = avatarWorkerTask.mUserId;
// If usernameData is not yet set or it differs from the new data
if (usernameData == null || !usernameData.equals(file)) {
// Cancel previous task
diff --git a/src/main/java/com/owncloud/android/files/FileMenuFilter.java b/src/main/java/com/owncloud/android/files/FileMenuFilter.java
index 0ef99cada9ce..2db72fa645d4 100644
--- a/src/main/java/com/owncloud/android/files/FileMenuFilter.java
+++ b/src/main/java/com/owncloud/android/files/FileMenuFilter.java
@@ -2,7 +2,9 @@
* ownCloud Android client application
*
* @author David A. Velasco
+ * @author Andy Scherzinger
* Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -98,7 +100,6 @@ public FileMenuFilter(OCFile targetFile, Account account, ComponentsGetter cg, C
public void filter(Menu menu, boolean inSingleFileFragment) {
if (mFiles == null || mFiles.size() <= 0) {
hideAll(menu);
-
} else {
List toShow = new ArrayList<>();
List toHide = new ArrayList<>();
@@ -156,166 +157,187 @@ public static void hideMenuItems(MenuItem... items) {
*/
private void filter(List toShow, List toHide, boolean inSingleFileFragment) {
boolean synchronizing = anyFileSynchronizing();
+ OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name);
+ boolean endToEndEncryptionEnabled = capability != null && capability.getEndToEndEncryption().isTrue();
- /// decision is taken for each possible action on a file in the menu
-
- // DOWNLOAD
- if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
- toHide.add(R.id.action_download_file);
+ filterDownload(toShow, toHide, synchronizing);
+ filterRename(toShow, toHide, synchronizing);
+ filterMoveCopy(toShow, toHide, synchronizing);
+ filterRemove(toShow, toHide, synchronizing);
+ filterSelectAll(toShow, toHide, inSingleFileFragment);
+ filterDeselectAll(toShow, toHide, inSingleFileFragment);
+ filterOpenWith(toShow, toHide, synchronizing);
+ filterCancelSync(toShow, toHide, synchronizing);
+ filterSync(toShow, toHide, synchronizing);
+ filterShareFile(toShow, toHide, capability);
+ filterDetails(toShow, toHide);
+ filterKeepAvailableOffline(toShow, toHide, synchronizing);
+ filterDontKeepAvailableOffline(toShow, toHide, synchronizing);
+ filterFavorite(toShow, toHide, synchronizing);
+ filterUnfavorite(toShow, toHide, synchronizing);
+ filterEncrypt(toShow, toHide, endToEndEncryptionEnabled);
+ filterUnsetEncrypted(toShow, toHide, endToEndEncryptionEnabled);
+ filterSetPictureAs(toShow, toHide);
+ }
+ private void filterShareFile(List toShow, List toHide, OCCapability capability) {
+ if (containsEncryptedFile() || (!isShareViaLinkAllowed() && !isShareWithUsersAllowed()) ||
+ !isSingleSelection() || !isShareApiEnabled(capability) || mOverflowMenu) {
+ toHide.add(R.id.action_send_share_file);
} else {
- toShow.add(R.id.action_download_file);
+ toShow.add(R.id.action_send_share_file);
}
+ }
- // RENAME
- if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
- toHide.add(R.id.action_rename_file);
-
+ private void filterDetails(List toShow, List toHide) {
+ if (isSingleSelection()) {
+ toShow.add(R.id.action_see_details);
} else {
- toShow.add(R.id.action_rename_file);
+ toHide.add(R.id.action_see_details);
}
+ }
- // MOVE & COPY
- if (mFiles.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
- toHide.add(R.id.action_move);
- toHide.add(R.id.action_copy);
+ private void filterKeepAvailableOffline(List toShow, List toHide, boolean synchronizing) {
+ if (!allFiles() || synchronizing || allKeptAvailableOffline()) {
+ toHide.add(R.id.action_keep_files_offline);
} else {
- toShow.add(R.id.action_move);
- toShow.add(R.id.action_copy);
+ toShow.add(R.id.action_keep_files_offline);
}
+ }
- // REMOVE
- if (mFiles.isEmpty() || synchronizing || containsEncryptedFolder()) {
- toHide.add(R.id.action_remove_file);
+ private void filterDontKeepAvailableOffline(List toShow, List toHide, boolean synchronizing) {
+ if (!allFiles() || synchronizing || allNotKeptAvailableOffline()) {
+ toHide.add(R.id.action_unset_keep_files_offline);
} else {
- toShow.add(R.id.action_remove_file);
+ toShow.add(R.id.action_unset_keep_files_offline);
}
+ }
- // SELECT ALL
- if (!inSingleFileFragment) {
- // Show only if at least one item isn't selected.
- if (mFiles.size() >= mNumberOfAllFiles || mOverflowMenu) {
- toHide.add(R.id.action_select_all_action_menu);
- } else {
- toShow.add(R.id.action_select_all_action_menu);
- }
+ private void filterFavorite(List toShow, List toHide, boolean synchronizing) {
+ if (mFiles.isEmpty() || synchronizing || allFavorites()) {
+ toHide.add(R.id.action_favorite);
} else {
- // Always hide in single file fragments
- toHide.add(R.id.action_select_all_action_menu);
+ toShow.add(R.id.action_favorite);
}
+ }
- // DESELECT ALL
- if (!inSingleFileFragment) {
- // Show only if at least one item is selected.
- if (mFiles.isEmpty() || mOverflowMenu) {
- toHide.add(R.id.action_deselect_all_action_menu);
- } else {
- toShow.add(R.id.action_deselect_all_action_menu);
- }
- }else {
- // Always hide in single file fragments
- toHide.add(R.id.action_deselect_all_action_menu);
+ private void filterUnfavorite(List toShow, List toHide, boolean synchronizing) {
+ if (mFiles.isEmpty() || synchronizing || allNotFavorites()) {
+ toHide.add(R.id.action_unset_favorite);
+ } else {
+ toShow.add(R.id.action_unset_favorite);
}
+ }
- // OPEN WITH (different to preview!)
- if (!isSingleFile() || !anyFileDown() || synchronizing) {
- toHide.add(R.id.action_open_file_with);
+ private void filterEncrypt(List toShow, List toHide, boolean endToEndEncryptionEnabled) {
+ if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
+ || !endToEndEncryptionEnabled) {
+ toHide.add(R.id.action_encrypted);
} else {
- toShow.add(R.id.action_open_file_with);
+ toShow.add(R.id.action_encrypted);
}
+ }
- // CANCEL SYNCHRONIZATION
- if (mFiles.isEmpty() || !synchronizing) {
- toHide.add(R.id.action_cancel_sync);
+ private void filterUnsetEncrypted(List toShow, List toHide, boolean endToEndEncryptionEnabled) {
+ if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder()
+ || !endToEndEncryptionEnabled) {
+ toHide.add(R.id.action_unset_encrypted);
+ } else {
+ toShow.add(R.id.action_unset_encrypted);
+ }
+ }
+ private void filterSetPictureAs(List toShow, List toHide) {
+ if (isSingleImage() && !MimeTypeUtil.isSVG(mFiles.iterator().next())) {
+ toShow.add(R.id.action_set_as_wallpaper);
} else {
- toShow.add(R.id.action_cancel_sync);
+ toHide.add(R.id.action_set_as_wallpaper);
}
+ }
- // SYNC CONTENTS (BOTH FILE AND FOLDER)
+ private void filterSync(List toShow, List toHide, boolean synchronizing) {
if (mFiles.isEmpty() || (!anyFileDown() && !containsFolder()) || synchronizing) {
toHide.add(R.id.action_sync_file);
-
} else {
toShow.add(R.id.action_sync_file);
}
+ }
- // SHARE FILE
- boolean shareViaLinkAllowed = (mContext != null &&
- mContext.getResources().getBoolean(R.bool.share_via_link_feature));
- boolean shareWithUsersAllowed = (mContext != null &&
- mContext.getResources().getBoolean(R.bool.share_with_users_feature));
-
- OCCapability capability = mComponentsGetter.getStorageManager().getCapability(mAccount.name);
- boolean shareApiEnabled = capability != null &&
- (capability.getFilesSharingApiEnabled().isTrue() ||
- capability.getFilesSharingApiEnabled().isUnknown()
- );
- if (containsEncryptedFile() || (!shareViaLinkAllowed && !shareWithUsersAllowed) ||
- !isSingleSelection() ||
- !shareApiEnabled || mOverflowMenu) {
- toHide.add(R.id.action_send_share_file);
- } else {
- toShow.add(R.id.action_send_share_file);
- }
-
- // SEE DETAILS
- if (!isSingleFile()) {
- toHide.add(R.id.action_see_details);
+ private void filterCancelSync(List toShow, List toHide, boolean synchronizing) {
+ if (mFiles.isEmpty() || !synchronizing) {
+ toHide.add(R.id.action_cancel_sync);
} else {
- toShow.add(R.id.action_see_details);
+ toShow.add(R.id.action_cancel_sync);
}
+ }
- // Kept available offline
- if (!allFiles() || synchronizing || allKeptAvailableOffline()) {
- toHide.add(R.id.action_keep_files_offline);
+ private void filterOpenWith(List toShow, List toHide, boolean synchronizing) {
+ if (!isSingleFile() || !anyFileDown() || synchronizing) {
+ toHide.add(R.id.action_open_file_with);
} else {
- toShow.add(R.id.action_keep_files_offline);
+ toShow.add(R.id.action_open_file_with);
}
+ }
- // Not kept available offline
- if (!allFiles() || synchronizing || allNotKeptAvailableOffline()) {
- toHide.add(R.id.action_unset_keep_files_offline);
+ private void filterDeselectAll(List toShow, List toHide, boolean inSingleFileFragment) {
+ if (inSingleFileFragment) {
+ // Always hide in single file fragments
+ toHide.add(R.id.action_deselect_all_action_menu);
} else {
- toShow.add(R.id.action_unset_keep_files_offline);
+ // Show only if at least one item is selected.
+ if (mFiles.isEmpty() || mOverflowMenu) {
+ toHide.add(R.id.action_deselect_all_action_menu);
+ } else {
+ toShow.add(R.id.action_deselect_all_action_menu);
+ }
}
+ }
- // Favorite
- if (mFiles.isEmpty() || synchronizing || allFavorites()) {
- toHide.add(R.id.action_favorite);
+ private void filterSelectAll(List toShow, List toHide, boolean inSingleFileFragment) {
+ if (!inSingleFileFragment) {
+ // Show only if at least one item isn't selected.
+ if (mFiles.size() >= mNumberOfAllFiles || mOverflowMenu) {
+ toHide.add(R.id.action_select_all_action_menu);
+ } else {
+ toShow.add(R.id.action_select_all_action_menu);
+ }
} else {
- toShow.add(R.id.action_favorite);
+ // Always hide in single file fragments
+ toHide.add(R.id.action_select_all_action_menu);
}
+ }
- // Unfavorite
- if (mFiles.isEmpty() || synchronizing || allNotFavorites()) {
- toHide.add(R.id.action_unset_favorite);
+ private void filterRemove(List toShow, List toHide, boolean synchronizing) {
+ if (mFiles.isEmpty() || synchronizing || containsEncryptedFolder()) {
+ toHide.add(R.id.action_remove_file);
} else {
- toShow.add(R.id.action_unset_favorite);
+ toShow.add(R.id.action_remove_file);
}
+ }
- // Encryption
- boolean endToEndEncryptionEnabled = capability != null && capability.getEndToEndEncryption().isTrue();
- if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || isEncryptedFolder()
- || !endToEndEncryptionEnabled) {
- toHide.add(R.id.action_encrypted);
+ private void filterMoveCopy(List toShow, List toHide, boolean synchronizing) {
+ if (mFiles.isEmpty() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
+ toHide.add(R.id.action_move);
+ toHide.add(R.id.action_copy);
} else {
- toShow.add(R.id.action_encrypted);
+ toShow.add(R.id.action_move);
+ toShow.add(R.id.action_copy);
}
+ }
- // Un-encrypt
- if (mFiles.isEmpty() || !isSingleSelection() || isSingleFile() || !isEncryptedFolder()
- || !endToEndEncryptionEnabled) {
- toHide.add(R.id.action_unset_encrypted);
+ private void filterRename(List toShow, List toHide, boolean synchronizing) {
+ if (!isSingleSelection() || synchronizing || containsEncryptedFile() || containsEncryptedFolder()) {
+ toHide.add(R.id.action_rename_file);
} else {
- toShow.add(R.id.action_unset_encrypted);
+ toShow.add(R.id.action_rename_file);
}
+ }
- // SET PICTURE AS
- if (isSingleImage() && !MimeTypeUtil.isSVG(mFiles.iterator().next())) {
- toShow.add(R.id.action_set_as_wallpaper);
+ private void filterDownload(List toShow, List toHide, boolean synchronizing) {
+ if (mFiles.isEmpty() || containsFolder() || anyFileDown() || synchronizing) {
+ toHide.add(R.id.action_download_file);
} else {
- toHide.add(R.id.action_set_as_wallpaper);
+ toShow.add(R.id.action_download_file);
}
}
@@ -364,6 +386,23 @@ private boolean anyFileUploading(FileUploaderBinder uploaderBinder) {
return uploading;
}
+ private boolean isShareApiEnabled(OCCapability capability) {
+ return capability != null &&
+ (capability.getFilesSharingApiEnabled().isTrue() ||
+ capability.getFilesSharingApiEnabled().isUnknown()
+ );
+ }
+
+ private boolean isShareWithUsersAllowed() {
+ return mContext != null &&
+ mContext.getResources().getBoolean(R.bool.share_with_users_feature);
+ }
+
+ private boolean isShareViaLinkAllowed() {
+ return mContext != null &&
+ mContext.getResources().getBoolean(R.bool.share_via_link_feature);
+ }
+
private boolean isSingleSelection() {
return mFiles.size() == SINGLE_SELECT_ITEMS;
}
diff --git a/src/main/java/com/owncloud/android/operations/UnshareOperation.java b/src/main/java/com/owncloud/android/operations/UnshareOperation.java
index 585ff6e86578..a30e650f8b73 100644
--- a/src/main/java/com/owncloud/android/operations/UnshareOperation.java
+++ b/src/main/java/com/owncloud/android/operations/UnshareOperation.java
@@ -1,8 +1,10 @@
-/**
+/*
* ownCloud Android client application
*
* @author masensio
+ * @author Andy Scherzinger
* Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- *
*/
package com.owncloud.android.operations;
@@ -109,4 +110,8 @@ private boolean existsFile(OwnCloudClient client, String remotePath){
return result.isSuccess();
}
+ public ShareType getShareType() {
+ return mShareType;
+ }
+
}
diff --git a/src/main/java/com/owncloud/android/operations/UpdateSharePermissionsOperation.java b/src/main/java/com/owncloud/android/operations/UpdateSharePermissionsOperation.java
index c798d6d54db6..a1f3b0a52948 100644
--- a/src/main/java/com/owncloud/android/operations/UpdateSharePermissionsOperation.java
+++ b/src/main/java/com/owncloud/android/operations/UpdateSharePermissionsOperation.java
@@ -1,8 +1,10 @@
-/**
+/*
* ownCloud Android client application
*
* @author David A. Velasco
+ * @author Andy Scherzinger
* Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- *
*/
package com.owncloud.android.operations;
@@ -31,26 +32,26 @@
/**
- * Updates an existing private share for a given file
+ * Updates an existing private share for a given file.
*/
-
public class UpdateSharePermissionsOperation extends SyncOperation {
private long mShareId;
private int mPermissions;
+ private long mExpirationDateInMillis;
private String mPath;
/**
* Constructor
*
- * @param shareId Private {@link OCShare} to update. Mandatory argument
+ * @param shareId Private {@link OCShare} to update. Mandatory argument
*/
public UpdateSharePermissionsOperation(long shareId) {
mShareId = shareId;
mPermissions = -1;
+ mExpirationDateInMillis = 0L;
}
-
/**
* Set permissions to update in private share.
*
@@ -61,6 +62,17 @@ public void setPermissions(int permissions) {
mPermissions = permissions;
}
+ /**
+ * Set expiration date to update private share.
+ *
+ * @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) {
@@ -69,18 +81,15 @@ protected RemoteOperationResult run(OwnCloudClient client) {
if (share == null) {
// TODO try to get remote share before failing?
- return new RemoteOperationResult(
- RemoteOperationResult.ResultCode.SHARE_NOT_FOUND
- );
+ return new RemoteOperationResult(RemoteOperationResult.ResultCode.SHARE_NOT_FOUND);
}
mPath = share.getPath();
// Update remote share with password
- UpdateRemoteShareOperation updateOp = new UpdateRemoteShareOperation(
- share.getRemoteId()
- );
+ UpdateRemoteShareOperation updateOp = new UpdateRemoteShareOperation(share.getRemoteId());
updateOp.setPermissions(mPermissions);
+ updateOp.setExpirationDate(mExpirationDateInMillis);
RemoteOperationResult result = updateOp.execute(client);
if (result.isSuccess()) {
diff --git a/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java b/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
index 802163068126..586a11e451a9 100644
--- a/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
+++ b/src/main/java/com/owncloud/android/providers/UsersAndGroupsSearchProvider.java
@@ -161,7 +161,7 @@ private Cursor searchForUsersOrGroups(Uri uri) {
userQuery, REQUESTED_PAGE, RESULTS_PER_PAGE
);
RemoteOperationResult result = searchRequest.execute(account, getContext());
- List names = new ArrayList();
+ List names = new ArrayList<>();
if (result.isSuccess()) {
for (Object o : result.getData()) {
// Get JSonObjects from response
diff --git a/src/main/java/com/owncloud/android/services/OperationsService.java b/src/main/java/com/owncloud/android/services/OperationsService.java
index e4fefed7562a..cbe3414a86ac 100644
--- a/src/main/java/com/owncloud/android/services/OperationsService.java
+++ b/src/main/java/com/owncloud/android/services/OperationsService.java
@@ -1,7 +1,11 @@
-/**
+/*
* ownCloud Android client application
*
+ * @author masensio
+ * @author David A. Velasco
+ * @author Andy Scherzinger
* Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -14,7 +18,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- *
*/
package com.owncloud.android.services;
@@ -583,8 +586,10 @@ private void nextOperation() {
} else if (shareId > 0) {
operation = new UpdateSharePermissionsOperation(shareId);
- int permissions = operationIntent.getIntExtra(EXTRA_SHARE_PERMISSIONS, 1);
+ int permissions = operationIntent.getIntExtra(EXTRA_SHARE_PERMISSIONS, -1);
((UpdateSharePermissionsOperation)operation).setPermissions(permissions);
+ long expirationDateInMillis = operationIntent.getLongExtra(EXTRA_SHARE_EXPIRATION_DATE_IN_MILLIS, 0L);
+ ((UpdateSharePermissionsOperation)operation).setExpirationDate(expirationDateInMillis);
}
} else if (action.equals(ACTION_CREATE_SHARE_WITH_SHAREE)) {
diff --git a/src/main/java/com/owncloud/android/ui/TextDrawable.java b/src/main/java/com/owncloud/android/ui/TextDrawable.java
index 5e6fb6e49a1b..0b779e2c136f 100644
--- a/src/main/java/com/owncloud/android/ui/TextDrawable.java
+++ b/src/main/java/com/owncloud/android/ui/TextDrawable.java
@@ -1,19 +1,20 @@
-/**
+/*
* ownCloud Android client application
*
* @author Andy Scherzinger
* @author Tobias Kaminsiky
* Copyright (C) 2016 ownCloud Inc.
- *
+ * Copyright (C) 2018 Andy Scherzinger
+ *
* 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 .
*/
@@ -103,6 +104,23 @@ public static TextDrawable createAvatar(String accountName, float radiusInDp) th
return createNamedAvatar(username, radiusInDp);
}
+ /**
+ * creates an avatar in form of a TextDrawable with the first letter of the account name in a circle with the
+ * given radius.
+ *
+ * @param userId userId to use
+ * @param radiusInDp the circle's radius
+ * @return the avatar as a TextDrawable
+ * @throws UnsupportedEncodingException if the charset is not supported when calculating the color values
+ * @throws NoSuchAlgorithmException if the specified algorithm is not available when calculating the color values
+ */
+ @NonNull
+ @NextcloudServer(max = 12)
+ public static TextDrawable createAvatarByUserId(String userId, float radiusInDp) throws
+ UnsupportedEncodingException, NoSuchAlgorithmException {
+ return createNamedAvatar(userId, radiusInDp);
+ }
+
/**
* creates an avatar in form of a TextDrawable with the first letter of a name in a circle with the
* given radius.
@@ -130,7 +148,7 @@ public static TextDrawable createNamedAvatar(String name, float radiusInDp) thro
* @param canvas The canvas to draw into
*/
@Override
- public void draw(Canvas canvas) {
+ public void draw(@NonNull Canvas canvas) {
canvas.drawCircle(mRadius, mRadius, mRadius, mBackground);
canvas.drawText(mText, mRadius, mRadius - ((mTextPaint.descent() + mTextPaint.ascent()) / 2), mTextPaint);
}
diff --git a/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java b/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java
index e11ab3252cf7..0959ad828735 100644
--- a/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ContactsPreferenceActivity.java
@@ -51,7 +51,6 @@
/**
* This activity shows all settings for contact backup/restore
*/
-
public class ContactsPreferenceActivity extends FileActivity implements FileFragment.ContainerActivity {
public static final String TAG = ContactsPreferenceActivity.class.getSimpleName();
@@ -171,6 +170,11 @@ public void showDetails(OCFile file) {
// not needed
}
+ @Override
+ public void showDetails(OCFile file, int activeTab) {
+ // not needed
+ }
+
@Override
public void onBrowsedDownTo(OCFile folder) {
// not needed
diff --git a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
index a99959452206..f13f509628de 100644
--- a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java
@@ -648,8 +648,8 @@ public void updateAccountList() {
View accountEndView = findNavigationViewChildById(R.id.drawer_account_end);
accountEndView.setTag(mAvatars[1].name);
- DisplayUtils.setAvatar(mAvatars[1], this,
- mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), accountEndView);
+ DisplayUtils.setAvatar(mAvatars[1], this, mOtherAccountAvatarRadiusDimension, getResources(),
+ getStorageManager(), accountEndView, this);
mAccountEndAccountAvatar.setVisibility(View.VISIBLE);
} else {
mAccountEndAccountAvatar.setVisibility(View.GONE);
@@ -660,8 +660,8 @@ public void updateAccountList() {
View accountMiddleView = findNavigationViewChildById(R.id.drawer_account_middle);
accountMiddleView.setTag(mAvatars[2].name);
- DisplayUtils.setAvatar(mAvatars[2], this,
- mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), accountMiddleView);
+ DisplayUtils.setAvatar(mAvatars[2], this, mOtherAccountAvatarRadiusDimension, getResources(),
+ getStorageManager(), accountMiddleView, this);
mAccountMiddleAccountAvatar.setVisibility(View.VISIBLE);
} else {
mAccountMiddleAccountAvatar.setVisibility(View.GONE);
@@ -695,7 +695,7 @@ private void repopulateAccountList(ArrayList accounts) {
account.name)
.setIcon(TextDrawable.createAvatar(account.name, mMenuAccountAvatarRadiusDimension));
DisplayUtils.setAvatar(account, this, mMenuAccountAvatarRadiusDimension, getResources(),
- getStorageManager(), accountMenuItem);
+ getStorageManager(), accountMenuItem, this);
}
} catch (Exception e) {
Log_OC.e(TAG, "Error calculating RGB value for account menu item.", e);
@@ -761,7 +761,7 @@ protected void setAccountInDrawer(Account account) {
currentAccountView.setTag(account.name);
DisplayUtils.setAvatar(account, this, mCurrentAccountAvatarRadiusDimension, getResources(),
- getStorageManager(), currentAccountView);
+ getStorageManager(), currentAccountView, this);
// check and show quota info if available
getAndDisplayUserQuota();
diff --git a/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
index 4495bc201e09..d150f08e1260 100644
--- a/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/FileActivity.java
@@ -553,6 +553,7 @@ public FileUploaderBinder getFileUploaderBinder() {
public void restart() {
Intent i = new Intent(this, FileDisplayActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ i.setAction(FileDisplayActivity.RESTART);
startActivity(i);
fetchExternalLinks(false);
diff --git a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
index f532bf101045..1bd19493965e 100644
--- a/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.java
@@ -3,8 +3,10 @@
*
* @author Bartek Przybylski
* @author David A. Velasco
+ * @author Andy Scherzinger
* Copyright (C) 2011 Bartek Przybylski
* Copyright (C) 2016 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -26,6 +28,7 @@
import android.accounts.AuthenticatorException;
import android.annotation.TargetApi;
import android.app.Activity;
+import android.app.SearchManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -38,13 +41,16 @@
import android.content.SyncRequest;
import android.content.pm.PackageManager;
import android.content.res.Resources.NotFoundException;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcelable;
import android.support.annotation.NonNull;
+import android.support.annotation.StringRes;
import android.support.design.widget.BottomNavigationView;
import android.support.design.widget.Snackbar;
+import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
@@ -71,22 +77,33 @@
import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
import com.owncloud.android.files.services.FileUploader;
import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.accounts.AccountUtils;
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.shares.OCShare;
+import com.owncloud.android.lib.resources.shares.ShareType;
import com.owncloud.android.media.MediaService;
import com.owncloud.android.media.MediaServiceBinder;
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.operations.UpdateSharePermissionsOperation;
+import com.owncloud.android.operations.UpdateShareViaLinkOperation;
import com.owncloud.android.operations.UploadFileOperation;
+import com.owncloud.android.providers.UsersAndGroupsSearchProvider;
import com.owncloud.android.syncadapter.FileSyncAdapter;
import com.owncloud.android.ui.dialog.SendShareDialog;
+import com.owncloud.android.ui.dialog.ShareLinkToDialog;
import com.owncloud.android.ui.dialog.SortingOrderDialogFragment;
import com.owncloud.android.ui.events.SyncEventFinished;
import com.owncloud.android.ui.events.TokenPushEvent;
@@ -131,6 +148,8 @@ public class FileDisplayActivity extends HookActivity
implements FileFragment.ContainerActivity,
OnEnforceableRefreshListener, SortingOrderDialogFragment.OnSortingOrderListener,
SendShareDialog.SendShareDialogDownloader {
+
+ public static final String RESTART = "RESTART";
private SyncBroadcastReceiver mSyncBroadcastReceiver;
private UploadFinishReceiver mUploadFinishReceiver;
@@ -141,6 +160,9 @@ public class FileDisplayActivity extends HookActivity
private View mLeftFragmentContainer;
private View mRightFragmentContainer;
+ private static final String TAG_PUBLIC_LINK = "PUBLIC_LINK";
+ private static final String FTAG_CHOOSER_DIALOG = "CHOOSER_DIALOG";
+
private static final String KEY_WAITING_TO_PREVIEW = "WAITING_TO_PREVIEW";
private static final String KEY_SYNC_IN_PROGRESS = "SYNC_IN_PROGRESS";
private static final String KEY_WAITING_TO_SEND = "WAITING_TO_SEND";
@@ -484,6 +506,65 @@ protected void onNewIntent(Intent intent) {
if (intent.getAction() != null && intent.getAction().equalsIgnoreCase(ACTION_DETAILS)) {
setIntent(intent);
setFile(intent.getParcelableExtra(EXTRA_FILE));
+ } else if (RESTART.equals(intent.getAction())) {
+ finish();
+ startActivity(intent);
+ } else // Verify the action and get the query
+ if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
+ String query = intent.getStringExtra(SearchManager.QUERY);
+ Log_OC.w(TAG, "Ignored Intent requesting to query for " + query);
+
+ } else if (UsersAndGroupsSearchProvider.ACTION_SHARE_WITH.equals(intent.getAction())) {
+ Uri data = intent.getData();
+ String dataString = intent.getDataString();
+ String shareWith = dataString.substring(dataString.lastIndexOf('/') + 1);
+
+ ArrayList shareeNames = new ArrayList<>();
+ for (OCShare share : getStorageManager().getSharesWithForAFile(getFile().getRemotePath(), getAccount().name)) {
+ shareeNames.add(share.getShareWith());
+ }
+
+ if (!shareeNames.contains(shareWith)) {
+ doShareWith(shareWith, data.getAuthority());
+ }
+
+ } else {
+ Log_OC.e(TAG, "Unexpected intent " + intent.toString());
+ }
+ }
+
+ private void doShareWith(String shareeName, String dataAuthority) {
+
+ ShareType shareType = UsersAndGroupsSearchProvider.getShareType(dataAuthority);
+
+ getFileOperationsHelper().shareFileWithSharee(
+ getFile(),
+ shareeName,
+ shareType,
+ getAppropiatePermissions(shareType)
+ );
+ }
+
+ private int getAppropiatePermissions(ShareType shareType) {
+
+ // check if the Share is FEDERATED
+ boolean isFederated = ShareType.FEDERATED.equals(shareType);
+
+ if (getFile().isSharedWithMe()) {
+ return OCShare.READ_PERMISSION_FLAG; // minimum permissions
+
+ } else if (isFederated) {
+ if (com.owncloud.android.authentication.AccountUtils
+ .getServerVersion(getAccount()).isNotReshareableFederatedSupported()) {
+ return (getFile().isFolder() ? OCShare.FEDERATED_PERMISSIONS_FOR_FOLDER_AFTER_OC9 :
+ OCShare.FEDERATED_PERMISSIONS_FOR_FILE_AFTER_OC9);
+ } else {
+ return (getFile().isFolder() ? OCShare.FEDERATED_PERMISSIONS_FOR_FOLDER_UP_TO_OC9 :
+ OCShare.FEDERATED_PERMISSIONS_FOR_FILE_UP_TO_OC9);
+ }
+ } else {
+ return (getFile().isFolder() ? OCShare.MAXIMUM_PERMISSIONS_FOR_FOLDER :
+ OCShare.MAXIMUM_PERMISSIONS_FOR_FILE);
}
}
@@ -1488,7 +1569,18 @@ public void onBrowsedDownTo(OCFile directory) {
*/
@Override
public void showDetails(OCFile file) {
- Fragment detailFragment = FileDetailFragment.newInstance(file, getAccount());
+ showDetails(file, 0);
+ }
+
+ /**
+ * Shows the information of the {@link OCFile} received as a
+ * parameter in the second fragment.
+ *
+ * @param file {@link OCFile} whose details will be shown
+ * @param activeTab the active tab in the details view
+ */
+ public void showDetails(OCFile file, int activeTab) {
+ Fragment detailFragment = FileDetailFragment.newInstance(file, getAccount(), activeTab);
setSecondFragment(detailFragment);
updateFragmentsVisibility(true);
updateActionBarTitleAndHomeButton(file);
@@ -1614,23 +1706,27 @@ public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationRe
if (operation instanceof RemoveFileOperation) {
onRemoveFileOperationFinish((RemoveFileOperation) operation, result);
-
} else if (operation instanceof RenameFileOperation) {
onRenameFileOperationFinish((RenameFileOperation) operation, result);
-
} else if (operation instanceof SynchronizeFileOperation) {
onSynchronizeFileOperationFinish((SynchronizeFileOperation) operation, result);
-
} else if (operation instanceof CreateFolderOperation) {
onCreateFolderOperationFinish((CreateFolderOperation) operation, result);
-
} else if (operation instanceof MoveFileOperation) {
onMoveFileOperationFinish((MoveFileOperation) operation, result);
-
} else if (operation instanceof CopyFileOperation) {
onCopyFileOperationFinish((CopyFileOperation) operation, result);
+ } else if (operation instanceof CreateShareViaLinkOperation) {
+ onCreateShareViaLinkOperationFinish((CreateShareViaLinkOperation) operation, result);
+ } else if (operation instanceof CreateShareWithShareeOperation) {
+ onUpdateShareInformation(result, R.string.sharee_add_failed);
+ } else if (operation instanceof UpdateShareViaLinkOperation) {
+ onUpdateShareInformation(result, R.string.updating_share_failed);
+ } else if (operation instanceof UpdateSharePermissionsOperation) {
+ onUpdateShareInformation(result, R.string.updating_share_failed);
+ } else if (operation instanceof UnshareOperation) {
+ onUpdateShareInformation(result, R.string.unsharing_failed);
}
-
}
private void refreshShowDetails() {
@@ -1723,6 +1819,117 @@ private void onMoveFileOperationFinish(MoveFileOperation operation,
}
}
+ private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation operation,
+ RemoteOperationResult result) {
+ FileDetailFragment fileDetailFragment = getShareFileFragment();
+ if (result.isSuccess()) {
+ updateFileFromDB();
+
+ // Create dialog to allow the user choose an app to send the link
+ Intent intentToShareLink = new Intent(Intent.ACTION_SEND);
+
+ // if share to user and share via link multiple ocshares are returned,
+ // therefore filtering for public_link
+ String link = "";
+ for (Object object : result.getData()) {
+ OCShare shareLink = (OCShare) object;
+ if (TAG_PUBLIC_LINK.equalsIgnoreCase(shareLink.getShareType().name())) {
+ link = shareLink.getShareLink();
+ break;
+ }
+ }
+
+ intentToShareLink.putExtra(Intent.EXTRA_TEXT, link);
+ intentToShareLink.setType("text/plain");
+
+ String username;
+ try {
+ OwnCloudAccount oca = new OwnCloudAccount(getAccount(), this);
+ if (oca.getDisplayName() != null && !oca.getDisplayName().isEmpty()) {
+ username = oca.getDisplayName();
+ } else {
+ username = AccountUtils.getUsernameForAccount(getAccount());
+ }
+ } catch (Exception e) {
+ username = AccountUtils.getUsernameForAccount(getAccount());
+ }
+
+ if (username != null) {
+ intentToShareLink.putExtra(
+ Intent.EXTRA_SUBJECT,
+ getString(
+ R.string.subject_user_shared_with_you,
+ username,
+ getFile().getFileName()
+ )
+ );
+ } else {
+ intentToShareLink.putExtra(
+ Intent.EXTRA_SUBJECT,
+ getString(
+ R.string.subject_shared_with_you,
+ getFile().getFileName()
+ )
+ );
+ }
+
+ String[] packagesToExclude = new String[]{getPackageName()};
+ DialogFragment chooserDialog = ShareLinkToDialog.newInstance(intentToShareLink, packagesToExclude);
+ chooserDialog.show(getSupportFragmentManager(), FTAG_CHOOSER_DIALOG);
+
+ fileDetailFragment.getFileDetailSharingFragment().refreshPublicShareFromDB();
+ refreshListOfFilesFragment(false);
+ } else {
+ // Detect Failure (403) --> maybe needs password
+ String password = operation.getPassword();
+ if (result.getCode() == RemoteOperationResult.ResultCode.SHARE_FORBIDDEN &&
+ (password == null || password.length() == 0) &&
+ getCapabilities().getFilesSharingPublicEnabled().isUnknown()) {
+ // Was tried without password, but not sure that it's optional.
+
+ // Try with password before giving up; see also ShareFileFragment#OnShareViaLinkListener
+ if (fileDetailFragment != null
+ && fileDetailFragment.isAdded()) { // only if added to the view hierarchy!!
+
+ fileDetailFragment.getFileDetailSharingFragment().requestPasswordForShareViaLink(true);
+ }
+
+ } else {
+ fileDetailFragment.getFileDetailSharingFragment().refreshPublicShareFromDB();
+ Snackbar.make(
+ findViewById(android.R.id.content),
+ ErrorMessageAdapter.getErrorCauseMessage(result, operation, getResources()),
+ Snackbar.LENGTH_LONG
+ ).show();
+ }
+ }
+ }
+
+ private void onUpdateShareInformation(RemoteOperationResult result, @StringRes int errorString) {
+ Fragment fileDetailFragment = getSecondFragment();
+
+ if (result.isSuccess()) {
+ updateFileFromDB();
+ refreshListOfFilesFragment(false);
+ } else if (fileDetailFragment.getView() != null) {
+ Snackbar.make(fileDetailFragment.getView(), errorString, Snackbar.LENGTH_LONG).show();
+ }
+
+ if (fileDetailFragment != null && fileDetailFragment instanceof FileDetailFragment) {
+ ((FileDetailFragment) fileDetailFragment).getFileDetailSharingFragment()
+ .onUpdateShareInformation(result, getFile());
+ }
+ }
+
+ /**
+ * Shortcut to get access to the {@link FileDetailFragment} instance, if any
+ *
+ * @return A {@link FileDetailFragment} instance, or null
+ */
+ private FileDetailFragment getShareFileFragment() {
+ return (FileDetailFragment) getSupportFragmentManager().findFragmentByTag(TAG_SECOND_FRAGMENT);
+ }
+
/**
* Updates the view associated to the activity after the finish of an operation trying to copy a
* file.
diff --git a/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java b/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
index f7b5d7e7d2fb..4f91873e6351 100644
--- a/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.java
@@ -532,17 +532,16 @@ public void onReceive(Context context, Intent intent) {
}
}
- /**
- * Shows the information of the {@link OCFile} received as a
- * parameter in the second fragment.
- *
- * @param file {@link OCFile} whose details will be shown
- */
@Override
public void showDetails(OCFile file) {
// not used at the moment
}
+ @Override
+ public void showDetails(OCFile file, int activeTab) {
+ // not used at the moment
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java b/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
index 509b92ba3156..2a111c92d9af 100644
--- a/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/ToolbarActivity.java
@@ -22,12 +22,15 @@
package com.owncloud.android.ui.activity;
+import android.graphics.Bitmap;
import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.support.annotation.ColorInt;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
+import android.widget.ImageView;
import android.widget.ProgressBar;
import com.owncloud.android.R;
@@ -40,6 +43,7 @@
*/
public abstract class ToolbarActivity extends BaseActivity {
private ProgressBar mProgressBar;
+ private ImageView mPreviewImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -66,6 +70,8 @@ protected void setupToolbar(boolean useBackgroundImage) {
ThemeUtils.colorToolbarProgressBar(this, ThemeUtils.primaryColor(this));
}
+ mPreviewImage = findViewById(R.id.preview_image);
+
ThemeUtils.colorStatusBar(this, primaryDarkColor);
if (toolbar.getOverflowIcon() != null) {
@@ -81,7 +87,7 @@ protected void setupToolbar(boolean useBackgroundImage) {
}
}
- protected void setupToolbar() {
+ public void setupToolbar() {
setupToolbar(false);
}
@@ -146,7 +152,60 @@ public boolean isRoot(OCFile file) {
* @param indeterminate true
to enable the indeterminate mode
*/
public void setIndeterminate(boolean indeterminate) {
- mProgressBar.setIndeterminate(indeterminate);
+ if (mProgressBar != null) {
+ mProgressBar.setIndeterminate(indeterminate);
+ }
+ }
+
+ /**
+ * Change the visibility for the toolbar's progress bar.
+ *
+ * @param visibility visibility of the progress bar
+ */
+ public void setProgressBarVisibility(int visibility) {
+ if (mProgressBar != null) {
+ mProgressBar.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Change the visibility for the toolbar's preview image.
+ *
+ * @param visibility visibility of the preview image
+ */
+ public void setPreviewImageVisibility(int visibility) {
+ if (mPreviewImage != null) {
+ mPreviewImage.setVisibility(visibility);
+ }
+ }
+
+ /**
+ * Change the bitmap for the toolbar's preview image.
+ *
+ * @param bitmap bitmap of the preview image
+ */
+ public void setPreviewImageBitmap(Bitmap bitmap) {
+ if (mPreviewImage != null) {
+ mPreviewImage.setImageBitmap(bitmap);
+ }
+ }
+
+ /**
+ * Change the drawable for the toolbar's preview image.
+ *
+ * @param drawable drawable of the preview image
+ */
+ public void setPreviewImageDrawable(Drawable drawable) {
+ if (mPreviewImage != null) {
+ mPreviewImage.setImageDrawable(drawable);
+ }
+ }
+
+ /**
+ * get the toolbar's preview image view.
+ */
+ public ImageView getPreviewImageView() {
+ return mPreviewImage;
}
/**
diff --git a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
index d2edb0795a1f..f30172c0d4dd 100644
--- a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
+++ b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java
@@ -263,7 +263,7 @@ private void populateUserInfoUi(UserInfo userInfo) {
userName.setText(account.name);
avatar.setTag(account.name);
DisplayUtils.setAvatar(account, UserInfoActivity.this, mCurrentAccountAvatarRadiusDimension, getResources(),
- getStorageManager(), avatar);
+ getStorageManager(), avatar, this);
int tint = ThemeUtils.primaryColor(account, this);
diff --git a/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
index 935faf4cc261..ec353d57aadf 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java
@@ -153,7 +153,7 @@ private void setAvatar(AccountViewHolderItem viewHolder, Account account) {
View viewItem = viewHolder.imageViewItem;
viewItem.setTag(account.name);
DisplayUtils.setAvatar(account, this, mAccountAvatarRadiusDimension, mContext.getResources(),
- mContext.getStorageManager(), viewItem);
+ mContext.getStorageManager(), viewItem, mContext);
} catch (Exception e) {
Log_OC.e(TAG, "Error calculating RGB value for account list item.", e);
// use user icon as a fallback
diff --git a/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
index 8b71890d99e3..11ae8ff74763 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/FileDetailTabAdapter.java
@@ -36,6 +36,8 @@ public class FileDetailTabAdapter extends FragmentStatePagerAdapter {
private OCFile file;
private Account account;
+ private FileDetailSharingFragment fileDetailSharingFragment;
+
public FileDetailTabAdapter(FragmentManager fm, OCFile file, Account account) {
super(fm);
@@ -49,12 +51,17 @@ public Fragment getItem(int position) {
case 0:
return FileDetailActivitiesFragment.newInstance(file, account);
case 1:
- return FileDetailSharingFragment.newInstance(file, account);
+ fileDetailSharingFragment = FileDetailSharingFragment.newInstance(file, account);
+ return fileDetailSharingFragment;
default:
return null;
}
}
+ public FileDetailSharingFragment getFileDetailSharingFragment() {
+ return fileDetailSharingFragment;
+ }
+
@Override
public int getCount() {
return 2;
diff --git a/src/main/java/com/owncloud/android/ui/adapter/ShareUserListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/ShareUserListAdapter.java
index af726e9b4108..e637e72ef99f 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/ShareUserListAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/ShareUserListAdapter.java
@@ -1,4 +1,4 @@
-/**
+/*
* ownCloud Android client application
*
* @author masensio
diff --git a/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
index 6d7d120fbede..5c80c2fc61e5 100644
--- a/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
+++ b/src/main/java/com/owncloud/android/ui/adapter/UserListAdapter.java
@@ -1,113 +1,367 @@
-/**
- * ownCloud Android client application
- *
- * @author masensio
- * Copyright (C) 2015 ownCloud Inc.
+/*
+ * Nextcloud Android client application
*
- * 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.
+ * @author masensio
+ * @author Andy Scherzinger
+ * Copyright (C) 2015 ownCloud GmbH
+ * Copyright (C) 2018 Andy Scherzinger
*
- * 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.
+ * 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.
*
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
+ * 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 .
*/
package com.owncloud.android.ui.adapter;
+import android.accounts.Account;
import android.content.Context;
+import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
+import android.support.v4.app.FragmentManager;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ArrayAdapter;
import android.widget.ImageView;
+import android.widget.PopupMenu;
import android.widget.TextView;
import com.owncloud.android.R;
+import com.owncloud.android.authentication.AccountUtils;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+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.lib.resources.status.OCCapability;
import com.owncloud.android.ui.TextDrawable;
+import com.owncloud.android.ui.dialog.ExpirationDatePickerDialogFragment;
+import com.owncloud.android.ui.fragment.util.FileDetailSharingFragmentHelper;
+import com.owncloud.android.utils.DisplayUtils;
+import com.owncloud.android.utils.ThemeUtils;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
-import java.util.ArrayList;
+import java.util.List;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
/**
- * Adapter to show a user/group in Share With List in Details View
+ * Adapter to show a user/group/email/remote in Sharing list in file details view.
*/
-public class UserListAdapter extends ArrayAdapter {
+public class UserListAdapter extends RecyclerView.Adapter
+ implements DisplayUtils.AvatarGenerationListener {
- private Context mContext;
- private ArrayList mShares;
- private float mAvatarRadiusDimension;
+ private ShareeListAdapterListener listener;
+ private OCCapability capabilities;
+ private FragmentManager fragmentManager;
+ private Context context;
+ private int accentColor;
+ private List shares;
+ private float avatarRadiusDimension;
+ private Account account;
+ private OCFile file;
+ private FileDataStorageManager storageManager;
- public UserListAdapter(Context context, int resource, ArrayList shares) {
- super(context, resource);
- mContext = context;
- mShares = shares;
-
- mAvatarRadiusDimension = context.getResources().getDimension(R.dimen.standard_padding);
- }
+ public UserListAdapter(FragmentManager fragmentManager, Context context, List shares, Account account,
+ OCFile file, ShareeListAdapterListener listener) {
+ this.context = context;
+ this.fragmentManager = fragmentManager;
+ this.shares = shares;
+ this.listener = listener;
+ this.account = account;
+ this.file = file;
- @Override
- public int getCount() {
- return mShares.size();
+ accentColor = ThemeUtils.primaryAccentColor(context);
+ storageManager = new FileDataStorageManager(account, context.getContentResolver());
+ capabilities = storageManager.getCapability(account.name);
+ avatarRadiusDimension = context.getResources().getDimension(R.dimen.user_icon_radius);
}
+ @NonNull
@Override
- public Object getItem(int position) {
- return mShares.get(position);
+ public UserViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.file_details_share_user_item, parent, false);
+ return new UserViewHolder(v);
}
@Override
- public long getItemId(int position) {
- return 0;
- }
+ public void onBindViewHolder(@NonNull UserViewHolder holder, int position) {
+ if (shares != null && shares.size() > position) {
+ final OCShare share = shares.get(position);
- @Override
- public @NonNull View getView(final int position, View convertView, @NonNull ViewGroup parent) {
- View view = convertView;
- if (view == null) {
- LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- view = inflater.inflate(R.layout.file_details_share_user_item, parent, false);
- }
-
- if (mShares != null && mShares.size() > position) {
- OCShare share = mShares.get(position);
-
- TextView userName = view.findViewById(R.id.userOrGroupName);
- ImageView icon = view.findViewById(R.id.userIcon);
String name = share.getSharedWithDisplayName();
if (share.getShareType() == ShareType.GROUP) {
- name = getContext().getString(R.string.share_group_clarification, name);
+ name = context.getString(R.string.share_group_clarification, name);
try {
- icon.setImageDrawable(TextDrawable.createNamedAvatar(name, mAvatarRadiusDimension));
+ holder.avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
} catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
- icon.setImageResource(R.drawable.ic_group);
+ holder.avatar.setImageResource(R.drawable.ic_group);
}
} else if (share.getShareType() == ShareType.EMAIL) {
- name = getContext().getString(R.string.share_email_clarification, name);
+ name = context.getString(R.string.share_email_clarification, name);
try {
- icon.setImageDrawable(TextDrawable.createNamedAvatar(name, mAvatarRadiusDimension));
+ holder.avatar.setImageDrawable(TextDrawable.createNamedAvatar(name, avatarRadiusDimension));
} catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
- icon.setImageResource(R.drawable.ic_email);
+ holder.avatar.setImageResource(R.drawable.ic_email);
}
} else {
- try {
- icon.setImageDrawable(TextDrawable.createNamedAvatar(name, mAvatarRadiusDimension));
- } catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
- icon.setImageResource(R.drawable.ic_user);
+ holder.avatar.setTag(share.getShareWith());
+ DisplayUtils.setAvatar(account, share.getShareWith(), this, avatarRadiusDimension,
+ context.getResources(), storageManager, holder.avatar, context);
+ }
+ holder.name.setText(name);
+
+ ThemeUtils.tintCheckbox(holder.allowEditing, accentColor);
+ holder.allowEditing.setChecked(canEdit(share));
+ holder.allowEditing.setOnClickListener(v -> allowEditClick(holder.allowEditing, share));
+
+ // bind listener to edit privileges
+ holder.editShareButton.setOnClickListener(v -> onOverflowIconClicked(v, holder.allowEditing, share));
+ }
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return shares.get(position).getId();
+ }
+
+ @Override
+ public int getItemCount() {
+ return shares.size();
+ }
+
+ private void allowEditClick(AppCompatCheckBox checkBox, @NonNull OCShare share) {
+ if (!share.isFolder()) {
+ share.setPermissions(listener.updatePermissionsToShare(
+ share,
+ canReshare(share),
+ checkBox.isChecked(),
+ false,
+ false,
+ false
+ ));
+ } else {
+ share.setPermissions(listener.updatePermissionsToShare(
+ share,
+ canReshare(share),
+ checkBox.isChecked(),
+ checkBox.isChecked(),
+ checkBox.isChecked(),
+ checkBox.isChecked()
+ ));
+ }
+ }
+
+ private void onOverflowIconClicked(View view, AppCompatCheckBox allowEditsCheckBox, OCShare share) {
+ // use grey as fallback for elements where custom theming is not available
+ if (ThemeUtils.themingEnabled(context)) {
+ context.getTheme().applyStyle(R.style.FallbackThemingTheme, true);
+ }
+ PopupMenu popup = new PopupMenu(context, view);
+ popup.inflate(R.menu.file_detail_sharing_menu);
+
+ prepareOptionsMenu(popup.getMenu(), share);
+
+ popup.setOnMenuItemClickListener(item -> optionsItemSelected(popup.getMenu(), item, allowEditsCheckBox, share));
+ popup.show();
+ }
+
+ /**
+ * Updates the sharee's menu with the current permissions of the {@link OCShare}
+ *
+ * @param menu the menu of the sharee/shared file
+ * @param share the shared file
+ */
+ private void prepareOptionsMenu(Menu menu, OCShare share) {
+
+ MenuItem reshareItem = menu.findItem(R.id.action_can_reshare);
+ if (isReshareForbidden(share)) {
+ reshareItem.setVisible(false);
+ }
+ reshareItem.setChecked(canReshare(share));
+
+ MenuItem editCreateItem = menu.findItem(R.id.action_can_edit_create);
+ MenuItem editChangeItem = menu.findItem(R.id.action_can_edit_change);
+ MenuItem editDeleteItem = menu.findItem(R.id.action_can_edit_delete);
+ if (file.isFolder() && isEditOptionsAvailable(share)) {
+ /// TODO change areEditOptionsAvailable in order to delete !isFederated
+ editCreateItem.setChecked(canCreate(share));
+ editChangeItem.setChecked(canUpdate(share));
+ editDeleteItem.setChecked(canDelete(share));
+ } else {
+ editCreateItem.setVisible(false);
+ editChangeItem.setVisible(false);
+ editDeleteItem.setVisible(false);
+ }
+
+ FileDetailSharingFragmentHelper.setupExpirationDateMenuItem(
+ menu.findItem(R.id.action_expiration_date), share.getExpirationDate(), context.getResources());
+ }
+
+ private boolean isEditOptionsAvailable(OCShare share) {
+ return !ShareType.FEDERATED.equals(share.getShareType())
+ || AccountUtils.getServerVersion(account).isNotReshareableFederatedSupported();
+ }
+
+ private boolean isReshareForbidden(OCShare share) {
+ return ShareType.FEDERATED.equals(share.getShareType()) ||
+ (capabilities != null && capabilities.getFilesSharingResharing().isFalse());
+ }
+
+ private boolean canEdit(OCShare share) {
+ return (share.getPermissions() &
+ (OCShare.CREATE_PERMISSION_FLAG | OCShare.UPDATE_PERMISSION_FLAG | OCShare.DELETE_PERMISSION_FLAG)) > 0;
+ }
+
+ private boolean canCreate(OCShare share) {
+ return (share.getPermissions() & OCShare.CREATE_PERMISSION_FLAG) > 0;
+ }
+
+ private boolean canUpdate(OCShare share) {
+ return (share.getPermissions() & OCShare.UPDATE_PERMISSION_FLAG) > 0;
+ }
+
+ private boolean canDelete(OCShare share) {
+ return (share.getPermissions() & OCShare.DELETE_PERMISSION_FLAG) > 0;
+ }
+
+ private boolean canReshare(OCShare share) {
+ return (share.getPermissions() & OCShare.SHARE_PERMISSION_FLAG) > 0;
+ }
+
+ private boolean optionsItemSelected(Menu menu, MenuItem item, AppCompatCheckBox allowEditsCheckBox, OCShare share) {
+ switch (item.getItemId()) {
+ case R.id.action_can_edit_create:
+ case R.id.action_can_edit_change:
+ case R.id.action_can_edit_delete: {
+ item.setChecked(!item.isChecked());
+ if (item.isChecked() && !allowEditsCheckBox.isChecked()) {
+ allowEditsCheckBox.setChecked(true);
}
+ share.setPermissions(
+ updatePermissionsToShare(
+ share,
+ menu.findItem(R.id.action_can_reshare).isChecked(),
+ allowEditsCheckBox.isChecked(),
+ menu.findItem(R.id.action_can_edit_create).isChecked(),
+ menu.findItem(R.id.action_can_edit_change).isChecked(),
+ menu.findItem(R.id.action_can_edit_delete).isChecked())
+ );
+ return true;
+ }
+ case R.id.action_can_reshare: {
+ item.setChecked(!item.isChecked());
+ share.setPermissions(
+ updatePermissionsToShare(
+ share,
+ menu.findItem(R.id.action_can_reshare).isChecked(),
+ allowEditsCheckBox.isChecked(),
+ menu.findItem(R.id.action_can_edit_create).isChecked(),
+ menu.findItem(R.id.action_can_edit_change).isChecked(),
+ menu.findItem(R.id.action_can_edit_delete).isChecked())
+ );
+ return true;
}
- userName.setText(name);
+ case R.id.action_unshare: {
+ listener.unshareWith(share);
+ shares.remove(share);
+ notifyDataSetChanged();
+ return true;
+ }
+ case R.id.action_expiration_date: {
+ ExpirationDatePickerDialogFragment dialog = ExpirationDatePickerDialogFragment.newInstance(share, -1);
+ dialog.show(
+ fragmentManager,
+ ExpirationDatePickerDialogFragment.DATE_PICKER_DIALOG
+ );
+ return true;
+ }
+ default:
+ return true;
+ }
+ }
+
+ private int updatePermissionsToShare(OCShare share, boolean canReshare, boolean canEdit, boolean canEditCreate,
+ boolean canEditChange, boolean canEditDelete) {
+ return listener.updatePermissionsToShare(
+ share,
+ canReshare,
+ canEdit,
+ canEditCreate,
+ canEditChange,
+ canEditDelete
+ );
+ }
+
+ @Override
+ public void avatarGenerated(Drawable avatarDrawable, Object callContext) {
+ if (callContext instanceof ImageView) {
+ ImageView iv = (ImageView) callContext;
+ iv.setImageDrawable(avatarDrawable);
+ }
+ }
+ @Override
+ public boolean shouldCallGeneratedCallback(String tag, Object callContext) {
+ if (callContext instanceof ImageView) {
+ ImageView iv = (ImageView) callContext;
+ return String.valueOf(iv.getTag()).equals(tag);
}
- return view;
+ return false;
+ }
+
+ class UserViewHolder extends RecyclerView.ViewHolder {
+ @BindView(R.id.avatar)
+ ImageView avatar;
+ @BindView(R.id.name)
+ TextView name;
+ @BindView(R.id.allowEditing)
+ AppCompatCheckBox allowEditing;
+ @BindView(R.id.editShareButton)
+ ImageView editShareButton;
+
+ UserViewHolder(View itemView) {
+ super(itemView);
+ ButterKnife.bind(this, itemView);
+ }
+ }
+
+ public interface ShareeListAdapterListener {
+ /**
+ * unshare with given sharee {@link OCShare}.
+ *
+ * @param share the share
+ */
+ void unshareWith(OCShare share);
+
+ /**
+ * Updates the permissions of the {@link OCShare}.
+ *
+ * @param share the share to be updated
+ * @param canReshare reshare permission
+ * @param canEdit edit permission
+ * @param canEditCreate create permission (folders only)
+ * @param canEditChange change permission (folders only)
+ * @param canEditDelete delete permission (folders only)
+ * @return permissions value set
+ */
+ int updatePermissionsToShare(OCShare share,
+ boolean canReshare,
+ boolean canEdit,
+ boolean canEditCreate,
+ boolean canEditChange,
+ boolean canEditDelete);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java b/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java
new file mode 100644
index 000000000000..1d5d0306f280
--- /dev/null
+++ b/src/main/java/com/owncloud/android/ui/decoration/SimpleListItemDividerDecoration.java
@@ -0,0 +1,78 @@
+/*
+ * Nextcloud Android client application
+ *
+ * @author Andy Scherzinger
+ * Copyright (C) 2018 Andy Scherzinger
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public
+ * License along with this program. If not, see .
+ */
+
+package com.owncloud.android.ui.decoration;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.v7.widget.DividerItemDecoration;
+import android.support.v7.widget.RecyclerView;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+/**
+ * DividerItemDecoration based on {@link DividerItemDecoration} adding a 72dp left padding.
+ */
+public class SimpleListItemDividerDecoration extends DividerItemDecoration {
+ private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
+
+ private final Rect bounds = new Rect();
+ private Drawable divider;
+ private int leftPadding;
+
+ /**
+ * Default divider will be used
+ */
+ public SimpleListItemDividerDecoration(Context context) {
+ super(context, DividerItemDecoration.VERTICAL);
+ final TypedArray styledAttributes = context.obtainStyledAttributes(ATTRS);
+ divider = styledAttributes.getDrawable(0);
+ leftPadding = Math.round(72 * (context.getResources().getDisplayMetrics().xdpi / DisplayMetrics.DENSITY_DEFAULT));
+ styledAttributes.recycle();
+ }
+
+ @Override
+ public void onDraw(Canvas canvas, RecyclerView parent, RecyclerView.State state) {
+ canvas.save();
+ final int right;
+ //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
+ if (parent.getClipToPadding()) {
+ right = parent.getWidth() - parent.getPaddingRight();
+ canvas.clipRect(leftPadding, parent.getPaddingTop(), right,
+ parent.getHeight() - parent.getPaddingBottom());
+ } else {
+ right = parent.getWidth();
+ }
+
+ final int childCount = parent.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = parent.getChildAt(i);
+ parent.getDecoratedBoundsWithMargins(child, bounds);
+ final int bottom = bounds.bottom + Math.round(child.getTranslationY());
+ final int top = bottom - 1;
+ divider.setBounds(leftPadding, top, right, bottom);
+ divider.draw(canvas);
+ }
+ canvas.restore();
+ }
+}
diff --git a/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
index 03739fdb01a5..0244ebcd2064 100644
--- a/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
+++ b/src/main/java/com/owncloud/android/ui/dialog/ExpirationDatePickerDialogFragment.java
@@ -1,8 +1,10 @@
-/**
+/*
* ownCloud Android client application
*
* @author David A. Velasco
+ * @author Andy Scherzinger
* Copyright (C) 2015 ownCloud Inc.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -15,7 +17,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- *
*/
package com.owncloud.android.ui.dialog;
@@ -24,11 +25,14 @@
import android.app.DatePickerDialog;
import android.app.Dialog;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.text.format.DateUtils;
import android.widget.DatePicker;
+import com.owncloud.android.R;
import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.lib.resources.shares.OCShare;
import com.owncloud.android.ui.activity.FileActivity;
import java.util.Calendar;
@@ -46,18 +50,24 @@ public class ExpirationDatePickerDialogFragment
/** Parameter constant for {@link OCFile} instance to set the expiration date */
private static final String ARG_FILE = "FILE";
+ /** Parameter constant for {@link OCShare} instance to set the expiration date */
+ private static final String ARG_SHARE = "SHARE";
+
/** 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;
+ private OCFile file;
+
+ /** Share to bind an expiration date */
+ private OCShare share;
/**
* 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
+ * @return New dialog instance
*/
public static ExpirationDatePickerDialogFragment newInstance(OCFile file, long chosenDateInMillis) {
Bundle arguments = new Bundle();
@@ -69,15 +79,34 @@ public static ExpirationDatePickerDialogFragment newInstance(OCFile file, long c
return dialog;
}
+ /**
+ * Factory method to create new instances
+ *
+ * @param share share to bind an expiration date
+ * @param chosenDateInMillis Date chosen when the dialog appears
+ * @return New dialog instance
+ */
+ public static ExpirationDatePickerDialogFragment newInstance(OCShare share, long chosenDateInMillis) {
+ Bundle arguments = new Bundle();
+ arguments.putParcelable(ARG_SHARE, share);
+ 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.
+ * @return A new dialog to let the user choose an expiration date that will be bound to a share link.
*/
@Override
+ @NonNull
public Dialog onCreateDialog(Bundle savedInstanceState) {
// Load arguments
- mFile = getArguments().getParcelable(ARG_FILE);
+ file = getArguments().getParcelable(ARG_FILE);
+ share = getArguments().getParcelable(ARG_SHARE);
// Chosen date received as an argument must be later than tomorrow ; default to tomorrow in other case
final Calendar chosenDate = Calendar.getInstance();
@@ -92,11 +121,23 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
// Create a new instance of DatePickerDialog
DatePickerDialog dialog = new DatePickerDialog(
getActivity(),
+ R.style.FallbackDatePickerDialogTheme,
this,
chosenDate.get(Calendar.YEAR),
chosenDate.get(Calendar.MONTH),
chosenDate.get(Calendar.DAY_OF_MONTH)
);
+ dialog.setButton(
+ Dialog.BUTTON_NEUTRAL,
+ getText(R.string.share_via_link_unset_password),
+ (dialog1, which) -> {
+ if (file != null) {
+ ((FileActivity) getActivity()).getFileOperationsHelper()
+ .setExpirationDateToShareViaLink(file, -1);
+ } else if (share != null) {
+ ((FileActivity) getActivity()).getFileOperationsHelper().setExpirationDateToShare(share,-1);
+ }
+ });
// Prevent days in the past may be chosen
DatePicker picker = dialog.getDatePicker();
@@ -127,9 +168,11 @@ public void onDateSet(DatePicker view, int year, int monthOfYear, int dayOfMonth
chosenDate.set(Calendar.DAY_OF_MONTH, dayOfMonth);
long chosenDateInMillis = chosenDate.getTimeInMillis();
- ((FileActivity)getActivity()).getFileOperationsHelper().setExpirationDateToShareViaLink(
- mFile,
- chosenDateInMillis
- );
+ if (file != null) {
+ ((FileActivity) getActivity()).getFileOperationsHelper()
+ .setExpirationDateToShareViaLink(file, chosenDateInMillis);
+ } else if (share != null) {
+ ((FileActivity) getActivity()).getFileOperationsHelper().setExpirationDateToShare(share,chosenDateInMillis);
+ }
}
}
diff --git a/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java b/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java
index 1ab9a4b1ad50..c050efa7eb39 100644
--- a/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java
+++ b/src/main/java/com/owncloud/android/ui/dialog/SendShareDialog.java
@@ -16,11 +16,13 @@
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
+import android.widget.LinearLayout;
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.FileDisplayActivity;
import com.owncloud.android.ui.adapter.SendButtonAdapter;
import com.owncloud.android.ui.components.SendButtonData;
import com.owncloud.android.ui.helpers.FileOperationsHelper;
@@ -34,8 +36,10 @@
* Nextcloud Android client application
*
* @author Tobias Kaminsky
+ * @author Andy Scherzinger
* Copyright (C) 2017 Tobias Kaminsky
* Copyright (C) 2017 Nextcloud GmbH.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
@@ -53,20 +57,23 @@
public class SendShareDialog extends BottomSheetDialogFragment {
private static final String KEY_OCFILE = "KEY_OCFILE";
+ private static final String KEY_HIDE_NCSHARING_OPTIONS = "KEY_HIDE_NCSHARING_OPTIONS";
private static final String TAG = SendShareDialog.class.getSimpleName();
public static final String PACKAGE_NAME = "PACKAGE_NAME";
public static final String ACTIVITY_NAME = "ACTIVITY_NAME";
private View view;
private OCFile file;
+ private boolean hideNcSharingOptions;
private FileOperationsHelper fileOperationsHelper;
- public static SendShareDialog newInstance(OCFile file) {
+ public static SendShareDialog newInstance(OCFile file, boolean hideNcSharingOptions) {
SendShareDialog dialogFragment = new SendShareDialog();
Bundle args = new Bundle();
args.putParcelable(KEY_OCFILE, file);
+ args.putBoolean(KEY_HIDE_NCSHARING_OPTIONS, hideNcSharingOptions);
dialogFragment.setArguments(args);
return dialogFragment;
@@ -81,6 +88,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
view = null;
file = getArguments().getParcelable(KEY_OCFILE);
+ hideNcSharingOptions = getArguments().getBoolean(KEY_HIDE_NCSHARING_OPTIONS, false);
}
@Nullable
@@ -89,6 +97,9 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
view = inflater.inflate(R.layout.send_share_fragment, container, false);
+ LinearLayout sendShareButtons = view.findViewById(R.id.send_share_buttons);
+ View divider = view.findViewById(R.id.divider);
+
// Share with people
TextView sharePeopleText = view.findViewById(R.id.share_people_button);
sharePeopleText.setOnClickListener(v -> shareFile(file));
@@ -105,7 +116,10 @@ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
themeShareButtonImage(shareLinkImageView);
shareLinkImageView.setOnClickListener(v -> shareFile(file));
- if (file.isSharedWithMe() && !file.canReshare()) {
+ if (hideNcSharingOptions) {
+ sendShareButtons.setVisibility(View.GONE);
+ divider.setVisibility(View.GONE);
+ } else if (file.isSharedWithMe() && !file.canReshare()) {
showResharingNotAllowedSnackbar();
if (file.isFolder()) {
@@ -216,7 +230,11 @@ private Intent createSendIntent() {
}
private void shareFile(OCFile file) {
- fileOperationsHelper.showShareFile(file);
+ if (getActivity() instanceof FileDisplayActivity) {
+ ((FileDisplayActivity) getActivity()).showDetails(file, 1);
+ } else {
+ fileOperationsHelper.showShareFile(file);
+ }
dismiss();
}
diff --git a/src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java b/src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java
index 744649f260cb..55aa75fb3a79 100644
--- a/src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java
+++ b/src/main/java/com/owncloud/android/ui/dialog/SharePasswordDialogFragment.java
@@ -1,7 +1,10 @@
-/**
+/*
* ownCloud Android client application
+ *
* @author masensio
- * Copyright (C) 2015 ownCloud Inc.
+ * @author Andy Scherzinger
+ * Copyright (C) 2015 ownCloud GmbH.
+ * Copyright (C) 2018 Andy Scherzinger
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2,
@@ -14,7 +17,6 @@
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
- *
*/
package com.owncloud.android.ui.dialog;
@@ -23,11 +25,13 @@
import android.graphics.PorterDuff;
import android.os.Bundle;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.TextView;
@@ -42,16 +46,14 @@
*
* Triggers the share when the password is introduced.
*/
-
-public class SharePasswordDialogFragment extends DialogFragment
- implements DialogInterface.OnClickListener {
+public class SharePasswordDialogFragment extends DialogFragment implements DialogInterface.OnClickListener {
private static final String ARG_FILE = "FILE";
private static final String ARG_CREATE_SHARE = "CREATE_SHARE";
public static final String PASSWORD_FRAGMENT = "PASSWORD_FRAGMENT";
- private OCFile mFile;
- private boolean mCreateShare;
+ private OCFile file;
+ private boolean createShare;
@Override
public void onStart() {
@@ -60,16 +62,18 @@ public void onStart() {
AlertDialog alertDialog = (AlertDialog) getDialog();
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ThemeUtils.primaryAccentColor(getContext()));
alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(ThemeUtils.primaryAccentColor(getContext()));
+ alertDialog.getButton(AlertDialog.BUTTON_NEUTRAL)
+ .setTextColor(getResources().getColor(R.color.highlight_textColor_Warning));
}
/**
* Public factory method to create new SharePasswordDialogFragment instances.
*
- * @param file OCFile bound to the public share that which password will be set or updated
- * @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.
- * @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 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.
+ * @return Dialog ready to show.
*/
public static SharePasswordDialogFragment newInstance(OCFile file, boolean createShare) {
SharePasswordDialogFragment frag = new SharePasswordDialogFragment();
@@ -80,11 +84,17 @@ public static SharePasswordDialogFragment newInstance(OCFile file, boolean creat
return frag;
}
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ return super.onCreateView(inflater, container, savedInstanceState);
+ }
+
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- mFile = getArguments().getParcelable(ARG_FILE);
- mCreateShare = getArguments().getBoolean(ARG_CREATE_SHARE, false);
+ file = getArguments().getParcelable(ARG_FILE);
+ createShare = getArguments().getBoolean(ARG_CREATE_SHARE, false);
// Inflate the layout for the dialog
LayoutInflater inflater = getActivity().getLayoutInflater();
@@ -92,7 +102,10 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
// Setup layout
EditText inputText = v.findViewById(R.id.share_password);
- inputText.getBackground().setColorFilter(ThemeUtils.primaryAccentColor(getContext()), PorterDuff.Mode.SRC_ATOP);
+ inputText.getBackground().setColorFilter(
+ ThemeUtils.primaryAccentColor(getContext()),
+ PorterDuff.Mode.SRC_ATOP
+ );
inputText.setText("");
inputText.requestFocus();
@@ -102,19 +115,17 @@ public Dialog onCreateDialog(Bundle savedInstanceState) {
builder.setView(v)
.setPositiveButton(R.string.common_ok, this)
.setNegativeButton(R.string.common_cancel, this)
+ .setNeutralButton(R.string.common_delete, this)
.setTitle(R.string.share_link_password_title);
Dialog d = builder.create();
d.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
return d;
}
-
@Override
public void onClick(DialogInterface dialog, int which) {
if (which == AlertDialog.BUTTON_POSITIVE) {
- String password =
- ((TextView)(getDialog().findViewById(R.id.share_password)))
- .getText().toString();
+ String password = ((TextView) (getDialog().findViewById(R.id.share_password))).getText().toString();
if (password.length() <= 0) {
Snackbar.make(
@@ -125,16 +136,17 @@ public void onClick(DialogInterface dialog, int which) {
return;
}
- if (mCreateShare) {
- // Share the file
- ((FileActivity) getActivity()).getFileOperationsHelper().
- shareFileViaLink(mFile, password);
+ setPassword(createShare, file, password);
+ } else if (which == AlertDialog.BUTTON_NEUTRAL) {
+ setPassword(createShare, file, null);
+ }
+ }
- } else {
- // updat existing link
- ((FileActivity) getActivity()).getFileOperationsHelper().
- setPasswordToShareViaLink(mFile, password);
- }
+ private void setPassword(boolean createShare, OCFile file, String password) {
+ if (createShare) {
+ ((FileActivity) getActivity()).getFileOperationsHelper().shareFileViaLink(file, password);
+ } else {
+ ((FileActivity) getActivity()).getFileOperationsHelper().setPasswordToShareViaLink(file, password);
}
}
}
diff --git a/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java b/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
index bc8c3a93bb46..f20d8a01246d 100644
--- a/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
+++ b/src/main/java/com/owncloud/android/ui/fragment/FileDetailActivitiesFragment.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -143,31 +144,25 @@ public View onCreateView(@NonNull LayoutInflater inflater,
fetchAndSetData(null);
- swipeListRefreshLayout.setOnRefreshListener(() -> {
- setLoadingMessage();
- if (swipeListRefreshLayout != null && swipeListRefreshLayout.isRefreshing()) {
- swipeListRefreshLayout.setRefreshing(false);
- }
- fetchAndSetData(null);
- }
- );
-
- swipeEmptyListRefreshLayout.setOnRefreshListener(() -> {
- setLoadingMessage();
- if (swipeEmptyListRefreshLayout != null && swipeEmptyListRefreshLayout.isRefreshing()) {
- swipeEmptyListRefreshLayout.setRefreshing(false);
- }
- fetchAndSetData(null);
- }
- );
+ swipeListRefreshLayout.setOnRefreshListener(
+ () -> onRefreshListLayout(swipeListRefreshLayout));
+ swipeEmptyListRefreshLayout.setOnRefreshListener(
+ () -> onRefreshListLayout(swipeEmptyListRefreshLayout));
return view;
}
+ private void onRefreshListLayout(SwipeRefreshLayout refreshLayout) {
+ setLoadingMessage();
+ if (refreshLayout != null && refreshLayout.isRefreshing()) {
+ refreshLayout.setRefreshing(false);
+ }
+ fetchAndSetData(null);
+ }
+
private void setLoadingMessage() {
emptyContentHeadline.setText(R.string.file_list_loading);
emptyContentMessage.setText("");
-
emptyContentIcon.setVisibility(View.GONE);
emptyContentProgressBar.setVisibility(View.VISIBLE);
}
@@ -216,6 +211,11 @@ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
private void fetchAndSetData(String pageUrl) {
final Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(MainApp.getAppContext());
final Context context = MainApp.getAppContext();
+ final FragmentActivity activity = getActivity();
+
+ final SwipeRefreshLayout empty = swipeEmptyListRefreshLayout;
+ final SwipeRefreshLayout list = swipeListRefreshLayout;
+
Thread t = new Thread(() -> {
OwnCloudAccount ocAccount;
@@ -241,15 +241,15 @@ private void fetchAndSetData(String pageUrl) {
final ArrayList