1 /* 2 * Copyright (C) 2020 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 android.service.quickaccesswallet; 18 19 import static android.permission.flags.Flags.walletRoleCrossUserEnabled; 20 21 import static com.android.permission.flags.Flags.crossUserRoleEnabled; 22 23 import android.Manifest; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.role.RoleManager; 27 import android.content.ComponentName; 28 import android.content.ContentResolver; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ResolveInfo; 33 import android.content.pm.ServiceInfo; 34 import android.content.res.Resources; 35 import android.content.res.TypedArray; 36 import android.content.res.XmlResourceParser; 37 import android.graphics.drawable.Drawable; 38 import android.os.Binder; 39 import android.os.UserHandle; 40 import android.provider.Settings; 41 import android.text.TextUtils; 42 import android.util.AttributeSet; 43 import android.util.Log; 44 import android.util.Pair; 45 import android.util.Xml; 46 47 import com.android.internal.R; 48 49 import org.xmlpull.v1.XmlPullParser; 50 import org.xmlpull.v1.XmlPullParserException; 51 52 import java.io.IOException; 53 import java.util.List; 54 55 /** 56 * {@link ServiceInfo} and meta-data about a {@link QuickAccessWalletService}. 57 * 58 * @hide 59 */ 60 class QuickAccessWalletServiceInfo { 61 62 private static final String TAG = "QAWalletSInfo"; 63 private static final String TAG_WALLET_SERVICE = "quickaccesswallet-service"; 64 65 private final ServiceInfo mServiceInfo; 66 private final ServiceMetadata mServiceMetadata; 67 private final TileServiceMetadata mTileServiceMetadata; 68 private final int mUserId; 69 QuickAccessWalletServiceInfo( @onNull ServiceInfo serviceInfo, @NonNull ServiceMetadata metadata, @NonNull TileServiceMetadata tileServiceMetadata, int userId)70 private QuickAccessWalletServiceInfo( 71 @NonNull ServiceInfo serviceInfo, 72 @NonNull ServiceMetadata metadata, 73 @NonNull TileServiceMetadata tileServiceMetadata, 74 int userId) { 75 mServiceInfo = serviceInfo; 76 mServiceMetadata = metadata; 77 mTileServiceMetadata = tileServiceMetadata; 78 mUserId = userId; 79 } 80 81 @Nullable tryCreate(@onNull Context context)82 static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) { 83 String defaultAppPackageName = null; 84 85 int defaultAppUser = UserHandle.myUserId(); 86 87 if (isWalletRoleAvailable(context)) { 88 Pair<String, Integer> roleAndUser = getDefaultWalletApp(context); 89 defaultAppPackageName = roleAndUser.first; 90 defaultAppUser = roleAndUser.second; 91 } else { 92 ComponentName defaultPaymentApp = getDefaultPaymentApp(context); 93 if (defaultPaymentApp == null) { 94 return null; 95 } 96 defaultAppPackageName = defaultPaymentApp.getPackageName(); 97 } 98 99 if (defaultAppPackageName == null || defaultAppUser < 0) { 100 return null; 101 } 102 103 ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName, 104 defaultAppUser); 105 if (serviceInfo == null) { 106 return null; 107 } 108 109 if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) { 110 Log.w(TAG, String.format("%s.%s does not require permission %s", 111 serviceInfo.packageName, serviceInfo.name, 112 Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE)); 113 return null; 114 } 115 116 ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo); 117 TileServiceMetadata tileServiceMetadata = 118 new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo)); 119 return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata, 120 defaultAppUser); 121 } 122 123 @NonNull getDefaultWalletApp(Context context)124 private static Pair<String, Integer> getDefaultWalletApp(Context context) { 125 UserHandle user = UserHandle.of(UserHandle.myUserId()); 126 127 final long token = Binder.clearCallingIdentity(); 128 try { 129 RoleManager roleManager = context.getSystemService(RoleManager.class); 130 131 if (walletRoleCrossUserEnabled() 132 && crossUserRoleEnabled() 133 && context.checkCallingOrSelfPermission( 134 Manifest.permission.INTERACT_ACROSS_USERS_FULL) 135 == PackageManager.PERMISSION_GRANTED) { 136 user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET); 137 if (user == null) { 138 return new Pair<>(null, UserHandle.myUserId()); 139 } 140 } 141 List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET, 142 user); 143 return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0), 144 user.getIdentifier()); 145 } finally { 146 Binder.restoreCallingIdentity(token); 147 } 148 } 149 isWalletRoleAvailable(Context context)150 private static boolean isWalletRoleAvailable(Context context) { 151 final long token = Binder.clearCallingIdentity(); 152 try { 153 RoleManager roleManager = context.getSystemService(RoleManager.class); 154 return roleManager.isRoleAvailable(RoleManager.ROLE_WALLET); 155 } finally { 156 Binder.restoreCallingIdentity(token); 157 } 158 } 159 getDefaultPaymentApp(Context context)160 private static ComponentName getDefaultPaymentApp(Context context) { 161 ContentResolver cr = context.getContentResolver(); 162 String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT); 163 return comp == null ? null : ComponentName.unflattenFromString(comp); 164 } 165 getWalletServiceInfo(Context context, String packageName, int userId)166 private static ServiceInfo getWalletServiceInfo(Context context, String packageName, 167 int userId) { 168 Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE); 169 intent.setPackage(packageName); 170 List<ResolveInfo> resolveInfos = 171 context.getPackageManager().queryIntentServicesAsUser(intent, 172 PackageManager.MATCH_DIRECT_BOOT_AWARE 173 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 174 | PackageManager.MATCH_DEFAULT_ONLY 175 | PackageManager.GET_META_DATA, userId); 176 return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo; 177 } 178 179 private static class TileServiceMetadata { 180 @Nullable 181 private final Drawable mTileIcon; 182 TileServiceMetadata(@ullable Drawable tileIcon)183 private TileServiceMetadata(@Nullable Drawable tileIcon) { 184 mTileIcon = tileIcon; 185 } 186 } 187 188 @Nullable parseTileServiceMetadata(Context context, ServiceInfo serviceInfo)189 private static Drawable parseTileServiceMetadata(Context context, ServiceInfo serviceInfo) { 190 PackageManager pm = context.getPackageManager(); 191 int tileIconDrawableId = 192 serviceInfo.metaData.getInt(QuickAccessWalletService.TILE_SERVICE_META_DATA); 193 if (tileIconDrawableId != 0) { 194 try { 195 Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo); 196 return resources.getDrawable(tileIconDrawableId, null); 197 } catch (PackageManager.NameNotFoundException e) { 198 Log.e(TAG, "Error parsing quickaccesswallet tile service meta-data", e); 199 } 200 } 201 return null; 202 } 203 204 static class ServiceMetadata { 205 @Nullable 206 private final String mSettingsActivity; 207 @Nullable 208 private final String mTargetActivity; 209 @Nullable 210 private final CharSequence mShortcutShortLabel; 211 @Nullable 212 private final CharSequence mShortcutLongLabel; 213 empty()214 private static ServiceMetadata empty() { 215 return new ServiceMetadata(null, null, null, null); 216 } 217 ServiceMetadata( String targetActivity, String settingsActivity, CharSequence shortcutShortLabel, CharSequence shortcutLongLabel)218 private ServiceMetadata( 219 String targetActivity, 220 String settingsActivity, 221 CharSequence shortcutShortLabel, 222 CharSequence shortcutLongLabel) { 223 mTargetActivity = targetActivity; 224 mSettingsActivity = settingsActivity; 225 mShortcutShortLabel = shortcutShortLabel; 226 mShortcutLongLabel = shortcutLongLabel; 227 } 228 } 229 parseServiceMetadata(Context context, ServiceInfo serviceInfo)230 static ServiceMetadata parseServiceMetadata(Context context, ServiceInfo serviceInfo) { 231 PackageManager pm = context.getPackageManager(); 232 final XmlResourceParser parser = 233 serviceInfo.loadXmlMetaData(pm, QuickAccessWalletService.SERVICE_META_DATA); 234 235 if (parser == null) { 236 return ServiceMetadata.empty(); 237 } 238 239 try { 240 Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo); 241 int type = 0; 242 while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) { 243 type = parser.next(); 244 } 245 246 if (TAG_WALLET_SERVICE.equals(parser.getName())) { 247 final AttributeSet allAttributes = Xml.asAttributeSet(parser); 248 TypedArray afsAttributes = null; 249 try { 250 afsAttributes = resources.obtainAttributes(allAttributes, 251 R.styleable.QuickAccessWalletService); 252 String targetActivity = afsAttributes.getString( 253 R.styleable.QuickAccessWalletService_targetActivity); 254 String settingsActivity = afsAttributes.getString( 255 R.styleable.QuickAccessWalletService_settingsActivity); 256 CharSequence shortcutShortLabel = afsAttributes.getText( 257 R.styleable.QuickAccessWalletService_shortcutShortLabel); 258 CharSequence shortcutLongLabel = afsAttributes.getText( 259 R.styleable.QuickAccessWalletService_shortcutLongLabel); 260 return new ServiceMetadata(targetActivity, settingsActivity, shortcutShortLabel, 261 shortcutLongLabel); 262 } finally { 263 if (afsAttributes != null) { 264 afsAttributes.recycle(); 265 } 266 } 267 } else { 268 Log.e(TAG, "Meta-data does not start with quickaccesswallet-service tag"); 269 } 270 } catch (PackageManager.NameNotFoundException 271 | IOException 272 | XmlPullParserException e) { 273 Log.e(TAG, "Error parsing quickaccesswallet service meta-data", e); 274 } 275 return ServiceMetadata.empty(); 276 } 277 278 /** 279 * @return the component name of the {@link QuickAccessWalletService} 280 */ 281 @NonNull getComponentName()282 ComponentName getComponentName() { 283 return mServiceInfo.getComponentName(); 284 } 285 getUserId()286 int getUserId() { 287 return mUserId; 288 } 289 /** 290 * @return the fully qualified name of the activity that hosts the full wallet. If available, 291 * this intent should be started with the action 292 * {@link QuickAccessWalletService#ACTION_VIEW_WALLET} 293 */ 294 @Nullable getWalletActivity()295 String getWalletActivity() { 296 return mServiceMetadata.mTargetActivity; 297 } 298 299 /** 300 * @return the fully qualified name of the activity that allows the user to change quick access 301 * wallet settings. If available, this intent should be started with the action {@link 302 * QuickAccessWalletService#ACTION_VIEW_WALLET_SETTINGS} 303 */ 304 @Nullable getSettingsActivity()305 String getSettingsActivity() { 306 return mServiceMetadata.mSettingsActivity; 307 } 308 309 @NonNull getWalletLogo(Context context)310 Drawable getWalletLogo(Context context) { 311 Drawable drawable = mServiceInfo.loadLogo(context.getPackageManager()); 312 if (drawable != null) { 313 return drawable; 314 } 315 return mServiceInfo.loadIcon(context.getPackageManager()); 316 } 317 318 @Nullable getTileIcon()319 Drawable getTileIcon() { 320 return mTileServiceMetadata.mTileIcon; 321 } 322 323 @NonNull getShortcutShortLabel(Context context)324 CharSequence getShortcutShortLabel(Context context) { 325 if (!TextUtils.isEmpty(mServiceMetadata.mShortcutShortLabel)) { 326 return mServiceMetadata.mShortcutShortLabel; 327 } 328 return mServiceInfo.loadLabel(context.getPackageManager()); 329 } 330 331 @NonNull getShortcutLongLabel(Context context)332 CharSequence getShortcutLongLabel(Context context) { 333 if (!TextUtils.isEmpty(mServiceMetadata.mShortcutLongLabel)) { 334 return mServiceMetadata.mShortcutLongLabel; 335 } 336 return mServiceInfo.loadLabel(context.getPackageManager()); 337 } 338 339 @NonNull getServiceLabel(Context context)340 CharSequence getServiceLabel(Context context) { 341 return mServiceInfo.loadLabel(context.getPackageManager()); 342 } 343 } 344