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