/*
 * 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 static android.permission.flags.Flags.walletRoleCrossUserEnabled;

import static com.android.permission.flags.Flags.crossUserRoleEnabled;

import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.role.RoleManager;
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.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.Pair;
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 final TileServiceMetadata mTileServiceMetadata;
    private final int mUserId;

    private QuickAccessWalletServiceInfo(
            @NonNull ServiceInfo serviceInfo,
            @NonNull ServiceMetadata metadata,
            @NonNull TileServiceMetadata tileServiceMetadata,
            int userId) {
        mServiceInfo = serviceInfo;
        mServiceMetadata = metadata;
        mTileServiceMetadata = tileServiceMetadata;
        mUserId = userId;
    }

    @Nullable
    static QuickAccessWalletServiceInfo tryCreate(@NonNull Context context) {
        String defaultAppPackageName = null;

        int defaultAppUser = UserHandle.myUserId();

        if (isWalletRoleAvailable(context)) {
            Pair<String, Integer> roleAndUser = getDefaultWalletApp(context);
            defaultAppPackageName = roleAndUser.first;
            defaultAppUser = roleAndUser.second;
        } else {
            ComponentName defaultPaymentApp = getDefaultPaymentApp(context);
            if (defaultPaymentApp == null) {
                return null;
            }
            defaultAppPackageName = defaultPaymentApp.getPackageName();
        }

        if (defaultAppPackageName == null || defaultAppUser < 0) {
            return null;
        }

        ServiceInfo serviceInfo = getWalletServiceInfo(context, defaultAppPackageName,
                defaultAppUser);
        if (serviceInfo == null) {
            return null;
        }

        if (!Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE.equals(serviceInfo.permission)) {
            Log.w(TAG, String.format("%s.%s does not require permission %s",
                    serviceInfo.packageName, serviceInfo.name,
                    Manifest.permission.BIND_QUICK_ACCESS_WALLET_SERVICE));
            return null;
        }

        ServiceMetadata metadata = parseServiceMetadata(context, serviceInfo);
        TileServiceMetadata tileServiceMetadata =
                new TileServiceMetadata(parseTileServiceMetadata(context, serviceInfo));
        return new QuickAccessWalletServiceInfo(serviceInfo, metadata, tileServiceMetadata,
                defaultAppUser);
    }

    @NonNull
    private static Pair<String, Integer> getDefaultWalletApp(Context context) {
        UserHandle user = UserHandle.of(UserHandle.myUserId());

        final long token = Binder.clearCallingIdentity();
        try {
            RoleManager roleManager = context.getSystemService(RoleManager.class);

            if (walletRoleCrossUserEnabled()
                    && crossUserRoleEnabled()
                    && context.checkCallingOrSelfPermission(
                    Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                    == PackageManager.PERMISSION_GRANTED) {
                user = roleManager.getActiveUserForRole(RoleManager.ROLE_WALLET);
                if (user == null) {
                    return new Pair<>(null, UserHandle.myUserId());
                }
            }
            List<String> roleHolders = roleManager.getRoleHoldersAsUser(RoleManager.ROLE_WALLET,
                    user);
            return new Pair<>(roleHolders.isEmpty() ? null : roleHolders.get(0),
                    user.getIdentifier());
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    private static boolean isWalletRoleAvailable(Context context) {
        final long token = Binder.clearCallingIdentity();
        try {
            RoleManager roleManager = context.getSystemService(RoleManager.class);
            return roleManager.isRoleAvailable(RoleManager.ROLE_WALLET);
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }

    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,
            int userId) {
        Intent intent = new Intent(QuickAccessWalletService.SERVICE_INTERFACE);
        intent.setPackage(packageName);
        List<ResolveInfo> resolveInfos =
                context.getPackageManager().queryIntentServicesAsUser(intent,
                        PackageManager.MATCH_DIRECT_BOOT_AWARE
                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
                        | PackageManager.MATCH_DEFAULT_ONLY
                        | PackageManager.GET_META_DATA, userId);
        return resolveInfos.isEmpty() ? null : resolveInfos.get(0).serviceInfo;
    }

    private static class TileServiceMetadata {
        @Nullable
        private final Drawable mTileIcon;

        private TileServiceMetadata(@Nullable Drawable tileIcon) {
            mTileIcon = tileIcon;
        }
    }

    @Nullable
    private static Drawable parseTileServiceMetadata(Context context, ServiceInfo serviceInfo) {
        PackageManager pm = context.getPackageManager();
        int tileIconDrawableId =
                serviceInfo.metaData.getInt(QuickAccessWalletService.TILE_SERVICE_META_DATA);
        if (tileIconDrawableId != 0) {
            try {
                Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
                return resources.getDrawable(tileIconDrawableId, null);
            } catch (PackageManager.NameNotFoundException e) {
                Log.e(TAG, "Error parsing quickaccesswallet tile service meta-data", e);
            }
        }
        return null;
    }

    static class ServiceMetadata {
        @Nullable
        private final String mSettingsActivity;
        @Nullable
        private final String mTargetActivity;
        @Nullable
        private final CharSequence mShortcutShortLabel;
        @Nullable
        private final CharSequence mShortcutLongLabel;

        private static ServiceMetadata empty() {
            return new ServiceMetadata(null, null, null, null);
        }

        private ServiceMetadata(
                String targetActivity,
                String settingsActivity,
                CharSequence shortcutShortLabel,
                CharSequence shortcutLongLabel) {
            mTargetActivity = targetActivity;
            mSettingsActivity = settingsActivity;
            mShortcutShortLabel = shortcutShortLabel;
            mShortcutLongLabel = shortcutLongLabel;
        }
    }

    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 ServiceMetadata.empty();
        }

        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 targetActivity = afsAttributes.getString(
                            R.styleable.QuickAccessWalletService_targetActivity);
                    String settingsActivity = afsAttributes.getString(
                            R.styleable.QuickAccessWalletService_settingsActivity);
                    CharSequence shortcutShortLabel = afsAttributes.getText(
                            R.styleable.QuickAccessWalletService_shortcutShortLabel);
                    CharSequence shortcutLongLabel = afsAttributes.getText(
                            R.styleable.QuickAccessWalletService_shortcutLongLabel);
                    return new ServiceMetadata(targetActivity, settingsActivity, shortcutShortLabel,
                            shortcutLongLabel);
                } 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 ServiceMetadata.empty();
    }

    /**
     * @return the component name of the {@link QuickAccessWalletService}
     */
    @NonNull
    ComponentName getComponentName() {
        return mServiceInfo.getComponentName();
    }

    int getUserId() {
        return mUserId;
    }
    /**
     * @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.mTargetActivity;
    }

    /**
     * @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;
    }

    @NonNull
    Drawable getWalletLogo(Context context) {
        Drawable drawable = mServiceInfo.loadLogo(context.getPackageManager());
        if (drawable != null) {
            return drawable;
        }
        return mServiceInfo.loadIcon(context.getPackageManager());
    }

    @Nullable
    Drawable getTileIcon() {
        return mTileServiceMetadata.mTileIcon;
    }

    @NonNull
    CharSequence getShortcutShortLabel(Context context) {
        if (!TextUtils.isEmpty(mServiceMetadata.mShortcutShortLabel)) {
            return mServiceMetadata.mShortcutShortLabel;
        }
        return mServiceInfo.loadLabel(context.getPackageManager());
    }

    @NonNull
    CharSequence getShortcutLongLabel(Context context) {
        if (!TextUtils.isEmpty(mServiceMetadata.mShortcutLongLabel)) {
            return mServiceMetadata.mShortcutLongLabel;
        }
        return mServiceInfo.loadLabel(context.getPackageManager());
    }

    @NonNull
    CharSequence getServiceLabel(Context context) {
        return mServiceInfo.loadLabel(context.getPackageManager());
    }
}
