1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.accounts; 18 19 import android.accounts.Account; 20 import android.app.settings.SettingsEnums; 21 import android.content.ContentResolver; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.graphics.Bitmap; 27 import android.net.Uri; 28 import android.os.Bundle; 29 import android.text.TextUtils; 30 import android.util.Log; 31 import android.widget.ImageView; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.lifecycle.Lifecycle; 35 import androidx.lifecycle.LifecycleObserver; 36 import androidx.lifecycle.MutableLiveData; 37 import androidx.lifecycle.OnLifecycleEvent; 38 39 import com.android.settings.R; 40 import com.android.settings.activityembedding.ActivityEmbeddingRulesController; 41 import com.android.settings.homepage.SettingsHomepageActivity; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settingslib.utils.ThreadUtils; 44 45 import java.net.URISyntaxException; 46 import java.util.List; 47 48 /** 49 * Avatar related work to the onStart method of registered observable classes 50 * in {@link SettingsHomepageActivity}. 51 */ 52 public class AvatarViewMixin implements LifecycleObserver { 53 private static final String TAG = "AvatarViewMixin"; 54 55 @VisibleForTesting 56 static final Intent INTENT_GET_ACCOUNT_DATA = 57 new Intent("android.content.action.SETTINGS_ACCOUNT_DATA"); 58 59 private static final String METHOD_GET_ACCOUNT_AVATAR = "getAccountAvatar"; 60 private static final String KEY_AVATAR_BITMAP = "account_avatar"; 61 private static final String KEY_ACCOUNT_NAME = "account_name"; 62 private static final String KEY_AVATAR_ICON = "avatar_icon"; 63 private static final String EXTRA_ACCOUNT_NAME = "extra.accountName"; 64 65 private final Context mContext; 66 private final ImageView mAvatarView; 67 private final MutableLiveData<Bitmap> mAvatarImage; 68 69 @VisibleForTesting 70 String mAccountName; 71 72 /** 73 * @return true if the avatar icon is supported. 74 */ isAvatarSupported(Context context)75 public static boolean isAvatarSupported(Context context) { 76 if (!context.getResources().getBoolean(R.bool.config_show_avatar_in_homepage)) { 77 Log.d(TAG, "Feature disabled by config. Skipping"); 78 return false; 79 } 80 return true; 81 } 82 AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView)83 public AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView) { 84 mContext = activity.getApplicationContext(); 85 mAvatarView = avatarView; 86 mAvatarView.setOnClickListener(v -> { 87 Intent intent; 88 try { 89 final String uri = mContext.getResources().getString( 90 R.string.config_account_intent_uri); 91 intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME); 92 } catch (URISyntaxException e) { 93 Log.w(TAG, "Error parsing avatar mixin intent, skipping", e); 94 return; 95 } 96 97 if (!TextUtils.isEmpty(mAccountName)) { 98 intent.putExtra(EXTRA_ACCOUNT_NAME, mAccountName); 99 } 100 101 final List<ResolveInfo> matchedIntents = 102 mContext.getPackageManager().queryIntentActivities(intent, 103 PackageManager.MATCH_SYSTEM_ONLY); 104 if (matchedIntents.isEmpty()) { 105 Log.w(TAG, "Cannot find any matching action VIEW_ACCOUNT intent."); 106 return; 107 } 108 109 // Set a component name since activity embedding requires a component name for 110 // registering a rule. 111 intent.setComponent(matchedIntents.get(0).getComponentInfo().getComponentName()); 112 ActivityEmbeddingRulesController.registerTwoPanePairRuleForSettingsHome( 113 mContext, 114 intent.getComponent(), 115 intent.getAction(), 116 false /* finishPrimaryWithSecondary */, 117 true /* finishSecondaryWithPrimary */, 118 false /* clearTop */); 119 120 FeatureFactory.getFeatureFactory().getMetricsFeatureProvider() 121 .logSettingsTileClick(KEY_AVATAR_ICON, SettingsEnums.SETTINGS_HOMEPAGE); 122 123 // Here may have two different UI while start the activity. 124 // It will display adding account UI when device has no any account. 125 // It will display account information page when intent added the specified account. 126 activity.startActivity(intent); 127 }); 128 129 mAvatarImage = new MutableLiveData<>(); 130 mAvatarImage.observe(activity, bitmap -> { 131 avatarView.setImageBitmap(bitmap); 132 }); 133 } 134 135 @OnLifecycleEvent(Lifecycle.Event.ON_START) onStart()136 public void onStart() { 137 if (hasAccount()) { 138 loadAccount(); 139 } else { 140 mAccountName = null; 141 mAvatarView.setImageResource(R.drawable.ic_account_circle_24dp); 142 } 143 } 144 145 @VisibleForTesting hasAccount()146 boolean hasAccount() { 147 final Account[] accounts = FeatureFactory.getFeatureFactory().getAccountFeatureProvider() 148 .getAccounts(mContext); 149 return (accounts != null) && (accounts.length > 0); 150 } 151 loadAccount()152 private void loadAccount() { 153 final String authority = queryProviderAuthority(); 154 if (TextUtils.isEmpty(authority)) { 155 return; 156 } 157 158 ThreadUtils.postOnBackgroundThread(() -> { 159 final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 160 .authority(authority) 161 .build(); 162 final Bundle bundle = mContext.getContentResolver().call(uri, 163 METHOD_GET_ACCOUNT_AVATAR, null /* arg */, null /* extras */); 164 final Bitmap bitmap = bundle.getParcelable(KEY_AVATAR_BITMAP); 165 mAccountName = bundle.getString(KEY_ACCOUNT_NAME, "" /* defaultValue */); 166 mAvatarImage.postValue(bitmap); 167 }); 168 } 169 170 @VisibleForTesting queryProviderAuthority()171 String queryProviderAuthority() { 172 final List<ResolveInfo> providers = 173 mContext.getPackageManager().queryIntentContentProviders(INTENT_GET_ACCOUNT_DATA, 174 PackageManager.MATCH_SYSTEM_ONLY); 175 if (providers.size() == 1) { 176 return providers.get(0).providerInfo.authority; 177 } else { 178 Log.w(TAG, "The size of the provider is " + providers.size()); 179 return null; 180 } 181 } 182 } 183