Loading src/com/android/settings/accounts/AccountFeatureProvider.java +2 −0 Original line number Diff line number Diff line Loading @@ -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(); } src/com/android/settings/accounts/AccountFeatureProviderImpl.java +6 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -13,4 +14,9 @@ public class AccountFeatureProviderImpl implements AccountFeatureProvider { public Account[] getAccounts(Context context) { return new Account[0]; } @Override public Intent getAccountSettingsDeeplinkIntent() { return null; } } src/com/android/settings/accounts/AvatarViewMixin.java +72 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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<Bitmap> 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) Loading @@ -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); } Loading @@ -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<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; } } } tests/robotests/src/com/android/settings/accounts/AvatarViewMixinTest.java +48 −11 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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 Loading Loading
src/com/android/settings/accounts/AccountFeatureProvider.java +2 −0 Original line number Diff line number Diff line Loading @@ -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(); }
src/com/android/settings/accounts/AccountFeatureProviderImpl.java +6 −0 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -13,4 +14,9 @@ public class AccountFeatureProviderImpl implements AccountFeatureProvider { public Account[] getAccounts(Context context) { return new Account[0]; } @Override public Intent getAccountSettingsDeeplinkIntent() { return null; } }
src/com/android/settings/accounts/AvatarViewMixin.java +72 −3 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -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<Bitmap> 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) Loading @@ -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); } Loading @@ -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<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; } } }
tests/robotests/src/com/android/settings/accounts/AvatarViewMixinTest.java +48 −11 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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(); Loading @@ -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 Loading