1 /* 2 * Copyright (C) 2023 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.permissioncontroller.role.ui.behavior.v35; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.graphics.Bitmap; 26 import android.graphics.drawable.BitmapDrawable; 27 import android.graphics.drawable.Drawable; 28 import android.nfc.cardemulation.ApduServiceInfo; 29 import android.nfc.cardemulation.CardEmulation; 30 import android.nfc.cardemulation.HostApduService; 31 import android.nfc.cardemulation.OffHostApduService; 32 import android.os.Build; 33 import android.os.UserHandle; 34 import android.permission.flags.Flags; 35 import android.text.TextUtils; 36 import android.util.Log; 37 38 import androidx.annotation.NonNull; 39 import androidx.annotation.Nullable; 40 import androidx.annotation.RequiresApi; 41 import androidx.core.util.Pair; 42 import androidx.preference.Preference; 43 44 import com.android.launcher3.icons.IconFactory; 45 import com.android.permissioncontroller.role.ui.RequestRoleItemView; 46 import com.android.permissioncontroller.role.ui.TwoTargetPreference; 47 import com.android.permissioncontroller.role.ui.behavior.RoleUiBehavior; 48 import com.android.role.controller.model.Role; 49 import com.android.role.controller.util.UserUtils; 50 51 import org.xmlpull.v1.XmlPullParserException; 52 53 import java.io.IOException; 54 import java.util.ArrayList; 55 import java.util.List; 56 57 /*** 58 * Class for UI behavior of Wallet role 59 */ 60 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 61 public class WalletRoleUiBehavior implements RoleUiBehavior { 62 63 private static final String LOG_TAG = WalletRoleUiBehavior.class.getSimpleName(); 64 65 @Override preparePreferenceAsUser(@onNull Role role, @NonNull TwoTargetPreference preference, @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user, @NonNull Context context)66 public void preparePreferenceAsUser(@NonNull Role role, @NonNull TwoTargetPreference preference, 67 @NonNull List<ApplicationInfo> applicationInfos, @NonNull UserHandle user, 68 @NonNull Context context) { 69 Context userContext = UserUtils.getUserContext(context, user); 70 if (!applicationInfos.isEmpty()) { 71 preparePreferenceInternal(preference.asPreference(), applicationInfos.get(0), 72 false, user, userContext); 73 } 74 } 75 76 @Override prepareRequestRoleItemViewAsUser(@onNull Role role, @NonNull RequestRoleItemView itemView, @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)77 public void prepareRequestRoleItemViewAsUser(@NonNull Role role, 78 @NonNull RequestRoleItemView itemView, @NonNull ApplicationInfo applicationInfo, 79 @NonNull UserHandle user, @NonNull Context context) { 80 if (isSystemApplication(applicationInfo)) { 81 Pair<Drawable, CharSequence> bannerAndLabel = getLabelAndIconIfItExists( 82 applicationInfo, user, context); 83 84 if (bannerAndLabel != null) { 85 itemView.getIconImageView().setImageDrawable(bannerAndLabel.first); 86 itemView.getTitleTextView().setText(bannerAndLabel.second); 87 } 88 } 89 } 90 91 @Override prepareApplicationPreferenceAsUser(@onNull Role role, @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)92 public void prepareApplicationPreferenceAsUser(@NonNull Role role, 93 @NonNull Preference preference, @NonNull ApplicationInfo applicationInfo, 94 @NonNull UserHandle user, @NonNull Context context) { 95 Context userContext = UserUtils.getUserContext(context, user); 96 preparePreferenceInternal(preference, applicationInfo, true, user, userContext); 97 } 98 preparePreferenceInternal(@onNull Preference preference, @NonNull ApplicationInfo applicationInfo, boolean setTitle, @NonNull UserHandle user, @NonNull Context context)99 private void preparePreferenceInternal(@NonNull Preference preference, 100 @NonNull ApplicationInfo applicationInfo, boolean setTitle, @NonNull UserHandle user, 101 @NonNull Context context) { 102 if (isSystemApplication(applicationInfo)) { 103 Pair<Drawable, CharSequence> bannerAndLabel = getLabelAndIconIfItExists( 104 applicationInfo, user, context); 105 if (bannerAndLabel != null) { 106 preference.setIcon(bannerAndLabel.first); 107 if (setTitle) { 108 preference.setTitle(bannerAndLabel.second); 109 } else { 110 preference.setSummary(bannerAndLabel.second); 111 } 112 } 113 } 114 } 115 116 @Nullable getLabelAndIconIfItExists( @onNull ApplicationInfo applicationInfo, @NonNull UserHandle user, @NonNull Context context)117 private Pair<Drawable, CharSequence> getLabelAndIconIfItExists( 118 @NonNull ApplicationInfo applicationInfo, 119 @NonNull UserHandle user, 120 @NonNull Context context) { 121 Pair<Drawable, CharSequence> result = null; 122 // If the flag is enabled , attempt to fetch it from property 123 if (Flags.walletRoleIconPropertyEnabled()) { 124 result = getBannerAndLabelFromPackageProperty(context, user, 125 applicationInfo.packageName); 126 } 127 if (result != null) { 128 return result; 129 } 130 List<ApduServiceInfo> serviceInfos = getNfcServicesForPackage( 131 applicationInfo.packageName, user, context); 132 // If it's null, indicating that the property is not set, perform a legacy icon lookup. 133 return getNonPaymentServiceBannerAndLabelIfExists(serviceInfos, user, context); 134 } 135 136 137 @Nullable getBannerAndLabelFromPackageProperty( @onNull Context context, @NonNull UserHandle user, @NonNull String packageName)138 private Pair<Drawable, CharSequence> getBannerAndLabelFromPackageProperty( 139 @NonNull Context context, @NonNull UserHandle user, @NonNull String packageName) { 140 List<ApduServiceInfo> apduServiceInfos = getNfcServicesForPackage(packageName, 141 user, context); 142 PackageManager packageManager = context.getPackageManager(); 143 int apduServiceInfoSize = apduServiceInfos.size(); 144 for (int i = 0; i < apduServiceInfoSize; i++) { 145 ApduServiceInfo serviceInfo = apduServiceInfos.get(i); 146 ComponentName componentName = serviceInfo.getComponent(); 147 try { 148 PackageManager.Property iconProperty = packageManager.getProperty( 149 ApduServiceInfo.PROPERTY_WALLET_PREFERRED_BANNER_AND_LABEL, componentName); 150 if (iconProperty.isBoolean() && iconProperty.getBoolean()) { 151 return loadBannerAndLabel(serviceInfo, packageManager, context, user); 152 } 153 } catch (PackageManager.NameNotFoundException e) { 154 continue; 155 } 156 } 157 return null; 158 } 159 160 @NonNull getNfcServicesForPackage(@onNull String packageName, @NonNull UserHandle user, @NonNull Context context)161 private static List<ApduServiceInfo> getNfcServicesForPackage(@NonNull String packageName, 162 @NonNull UserHandle user, @NonNull Context context) { 163 PackageManager packageManager = context.getPackageManager(); 164 Intent hostApduIntent = new Intent(HostApduService.SERVICE_INTERFACE); 165 Intent offHostApduIntent = new Intent(OffHostApduService.SERVICE_INTERFACE); 166 hostApduIntent.setPackage(packageName); 167 offHostApduIntent.setPackage(packageName); 168 List<ResolveInfo> hostApduServices = packageManager.queryIntentServicesAsUser( 169 hostApduIntent, 170 PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA 171 | PackageManager.MATCH_DISABLED_COMPONENTS), user); 172 List<ResolveInfo> offHostApduServices = packageManager.queryIntentServicesAsUser( 173 offHostApduIntent, 174 PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA 175 | PackageManager.MATCH_DISABLED_COMPONENTS), user); 176 List<ApduServiceInfo> nfcServices = new ArrayList<>(); 177 int apduServiceInfoSize = hostApduServices.size(); 178 for (int i = 0; i < apduServiceInfoSize; i++) { 179 ResolveInfo resolveInfo = hostApduServices.get(i); 180 ApduServiceInfo apduServiceInfo; 181 try { 182 apduServiceInfo = new ApduServiceInfo(packageManager, resolveInfo, true); 183 } catch (XmlPullParserException | IOException e) { 184 Log.e(LOG_TAG, "Error creating the apduserviceinfo.", e); 185 continue; 186 } 187 nfcServices.add(apduServiceInfo); 188 } 189 int offHostApduServiceInfoSize = offHostApduServices.size(); 190 for (int i = 0; i < offHostApduServiceInfoSize; i++) { 191 ResolveInfo resolveInfo = offHostApduServices.get(i); 192 ApduServiceInfo apduServiceInfo; 193 try { 194 apduServiceInfo = new ApduServiceInfo(packageManager, resolveInfo, false); 195 } catch (XmlPullParserException | IOException e) { 196 Log.e(LOG_TAG, "Error creating the apduserviceinfo.", e); 197 continue; 198 } 199 nfcServices.add(apduServiceInfo); 200 } 201 return nfcServices; 202 } 203 204 @Nullable getNonPaymentServiceBannerAndLabelIfExists( @onNull List<ApduServiceInfo> apduServiceInfos, @NonNull UserHandle user, @NonNull Context context)205 private Pair<Drawable, CharSequence> getNonPaymentServiceBannerAndLabelIfExists( 206 @NonNull List<ApduServiceInfo> apduServiceInfos, @NonNull UserHandle user, 207 @NonNull Context context) { 208 Context userContext = UserUtils.getUserContext(context, user); 209 PackageManager userPackageManager = userContext.getPackageManager(); 210 Pair<Drawable, CharSequence> bannerAndLabel; 211 int apduServiceInfoSize = apduServiceInfos.size(); 212 for (int i = 0; i < apduServiceInfoSize; i++) { 213 ApduServiceInfo serviceInfo = apduServiceInfos.get(i); 214 if (serviceInfo.getAids().isEmpty()) { 215 bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager, context, 216 user); 217 if (bannerAndLabel != null) { 218 return bannerAndLabel; 219 } 220 } else { 221 List<String> aids = serviceInfo.getAids(); 222 int aidsSize = aids.size(); 223 for (int j = 0; j < aidsSize; j++) { 224 String aid = aids.get(j); 225 String category = serviceInfo.getCategoryForAid(aid); 226 if (!CardEmulation.CATEGORY_PAYMENT.equals(category)) { 227 bannerAndLabel = loadBannerAndLabel(serviceInfo, userPackageManager, 228 context, user); 229 if (bannerAndLabel != null) { 230 return bannerAndLabel; 231 } 232 } 233 } 234 } 235 } 236 return null; 237 } 238 239 @Nullable loadBannerAndLabel(@onNull ApduServiceInfo info, @NonNull PackageManager userPackageManager, @NonNull Context context, @NonNull UserHandle user)240 private Pair<Drawable, CharSequence> loadBannerAndLabel(@NonNull ApduServiceInfo info, 241 @NonNull PackageManager userPackageManager, @NonNull Context context, 242 @NonNull UserHandle user) { 243 Drawable drawable = info.loadBanner(userPackageManager); 244 if (drawable != null) { 245 try (IconFactory factory = IconFactory.obtain(context)) { 246 Bitmap badged = 247 factory.createBadgedIconBitmap(drawable, user, 248 false).icon; 249 if (badged != null) { 250 drawable = new BitmapDrawable(context.getResources(), badged); 251 } 252 } 253 } 254 255 CharSequence label = info.loadLabel(userPackageManager); 256 if (drawable != null && !TextUtils.isEmpty(label)) { 257 return new Pair<>(drawable, label); 258 } else { 259 return null; 260 } 261 } 262 isSystemApplication(@onNull ApplicationInfo applicationInfo)263 private static boolean isSystemApplication(@NonNull ApplicationInfo applicationInfo) { 264 return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; 265 } 266 } 267