• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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