Merge "Added account avatar in the end of search bar"

This commit is contained in:
Fan Zhang
2018-11-09 20:39:00 +00:00
committed by Android (Google) Code Review
4 changed files with 128 additions and 14 deletions

View File

@@ -18,8 +18,10 @@ package com.android.settings.accounts;
import android.accounts.Account; import android.accounts.Account;
import android.content.Context; import android.content.Context;
import android.content.Intent;
public interface AccountFeatureProvider { public interface AccountFeatureProvider {
String getAccountType(); String getAccountType();
Account[] getAccounts(Context context); Account[] getAccounts(Context context);
Intent getAccountSettingsDeeplinkIntent();
} }

View File

@@ -2,6 +2,7 @@ package com.android.settings.accounts;
import android.accounts.Account; import android.accounts.Account;
import android.content.Context; import android.content.Context;
import android.content.Intent;
public class AccountFeatureProviderImpl implements AccountFeatureProvider { public class AccountFeatureProviderImpl implements AccountFeatureProvider {
@Override @Override
@@ -13,4 +14,9 @@ public class AccountFeatureProviderImpl implements AccountFeatureProvider {
public Account[] getAccounts(Context context) { public Account[] getAccounts(Context context) {
return new Account[0]; return new Account[0];
} }
@Override
public Intent getAccountSettingsDeeplinkIntent() {
return null;
}
} }

View File

@@ -17,18 +17,30 @@
package com.android.settings.accounts; package com.android.settings.accounts;
import android.accounts.Account; import android.accounts.Account;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.OnLifecycleEvent; import androidx.lifecycle.OnLifecycleEvent;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.homepage.SettingsHomepageActivity;
import com.android.settings.overlay.FeatureFactory; import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.utils.ThreadUtils;
import java.util.List;
/** /**
* Avatar related work to the onStart method of registered observable classes * Avatar related work to the onStart method of registered observable classes
@@ -37,12 +49,39 @@ import com.android.settings.overlay.FeatureFactory;
public class AvatarViewMixin implements LifecycleObserver { public class AvatarViewMixin implements LifecycleObserver {
private static final String TAG = "AvatarViewMixin"; private static final String TAG = "AvatarViewMixin";
@VisibleForTesting
static final Intent INTENT_GET_ACCOUNT_DATA =
new Intent("android.content.action.SETTINGS_ACCOUNT_DATA");
private static final String METHOD_GET_ACCOUNT_AVATAR = "getAccountAvatar";
private static final String KEY_AVATAR_BITMAP = "account_avatar";
private static final int REQUEST_CODE = 1013;
private final Context mContext; private final Context mContext;
private final ImageView mAvatarView; private final ImageView mAvatarView;
private final MutableLiveData<Bitmap> mAvatarImage;
public AvatarViewMixin(Context context, ImageView avatarView) { public AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView) {
mContext = context.getApplicationContext(); mContext = activity.getApplicationContext();
mAvatarView = avatarView; mAvatarView = avatarView;
mAvatarView.setOnClickListener(v -> {
if (hasAccount()) {
//TODO(b/117509285) launch the new page of the MeCard
} else {
final Intent intent = FeatureFactory.getFactory(mContext)
.getAccountFeatureProvider()
.getAccountSettingsDeeplinkIntent();
if (intent != null) {
activity.startActivityForResult(intent, REQUEST_CODE);
}
}
});
mAvatarImage = new MutableLiveData<>();
mAvatarImage.observe(activity, bitmap -> {
avatarView.setImageBitmap(bitmap);
});
} }
@OnLifecycleEvent(Lifecycle.Event.ON_START) @OnLifecycleEvent(Lifecycle.Event.ON_START)
@@ -52,7 +91,7 @@ public class AvatarViewMixin implements LifecycleObserver {
return; return;
} }
if (hasAccount()) { if (hasAccount()) {
//TODO(b/117509285): To migrate account icon on search bar loadAvatar();
} else { } else {
mAvatarView.setImageResource(R.drawable.ic_account_circle_24dp); mAvatarView.setImageResource(R.drawable.ic_account_circle_24dp);
} }
@@ -64,4 +103,34 @@ public class AvatarViewMixin implements LifecycleObserver {
mContext).getAccountFeatureProvider().getAccounts(mContext); mContext).getAccountFeatureProvider().getAccounts(mContext);
return (accounts != null) && (accounts.length > 0); return (accounts != null) && (accounts.length > 0);
} }
private void loadAvatar() {
final String authority = queryProviderAuthority();
if (TextUtils.isEmpty(authority)) {
return;
}
ThreadUtils.postOnBackgroundThread(() -> {
final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
.authority(authority)
.build();
final Bundle bundle = mContext.getContentResolver().call(uri,
METHOD_GET_ACCOUNT_AVATAR, null /* arg */, null /* extras */);
final Bitmap bitmap = bundle.getParcelable(KEY_AVATAR_BITMAP);
mAvatarImage.postValue(bitmap);
});
}
@VisibleForTesting
String queryProviderAuthority() {
final List<ResolveInfo> providers =
mContext.getPackageManager().queryIntentContentProviders(INTENT_GET_ACCOUNT_DATA,
PackageManager.MATCH_SYSTEM_ONLY);
if (providers.size() == 1) {
return providers.get(0).providerInfo.authority;
} else {
Log.w(TAG, "The size of the provider is " + providers.size());
return null;
}
}
} }

View File

@@ -24,6 +24,10 @@ import static org.mockito.Mockito.verify;
import android.accounts.Account; import android.accounts.Account;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ProviderInfo;
import android.content.pm.ResolveInfo;
import android.widget.ImageView; import android.widget.ImageView;
import com.android.settings.homepage.SettingsHomepageActivity; import com.android.settings.homepage.SettingsHomepageActivity;
@@ -39,38 +43,45 @@ import org.robolectric.android.controller.ActivityController;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements; import org.robolectric.annotation.Implements;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.shadows.ShadowPackageManager;
@RunWith(SettingsRobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
public class AvatarViewMixinTest { public class AvatarViewMixinTest {
private static final String DUMMY_ACCOUNT = "test@domain.com"; private static final String DUMMY_ACCOUNT = "test@domain.com";
private static final String DUMMY_DOMAIN = "domain.com"; private static final String DUMMY_DOMAIN = "domain.com";
private static final String DUMMY_AUTHORITY = "authority.domain.com";
private Context mContext; private Context mContext;
private ImageView mImageView; private ImageView mImageView;
private ActivityController mController;
private SettingsHomepageActivity mActivity;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = RuntimeEnvironment.application;
mImageView = new ImageView(mContext); mImageView = new ImageView(mContext);
mController = Robolectric.buildActivity(SettingsHomepageActivity.class).create();
mActivity = (SettingsHomepageActivity) mController.get();
} }
@Test @Test
public void hasAccount_useDefaultAccountData_returnFalse() { public void hasAccount_useDefaultAccountData_returnFalse() {
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mContext, mImageView); final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mActivity, mImageView);
assertThat(avatarViewMixin.hasAccount()).isFalse(); assertThat(avatarViewMixin.hasAccount()).isFalse();
} }
@Test @Test
@Config(shadows = ShadowAccountFeatureProviderImpl.class) @Config(shadows = ShadowAccountFeatureProviderImpl.class)
public void hasAccount_useShadowAccountData_returnTrue() { public void hasAccount_useShadowAccountData_returnTrue() {
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mContext, mImageView); final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mActivity, mImageView);
assertThat(avatarViewMixin.hasAccount()).isTrue(); assertThat(avatarViewMixin.hasAccount()).isTrue();
} }
@Test @Test
public void onStart_configDisabled_doNothing() { public void onStart_configDisabled_doNothing() {
final AvatarViewMixin mixin = spy(new AvatarViewMixin(mContext, mImageView)); final AvatarViewMixin mixin = spy(new AvatarViewMixin(mActivity, mImageView));
mixin.onStart(); mixin.onStart();
verify(mixin, never()).hasAccount(); verify(mixin, never()).hasAccount();
@@ -79,19 +90,45 @@ public class AvatarViewMixinTest {
@Test @Test
@Config(qualifiers = "mcc999") @Config(qualifiers = "mcc999")
public void onStart_useMockAvatarViewMixin_shouldBeExecuted() { public void onStart_useMockAvatarViewMixin_shouldBeExecuted() {
final AvatarViewMixin mockAvatar = spy(new AvatarViewMixin(mContext, mImageView)); final AvatarViewMixin mockAvatar = spy(new AvatarViewMixin(mActivity, mImageView));
final ActivityController controller = Robolectric.buildActivity( mActivity.getLifecycle().addObserver(mockAvatar);
SettingsHomepageActivity.class).create(); mController.start();
final SettingsHomepageActivity settingsHomepageActivity =
(SettingsHomepageActivity) controller.get();
settingsHomepageActivity.getLifecycle().addObserver(mockAvatar);
controller.start();
verify(mockAvatar).hasAccount(); verify(mockAvatar).hasAccount();
} }
@Implements(AccountFeatureProviderImpl.class) @Test
public void queryProviderAuthority_useShadowPackagteManager_returnNull() {
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mActivity, mImageView);
assertThat(avatarViewMixin.queryProviderAuthority()).isNull();
}
@Test
public void queryProviderAuthority_useNewShadowPackagteManager_returnAuthority() {
final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mActivity, mImageView);
ShadowPackageManager shadowPackageManager = Shadow.extract(mContext.getPackageManager());
final PackageInfo accountProvider = new PackageInfo();
accountProvider.packageName = "test.pkg";
accountProvider.applicationInfo = new ApplicationInfo();
accountProvider.applicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
accountProvider.applicationInfo.packageName = accountProvider.packageName;
accountProvider.providers = new ProviderInfo[1];
accountProvider.providers[0] = new ProviderInfo();
accountProvider.providers[0].authority = DUMMY_AUTHORITY;
accountProvider.providers[0].packageName = accountProvider.packageName;
accountProvider.providers[0].name = "test.class";
accountProvider.providers[0].applicationInfo = accountProvider.applicationInfo;
final ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.providerInfo = accountProvider.providers[0];
shadowPackageManager.addResolveInfoForIntent(AvatarViewMixin.INTENT_GET_ACCOUNT_DATA,
resolveInfo);
assertThat(avatarViewMixin.queryProviderAuthority()).isEqualTo(DUMMY_AUTHORITY);
}
@Implements(value = AccountFeatureProviderImpl.class)
public static class ShadowAccountFeatureProviderImpl { public static class ShadowAccountFeatureProviderImpl {
@Implementation @Implementation