UsersAndGroupsSearchProvider.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author David A. Velasco
  5. * @author Juan Carlos González Cabrero
  6. * Copyright (C) 2015 ownCloud Inc.
  7. * <p/>
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License version 2,
  10. * as published by the Free Software Foundation.
  11. * <p/>
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. * <p/>
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  19. */
  20. package com.owncloud.android.providers;
  21. import android.accounts.Account;
  22. import android.app.SearchManager;
  23. import android.content.ContentProvider;
  24. import android.content.ContentValues;
  25. import android.content.UriMatcher;
  26. import android.database.Cursor;
  27. import android.database.MatrixCursor;
  28. import android.net.Uri;
  29. import android.os.Handler;
  30. import android.os.Looper;
  31. import android.provider.BaseColumns;
  32. import android.support.annotation.Nullable;
  33. import android.widget.Toast;
  34. import com.owncloud.android.R;
  35. import com.owncloud.android.authentication.AccountUtils;
  36. import com.owncloud.android.datamodel.FileDataStorageManager;
  37. import com.owncloud.android.lib.common.operations.RemoteOperationResult;
  38. import com.owncloud.android.lib.common.utils.Log_OC;
  39. import com.owncloud.android.lib.resources.shares.GetRemoteShareesOperation;
  40. import com.owncloud.android.lib.resources.shares.ShareType;
  41. import com.owncloud.android.utils.ErrorMessageAdapter;
  42. import org.json.JSONException;
  43. import org.json.JSONObject;
  44. import java.util.ArrayList;
  45. import java.util.HashMap;
  46. import java.util.Iterator;
  47. import java.util.List;
  48. /**
  49. * Content provider for search suggestions, to search for users and groups existing in an ownCloud server.
  50. */
  51. public class UsersAndGroupsSearchProvider extends ContentProvider {
  52. private static final String TAG = UsersAndGroupsSearchProvider.class.getSimpleName();
  53. private static final String[] COLUMNS = {
  54. BaseColumns._ID,
  55. SearchManager.SUGGEST_COLUMN_TEXT_1,
  56. SearchManager.SUGGEST_COLUMN_ICON_1,
  57. SearchManager.SUGGEST_COLUMN_INTENT_DATA
  58. };
  59. private static final int SEARCH = 1;
  60. private static final int RESULTS_PER_PAGE = 50;
  61. private static final int REQUESTED_PAGE = 1;
  62. public static String AUTHORITY;
  63. public static String ACTION_SHARE_WITH;
  64. public static final String CONTENT = "content";
  65. public static String DATA_USER;
  66. public static String DATA_GROUP;
  67. public static String DATA_REMOTE;
  68. private UriMatcher mUriMatcher;
  69. private static HashMap<String, ShareType> sShareTypes = new HashMap<>();
  70. public static ShareType getShareType(String authority) {
  71. return sShareTypes.get(authority);
  72. }
  73. @Nullable
  74. @Override
  75. public String getType(Uri uri) {
  76. // TODO implement
  77. return null;
  78. }
  79. @Override
  80. public boolean onCreate() {
  81. AUTHORITY = getContext().getResources().getString(R.string.users_and_groups_search_authority);
  82. ACTION_SHARE_WITH = getContext().getResources().getString(R.string.users_and_groups_share_with);
  83. DATA_USER = AUTHORITY + ".data.user";
  84. DATA_GROUP = AUTHORITY + ".data.group";
  85. DATA_REMOTE = AUTHORITY + ".data.remote";
  86. sShareTypes.put(DATA_USER, ShareType.USER);
  87. sShareTypes.put(DATA_GROUP, ShareType.GROUP);
  88. sShareTypes.put(DATA_REMOTE, ShareType.FEDERATED);
  89. mUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
  90. mUriMatcher.addURI(AUTHORITY, SearchManager.SUGGEST_URI_PATH_QUERY + "/*", SEARCH);
  91. return true;
  92. }
  93. /**
  94. * TODO description
  95. * <p/>
  96. * Reference: http://developer.android.com/guide/topics/search/adding-custom-suggestions.html#CustomContentProvider
  97. *
  98. * @param uri Content {@link Uri}, formattted as
  99. * "content://com.nextcloud.android.providers.UsersAndGroupsSearchProvider/" +
  100. * {@link android.app.SearchManager#SUGGEST_URI_PATH_QUERY} + "/" + 'userQuery'
  101. * @param projection Expected to be NULL.
  102. * @param selection Expected to be NULL.
  103. * @param selectionArgs Expected to be NULL.
  104. * @param sortOrder Expected to be NULL.
  105. * @return Cursor with users and groups in the ownCloud server that match 'userQuery'.
  106. */
  107. @Nullable
  108. @Override
  109. public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
  110. Log_OC.d(TAG, "query received in thread " + Thread.currentThread().getName());
  111. int match = mUriMatcher.match(uri);
  112. switch (match) {
  113. case SEARCH:
  114. return searchForUsersOrGroups(uri);
  115. default:
  116. return null;
  117. }
  118. }
  119. private Cursor searchForUsersOrGroups(Uri uri) {
  120. MatrixCursor response = null;
  121. String userQuery = uri.getLastPathSegment().toLowerCase();
  122. /// need to trust on the AccountUtils to get the current account since the query in the client side is not
  123. /// directly started by our code, but from SearchView implementation
  124. Account account = AccountUtils.getCurrentOwnCloudAccount(getContext());
  125. /// request to the OC server about users and groups matching userQuery
  126. GetRemoteShareesOperation searchRequest = new GetRemoteShareesOperation(
  127. userQuery, REQUESTED_PAGE, RESULTS_PER_PAGE
  128. );
  129. RemoteOperationResult result = searchRequest.execute(account, getContext());
  130. List<JSONObject> names = new ArrayList<JSONObject>();
  131. if (result.isSuccess()) {
  132. for (Object o : result.getData()) {
  133. // Get JSonObjects from response
  134. names.add((JSONObject) o);
  135. }
  136. } else {
  137. showErrorMessage(result);
  138. }
  139. /// convert the responses from the OC server to the expected format
  140. if (names.size() > 0) {
  141. response = new MatrixCursor(COLUMNS);
  142. Iterator<JSONObject> namesIt = names.iterator();
  143. JSONObject item;
  144. String displayName = null;
  145. int icon = 0;
  146. Uri dataUri = null;
  147. int count = 0;
  148. Uri userBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_USER).build();
  149. Uri groupBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_GROUP).build();
  150. Uri remoteBaseUri = new Uri.Builder().scheme(CONTENT).authority(DATA_REMOTE).build();
  151. FileDataStorageManager manager = new FileDataStorageManager(account, getContext().getContentResolver());
  152. boolean federatedShareAllowed = manager.getCapability(account.name).getFilesSharingFederationOutgoing()
  153. .isTrue();
  154. try {
  155. while (namesIt.hasNext()) {
  156. item = namesIt.next();
  157. String userName = item.getString(GetRemoteShareesOperation.PROPERTY_LABEL);
  158. JSONObject value = item.getJSONObject(GetRemoteShareesOperation.NODE_VALUE);
  159. int type = value.getInt(GetRemoteShareesOperation.PROPERTY_SHARE_TYPE);
  160. String shareWith = value.getString(GetRemoteShareesOperation.PROPERTY_SHARE_WITH);
  161. if (ShareType.GROUP.getValue() == type) {
  162. displayName = getContext().getString(R.string.share_group_clarification, userName);
  163. icon = R.drawable.ic_group;
  164. dataUri = Uri.withAppendedPath(groupBaseUri, shareWith);
  165. } else if (ShareType.FEDERATED.getValue() == type && federatedShareAllowed) {
  166. icon = R.drawable.ic_user;
  167. if (userName.equals(shareWith)) {
  168. displayName = getContext().getString(R.string.share_remote_clarification, userName);
  169. } else {
  170. String[] uriSplitted = shareWith.split("@");
  171. displayName = getContext().getString(R.string.share_known_remote_clarification, userName,
  172. uriSplitted[uriSplitted.length - 1]);
  173. }
  174. dataUri = Uri.withAppendedPath(remoteBaseUri, shareWith);
  175. } else if (ShareType.USER.getValue() == type) {
  176. displayName = userName;
  177. icon = R.drawable.ic_user;
  178. dataUri = Uri.withAppendedPath(userBaseUri, shareWith);
  179. }
  180. if (displayName != null && dataUri != null) {
  181. response.newRow()
  182. .add(count++) // BaseColumns._ID
  183. .add(displayName) // SearchManager.SUGGEST_COLUMN_TEXT_1
  184. .add(icon) // SearchManager.SUGGEST_COLUMN_ICON_1
  185. .add(dataUri);
  186. }
  187. }
  188. } catch (JSONException e) {
  189. Log_OC.e(TAG, "Exception while parsing data of users/groups", e);
  190. }
  191. }
  192. return response;
  193. }
  194. @Nullable
  195. @Override
  196. public Uri insert(Uri uri, ContentValues values) {
  197. // TODO implementation
  198. return null;
  199. }
  200. @Override
  201. public int delete(Uri uri, String selection, String[] selectionArgs) {
  202. // TODO implementation
  203. return 0;
  204. }
  205. @Override
  206. public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
  207. // TODO implementation
  208. return 0;
  209. }
  210. /**
  211. * Show error message
  212. *
  213. * @param result Result with the failure information.
  214. */
  215. public void showErrorMessage(final RemoteOperationResult result) {
  216. Handler handler = new Handler(Looper.getMainLooper());
  217. handler.post(new Runnable() {
  218. @Override
  219. public void run() {
  220. // The Toast must be shown in the main thread to grant that will be hidden correctly; otherwise
  221. // the thread may die before, an exception will occur, and the message will be left on the screen
  222. // until the app dies
  223. Toast.makeText(
  224. getContext().getApplicationContext(),
  225. ErrorMessageAdapter.getErrorCauseMessage(
  226. result,
  227. null,
  228. getContext().getResources()
  229. ),
  230. Toast.LENGTH_SHORT
  231. ).show();
  232. }
  233. });
  234. }
  235. }