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.UserUtils; 45 46 import org.xmlpull.v1.XmlPullParserException; 47 48 import java.io.IOException; 49 import java.util.ArrayList; 50 import java.util.Collections; 51 import java.util.List; 52 import java.util.Set; 53 54 /** 55 * Handles the behavior of the wallet role. 56 */ 57 @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) 58 public class WalletRoleBehavior implements RoleBehavior { 59 60 private static final String LOG_TAG = WalletRoleBehavior.class.getSimpleName(); 61 62 @Override isAvailableAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)63 public boolean isAvailableAsUser(@NonNull Role role, @NonNull UserHandle user, 64 @NonNull Context context) { 65 return SdkLevel.isAtLeastV() && Flags.walletRoleEnabled() 66 && !UserUtils.isProfile(user, context); 67 } 68 69 @Nullable 70 @Override getDefaultHoldersAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)71 public List<String> getDefaultHoldersAsUser(@NonNull Role role, @NonNull UserHandle user, 72 @NonNull Context context) { 73 Context userContext = UserUtils.getUserContext(context, user); 74 ComponentName preferredPaymentService = 75 CardEmulation.getPreferredPaymentService(userContext); 76 if (preferredPaymentService != null) { 77 return Collections.singletonList(preferredPaymentService.getPackageName()); 78 } 79 80 return null; 81 } 82 83 @Nullable 84 @Override getFallbackHolderAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)85 public String getFallbackHolderAsUser(@NonNull Role role, @NonNull UserHandle user, 86 @NonNull Context context) { 87 return CollectionUtils.firstOrNull(role.getDefaultHoldersAsUser(user, context)); 88 } 89 90 @Nullable 91 @Override isPackageQualifiedAsUser(@onNull Role role, @NonNull String packageName, @NonNull UserHandle user, @NonNull Context context)92 public Boolean isPackageQualifiedAsUser(@NonNull Role role, @NonNull String packageName, 93 @NonNull UserHandle user, @NonNull Context context) { 94 return !getQualifyingPackageNamesInternal(packageName, user, context).isEmpty(); 95 } 96 97 @Nullable 98 @Override getQualifyingPackagesAsUser(@onNull Role role, @NonNull UserHandle user, @NonNull Context context)99 public List<String> getQualifyingPackagesAsUser(@NonNull Role role, @NonNull UserHandle user, 100 @NonNull Context context) { 101 return new ArrayList<>(getQualifyingPackageNamesInternal(null, user, context)); 102 } 103 104 @NonNull getQualifyingPackageNamesInternal(@ullable String packageName, @NonNull UserHandle user, @NonNull Context context)105 private static Set<String> getQualifyingPackageNamesInternal(@Nullable String packageName, 106 @NonNull UserHandle user, @NonNull Context context) { 107 Set<String> packageNames = resolvePackageNames(QuickAccessWalletService.SERVICE_INTERFACE, 108 packageName, user, context); 109 if (isNfcHostCardEmulationSupported(context)) { 110 packageNames.addAll(getQualifyingApduServicesAsUser(packageName, false, user, 111 context)); 112 packageNames.addAll(getQualifyingApduServicesAsUser(packageName, true, user, 113 context)); 114 } 115 return packageNames; 116 } 117 118 @NonNull resolvePackageNames(@onNull String action, @Nullable String packageName, @NonNull UserHandle user, @NonNull Context context)119 private static Set<String> resolvePackageNames(@NonNull String action, 120 @Nullable String packageName, @NonNull UserHandle user, @NonNull Context context) { 121 Intent intent = new Intent(action).setPackage(packageName); 122 PackageManager packageManager = context.getPackageManager(); 123 List<ResolveInfo> resolveInfos = packageManager 124 .queryIntentServicesAsUser(intent, PackageManager.MATCH_DIRECT_BOOT_AWARE 125 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, user); 126 Set<String> packageNames = new ArraySet<>(); 127 int resolveInfosSize = resolveInfos.size(); 128 for (int i = 0; i < resolveInfosSize; i++) { 129 ServiceInfo serviceInfo = resolveInfos.get(i).serviceInfo; 130 if (!serviceInfo.exported) { 131 continue; 132 } 133 packageNames.add(serviceInfo.packageName); 134 } 135 return packageNames; 136 } 137 138 @NonNull getQualifyingApduServicesAsUser(@ullable String packageName, boolean onHost, @NonNull UserHandle user, @NonNull Context context)139 private static Set<String> getQualifyingApduServicesAsUser(@Nullable String packageName, 140 boolean onHost, @NonNull UserHandle user, @NonNull Context context) { 141 Context userContext = UserUtils.getUserContext(context, user); 142 PackageManager userPackageManager = userContext.getPackageManager(); 143 Intent intent = new Intent( 144 onHost ? HostApduService.SERVICE_INTERFACE : OffHostApduService.SERVICE_INTERFACE) 145 .setPackage(packageName); 146 List<ResolveInfo> resolveInfos = userPackageManager.queryIntentServices(intent, 147 PackageManager.MATCH_DIRECT_BOOT_AWARE 148 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.GET_META_DATA); 149 Set<String> packageNames = new ArraySet<>(); 150 int resolveInfosSize = resolveInfos.size(); 151 for (int i = 0; i < resolveInfosSize; i++) { 152 ResolveInfo resolveInfo = resolveInfos.get(i); 153 ServiceInfo serviceInfo = resolveInfo.serviceInfo; 154 if (!serviceInfo.exported) { 155 continue; 156 } 157 ApduServiceInfo apduServiceInfo; 158 try { 159 apduServiceInfo = new ApduServiceInfo(userPackageManager, resolveInfo, onHost); 160 } catch (IOException | XmlPullParserException e) { 161 Log.w(LOG_TAG, "Unable to create ApduServiceInfo for " + resolveInfo, e); 162 continue; 163 } 164 if (apduServiceInfo.hasCategory(CardEmulation.CATEGORY_PAYMENT)) { 165 packageNames.add(resolveInfo.serviceInfo.packageName); 166 } 167 } 168 return packageNames; 169 } 170 isNfcHostCardEmulationSupported(@onNull Context context)171 private static boolean isNfcHostCardEmulationSupported(@NonNull Context context) { 172 return context.getPackageManager().hasSystemFeature( 173 PackageManager.FEATURE_NFC_HOST_CARD_EMULATION); 174 } 175 } 176