diff --git a/owncloud-android-library b/owncloud-android-library index dc14474c51d..a13b5ec17c9 160000 --- a/owncloud-android-library +++ b/owncloud-android-library @@ -1 +1 @@ -Subproject commit dc14474c51d19884629a190e24a8de42aba1a268 +Subproject commit a13b5ec17c9e9550e519a884fca0d428c4f96507 diff --git a/res/layout/account_item.xml b/res/layout/account_item.xml index fb9f85b57ff..f6a79fe5863 100644 --- a/res/layout/account_item.xml +++ b/res/layout/account_item.xml @@ -18,6 +18,7 @@ --> @@ -31,7 +32,7 @@ android:layout_centerInParent="true" android:layout_marginLeft="@dimen/standard_margin" android:layout_marginStart="@dimen/standard_margin" - android:src="@drawable/ic_menu_archive" + android:src="@drawable/ic_account_plus" /> { @Override @@ -122,6 +123,14 @@ public static void addBitmapToCache(String key, Bitmap bitmap) { } } + public static void removeBitmapFromCache(String key) { + synchronized (mThumbnailsDiskCacheLock) { + if (mThumbnailCache != null) { + mThumbnailCache.removeKey(key); + } + } + } + public static Bitmap getBitmapFromDiskCache(String key) { synchronized (mThumbnailsDiskCacheLock) { @@ -360,34 +369,72 @@ private Bitmap doFileInBackground() { } - public static class AvatarGenerationTask extends AsyncTask { + /** + * Show the avatar corresponding to the received account in an {@link ImageView} ir {@link MenuItem}. + * + * The avatar is loaded if available in the cache and bound to the received UI element. The avatar is not + * fetched from the server if not available, unless the parameter 'fetchFromServer' is set to 'true'. + * + * If there is no avatar stored and cannot be fetched, a colored icon is generated with the first + * letter of the account username. + * + * If this is not possible either, a predefined user icon is bound instead. + */ + public static class GetAvatarTask extends AsyncTask { private final WeakReference mImageViewReference; private final WeakReference mMenuItemReference; private Account mAccount; + private float mDisplayRadius; + private boolean mFetchFromServer; + private String mUsername; private OwnCloudClient mClient; - public AvatarGenerationTask(ImageView imageView, Account account) { + /** + * Builds an instance to show the avatar corresponding to the received account in an {@link ImageView}. + * + * @param imageView The {@link ImageView} to bind the avatar to. + * @param account OC account which avatar will be shown. + * @param displayRadius The radius of the circle where the avatar will be clipped into. + * @param fetchFromServer When 'true', if there is no avatar stored in the cache, it's fetched from + * the server. When 'false', server is not accessed, the fallback avatar is + * generated instead. USE WITH CARE, probably to be removed in the future. + */ + public GetAvatarTask(ImageView imageView, Account account, float displayRadius, boolean fetchFromServer){ if (account == null) { throw new IllegalArgumentException("Received NULL account"); } mMenuItemReference = null; mImageViewReference = new WeakReference<>(imageView); mAccount = account; + mDisplayRadius = displayRadius; + mFetchFromServer = fetchFromServer; } - public AvatarGenerationTask(MenuItem menuItem, Account account) { + /** + * Builds an instance to show the avatar corresponding to the received account in an {@link MenuItem}. + * + * @param menuItem The {@ImageView} to bind the avatar to. + * @param account OC account which avatar will be shown. + * @param displayRadius The radius of the circle where the avatar will be clipped into. + * @param fetchFromServer When 'true', if there is no avatar stored in the cache, it's fetched from + * the server. When 'false', server is not accessed, the fallback avatar is + * generated instead. USE WITH CARE, probably to be removed in the future. + */ + public GetAvatarTask(MenuItem menuItem, Account account, float displayRadius, boolean fetchFromServer) { if (account == null) { throw new IllegalArgumentException("Received NULL account"); } mImageViewReference = null; mMenuItemReference = new WeakReference<>(menuItem); mAccount = account; + mDisplayRadius = displayRadius; + mFetchFromServer = fetchFromServer; } @Override - protected Bitmap doInBackground(Object... params) { - Bitmap thumbnail = null; + protected Drawable doInBackground(Object... params) { + Drawable thumbnail = null; try { OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, @@ -409,24 +456,30 @@ protected Bitmap doInBackground(Object... params) { return thumbnail; } - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null) { - if (mImageViewReference != null) { - ImageView imageView = mImageViewReference.get(); - AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(imageView); - if (this == avatarWorkerTask) { - if (String.valueOf(imageView.getTag()).equals(mUsername)) { - imageView.setImageBitmap(bitmap); - } + @Override + protected void onPostExecute(Drawable avatar) { + if (mImageViewReference != null) { + ImageView imageView = mImageViewReference.get(); + if (imageView != null) { + if (avatar != null) { + imageView.setImageDrawable(avatar); + } else { + // really needed? + imageView.setImageResource( + R.drawable.ic_account_circle + ); } - } else if (mMenuItemReference != null) { - MenuItem menuItem = mMenuItemReference.get(); - AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(menuItem); - if (this == avatarWorkerTask) { - if (String.valueOf(menuItem.getTitle()).equals(mUsername)) { - menuItem.setIcon(new BitmapDrawable(MainApp.getAppContext().getResources(), - bitmap)); - } + } + } else if (mMenuItemReference != null) { + MenuItem menuItem = mMenuItemReference.get(); + if (menuItem != null) { + if (avatar != null) { + menuItem.setIcon(avatar); + } else { + // really needed + menuItem.setIcon( + R.drawable.ic_account_circle + ); } } } @@ -442,56 +495,98 @@ private int getAvatarDimension(){ return Math.round(r.getDimension(R.dimen.file_avatar_size)); } - private Bitmap doAvatarInBackground() { + private Drawable doAvatarInBackground() { + + Drawable avatarDrawable = null; final String imageKey = "a_" + mUsername; // Check disk cache in background thread - Bitmap avatar = getBitmapFromDiskCache(imageKey); + Bitmap avatarBitmap = getBitmapFromDiskCache(imageKey); - // Not found in disk cache - if (avatar == null) { + if (avatarBitmap != null) { + avatarDrawable = BitmapUtils.bitmapToCircularBitmapDrawable( + MainApp.getAppContext().getResources(), + avatarBitmap + ); - int px = getAvatarDimension(); + } else { + // Not found in disk cache + if (mFetchFromServer) { + int px = getAvatarDimension(); - // Download avatar from server - OwnCloudVersion serverOCVersion = AccountUtils.getServerVersion(mAccount); - if (mClient != null && serverOCVersion != null) { - if (serverOCVersion.supportsRemoteThumbnails()) { - GetMethod get = null; - try { - String uri = mClient.getBaseUri() + "" + + // Download avatar from server + OwnCloudVersion serverOCVersion = AccountUtils.getServerVersion(mAccount); + if (mClient != null && serverOCVersion != null) { + if (serverOCVersion.supportsRemoteThumbnails()) { + GetMethod get = null; + try { + String uri = mClient.getBaseUri() + "" + "/index.php/avatar/" + AccountUtils.getUsernameOfAccount(mUsername) + "/" + px; - Log_OC.d("Avatar", "URI: " + uri); - get = new GetMethod(uri); - int status = mClient.executeMethod(get); - if (status == HttpStatus.SC_OK) { - InputStream inputStream = get.getResponseBodyAsStream(); - Bitmap bitmap = BitmapFactory.decodeStream(inputStream); - avatar = ThumbnailUtils.extractThumbnail(bitmap, px, px); - - // Add avatar to cache - if (avatar != null) { - addBitmapToCache(imageKey, avatar); + Log_OC.d("Avatar", "URI: " + uri); + get = new GetMethod(uri); + int status = mClient.executeMethod(get); + if (status == HttpStatus.SC_OK) { + InputStream inputStream = get.getResponseBodyAsStream(); + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); + avatarBitmap = ThumbnailUtils.extractThumbnail(bitmap, px, px); + + // Add avatar to cache + if (avatarBitmap != null) { + addBitmapToCache(imageKey, avatarBitmap); + } + } else { + mClient.exhaustResponse(get.getResponseBodyAsStream()); + } + } catch (Exception e) { + Log_OC.e(TAG, "Error downloading avatar", e); + } finally { + if (get != null) { + get.releaseConnection(); } - } else { - mClient.exhaustResponse(get.getResponseBodyAsStream()); - } - } catch (Exception e) { - Log_OC.e(TAG, "Error downloading avatar", e); - } finally { - if (get != null) { - get.releaseConnection(); } + } else { + Log_OC.d(TAG, "Server too old"); } - } else { - Log_OC.d(TAG, "Server too old"); + } + } + if (avatarBitmap != null) { + avatarDrawable = BitmapUtils.bitmapToCircularBitmapDrawable( + MainApp.getAppContext().getResources(), + avatarBitmap + ); + + } else { + // generate placeholder from user name + try { + avatarDrawable = DefaultAvatarTextDrawable.createAvatar(mUsername, mDisplayRadius); + + } catch (Exception e) { + // nothing to do, return null to apply default icon + Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); } } } - return avatar; + return avatarDrawable; + } + + } + + public static String addAvatarToCache(String accountName, byte[] avatarData, int dimension) { + final String imageKey = "a_" + accountName; + + Bitmap bitmap = BitmapFactory.decodeByteArray(avatarData, 0, avatarData.length); + bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension); + // Add avatar to cache + if (bitmap != null) { + addBitmapToCache(imageKey, bitmap); } + return imageKey; + } + public static void removeAvatarFromCache(String accountName) { + final String imageKey = "a_" + accountName; + removeBitmapFromCache(imageKey); } public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageView) { @@ -514,7 +609,7 @@ public static boolean cancelPotentialThumbnailWork(Object file, ImageView imageV } public static boolean cancelPotentialAvatarWork(Object file, ImageView imageView) { - final AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(imageView); + final GetAvatarTask avatarWorkerTask = getAvatarWorkerTask(imageView); if (avatarWorkerTask != null) { final Object usernameData = avatarWorkerTask.mUsername; @@ -533,7 +628,7 @@ public static boolean cancelPotentialAvatarWork(Object file, ImageView imageView } public static boolean cancelPotentialAvatarWork(Object file, MenuItem menuItem) { - final AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(menuItem); + final GetAvatarTask avatarWorkerTask = getAvatarWorkerTask(menuItem); if (avatarWorkerTask != null) { final Object usernameData = avatarWorkerTask.mUsername; @@ -562,21 +657,21 @@ private static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) return null; } - private static AvatarGenerationTask getAvatarWorkerTask(ImageView imageView) { + private static GetAvatarTask getAvatarWorkerTask(ImageView imageView) { if (imageView != null) { return getAvatarWorkerTask(imageView.getDrawable()); } return null; } - private static AvatarGenerationTask getAvatarWorkerTask(MenuItem menuItem) { + private static GetAvatarTask getAvatarWorkerTask(MenuItem menuItem) { if (menuItem != null) { return getAvatarWorkerTask(menuItem.getIcon()); } return null; } - private static AvatarGenerationTask getAvatarWorkerTask(Drawable drawable) { + private static GetAvatarTask getAvatarWorkerTask(Drawable drawable) { if (drawable instanceof AsyncAvatarDrawable) { final AsyncAvatarDrawable asyncDrawable = (AsyncAvatarDrawable) drawable; return asyncDrawable.getAvatarWorkerTask(); @@ -602,18 +697,18 @@ public ThumbnailGenerationTask getBitmapWorkerTask() { } public static class AsyncAvatarDrawable extends BitmapDrawable { - private final WeakReference avatarWorkerTaskReference; + private final WeakReference avatarWorkerTaskReference; public AsyncAvatarDrawable( - Resources res, Bitmap bitmap, AvatarGenerationTask avatarWorkerTask + Resources res, Bitmap bitmap, GetAvatarTask avatarWorkerTask ) { super(res, bitmap); avatarWorkerTaskReference = - new WeakReference(avatarWorkerTask); + new WeakReference(avatarWorkerTask); } - public AvatarGenerationTask getAvatarWorkerTask() { + public GetAvatarTask getAvatarWorkerTask() { return avatarWorkerTaskReference.get(); } } diff --git a/src/com/owncloud/android/datamodel/UserProfile.java b/src/com/owncloud/android/datamodel/UserProfile.java new file mode 100644 index 00000000000..2f0324c4364 --- /dev/null +++ b/src/com/owncloud/android/datamodel/UserProfile.java @@ -0,0 +1,93 @@ +package com.owncloud.android.datamodel; + +import android.support.annotation.Nullable; + +/** + * ownCloud Android client application + * + * @author David A. Velasco + * Copyright (C) 2016 ownCloud GmbH. + * + * 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 . + * + */ +public class UserProfile { + + private long mId; + private String mAccountName; + + private String mUserId; + private String mDisplayName = ""; + private String mEmail = ""; + + private UserAvatar mAvatar; + + public UserProfile(String accountName, String userId, String displayName, String email) { + mAccountName = accountName; + mUserId = userId; + mDisplayName = displayName; + mEmail = email; + + mAvatar = null; + } + + public String getAccountName() { + return mAccountName; + } + + public String getUserId() { + return mUserId; + } + + public String getDisplayName() { + return mDisplayName; + } + + public String getEmail() { + return mEmail; + } + + @Nullable + public UserAvatar getAvatar() { + return mAvatar; + } + + public void setAvatar(UserAvatar avatar) { + mAvatar = avatar; + } + + public static class UserAvatar { + + private String mCacheKey; + private String mMimeType; + private String mEtag; + + public UserAvatar(String cacheKey, String mimeType, String etag) { + mCacheKey = cacheKey; + mMimeType = mimeType; + mEtag = etag; + } + + public String getCacheKey() { + return mCacheKey; + } + + public String getMimeType() { + return mMimeType; + } + + public String getEtag() { + return mEtag; + } + } +} diff --git a/src/com/owncloud/android/datamodel/UserProfilesRepository.java b/src/com/owncloud/android/datamodel/UserProfilesRepository.java new file mode 100644 index 00000000000..4dbbd6899b3 --- /dev/null +++ b/src/com/owncloud/android/datamodel/UserProfilesRepository.java @@ -0,0 +1,194 @@ +package com.owncloud.android.datamodel; + +import android.content.ContentValues; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import com.owncloud.android.MainApp; +import com.owncloud.android.db.ProviderMeta; +import com.owncloud.android.lib.common.utils.Log_OC; + +import java.io.File; + +/** + * ownCloud Android client application + * + * Copyright (C) 2016 ownCloud GmbH. + * + * 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 . + * + */ + +/** + * Minimum to get things working. + * + * Working around FileContentProvider, we have no interest in exporting user profiles to other apps. + */ +public class UserProfilesRepository { + + private static final String TAG = UserProfilesRepository.class.getName(); + + private SQLiteDatabase mDb; + + public UserProfilesRepository() { + File dbFile = MainApp.getAppContext().getDatabasePath(ProviderMeta.DB_NAME); + mDb = SQLiteDatabase.openDatabase( + dbFile.getAbsolutePath(), + null, + SQLiteDatabase.OPEN_READWRITE + ); + } + + /** + * Persist a user profile. + * + * Minimum to get things working: only storing info about avatar. + * + * Working around ContentProvider + * + * @param userProfile User profile. + */ + public void update(UserProfile userProfile) { + + if (userProfile == null) { + throw new IllegalArgumentException("Received userProfile with NULL value"); + } + + if (userProfile.getAvatar() != null) { + // map avatar properties to columns + ContentValues avatarValues = new ContentValues(); + avatarValues.put( + ProviderMeta.ProviderTableMeta.USER_AVATARS__ACCOUNT_NAME, + userProfile.getAccountName() + ); + avatarValues.put( + ProviderMeta.ProviderTableMeta.USER_AVATARS__CACHE_KEY, + userProfile.getAvatar().getCacheKey() + ); + avatarValues.put( + ProviderMeta.ProviderTableMeta.USER_AVATARS__ETAG, + userProfile.getAvatar().getEtag() + ); + avatarValues.put( + ProviderMeta.ProviderTableMeta.USER_AVATARS__MIME_TYPE, + userProfile.getAvatar().getMimeType() + ); + + mDb.beginTransaction(); + try { + if (avatarExists(userProfile)) { + // not new, UPDATE + int count = mDb.update( + ProviderMeta.ProviderTableMeta.USER_AVATARS__TABLE_NAME, + avatarValues, + ProviderMeta.ProviderTableMeta.USER_AVATARS__ACCOUNT_NAME + "=?", + new String[]{String.valueOf(userProfile.getAccountName())} + ); + Log_OC.d(TAG, "Avatar updated"); + + } else { + // new, CREATE + mDb.insert( + ProviderMeta.ProviderTableMeta.USER_AVATARS__TABLE_NAME, + null, + avatarValues + ); + Log_OC.d(TAG, "Avatar inserted"); + } + mDb.setTransactionSuccessful(); + + } finally { + mDb.endTransaction(); + } + } + } + + /** + * Gets the information about a user avatar bound to an OC account. + * + * Shortcut method prevent retrieving a full {@link UserProfile}, + * specially now that {@link UserProfile}s are not really stored. Naughty trick. + * + * @param accountName Name of an OC account. + * @return Information about a user avatar bound to an OC account, or NULL if + * there is no avatar for the given account. + */ + public UserProfile.UserAvatar getAvatar(String accountName) { + UserProfile.UserAvatar avatar = null; + Cursor c = null; + try { + c = mDb.query( + ProviderMeta.ProviderTableMeta.USER_AVATARS__TABLE_NAME, + null, + ProviderMeta.ProviderTableMeta.USER_AVATARS__ACCOUNT_NAME + "=?", + new String[]{accountName}, + null, null, null + ); + if (c != null && c.moveToFirst()) { + avatar = new UserProfile.UserAvatar( + c.getString(c.getColumnIndex( + ProviderMeta.ProviderTableMeta.USER_AVATARS__CACHE_KEY + )), + c.getString(c.getColumnIndex( + ProviderMeta.ProviderTableMeta.USER_AVATARS__MIME_TYPE + )), + c.getString( + c.getColumnIndex(ProviderMeta.ProviderTableMeta.USER_AVATARS__ETAG + )) + ); + } // else, no avatar to return + } catch (Exception e) { + Log_OC.e(TAG, "Exception while querying avatar", e); + } finally { + if (c != null) { + c.close(); + } + } + return avatar; + } + + public void deleteAvatar(String accountName) { + try { + mDb.delete( + ProviderMeta.ProviderTableMeta.USER_AVATARS__TABLE_NAME, + ProviderMeta.ProviderTableMeta.USER_AVATARS__ACCOUNT_NAME + "=?", + new String[]{String.valueOf(accountName)} + ); + Log_OC.d(TAG, "Avatar deleted"); + + } catch (Exception e) { + Log_OC.e(TAG, "Exception while deleting avatar", e); + } + } + + private boolean avatarExists(UserProfile userProfile) { + boolean exists = false; + Cursor c = null; + try { + c = mDb.query( + ProviderMeta.ProviderTableMeta.USER_AVATARS__TABLE_NAME, + null, + ProviderMeta.ProviderTableMeta.USER_AVATARS__ACCOUNT_NAME + "=?", + new String[]{userProfile.getAccountName()}, + null, null, null + ); + exists = (c != null && c.moveToFirst()); + } finally { + if (c != null) { + c.close(); + } + } + return exists; + } + +} diff --git a/src/com/owncloud/android/db/ProviderMeta.java b/src/com/owncloud/android/db/ProviderMeta.java index e7cc5329a34..cfce7ce9456 100644 --- a/src/com/owncloud/android/db/ProviderMeta.java +++ b/src/com/owncloud/android/db/ProviderMeta.java @@ -33,7 +33,7 @@ public class ProviderMeta { public static final String DB_NAME = "filelist"; - public static final int DB_VERSION = 14; + public static final int DB_VERSION = 15; private ProviderMeta() { } @@ -43,6 +43,8 @@ static public class ProviderTableMeta implements BaseColumns { public static final String OCSHARES_TABLE_NAME = "ocshares"; public static final String CAPABILITIES_TABLE_NAME = "capabilities"; public static final String UPLOADS_TABLE_NAME = "list_of_uploads"; + public static final String USER_AVATARS__TABLE_NAME = "user_avatars"; + public static final Uri CONTENT_URI = Uri.parse("content://" + MainApp.getAuthority() + "/"); public static final Uri CONTENT_URI_FILE = Uri.parse("content://" @@ -151,5 +153,11 @@ static public class ProviderTableMeta implements BaseColumns { public static final String UPLOADS_DEFAULT_SORT_ORDER = ProviderTableMeta._ID + " collate nocase desc"; + + // Columns of user_avatars table + public static final String USER_AVATARS__ACCOUNT_NAME = "account_name"; + public static final String USER_AVATARS__CACHE_KEY = "cache_key"; + public static final String USER_AVATARS__ETAG = "etag"; + public static final String USER_AVATARS__MIME_TYPE = "mime_type"; } } diff --git a/src/com/owncloud/android/operations/GetUserProfileOperation.java b/src/com/owncloud/android/operations/GetUserProfileOperation.java index 0ece675254e..e5985093b71 100644 --- a/src/com/owncloud/android/operations/GetUserProfileOperation.java +++ b/src/com/owncloud/android/operations/GetUserProfileOperation.java @@ -21,15 +21,24 @@ import android.accounts.Account; import android.accounts.AccountManager; +import android.content.res.Resources; import com.owncloud.android.MainApp; +import com.owncloud.android.R; +import com.owncloud.android.datamodel.ThumbnailsCacheManager; +import com.owncloud.android.datamodel.UserProfile; +import com.owncloud.android.datamodel.UserProfilesRepository; import com.owncloud.android.lib.common.OwnCloudClient; import com.owncloud.android.lib.common.accounts.AccountUtils; import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; +import com.owncloud.android.lib.resources.users.GetRemoteUserAvatarOperation; import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation; import com.owncloud.android.lib.resources.users.GetRemoteUserInfoOperation.UserInfo; import com.owncloud.android.operations.common.SyncOperation; +import java.util.ArrayList; + /** * Get and save user's profile from the server. * @@ -37,6 +46,9 @@ */ public class GetUserProfileOperation extends SyncOperation { + private static final String TAG = GetUserProfileOperation.class.getName(); + + /** * Performs the operation. * @@ -51,22 +63,110 @@ public class GetUserProfileOperation extends SyncOperation { @Override protected RemoteOperationResult run(OwnCloudClient client) { - // get display name - GetRemoteUserInfoOperation getDisplayName = new GetRemoteUserInfoOperation(); - RemoteOperationResult result = getDisplayName.execute(client); - - if (result.isSuccess()) { - // store display name with account data - AccountManager accountManager = AccountManager.get(MainApp.getAppContext()); - UserInfo userInfo = (UserInfo) result.getData().get(0); - Account storedAccount = getStorageManager().getAccount(); - accountManager.setUserData( - storedAccount, - AccountUtils.Constants.KEY_DISPLAY_NAME, - userInfo.mDisplayName - ); + UserProfile userProfile = null; + RemoteOperationResult result = null; + + try { + /// get display name + GetRemoteUserInfoOperation getDisplayName = new GetRemoteUserInfoOperation(); + RemoteOperationResult remoteResult = getDisplayName.execute(client); + if (remoteResult.isSuccess()) { + // store display name with account data + AccountManager accountManager = AccountManager.get(MainApp.getAppContext()); + UserInfo userInfo = (UserInfo) remoteResult.getData().get(0); + Account storedAccount = getStorageManager().getAccount(); + accountManager.setUserData( + storedAccount, + AccountUtils.Constants.KEY_DISPLAY_NAME, // keep also there, for the moment + userInfo.mDisplayName + ); + + // map user info into UserProfile instance + userProfile = new UserProfile( + storedAccount.name, + userInfo.mId, + userInfo.mDisplayName, + userInfo.mEmail + ); + + /// get avatar (optional for success) + int dimension = getAvatarDimension(); + UserProfile.UserAvatar currentUserAvatar = + getUserProfilesRepository().getAvatar(storedAccount.name); + GetRemoteUserAvatarOperation getAvatarOperation = new GetRemoteUserAvatarOperation( + dimension, + (currentUserAvatar == null) ? "" : currentUserAvatar.getEtag() + ); + remoteResult = getAvatarOperation.execute(client); + if (remoteResult.isSuccess()) { + GetRemoteUserAvatarOperation.ResultData avatar = + (GetRemoteUserAvatarOperation.ResultData) remoteResult.getData().get(0); + + // + byte[] avatarData = avatar.getAvatarData(); + String avatarKey = ThumbnailsCacheManager.addAvatarToCache( + storedAccount.name, + avatarData, + dimension + ); + + UserProfile.UserAvatar userAvatar = new UserProfile.UserAvatar( + avatarKey, avatar.getMimeType(), avatar.getEtag() + ); + userProfile.setAvatar(userAvatar); + + } else if (remoteResult.getCode().equals( + RemoteOperationResult.ResultCode.FILE_NOT_FOUND + )) { + Log_OC.i(TAG, "No avatar available, removing cached copy"); + getUserProfilesRepository().deleteAvatar(storedAccount.name); + ThumbnailsCacheManager.removeAvatarFromCache(storedAccount.name); + + } // others are ignored, including 304 (not modified), so the avatar is only stored + // if changed in the server :D + + /// store userProfile + getUserProfilesRepository().update(userProfile); + + result = new RemoteOperationResult(RemoteOperationResult.ResultCode.OK); + ArrayList data = new ArrayList<>(); + data.add(userProfile); + result.setData(data); + + } else { + result = remoteResult; + } + } catch (Exception e) { + Log_OC.e(TAG, "Exception while getting user profile: ", e); + result = new RemoteOperationResult(e); } + return result; } + /** + * Converts size of file icon from dp to pixel + * @return int + */ + private int getAvatarDimension(){ + // Converts dp to pixel + Resources r = MainApp.getAppContext().getResources(); + return Math.round(r.getDimension(R.dimen.file_avatar_size)); + } + + + /** + * Really bad place to have this. Only here to prevent go further with refactoring. + * + * @return Reference to a {@link UserProfilesRepository}. + */ + private static UserProfilesRepository getUserProfilesRepository() { + if (sUserProfilesRepository == null) { + sUserProfilesRepository = new UserProfilesRepository(); + } + return sUserProfilesRepository; + } + + private static UserProfilesRepository sUserProfilesRepository; + } diff --git a/src/com/owncloud/android/operations/RefreshFolderOperation.java b/src/com/owncloud/android/operations/RefreshFolderOperation.java index 0669b8eb874..bd76b843f09 100644 --- a/src/com/owncloud/android/operations/RefreshFolderOperation.java +++ b/src/com/owncloud/android/operations/RefreshFolderOperation.java @@ -144,10 +144,10 @@ public RefreshFolderOperation(OCFile folder, mStorageManager = dataStorageManager; mAccount = account; mContext = context; - mForgottenLocalFiles = new HashMap(); + mForgottenLocalFiles = new HashMap<>(); mRemoteFolderChanged = false; mIgnoreETag = ignoreETag; - mFilesToSyncContents = new Vector(); + mFilesToSyncContents = new Vector<>(); } @@ -180,7 +180,7 @@ public List getChildren() { */ @Override protected RemoteOperationResult run(OwnCloudClient client) { - RemoteOperationResult result = null; + RemoteOperationResult result; mFailsInFavouritesFound = 0; mConflictsFound = 0; mForgottenLocalFiles.clear(); @@ -247,7 +247,7 @@ private void updateUserProfile() { if (!result.isSuccess()) { Log_OC.w(TAG, "Couldn't update user profile from server"); } else { - Log_OC.i(TAG, "Got display name: " + result.getData().get(0)); + Log_OC.i(TAG, "Got user profile"); } } @@ -261,7 +261,7 @@ private void updateCapabilities(){ private RemoteOperationResult checkForChanges(OwnCloudClient client) { mRemoteFolderChanged = true; - RemoteOperationResult result = null; + RemoteOperationResult result; String remotePath = mLocalFolder.getRemotePath(); Log_OC.d(TAG, "Checking changes in " + mAccount.name + remotePath); @@ -459,7 +459,7 @@ private void synchronizeData(ArrayList folderAndFiles, OwnCloudClient cl private void startContentSynchronizations( List filesToSyncContents, OwnCloudClient client ) { - RemoteOperationResult contentsResult = null; + RemoteOperationResult contentsResult; for (SynchronizeFileOperation op: filesToSyncContents) { contentsResult = op.execute(mStorageManager, mContext); // async if (!contentsResult.isSuccess()) { @@ -488,7 +488,7 @@ private void startContentSynchronizations( * the operation. */ private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) { - RemoteOperationResult result = null; + RemoteOperationResult result; // remote request GetRemoteSharesForFileOperation operation = @@ -497,7 +497,7 @@ private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) { if (result.isSuccess()) { // update local database - ArrayList shares = new ArrayList(); + ArrayList shares = new ArrayList<>(); for(Object obj: result.getData()) { shares.add((OCShare) obj); } @@ -515,7 +515,6 @@ private RemoteOperationResult refreshSharesForFolder(OwnCloudClient client) { * @param event * @param dirRemotePath Remote path of a folder that was just synchronized * (with or without success) - * @param result */ private void sendLocalBroadcast( String event, String dirRemotePath, RemoteOperationResult result diff --git a/src/com/owncloud/android/providers/FileContentProvider.java b/src/com/owncloud/android/providers/FileContentProvider.java index ef2ce0063d5..0320c1a4464 100644 --- a/src/com/owncloud/android/providers/FileContentProvider.java +++ b/src/com/owncloud/android/providers/FileContentProvider.java @@ -534,6 +534,8 @@ public void onCreate(SQLiteDatabase db) { // Create uploads table createUploadsTable(db); + // Create user profiles table + createUserProfilesTable(db); } @Override @@ -763,6 +765,19 @@ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } + if (oldVersion < 15 && newVersion >= 15) { + Log_OC.i("SQL", "Entering in the #15 ADD in onUpgrade"); + db.beginTransaction(); + try { + // Create user profiles table + createUserProfilesTable(db); + upgraded = true; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + } + if (!upgraded) Log_OC.i("SQL", "OUT of the ADD in onUpgrade; oldVersion == " + oldVersion + ", newVersion == " + newVersion); @@ -863,18 +878,20 @@ private void createUploadsTable(SQLiteDatabase db){ + ProviderTableMeta.UPLOADS_LAST_RESULT + " INTEGER, " // Upload LastResult + ProviderTableMeta.UPLOADS_CREATED_BY + " INTEGER );" // Upload createdBy ); + } - - /* before: - // PRIMARY KEY should always imply NOT NULL. Unfortunately, due to a - // bug in some early versions, this is not the case in SQLite. - //db.execSQL("CREATE TABLE " + TABLE_UPLOAD + " (" + " path TEXT PRIMARY KEY NOT NULL UNIQUE," - // + " uploadStatus INTEGER NOT NULL, uploadObject TEXT NOT NULL);"); - // uploadStatus is used to easy filtering, it has precedence over - // uploadObject.getUploadStatus() - */ + private void createUserProfilesTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE " + ProviderTableMeta.USER_AVATARS__TABLE_NAME + "(" + + ProviderTableMeta._ID + " INTEGER PRIMARY KEY, " + + ProviderTableMeta.USER_AVATARS__ACCOUNT_NAME + " TEXT, " + + ProviderTableMeta.USER_AVATARS__CACHE_KEY + " TEXT, " + + ProviderTableMeta.USER_AVATARS__MIME_TYPE + " TEXT, " + + ProviderTableMeta.USER_AVATARS__ETAG + " TEXT );" + ); } + + /** * Version 10 of database does not modify its scheme. It coincides with the upgrade of the ownCloud account names * structure to include in it the path to the server instance. Updating the account names and path to local files diff --git a/src/com/owncloud/android/ui/activity/DrawerActivity.java b/src/com/owncloud/android/ui/activity/DrawerActivity.java index 70bd167dec8..d1481a64090 100644 --- a/src/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/com/owncloud/android/ui/activity/DrawerActivity.java @@ -24,7 +24,6 @@ import android.accounts.AccountManagerFuture; import android.content.Intent; import android.content.res.Configuration; -import android.graphics.Bitmap; import android.os.Build; import android.os.Bundle; import android.support.design.widget.NavigationView; @@ -44,8 +43,6 @@ import com.owncloud.android.datamodel.ThumbnailsCacheManager; import com.owncloud.android.lib.common.OwnCloudAccount; import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.ui.DefaultAvatarTextDrawable; -import com.owncloud.android.utils.BitmapUtils; import com.owncloud.android.utils.DisplayUtils; /** @@ -119,7 +116,7 @@ public abstract class DrawerActivity extends ToolbarActivity { /** * accounts for the (max) three displayed accounts in the drawer header. */ - private Account[] mAvatars = new Account[3]; + private Account[] mAccountsWithAvatars = new Account[3]; /** * Initializes the drawer, its content and highlights the menu item with the given id. @@ -342,20 +339,26 @@ public void updateAccountList() { populateDrawerOwnCloudAccounts(); // activate second/end account avatar - if (mAvatars[1] != null) { - DisplayUtils.setAvatar(mAvatars[1], - (ImageView) findNavigationViewChildById(R.id.drawer_account_end), - mOtherAccountAvatarRadiusDimension, getResources()); + if (mAccountsWithAvatars[1] != null) { + DisplayUtils.showAccountAvatar( + mAccountsWithAvatars[1], + (ImageView) findNavigationViewChildById(R.id.drawer_account_end), + mOtherAccountAvatarRadiusDimension, + false + ); mAccountEndAccountAvatar.setVisibility(View.VISIBLE); } else { mAccountEndAccountAvatar.setVisibility(View.GONE); } // activate third/middle account avatar - if (mAvatars[2] != null) { - DisplayUtils.setAvatar(mAvatars[2], - (ImageView) findNavigationViewChildById(R.id.drawer_account_middle), - mOtherAccountAvatarRadiusDimension, getResources()); + if (mAccountsWithAvatars[2] != null) { + DisplayUtils.showAccountAvatar( + mAccountsWithAvatars[2], + (ImageView) findNavigationViewChildById(R.id.drawer_account_middle), + mOtherAccountAvatarRadiusDimension, + false + ); mAccountMiddleAccountAvatar.setVisibility(View.VISIBLE); } else { mAccountMiddleAccountAvatar.setVisibility(View.GONE); @@ -386,27 +389,14 @@ private void repopulateAccountList(Account[] accounts) { MENU_ORDER_ACCOUNT, accounts[i].name ); - try { - setAvatar(accounts[i], accountMenuItem); - - } catch (Exception e1) { - Log_OC.w(TAG, "Error retrieving avatar, generating colored letter instead"); - try { - accountMenuItem.setIcon( - DefaultAvatarTextDrawable.createAvatar( - accounts[i].name, - mMenuAccountAvatarRadiusDimension - ) - ); - - } catch (Exception e2) { - Log_OC.w( - TAG, - "Error calculating color for account icon, using static icon instead" - ); - accountMenuItem.setIcon(R.drawable.ic_user); - } - } + ThumbnailsCacheManager.GetAvatarTask task = + new ThumbnailsCacheManager.GetAvatarTask( + accountMenuItem, + accounts[i], + mMenuAccountAvatarRadiusDimension, + false + ); + task.execute(); } } @@ -462,61 +452,12 @@ protected void setAccountInDrawer(Account account) { username.setText(AccountUtils.getUsernameOfAccount(account.name)); } - DisplayUtils.setAvatar(account, (ImageView) findNavigationViewChildById(R.id.drawer_current_account), - mCurrentAccountAvatarRadiusDimension, getResources()); - } - } - - /** - * fetches and sets the avatar of the current account in the drawer in case the drawer is available. - * - * @param account the account to be set in the drawer - * @param menuItem the menuItem to set the avatar on - */ - private void setAvatar(Account account, MenuItem menuItem) { - if (mDrawerLayout != null && account != null) { - - // Thumbnail in Cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache("a_" + account.name); - - if (thumbnail != null) { - menuItem.setIcon( - BitmapUtils.bitmapToCircularBitmapDrawable(MainApp.getAppContext().getResources(), thumbnail) - ); - } else { - // generate new avatar - if (ThumbnailsCacheManager.cancelPotentialAvatarWork(account.name, menuItem)) { - final ThumbnailsCacheManager.AvatarGenerationTask task = - new ThumbnailsCacheManager.AvatarGenerationTask( - menuItem, account - ); - if (thumbnail == null) { - try { - menuItem.setIcon( - DefaultAvatarTextDrawable.createAvatar( - account.name, - mMenuAccountAvatarRadiusDimension - ) - ); - } catch (Exception e) { - Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); - menuItem.setIcon(R.drawable.ic_account_circle); - } - } else { - final ThumbnailsCacheManager.AsyncAvatarDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncAvatarDrawable( - getResources(), - thumbnail, - task - ); - menuItem.setIcon( - BitmapUtils.bitmapToCircularBitmapDrawable( - MainApp.getAppContext().getResources(), asyncDrawable.getBitmap()) - ); - } - task.execute(account.name); - } - } + DisplayUtils.showAccountAvatar( + account, + (ImageView) findNavigationViewChildById(R.id.drawer_current_account), + mCurrentAccountAvatarRadiusDimension, + false + ); } } @@ -685,16 +626,16 @@ protected void onAccountCreationSuccessful(AccountManagerFuture future) * always the current account. */ private void populateDrawerOwnCloudAccounts() { - mAvatars = new Account[3]; + mAccountsWithAvatars = new Account[3]; Account[] accountsAll = AccountManager.get(this).getAccountsByType (MainApp.getAccountType()); Account currentAccount = AccountUtils.getCurrentOwnCloudAccount(this); - mAvatars[0] = currentAccount; + mAccountsWithAvatars[0] = currentAccount; int j = 0; for (int i = 1; i <= 2 && i < accountsAll.length && j < accountsAll.length; j++) { if (!currentAccount.equals(accountsAll[j])) { - mAvatars[i] = accountsAll[j]; + mAccountsWithAvatars[i] = accountsAll[j]; i++; } } diff --git a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java index c8029dc7916..78b03448a83 100644 --- a/src/com/owncloud/android/ui/activity/FileDisplayActivity.java +++ b/src/com/owncloud/android/ui/activity/FileDisplayActivity.java @@ -239,8 +239,7 @@ public void onRequestPermissionsResult(int requestCode, if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted - startSynchronization(); - // toggle on is save since this is the only scenario this code gets accessed + startSyncFolderOperation(getFile(), false); } else { // permission denied --> do nothing } diff --git a/src/com/owncloud/android/ui/adapter/AccountListAdapter.java b/src/com/owncloud/android/ui/adapter/AccountListAdapter.java index 6ea2114302b..db0cbf96a17 100644 --- a/src/com/owncloud/android/ui/adapter/AccountListAdapter.java +++ b/src/com/owncloud/android/ui/adapter/AccountListAdapter.java @@ -105,8 +105,12 @@ public View getView(final int position, View convertView, ViewGroup parent) { ); try { - DisplayUtils.setAvatar(account, viewHolder.imageViewItem, mAccountAvatarRadiusDimension, - mContext.getResources()); + DisplayUtils.showAccountAvatar( + account, + viewHolder.imageViewItem, + mAccountAvatarRadiusDimension, + true + ); } 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/com/owncloud/android/utils/DisplayUtils.java b/src/com/owncloud/android/utils/DisplayUtils.java index 8a01068f94f..ef96c43a42f 100644 --- a/src/com/owncloud/android/utils/DisplayUtils.java +++ b/src/com/owncloud/android/utils/DisplayUtils.java @@ -301,52 +301,38 @@ public static void colorSnackbar(Context context, Snackbar snackbar) { } /** - * fetches and sets the avatar of the current account in the drawer in case the drawer is available. + * Show the avatar corresponding to the received account in an {@ImageView}. + * + * The avatar is shown if available locally in {@link ThumbnailsCacheManager}. The avatar is not + * fetched from the server if not available. + * + * If there is no avatar stored, a colored icon is generated with the first letter of the account username. + * + * If this is not possible either, a predefined user icon is shown instead. + * + * @param account OC account which avatar will be shown. + * @param displayView The image view to set the avatar on. + * @param displayRadius The radius of the circle where the avatar will be clipped into. + * @param fetchFromServer When 'true', if there is no avatar stored in the cache, it's fetched from + * the server. When 'false', server is not accessed, the fallback avatar is + * generated instead. USE WITH CARE, probably to be removed in the future. * - * @param account the account to be set in the drawer - * @param userIcon the image view to set the avatar on - * @param avatarRadius the avatar radius - * @param resources reference for density information */ - public static void setAvatar(Account account, ImageView userIcon, float avatarRadius, Resources resources) { + public static void showAccountAvatar( + Account account, + ImageView displayView, + float displayRadius, + boolean fetchFromServer + ) { if (account != null) { + // not just accessibility support, used to know what account is bound to each imageView + displayView.setContentDescription(account.name); - userIcon.setContentDescription(account.name); - - // Thumbnail in Cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache("a_" + account.name); - - if (thumbnail != null) { - userIcon.setImageDrawable( - BitmapUtils.bitmapToCircularBitmapDrawable( - MainApp.getAppContext().getResources(), thumbnail - ) + final ThumbnailsCacheManager.GetAvatarTask task = + new ThumbnailsCacheManager.GetAvatarTask( + displayView, account, displayRadius, fetchFromServer ); - } else { - // generate new avatar - if (ThumbnailsCacheManager.cancelPotentialAvatarWork(account.name, userIcon)) { - final ThumbnailsCacheManager.AvatarGenerationTask task = - new ThumbnailsCacheManager.AvatarGenerationTask(userIcon, account); - if (thumbnail == null) { - try { - userIcon.setImageDrawable( - DefaultAvatarTextDrawable.createAvatar(account.name, avatarRadius) - ); - } catch (Exception e) { - Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); - userIcon.setImageResource(R.drawable.ic_account_circle); - } - } else { - final ThumbnailsCacheManager.AsyncAvatarDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncAvatarDrawable(resources, thumbnail, task); - userIcon.setImageDrawable( - BitmapUtils.bitmapToCircularBitmapDrawable( - MainApp.getAppContext().getResources(), asyncDrawable.getBitmap()) - ); - } - task.execute(account.name); - } - } + task.execute(); } } }