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