Commit 49b037f9 authored by Alice Wang's avatar Alice Wang Committed by Commit Bot

[Android][Signin] Use monogram for account avatar

This CL uses monogram for account avatar when user is signed in and no
profile photo is uploaded. Prior to this CL, the placeholder image was
used in this case.

Bug: 1127886
Change-Id: I7790911e338d8b3f7d4ab4ec9fe3e25e9dcce4b9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2416373
Commit-Queue: Alice Wang <aliceywang@chromium.org>
Reviewed-by: default avatarBoris Sazonov <bsazonov@chromium.org>
Reviewed-by: default avatarMihai Sardarescu <msarda@chromium.org>
Reviewed-by: default avatarTanmoy Mollik <triploblastic@chromium.org>
Cr-Commit-Position: refs/heads/master@{#808871}
parent a994dcc4
......@@ -27,9 +27,11 @@ import androidx.appcompat.content.res.AppCompatResources;
import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.browser_ui.util.AvatarGenerator;
import org.chromium.components.signin.AccountManagerFacadeProvider;
import org.chromium.components.signin.ProfileDataSource;
import org.chromium.components.signin.base.AccountInfo;
import java.util.ArrayList;
import java.util.HashMap;
......@@ -239,8 +241,8 @@ public class ProfileDataCache implements ProfileDownloader.Observer, ProfileData
private DisplayableProfileData createDisplayableProfileData(
ProfileDataSource.ProfileData profileData) {
return new DisplayableProfileData(profileData.getAccountName(),
prepareAvatar(profileData.getAvatar()), profileData.getFullName(),
profileData.getGivenName());
prepareAvatar(profileData.getAvatar(), profileData.getAccountName()),
profileData.getFullName(), profileData.getGivenName());
}
@Override
......@@ -248,7 +250,8 @@ public class ProfileDataCache implements ProfileDownloader.Observer, ProfileData
Bitmap bitmap) {
ThreadUtils.assertOnUiThread();
mCachedProfileData.put(accountId,
new DisplayableProfileData(accountId, prepareAvatar(bitmap), fullName, givenName));
new DisplayableProfileData(
accountId, prepareAvatar(bitmap, accountId), fullName, givenName));
for (Observer observer : mObservers) {
observer.onProfileDataUpdated(accountId);
}
......@@ -313,7 +316,12 @@ public class ProfileDataCache implements ProfileDownloader.Observer, ProfileData
badge, badgeSize, new Point(badgePositionX, badgePositionY), badgeBorderSize);
}
private Drawable prepareAvatar(Bitmap bitmap) {
private Drawable prepareAvatar(Bitmap bitmap, String accountEmail) {
if (bitmap == null) {
// If the given bitmap is null, try to fetch the account image which can be monogram
// from IdentityManager
bitmap = getAccountImageFromIdentityManager(accountEmail);
}
Drawable croppedAvatar = bitmap != null
? AvatarGenerator.makeRoundAvatar(mContext.getResources(), bitmap, mImageSize)
: mPlaceholderImage;
......@@ -365,4 +373,23 @@ public class ProfileDataCache implements ProfileDownloader.Observer, ProfileData
drawable.draw(canvas);
return new BitmapDrawable(context.getResources(), output);
}
/**
* Fetches the account image stored in {@link AccountInfo}.
*
* If user is signed in and has a profile photo, the profile photo will be returned, otherwise,
* a monogram is returned.
* If the user is signed out, returns null.
*
* TODO(https://crbug.com/1130545): We should refactor the different sources for getting
* the profile image.
*/
private static @Nullable Bitmap getAccountImageFromIdentityManager(String accountEmail) {
AccountInfo accountInfo =
IdentityServicesProvider.get()
.getIdentityManager(Profile.getLastUsedRegularProfile())
.findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
accountEmail);
return accountInfo != null ? accountInfo.getAccountImage() : null;
}
}
......@@ -10,7 +10,9 @@ import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import android.support.test.InstrumentationRegistry;
......@@ -24,6 +26,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.mockito.Spy;
import org.chromium.base.test.util.CommandLineFlags;
......@@ -31,6 +35,7 @@ import org.chromium.base.test.util.Feature;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.chrome.test.util.browser.Features;
......@@ -63,6 +68,12 @@ public class AccountPickerDialogFragmentTest extends DummyUiActivityTestCase {
public final AccountManagerTestRule mAccountManagerTestRule =
new AccountManagerTestRule(new FakeProfileDataSource());
@Mock
private Profile mProfileMock;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private IdentityServicesProvider mIdentityServicesProviderMock;
@Spy
private DummyAccountPickerTargetFragment mTargetFragment =
new DummyAccountPickerTargetFragment();
......@@ -78,6 +89,13 @@ public class AccountPickerDialogFragmentTest extends DummyUiActivityTestCase {
@Before
public void setUp() {
initMocks(this);
Profile.setLastUsedProfileForTesting(mProfileMock);
IdentityServicesProvider.setInstanceForTests(mIdentityServicesProviderMock);
when(mIdentityServicesProviderMock.getIdentityManager(mProfileMock)
.findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
anyString()))
.thenReturn(null);
addAccount(mAccountName1, mFullName1);
addAccount(mAccountName2, "");
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
......@@ -89,7 +107,11 @@ public class AccountPickerDialogFragmentTest extends DummyUiActivityTestCase {
@After
public void tearDown() {
if (mDialog.getDialog() != null) mDialog.dismiss();
if (mDialog.getDialog() != null) {
mDialog.dismiss();
}
IdentityServicesProvider.setInstanceForTests(null);
Profile.setLastUsedProfileForTesting(null);
}
@Test
......
......@@ -34,6 +34,7 @@ import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.browser.externalauth.ExternalAuthUtils;
import org.chromium.chrome.browser.sync.AndroidSyncSettings;
import org.chromium.components.signin.AccountTrackerService;
import org.chromium.components.signin.base.AccountInfo;
import org.chromium.components.signin.base.CoreAccountId;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ClearAccountsAction;
......@@ -53,7 +54,6 @@ public class SigninManagerTest {
@Rule
public final JniMocker mocker = new JniMocker();
@Mock
SigninManager.Natives mNativeMock;
......@@ -90,8 +90,8 @@ public class SigninManagerTest {
mAccountTrackerService, mIdentityManager, mIdentityMutator, mAndroidSyncSettings,
externalAuthUtils);
mAccount = new CoreAccountInfo(
new CoreAccountId("gaia-id-user"), "user@domain.com", "gaia-id-user");
mAccount = new AccountInfo(
new CoreAccountId("gaia-id-user"), "user@domain.com", "gaia-id-user", null);
}
@Test
......@@ -299,8 +299,8 @@ public class SigninManagerTest {
@Test
public void callbackNotifiedOnSignin() {
CoreAccountInfo account = new CoreAccountInfo(
new CoreAccountId("test_at_gmail.com"), "test@gmail.com", "test_at_gmail.com");
AccountInfo account = new AccountInfo(new CoreAccountId("test_at_gmail.com"),
"test@gmail.com", "test_at_gmail.com", null);
// No need to seed accounts to the native code.
doReturn(true).when(mAccountTrackerService).checkAndSeedSystemAccounts();
......@@ -337,8 +337,8 @@ public class SigninManagerTest {
@Test(expected = AssertionError.class)
public void failIfAlreadySignedin() {
CoreAccountInfo account = new CoreAccountInfo(
new CoreAccountId("test_at_gmail.com"), "test@gmail.com", "test_at_gmail.com");
AccountInfo account = new AccountInfo(new CoreAccountId("test_at_gmail.com"),
"test@gmail.com", "test_at_gmail.com", null);
// No need to seed accounts to the native code.
doReturn(true).when(mAccountTrackerService).checkAndSeedSystemAccounts();
......
......@@ -4,7 +4,9 @@
package org.chromium.chrome.browser.signin.account_picker;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
import org.junit.After;
......@@ -13,11 +15,14 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.mockito.Mock;
import org.robolectric.RuntimeEnvironment;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.DisplayableProfileData;
import org.chromium.chrome.browser.signin.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.account_picker.AccountPickerCoordinator.AccountPickerAccessPoint;
import org.chromium.chrome.browser.signin.account_picker.AccountPickerProperties.AddAccountRowProperties;
import org.chromium.chrome.browser.signin.account_picker.AccountPickerProperties.ExistingAccountRowProperties;
......@@ -43,6 +48,12 @@ public class AccountPickerMediatorTest {
public final AccountManagerTestRule mAccountManagerTestRule =
new AccountManagerTestRule(mFakeProfileDataSource);
@Mock
private Profile mProfileMock;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private IdentityServicesProvider mIdentityServicesProviderMock;
@Mock
private AccountPickerCoordinator.Listener mListenerMock;
......@@ -53,6 +64,12 @@ public class AccountPickerMediatorTest {
@Before
public void setUp() {
initMocks(this);
Profile.setLastUsedProfileForTesting(mProfileMock);
IdentityServicesProvider.setInstanceForTests(mIdentityServicesProviderMock);
when(mIdentityServicesProviderMock.getIdentityManager(mProfileMock)
.findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
anyString()))
.thenReturn(null);
}
@After
......@@ -60,6 +77,8 @@ public class AccountPickerMediatorTest {
if (mMediator != null) {
mMediator.destroy();
}
IdentityServicesProvider.setInstanceForTests(null);
Profile.setLastUsedProfileForTesting(null);
}
@Test
......
......@@ -16,6 +16,7 @@ android_library("java") {
]
sources = [
"java/src/org/chromium/components/signin/base/AccountInfo.java",
"java/src/org/chromium/components/signin/base/CoreAccountId.java",
"java/src/org/chromium/components/signin/base/CoreAccountInfo.java",
"java/src/org/chromium/components/signin/base/GoogleServiceAuthError.java",
......@@ -30,6 +31,7 @@ android_library("java") {
generate_jni("jni_headers") {
namespace = "signin"
sources = [
"java/src/org/chromium/components/signin/base/AccountInfo.java",
"java/src/org/chromium/components/signin/base/CoreAccountId.java",
"java/src/org/chromium/components/signin/base/CoreAccountInfo.java",
"java/src/org/chromium/components/signin/base/GoogleServiceAuthError.java",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.signin.base;
import android.graphics.Bitmap;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
/**
* Stores all the information known about an account.
*
* This class has a native counterpart called AccountInfo.
*/
public class AccountInfo extends CoreAccountInfo {
private final @Nullable Bitmap mAccountImage;
@VisibleForTesting
@CalledByNative
public AccountInfo(
CoreAccountId id, String email, String gaiaId, @Nullable Bitmap accountImage) {
super(id, email, gaiaId);
mAccountImage = accountImage;
}
/**
* Gets the account's image.
* It can be the image user uploaded, monogram or null.
*/
public @Nullable Bitmap getAccountImage() {
return mAccountImage;
}
}
\ No newline at end of file
......@@ -14,6 +14,7 @@ import org.chromium.base.ObserverList;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.base.AccountInfo;
import org.chromium.components.signin.base.CoreAccountInfo;
/**
......@@ -163,8 +164,8 @@ public class IdentityManager {
* Looks up and returns information for account with given |email|. If the account
* cannot be found, return a null value.
*/
public @Nullable CoreAccountInfo
findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(String email) {
public @Nullable AccountInfo findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
String email) {
return IdentityManagerJni.get()
.findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
mNativeIdentityManager, email);
......@@ -242,7 +243,7 @@ public class IdentityManager {
@Nullable
CoreAccountInfo getPrimaryAccountInfo(long nativeIdentityManager, int consentLevel);
@Nullable
CoreAccountInfo findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
AccountInfo findExtendedAccountInfoForAccountWithRefreshTokenByEmailAddress(
long nativeIdentityManager, String email);
CoreAccountInfo[] getAccountsWithRefreshTokens(long nativeIdentityManager);
}
......
......@@ -7,8 +7,11 @@
#if defined(OS_ANDROID)
#include "base/android/jni_string.h"
#include "components/signin/public/android/jni_headers/AccountInfo_jni.h"
#include "components/signin/public/android/jni_headers/CoreAccountId_jni.h"
#include "components/signin/public/android/jni_headers/CoreAccountInfo_jni.h"
#include "ui/gfx/android/java_bitmap.h"
#include "ui/gfx/image/image_skia.h"
#endif
namespace {
......@@ -136,6 +139,19 @@ base::android::ScopedJavaLocalRef<jobject> ConvertToJavaCoreAccountInfo(
base::android::ConvertUTF8ToJavaString(env, account_info.gaia));
}
base::android::ScopedJavaLocalRef<jobject> ConvertToJavaAccountInfo(
JNIEnv* env,
const AccountInfo& account_info) {
gfx::Image avatar_image = account_info.account_image;
return signin::Java_AccountInfo_Constructor(
env, ConvertToJavaCoreAccountId(env, account_info.account_id),
base::android::ConvertUTF8ToJavaString(env, account_info.email),
base::android::ConvertUTF8ToJavaString(env, account_info.gaia),
avatar_image.IsEmpty()
? nullptr
: gfx::ConvertToJavaBitmap(avatar_image.AsImageSkia().bitmap()));
}
base::android::ScopedJavaLocalRef<jobject> ConvertToJavaCoreAccountId(
JNIEnv* env,
const CoreAccountId& account_id) {
......
......@@ -86,6 +86,11 @@ base::android::ScopedJavaLocalRef<jobject> ConvertToJavaCoreAccountInfo(
JNIEnv* env,
const CoreAccountInfo& account_info);
// Constructs a Java AccountInfo from the provided C++ AccountInfo
base::android::ScopedJavaLocalRef<jobject> ConvertToJavaAccountInfo(
JNIEnv* env,
const AccountInfo& account_info);
// Constructs a Java CoreAccountId from the provided C++ CoreAccountId
base::android::ScopedJavaLocalRef<jobject> ConvertToJavaCoreAccountId(
JNIEnv* env,
......
......@@ -414,7 +414,7 @@ base::android::ScopedJavaLocalRef<jobject> IdentityManager::
base::android::ConvertJavaStringToUTF8(env, j_email));
if (!account_info.has_value())
return nullptr;
return ConvertToJavaCoreAccountInfo(env, account_info.value());
return ConvertToJavaAccountInfo(env, account_info.value());
}
base::android::ScopedJavaLocalRef<jobjectArray>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment