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.role.controller.behavior.v35; 18 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.PackageManager; 23 import android.content.pm.ResolveInfo; 24 import android.content.pm.ServiceInfo; 25 import android.nfc.cardemulation.ApduServiceInfo; 26 import android.nfc.cardemulation.CardEmulation; 27 import android.nfc.cardemulation.HostApduService; 28 import android.nfc.cardemulation.OffHostApduService; 29 import android.os.Build; 30 import android.os.UserHandle; 31 import android.permission.flags.Flags; 32 import android.service.quickaccesswallet.QuickAccessWalletService; 33 import android.util.ArraySet; 34 import android.util.Log; 35 36 import androidx.annotation.NonNull; 37 import androidx.annotation.Nullable; 38 import androidx.annotation.RequiresApi; 39 40 import com.android.modules.utils.build.SdkLevel; 41 import com.android.role.controller.model.Role; 42 import com.android.role.controller.model.RoleBehavior; 43 import com.android.role.controller.util.CollectionUtils; 44 import com.android.role.controller.util.RoleFlags; 45 import com.android.role.controller.util.UserUtils; 46 47 import org.xmlpull.v1.XmlPullParserException; 48 49 import java.io.IOException; 50 import java.util.ArrayList; 51 import java.util.Collections; 52 import java.util.List; 53 import java.util.Set; 54 55 /** 56 * Handles the behavior of the wallet role. 57 */ 58 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 59 public class WalletRoleBehavior implements RoleBehavior { 60 61 private static final String LOG_TAG = WalletRoleBehavior.class.getSimpleName(); 62 63 @Override isAvailableAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)64 public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user, 65 @NonNull Context context) { 66 if (!(SdkLevel.isAtLeastV() && Flags.walletRoleEnabled())) { 67 return false; 68 } 69 70 if (Flags.walletRoleCrossUserEnabled() && RoleFlags.isProfileGroupExclusivityAvailable()) { 71 return !UserUtils.isPrivateProfile(user, context); 72 } else { 73 return !UserUtils.isProfile(user, context); 74 } 75 } 76 77 @Nullable 78 @Override getExclusivity()79 public Integer getExclusivity() { 80 if (Flags.walletRoleCrossUserEnabled() && RoleFlags.isProfileGroupExclusivityAvailable()) { 81 return Role.EXCLUSIVITY_PROFILE_GROUP; 82 } 83 84 return Role.EXCLUSIVITY_USER; 85 } 86 87 @Nullable 88 @Override getDefaultHoldersAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)89 public List<String> getDefaultHoldersAsUser(@NonNull Role role, @NonNull UserHandle user, 90 @NonNull Context context) { 91 Context userContext = UserUtils.getUserContext(context, user); 92 ComponentName preferredPaymentService = 93 CardEmulation.getPreferredPaymentService(userContext); 94 if (preferredPaymentService != null) { 95 return Collections.singletonList(preferredPaymentService.getPackageName()); 96 } 97 98 return null; 99 } 100 101 @Nullable 102 @Override getFallbackHolderAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)103 public String getFallbackHolderAsUser(@NonNull Role role, @NonNull UserHandle user, 104 @NonNull Context context) { 105 return CollectionUtils.firstOrNull(role.getDefaultHoldersAsUser(user, context)); 106 } 107 108 @Nullable 109 @Override isPackageQualifiedAsUser(@onNull Role role, @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context)110 public Boolean isPackageQualifiedAsUser(@NonNull Role role, @NonNull String packageName, 111 @NonNull UserHandle user, @NonNull Context context) { 112 return !getQualifyingPackageNamesInternal(packageName, user, context).isEmpty(); 113 } 114 115 @Nullable 116 @Override getQualifyingPackagesAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)117 public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user, 118 @NonNull Context context) { 119 return new ArrayList<>(getQualifyingPackageNamesInternal(null, user, context)); 120 } 121 122 @NonNull getQualifyingPackageNamesInternal(@ullable String packageName, @NonNull UserHandle user, @NonNull Context context)123 private static Set<String> getQualifyingPackageNamesInternal(@Nullable String packageName, 124 @NonNull UserHandle user, @NonNull Context context) { 125 Set<String> packageNames = resolvePackageNames(QuickAccessWalletService.SERVICE_INTERFACE, 126 packageName, user, context); 127 if (isNfcHostCardEmulationSupported(context)) { 128 packageNames.addAll(getQualifyingApduServicesAsUser(packageName, false, user, 129 context)); 130 packageNames.addAll(getQualifyingApduServicesAsUser(packageName, true, user, 131 context)); 132 } 133 return packageNames; 134 } 135 136 @NonNull resolvePackageNames(@onNull String action, @Nullable String packageName, @NonNull UserHandle user, @NonNull Context context)137 private static Set<String> resolvePackageNames(@NonNull String action, 138 @Nullable String packageName, @NonNull UserHandle user, @NonNull Context context) { 139 Intent intent = new Intent(action).setPackage(packageName); 140 PackageManager packageManager = context.getPackageManager(); 141 List<ResolveInfo> resolveInfos = packageManager 142 .queryIntentServicesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE 143 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, user); 144 Set<String> packageNames = new ArraySet<>(); 145 int resolveInfosSize = resolveInfos.size(); 146 for (int i = 0; i < resolveInfosSize; i++) { 147 ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo; 148 if (!serviceInfo.exported) { 149 continue; 150 } 151 packageNames.add(serviceInfo.packageName); 152 } 153 return packageNames; 154 } 155 156 @NonNull getQualifyingApduServicesAsUser(@ullable String packageName, boolean onHost, @NonNull UserHandle user, @NonNull Context context)157 private static Set<String> getQualifyingApduServicesAsUser(@Nullable String packageName, 158 boolean onHost, @NonNull UserHandle user, @NonNull Context context) { 159 Context userContext = UserUtils.getUserContext(context, user); 160 PackageManager userPackageManager = userContext.getPackageManager(); 161 Intent intent = new Intent( 162 onHost ? HostApduService.SERVICE_INTERFACE : OffHostApduService.SERVICE_INTERFACE) 163 .setPackage(packageName); 164 List<ResolveInfo> resolveInfos = userPackageManager.queryIntentServices(intent, 165 PackageManager.MATCH_DIRECT_BOOT_AWARE 166 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.GET_META_DATA); 167 Set<String> packageNames = new ArraySet<>(); 168 int resolveInfosSize = resolveInfos.size(); 169 for (int i = 0; i < resolveInfosSize; i++) { 170 ResolveInfo resolveInfo = resolveInfos.get(i); 171 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 172 if (!serviceInfo.exported) { 173 continue; 174 } 175 ApduServiceInfo apduServiceInfo; 176 try { 177 apduServiceInfo = new ApduServiceInfo(userPackageManager, resolveInfo, onHost); 178 } catch (IOException | XmlPullParserException e) { 179 Log.w(LOG_TAG, "Unable to create ApduServiceInfo for " + resolveInfo, e); 180 continue; 181 } 182 if (apduServiceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { 183 packageNames.add(resolveInfo.serviceInfo.packageName); 184 } 185 } 186 return packageNames; 187 } 188 isNfcHostCardEmulationSupported(@onNull Context context)189 private static boolean isNfcHostCardEmulationSupported(@NonNull Context context) { 190 return context.getPackageManager().hasSystemFeature( 191 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION); 192 } 193 } 194