Add QuickAccessWallet API

Adds a new API that allows applications to provide payment cards and
other relevant passes to SysUI which are then shown in the Quick Access
Wallet (long press on Pixel). See go/aospqaw-dd for details.

Bug: 144342153
Test: manual - started device, didn't blow up
Test: atest - run from frameworks/base dir
Change-Id: I8fef3116e6e4bd1f8a4f5a907892ea8993b49b0e
diff --git a/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
new file mode 100644
index 0000000..8793f28
--- /dev/null
+++ b/core/java/android/service/quickaccesswallet/QuickAccessWalletServiceInfo.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.quickaccesswallet;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.provider.Settings;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+
+import com.android.internal.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * {@link ServiceInfo} and meta-data about a {@link QuickAccessWalletService}.
+ *
+ * @hide
+ */
+class QuickAccessWalletServiceInfo {
+
+    private static final String TAG = "QAWalletSInfo";
+    private static final String TAG_WALLET_SERVICE = "quickaccesswallet-service";
+
+    private final ServiceInfo mServiceInfo;
+    private final ServiceMetadata mServiceMetadata;
+
+    private QuickAccessWalletServiceInfo(
+            @NonNull ServiceInfo serviceInfo,
+            @NonNull ServiceMetadata metadata) {
+        mServiceInfo = serviceInfo;
+        mServiceMetadata = metadata;
+    }
+
+    @Nullable
+    static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
+        ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
+        if (defaultPaymentApp == null) {
+            Log.d(TAG, "create: default payment app not set");
+            return null;
+        }
+
+        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultPaymentApp.getPackageName());
+        if (serviceInfo == null) {
+            Log.d(TAG, "create: unable to resolve service intent");
+            return null;
+        }
+
+        if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) {
+            Log.w(TAG, String.format("QuickAccessWalletService from %s does not have permission %s",
+                    serviceInfo.packageName, Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
+            return null;
+        }
+
+        ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
+        return new QuickAccessWalletServiceInfo(serviceInfo, metadata);
+    }
+
+    private static ComponentName getDefaultPaymentApp(Context context) {
+        ContentResolver cr = context.getContentResolver();
+        String comp = Settings.Secure.getString(cr, Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
+        return comp == null ? null : ComponentName.unflattenFromString(comp);
+    }
+
+    private static ServiceInfo getWalletServiceInfo(Context context, String packageName) {
+        Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
+        intent.setPackage(packageName);
+        List<ResolveInfo> resolveInfos =
+                context.getPackageManager().queryIntentServices(intent,
+                        PackageManager.MATCH_DEFAULT_ONLY);
+        return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
+    }
+
+    private static class ServiceMetadata {
+        @Nullable
+        private final String mSettingsActivity;
+        @Nullable
+        private final String mWalletActivity;
+
+        private ServiceMetadata(String settingsActivity, String walletActivity) {
+            this.mSettingsActivity = settingsActivity;
+            this.mWalletActivity = walletActivity;
+        }
+    }
+
+    private static ServiceMetadata parseServiceMetadata(Context context, ServiceInfo serviceInfo) {
+        PackageManager pm = context.getPackageManager();
+        final XmlResourceParser parser =
+                serviceInfo.loadXmlMetaData(pm, QuickAccessWalletService.SERVICE_META_DATA);
+
+        if (parser == null) {
+            return new ServiceMetadata(null, null);
+        }
+
+        try {
+            Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+            int type = 0;
+            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                type = parser.next();
+            }
+
+            if (TAG_WALLET_SERVICE.equals(parser.getName())) {
+                final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+                TypedArray afsAttributes = null;
+                try {
+                    afsAttributes = resources.obtainAttributes(allAttributes,
+                            R.styleable.QuickAccessWalletService);
+                    String settingsActivity = afsAttributes.getString(
+                            R.styleable.QuickAccessWalletService_settingsActivity);
+                    String walletActivity = afsAttributes.getString(
+                            R.styleable.QuickAccessWalletService_targetActivity);
+                    return new ServiceMetadata(settingsActivity, walletActivity);
+                } finally {
+                    if (afsAttributes != null) {
+                        afsAttributes.recycle();
+                    }
+                }
+            } else {
+                Log.e(TAG, "Meta-data does not start with quickaccesswallet-service tag");
+            }
+
+        } catch (PackageManager.NameNotFoundException
+                | IOException
+                | XmlPullParserException e) {
+            Log.e(TAG, "Error parsing quickaccesswallet service meta-data", e);
+        }
+        return new ServiceMetadata(null, null);
+    }
+
+    /**
+     * @return the component name of the {@link QuickAccessWalletService}
+     */
+    @NonNull
+    ComponentName getComponentName() {
+        return mServiceInfo.getComponentName();
+    }
+
+    /**
+     * @return the fully qualified name of the activity that hosts the full wallet. If available,
+     * this intent should be started with the action
+     * {@link QuickAccessWalletService#ACTION_VIEW_WALLET}
+     */
+    @Nullable
+    String getWalletActivity() {
+        return mServiceMetadata.mWalletActivity;
+    }
+
+    /**
+     * @return the fully qualified name of the activity that allows the user to change quick access
+     * wallet settings. If available, this intent should be started with the action {@link
+     * QuickAccessWalletService#ACTION_VIEW_WALLET_SETTINGS}
+     */
+    @Nullable
+    String getSettingsActivity() {
+        return mServiceMetadata.mSettingsActivity;
+    }
+}