diff --git a/src/com/android/settings/accounts/AccountFeatureProvider.java b/src/com/android/settings/accounts/AccountFeatureProvider.java index 9829ca64b4c..fd650959ffd 100644 --- a/src/com/android/settings/accounts/AccountFeatureProvider.java +++ b/src/com/android/settings/accounts/AccountFeatureProvider.java @@ -18,8 +18,10 @@ package com.android.settings.accounts; import android.accounts.Account; import android.content.Context; +import android.content.Intent; public interface AccountFeatureProvider { String getAccountType(); Account[] getAccounts(Context context); + Intent getAccountSettingsDeeplinkIntent(); } diff --git a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java index 90b581ba80e..2e0f432e02d 100644 --- a/src/com/android/settings/accounts/AccountFeatureProviderImpl.java +++ b/src/com/android/settings/accounts/AccountFeatureProviderImpl.java @@ -2,6 +2,7 @@ package com.android.settings.accounts; import android.accounts.Account; import android.content.Context; +import android.content.Intent; public class AccountFeatureProviderImpl implements AccountFeatureProvider { @Override @@ -13,4 +14,9 @@ public class AccountFeatureProviderImpl implements AccountFeatureProvider { public Account[] getAccounts(Context context) { return new Account[0]; } + + @Override + public Intent getAccountSettingsDeeplinkIntent() { + return null; + } } diff --git a/src/com/android/settings/accounts/AvatarViewMixin.java b/src/com/android/settings/accounts/AvatarViewMixin.java index 6dcf312a167..78750b18571 100644 --- a/src/com/android/settings/accounts/AvatarViewMixin.java +++ b/src/com/android/settings/accounts/AvatarViewMixin.java @@ -17,18 +17,30 @@ package com.android.settings.accounts; import android.accounts.Account; +import android.content.ContentResolver; 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.widget.ImageView; import androidx.annotation.VisibleForTesting; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; +import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.OnLifecycleEvent; import com.android.settings.R; import com.android.settings.homepage.SettingsHomepageActivity; 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 @@ -37,12 +49,39 @@ import com.android.settings.overlay.FeatureFactory; public class AvatarViewMixin implements LifecycleObserver { 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 ImageView mAvatarView; + private final MutableLiveData mAvatarImage; - public AvatarViewMixin(Context context, ImageView avatarView) { - mContext = context.getApplicationContext(); + public AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView) { + mContext = activity.getApplicationContext(); 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) @@ -52,7 +91,7 @@ public class AvatarViewMixin implements LifecycleObserver { return; } if (hasAccount()) { - //TODO(b/117509285): To migrate account icon on search bar + loadAvatar(); } else { mAvatarView.setImageResource(R.drawable.ic_account_circle_24dp); } @@ -64,4 +103,34 @@ public class AvatarViewMixin implements LifecycleObserver { mContext).getAccountFeatureProvider().getAccounts(mContext); 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 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; + } + } } diff --git a/tests/robotests/src/com/android/settings/accounts/AvatarViewMixinTest.java b/tests/robotests/src/com/android/settings/accounts/AvatarViewMixinTest.java index b3d929c8b38..039d2e231d3 100644 --- a/tests/robotests/src/com/android/settings/accounts/AvatarViewMixinTest.java +++ b/tests/robotests/src/com/android/settings/accounts/AvatarViewMixinTest.java @@ -24,6 +24,10 @@ import static org.mockito.Mockito.verify; import android.accounts.Account; 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 com.android.settings.homepage.SettingsHomepageActivity; @@ -39,38 +43,45 @@ import org.robolectric.android.controller.ActivityController; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; +import org.robolectric.shadow.api.Shadow; +import org.robolectric.shadows.ShadowPackageManager; @RunWith(SettingsRobolectricTestRunner.class) public class AvatarViewMixinTest { private static final String DUMMY_ACCOUNT = "test@domain.com"; private static final String DUMMY_DOMAIN = "domain.com"; + private static final String DUMMY_AUTHORITY = "authority.domain.com"; private Context mContext; private ImageView mImageView; + private ActivityController mController; + private SettingsHomepageActivity mActivity; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mImageView = new ImageView(mContext); + mController = Robolectric.buildActivity(SettingsHomepageActivity.class).create(); + mActivity = (SettingsHomepageActivity) mController.get(); } @Test public void hasAccount_useDefaultAccountData_returnFalse() { - final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mContext, mImageView); + final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mActivity, mImageView); assertThat(avatarViewMixin.hasAccount()).isFalse(); } @Test @Config(shadows = ShadowAccountFeatureProviderImpl.class) public void hasAccount_useShadowAccountData_returnTrue() { - final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mContext, mImageView); + final AvatarViewMixin avatarViewMixin = new AvatarViewMixin(mActivity, mImageView); assertThat(avatarViewMixin.hasAccount()).isTrue(); } @Test public void onStart_configDisabled_doNothing() { - final AvatarViewMixin mixin = spy(new AvatarViewMixin(mContext, mImageView)); + final AvatarViewMixin mixin = spy(new AvatarViewMixin(mActivity, mImageView)); mixin.onStart(); verify(mixin, never()).hasAccount(); @@ -79,19 +90,45 @@ public class AvatarViewMixinTest { @Test @Config(qualifiers = "mcc999") 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( - SettingsHomepageActivity.class).create(); - final SettingsHomepageActivity settingsHomepageActivity = - (SettingsHomepageActivity) controller.get(); - settingsHomepageActivity.getLifecycle().addObserver(mockAvatar); - controller.start(); + mActivity.getLifecycle().addObserver(mockAvatar); + mController.start(); 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 { @Implementation