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.ActivityManager; 21 import android.app.settings.SettingsEnums; 22 import android.content.ContentResolver; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.graphics.Bitmap; 28 import android.net.Uri; 29 import android.os.Bundle; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.widget.ImageView; 33 34 import androidx.annotation.VisibleForTesting; 35 import androidx.lifecycle.Lifecycle; 36 import androidx.lifecycle.LifecycleObserver; 37 import androidx.lifecycle.MutableLiveData; 38 import androidx.lifecycle.OnLifecycleEvent; 39 40 import com.android.settings.R; 41 import com.android.settings.homepage.SettingsHomepageActivity; 42 import com.android.settings.overlay.FeatureFactory; 43 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; 44 import com.android.settingslib.utils.ThreadUtils; 45 46 import java.net.URISyntaxException; 47 import java.util.List; 48 49 /** 50 * Avatar related work to the onStart method of registered observable classes 51 * in {@link SettingsHomepageActivity}. 52 */ 53 public class AvatarViewMixin implements LifecycleObserver { 54 private static final String TAG = "AvatarViewMixin"; 55 56 @VisibleForTesting 57 static final Intent INTENT_GET_ACCOUNT_DATA = 58 new Intent("android.content.action.SETTINGS_ACCOUNT_DATA"); 59 60 private static final String METHOD_GET_ACCOUNT_AVATAR = "getAccountAvatar"; 61 private static final String KEY_AVATAR_BITMAP = "account_avatar"; 62 private static final String KEY_ACCOUNT_NAME = "account_name"; 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 private final ActivityManager mActivityManager; 69 70 private String mAccountName; 71 AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView)72 public AvatarViewMixin(SettingsHomepageActivity activity, ImageView avatarView) { 73 mContext = activity.getApplicationContext(); 74 mActivityManager = mContext.getSystemService(ActivityManager.class); 75 mAvatarView = avatarView; 76 mAvatarView.setOnClickListener(v -> { 77 Intent intent; 78 try { 79 final String uri = mContext.getResources().getString( 80 R.string.config_account_intent_uri); 81 intent = Intent.parseUri(uri, Intent.URI_INTENT_SCHEME); 82 } catch (URISyntaxException e) { 83 Log.w(TAG, "Error parsing avatar mixin intent, skipping", e); 84 return; 85 } 86 87 if (!TextUtils.isEmpty(mAccountName)) { 88 intent.putExtra(EXTRA_ACCOUNT_NAME, mAccountName); 89 } 90 91 final List<ResolveInfo> matchedIntents = 92 mContext.getPackageManager().queryIntentActivities(intent, 93 PackageManager.MATCH_SYSTEM_ONLY); 94 if (matchedIntents.isEmpty()) { 95 Log.w(TAG, "Cannot find any matching action VIEW_ACCOUNT intent."); 96 return; 97 } 98 99 final MetricsFeatureProvider metricsFeatureProvider = FeatureFactory.getFactory( 100 mContext).getMetricsFeatureProvider(); 101 metricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, 102 SettingsEnums.CLICK_ACCOUNT_AVATAR, SettingsEnums.SETTINGS_HOMEPAGE, 103 null /* key */, Integer.MIN_VALUE /* value */); 104 105 // Here may have two different UI while start the activity. 106 // It will display adding account UI when device has no any account. 107 // It will display account information page when intent added the specified account. 108 activity.startActivity(intent); 109 }); 110 111 mAvatarImage = new MutableLiveData<>(); 112 mAvatarImage.observe(activity, bitmap -> { 113 avatarView.setImageBitmap(bitmap); 114 }); 115 } 116 117 @OnLifecycleEvent(Lifecycle.Event.ON_START) onStart()118 public void onStart() { 119 if (!mContext.getResources().getBoolean(R.bool.config_show_avatar_in_homepage)) { 120 Log.d(TAG, "Feature disabled by config. Skipping"); 121 return; 122 } 123 if (mActivityManager.isLowRamDevice()) { 124 Log.d(TAG, "Feature disabled on low ram device. Skipping"); 125 return; 126 } 127 if (hasAccount()) { 128 loadAccount(); 129 } else { 130 mAvatarView.setImageResource(R.drawable.ic_account_circle_24dp); 131 } 132 } 133 134 @VisibleForTesting hasAccount()135 boolean hasAccount() { 136 final Account accounts[] = FeatureFactory.getFactory( 137 mContext).getAccountFeatureProvider().getAccounts(mContext); 138 return (accounts != null) && (accounts.length > 0); 139 } 140 loadAccount()141 private void loadAccount() { 142 final String authority = queryProviderAuthority(); 143 if (TextUtils.isEmpty(authority)) { 144 return; 145 } 146 147 ThreadUtils.postOnBackgroundThread(() -> { 148 final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 149 .authority(authority) 150 .build(); 151 final Bundle bundle = mContext.getContentResolver().call(uri, 152 METHOD_GET_ACCOUNT_AVATAR, null /* arg */, null /* extras */); 153 final Bitmap bitmap = bundle.getParcelable(KEY_AVATAR_BITMAP); 154 mAccountName = bundle.getString(KEY_ACCOUNT_NAME, "" /* defaultValue */); 155 mAvatarImage.postValue(bitmap); 156 }); 157 } 158 159 @VisibleForTesting queryProviderAuthority()160 String queryProviderAuthority() { 161 final List<ResolveInfo> providers = 162 mContext.getPackageManager().queryIntentContentProviders(INTENT_GET_ACCOUNT_DATA, 163 PackageManager.MATCH_SYSTEM_ONLY); 164 if (providers.size() == 1) { 165 return providers.get(0).providerInfo.authority; 166 } else { 167 Log.w(TAG, "The size of the provider is " + providers.size()); 168 return null; 169 } 170 } 171 } 172