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