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