Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit c2867eba authored by alperozturk's avatar alperozturk
Browse files

use share api

parent 35fa9765
Loading
Loading
Loading
Loading
+21 −0
Original line number Diff line number Diff line
@@ -2,6 +2,7 @@ package it.niedermann.owncloud.notes.persistence

import android.app.Application
import android.content.Context
import android.util.Log
import com.nextcloud.android.sso.api.EmptyResponse
import com.nextcloud.android.sso.model.SingleSignOnAccount
import com.owncloud.android.lib.resources.shares.OCShare
@@ -9,7 +10,10 @@ import com.owncloud.android.lib.resources.shares.ShareType
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import it.niedermann.owncloud.notes.persistence.entity.Note
import it.niedermann.owncloud.notes.share.model.ShareesData
import it.niedermann.owncloud.notes.shared.model.ApiVersion
import org.json.JSONObject
import java.util.ArrayList

class ShareRepository private constructor(private val applicationContext: Context) {

@@ -28,6 +32,23 @@ class ShareRepository private constructor(private val applicationContext: Contex
        }.subscribeOn(Schedulers.io())
    }

    fun getSharees(
        account: SingleSignOnAccount,
        searchString: String,
        page: Int,
        perPage: Int
    ): Single<ShareesData> {
        return Single.fromCallable {
            val shareAPI = apiProvider.getShareAPI(applicationContext, account)
            val call2 = shareAPI.getSharees2(search = searchString, page = page, perPage = perPage)
            val response2 = call2.execute()

            val call = shareAPI.getSharees(search = searchString, page = page, perPage = perPage)
            val response = call.execute()
            response.body()?.ocs?.data ?: throw RuntimeException("No shares available")
        }.subscribeOn(Schedulers.io())
    }

    fun getShares(
        account: SingleSignOnAccount,
        remoteId: Long
+22 −0
Original line number Diff line number Diff line
@@ -3,14 +3,36 @@ package it.niedermann.owncloud.notes.persistence.sync
import com.nextcloud.android.sso.api.EmptyResponse
import com.owncloud.android.lib.resources.shares.OCShare
import com.owncloud.android.lib.resources.shares.ShareType
import it.niedermann.owncloud.notes.share.model.ShareesData
import it.niedermann.owncloud.notes.shared.model.OcsResponse
import retrofit2.Call
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.Query

interface ShareAPI {
    @GET("sharees")
    fun getSharees2(
        @Query("format") format: String = "json",
        @Query("itemType") itemType: String = "note",
        @Query("search") search: String,
        @Query("page") page: Int,
        @Query("perPage") perPage: Int,
        @Query("lookup") lookup: Boolean = true,
    ): Call<Any>

    @GET("sharees")
    fun getSharees(
        @Query("format") format: String = "json",
        @Query("itemType") itemType: String = "note",
        @Query("search") search: String,
        @Query("page") page: Int,
        @Query("perPage") perPage: Int,
        @Query("lookup") lookup: Boolean = true,
    ): Call<OcsResponse<ShareesData>>

    @GET("shares")
    fun getShares(remoteId: Long): Call<OcsResponse<List<OCShare>>>

+9 −7
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import it.niedermann.owncloud.notes.branding.BrandedActivity;
import it.niedermann.owncloud.notes.branding.BrandedSnackbar;
import it.niedermann.owncloud.notes.branding.BrandingUtil;
import it.niedermann.owncloud.notes.databinding.ActivityNoteShareBinding;
import it.niedermann.owncloud.notes.persistence.ShareRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.persistence.entity.Note;
import it.niedermann.owncloud.notes.share.adapter.ShareeListAdapter;
@@ -56,6 +57,7 @@ import it.niedermann.owncloud.notes.share.dialog.SharePasswordDialogFragment;
import it.niedermann.owncloud.notes.share.helper.UsersAndGroupsSearchProvider;
import it.niedermann.owncloud.notes.share.listener.FileDetailsSharingMenuBottomSheetActions;
import it.niedermann.owncloud.notes.share.listener.ShareeListAdapterListener;
import it.niedermann.owncloud.notes.share.model.ShareesData;
import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig;
import it.niedermann.owncloud.notes.share.operations.ClientFactoryImpl;
import it.niedermann.owncloud.notes.share.operations.RetrieveHoverCardAsyncTask;
@@ -146,8 +148,7 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap

    }

    private void setupSearchView(@Nullable SearchManager searchManager,
                                       ComponentName componentName) {
    private void setupSearchView(@Nullable SearchManager searchManager, ComponentName componentName) {
        if (searchManager == null) {
            binding.searchView.setVisibility(View.GONE);
            return;
@@ -162,8 +163,8 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap
        // avoid fullscreen with softkeyboard
        binding.searchView.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);

        UsersAndGroupsSearchProvider provider = new UsersAndGroupsSearchProvider(account, clientFactory.create());

        ShareRepository repository = ShareRepository.getInstance(getApplicationContext());
        UsersAndGroupsSearchProvider provider = new UsersAndGroupsSearchProvider(this, account, repository);
        binding.searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
@@ -174,9 +175,10 @@ public class NoteShareActivity extends BrandedActivity implements ShareeListAdap

            @Override
            public boolean onQueryTextChange(String newText) {
                Log_OC.e(NoteShareActivity.class.getSimpleName(), "Failed to pick email address as Cursor is null." + newText);

                // leave it for the parent listener in the hierarchy / default behaviour
                new Thread(() -> {{
                    ShareesData data = provider.searchForUsersOrGroups(newText);
                    Log_OC.e(NoteShareActivity.class.getSimpleName(), "Fetched" + newText);
                }}).start();
                return false;
            }
        });
+38 −283
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.share.helper;


import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_CLEAR_AT;
import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_ICON;
import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_MESSAGE;
import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_STATUS;

import android.app.SearchManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
import com.owncloud.android.lib.common.OwnCloudClient;
import com.owncloud.android.lib.common.operations.RemoteOperationResult;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
import com.owncloud.android.lib.common.utils.Log_OC;
import com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation;
import com.owncloud.android.lib.resources.shares.ShareType;
@@ -36,7 +36,6 @@ import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -44,25 +43,20 @@ import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutionException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_CLEAR_AT;
import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_ICON;
import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_MESSAGE;
import static com.owncloud.android.lib.resources.shares.GetShareesRemoteOperation.PROPERTY_STATUS;

import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl;
import it.niedermann.owncloud.notes.R;
import it.niedermann.owncloud.notes.persistence.ApiProvider;
import it.niedermann.owncloud.notes.persistence.ShareRepository;
import it.niedermann.owncloud.notes.persistence.entity.Account;
import it.niedermann.owncloud.notes.share.model.ShareesData;
import it.niedermann.owncloud.notes.share.model.UsersAndGroupsSearchConfig;
import it.niedermann.owncloud.notes.shared.model.ApiVersion;

/**
 * Content provider for search suggestions, to search for users and groups existing in an ownCloud server.
 */
public class UsersAndGroupsSearchProvider extends ContentProvider {
public class UsersAndGroupsSearchProvider  {

    private static final String TAG = UsersAndGroupsSearchProvider.class.getSimpleName();

@@ -93,40 +87,17 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {

    private UriMatcher mUriMatcher;

    private OwnCloudClient client;
    private ShareRepository repository;
    private Account account;
    private Context context;

    public UsersAndGroupsSearchProvider(Account account, OwnCloudClient client) {
    public UsersAndGroupsSearchProvider(Context context, Account account, ShareRepository repository) {
        this.context = context;
        this.account = account;
        this.client = client;
    }

    private static final Map<String, ShareType> sShareTypes = new HashMap<>();

    public static ShareType getShareType(String authority) {

        return sShareTypes.get(authority);
    }

    private static void setActionShareWith(@NonNull Context context) {
        ACTION_SHARE_WITH = context.getString(R.string.users_and_groups_share_with);
    }

    @Nullable
    @Override
    public String getType(@NonNull Uri uri) {
        // TODO implement
        return null;
    }
        this.repository = repository;

    @Override
    public boolean onCreate() {
        if (getContext() == null) {
            return false;
        }

        AUTHORITY = getContext().getString(R.string.users_and_groups_search_authority);
        setActionShareWith(getContext());
        AUTHORITY = context.getString(R.string.users_and_groups_search_authority);
        setActionShareWith(context);
        DATA_USER = AUTHORITY + ".data.user";
        DATA_GROUP = AUTHORITY + ".data.group";
        DATA_ROOM = AUTHORITY + ".data.room";
@@ -143,223 +114,33 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {

        mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
        mUriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH);

        return true;
    }

    /**
     * returns sharee from server
     *
     * Reference: http://developer.android.com/guide/topics/search/adding-custom-suggestions.html#CustomContentProvider
     *
     * @param uri           Content {@link Uri}, formatted as "content://com.nextcloud.android.providers.UsersAndGroupsSearchProvider/"
     *                      + {@link android.app.SearchManager#SUGGEST_URI_PATH_QUERY} + "/" +
     *                      'userQuery'
     * @param projection    Expected to be NULL.
     * @param selection     Expected to be NULL.
     * @param selectionArgs Expected to be NULL.
     * @param sortOrder     Expected to be NULL.
     * @return Cursor with possible sharees in the server that match 'query'.
     */
    @Nullable
    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        Log_OC.d(TAG, "query received in thread " + Thread.currentThread().getName());

        int match = mUriMatcher.match(uri);
        if (match == SEARCH) {
            return searchForUsersOrGroups(uri);
        }
        return null;
    }

    private Cursor searchForUsersOrGroups(Uri uri) {

        // TODO check searchConfig and filter results
        Log.d(TAG, "searchForUsersOrGroups: searchConfig only users: " +  UsersAndGroupsSearchConfig.INSTANCE.getSearchOnlyUsers());

        String lastPathSegment = uri.getLastPathSegment();

        if (lastPathSegment == null) {
            throw new IllegalArgumentException("Wrong URI passed!");
        }

        String userQuery = lastPathSegment.toLowerCase(Locale.ROOT);

        // ApiProvider.getInstance().getFilesAPI(getContext(), account, ApiVersion.API_VERSION_1_0).getDirectEditingInfo();


        // request to the OC server about users and groups matching userQuery
        GetShareesRemoteOperation searchRequest = new GetShareesRemoteOperation(userQuery,
                REQUESTED_PAGE,
                RESULTS_PER_PAGE);
        RemoteOperationResult<ArrayList<JSONObject>> result = searchRequest.execute(client);
        List<JSONObject> names = new ArrayList<>();
    private static final Map<String, ShareType> sShareTypes = new HashMap<>();

        if (result.isSuccess()) {
            names = result.getResultData();
        } else {
            showErrorMessage(result);
    public static ShareType getShareType(String authority) {
        return sShareTypes.get(authority);
    }

        MatrixCursor response = null;
        // convert the responses from the OC server to the expected format
        if (names.size() > 0) {
            if (getContext() == null) {
                throw new IllegalArgumentException("Context may not be null!");
    private static void setActionShareWith(@NonNull Context context) {
        ACTION_SHARE_WITH = context.getString(R.string.users_and_groups_share_with);
    }

            response = new MatrixCursor(COLUMNS);

            Uri userBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_USER).build();
            Uri groupBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_GROUP).build();
            Uri roomBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_ROOM).build();
            Uri remoteBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_REMOTE).build();
            Uri emailBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_EMAIL).build();
            Uri circleBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_CIRCLE).build();

    public ShareesData searchForUsersOrGroups(String userQuery) {
        final SingleSignOnAccount ssoAcc;
        try {
                Iterator<JSONObject> namesIt = names.iterator();
                JSONObject item;
                String displayName;
                String subline = null;
                Object icon = 0;
                Uri dataUri;
                int count = 0;
                while (namesIt.hasNext()) {
                    item = namesIt.next();
                    dataUri = null;
                    displayName = null;
                    String userName = item.getString(GetShareesRemoteOperation.PROPERTY_LABEL);
                    String name = item.isNull("name") ? "" : item.getString("name");
                    JSONObject value = item.getJSONObject(GetShareesRemoteOperation.NODE_VALUE);
                    ShareType type = ShareType.fromValue(value.getInt(GetShareesRemoteOperation.PROPERTY_SHARE_TYPE));
                    String shareWith = value.getString(GetShareesRemoteOperation.PROPERTY_SHARE_WITH);

                    Status status;
                    JSONObject statusObject = item.optJSONObject(PROPERTY_STATUS);

                    if (statusObject != null) {
                        status = new Status(
                                StatusType.valueOf(statusObject.getString(PROPERTY_STATUS).toUpperCase(Locale.US)),
                                statusObject.isNull(PROPERTY_MESSAGE) ? "" : statusObject.getString(PROPERTY_MESSAGE),
                                statusObject.isNull(PROPERTY_ICON) ? "" : statusObject.getString(PROPERTY_ICON),
                                statusObject.isNull(PROPERTY_CLEAR_AT) ? -1 : statusObject.getLong(PROPERTY_CLEAR_AT));
                    } else {
                        status = new Status(StatusType.OFFLINE, "", "", -1);
                    }

                    if ( UsersAndGroupsSearchConfig.INSTANCE.getSearchOnlyUsers() && type != ShareType.USER) {
                        // skip all types but users, as E2E secure share is only allowed to users on same server
                        continue;
                    }

                    switch (type) {
                        case GROUP:
                            displayName = userName;
                            icon = R.drawable.ic_group;
                            dataUri = Uri.withAppendedPath(groupBaseUri, shareWith);
                            break;

                        case FEDERATED:
                            if (true) {
                                icon = R.drawable.ic_account_circle_grey_24dp;
                                dataUri = Uri.withAppendedPath(remoteBaseUri, shareWith);

                                if (userName.equals(shareWith)) {
                                    displayName = name;
                                    subline = getContext().getString(R.string.remote);
                                } else {
                                    String[] uriSplitted = shareWith.split("@");
                                    displayName = name;
                                    subline = getContext().getString(R.string.share_known_remote_on_clarification,
                                            uriSplitted[uriSplitted.length - 1]);
                                }
                            }
                            break;

                        case USER:
                            displayName = userName;
                            subline = (status.getMessage() == null || status.getMessage().isEmpty()) ? null :
                                    status.getMessage();
                            Uri.Builder builder = Uri.parse("content://" + AUTHORITY + "/icon").buildUpon();

                            builder.appendQueryParameter("shareWith", shareWith);
                            builder.appendQueryParameter("displayName", displayName);
                            builder.appendQueryParameter("status", status.getStatus().toString());

                            if (!TextUtils.isEmpty(status.getIcon()) && !"null".equals(status.getIcon())) {
                                builder.appendQueryParameter("icon", status.getIcon());
                            }

                            icon = builder.build();

                            dataUri = Uri.withAppendedPath(userBaseUri, shareWith);
                            break;

                        case EMAIL:
                            icon = R.drawable.ic_email;
                            displayName = name;
                            subline = shareWith;
                            dataUri = Uri.withAppendedPath(emailBaseUri, shareWith);
                            break;

                        case ROOM:
                            icon = R.drawable.ic_talk;
                            displayName = userName;
                            dataUri = Uri.withAppendedPath(roomBaseUri, shareWith);
                            break;

                        case CIRCLE:
                            icon = R.drawable.ic_circles;
                            displayName = userName;
                            dataUri = Uri.withAppendedPath(circleBaseUri, shareWith);
                            break;

                        default:
                            break;
                    }

                    if (displayName != null && dataUri != null) {
                        response.newRow()
                                .add(count++)             // BaseColumns._ID
                                .add(displayName)         // SearchManager.SUGGEST_COLUMN_TEXT_1
                                .add(subline)             // SearchManager.SUGGEST_COLUMN_TEXT_2
                                .add(icon)                // SearchManager.SUGGEST_COLUMN_ICON_1
                                .add(dataUri);
                    }
                }

            } catch (JSONException e) {
                Log_OC.e(TAG, "Exception while parsing data of users/groups", e);
            }
        }

        return response;
            ssoAcc = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
            return repository.getSharees(ssoAcc, userQuery, REQUESTED_PAGE, RESULTS_PER_PAGE).blockingGet();
        } catch (Exception e) {
            Log_OC.e(TAG, "Exception while searching", e);
        }

    @Nullable
    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        return null;
    }

    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }

    @Nullable
    @Override
    public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException {
        try {
            Bitmap avatar = Glide.with(getContext())
            Bitmap avatar = Glide.with(context)
                    .asBitmap()
                    .load(new SingleSignOnUrl(account.getAccountName(), account.getUrl() + "/index.php/avatar/" + Uri.encode(account.getUserName()) + "/64"))
                    .placeholder(R.drawable.ic_account_circle_grey_24dp)
@@ -369,7 +150,7 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
                    .get();

            // create a file to write bitmap data
            File f = new File(getContext().getCacheDir(), "test");
            File f = new File(context.getCacheDir(), "test");
            try {
                if (f.exists()) {
                    if (!f.delete()) {
@@ -404,30 +185,4 @@ public class UsersAndGroupsSearchProvider extends ContentProvider {
            throw new RuntimeException(e);
        }
    }

    /**
     * Show error message
     *
     * @param result Result with the failure information.
     */
    private void showErrorMessage(final RemoteOperationResult result) {
        Handler handler = new Handler(Looper.getMainLooper());
        handler.post(() -> {
            // The Toast must be shown in the main thread to grant that will be hidden correctly; otherwise
            // the thread may die before, an exception will occur, and the message will be left on the screen
            // until the app dies

            Context context = getContext();

            if (context == null) {
                throw new IllegalArgumentException("Context may not be null!");
            }

            Toast.makeText(getContext().getApplicationContext(),
                    result.getMessage(),
                    Toast.LENGTH_SHORT).show();
        });
    }


}
+34 −0
Original line number Diff line number Diff line
package it.niedermann.owncloud.notes.share.model

data class ShareesData(
    val exact: ExactMatches,
    val users: List<ShareeItem>,
    val groups: List<ShareeItem>,
    val remotes: List<ShareeItem>,
    val remote_groups: List<ShareeItem>,
    val emails: List<ShareeItem>,
    val circles: List<ShareeItem>,
    val rooms: List<ShareeItem>,
    val lookup: List<ShareeItem>,
    val lookupEnabled: Boolean
)

data class ExactMatches(
    val users: List<ShareeItem>,
    val groups: List<ShareeItem>,
    val remotes: List<ShareeItem>,
    val remote_groups: List<ShareeItem>,
    val emails: List<ShareeItem>,
    val circles: List<ShareeItem>,
    val rooms: List<ShareeItem>
)

data class ShareeItem(
    val label: String,
    val value: ShareeValue
)

data class ShareeValue(
    val shareType: Double,
    val shareWith: String
)