/*
 * 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.content.pm.parsing.component;

import static android.content.pm.parsing.component.ComponentParseUtils.flag;

import android.annotation.NonNull;
import android.content.pm.ActivityInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.parsing.ParsingPackage;
import android.content.pm.parsing.ParsingPackageUtils;
import android.content.pm.parsing.ParsingUtils;
import android.content.pm.parsing.result.ParseInput;
import android.content.pm.parsing.result.ParseResult;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;

import com.android.internal.R;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.Objects;

/** @hide */
public class ParsedServiceUtils {

    private static final String TAG = ParsingPackageUtils.TAG;

    @NonNull
    public static ParseResult<ParsedService> parseService(String[] separateProcesses,
            ParsingPackage pkg, Resources res, XmlResourceParser parser, int flags,
            boolean useRoundIcon, ParseInput input)
            throws XmlPullParserException, IOException {
        boolean visibleToEphemeral;
        boolean setExported;

        final String packageName = pkg.getPackageName();
        final ParsedService service = new ParsedService();
        String tag = parser.getName();

        TypedArray sa = res.obtainAttributes(parser, R.styleable.AndroidManifestService);
        try {
            ParseResult<ParsedService> result = ParsedMainComponentUtils.parseMainComponent(
                    service, tag, separateProcesses, pkg, sa, flags, useRoundIcon, input,
                    R.styleable.AndroidManifestService_banner,
                    R.styleable.AndroidManifestService_description,
                    R.styleable.AndroidManifestService_directBootAware,
                    R.styleable.AndroidManifestService_enabled,
                    R.styleable.AndroidManifestService_icon,
                    R.styleable.AndroidManifestService_label,
                    R.styleable.AndroidManifestService_logo,
                    R.styleable.AndroidManifestService_name,
                    R.styleable.AndroidManifestService_process,
                    R.styleable.AndroidManifestService_roundIcon,
                    R.styleable.AndroidManifestService_splitName
            );

            if (result.isError()) {
                return result;
            }

            setExported = sa.hasValue(R.styleable.AndroidManifestService_exported);
            if (setExported) {
                service.exported = sa.getBoolean(R.styleable.AndroidManifestService_exported,
                        false);
            }

            String permission = sa.getNonConfigurationString(
                    R.styleable.AndroidManifestService_permission, 0);
            service.setPermission(permission != null ? permission : pkg.getPermission());

            service.foregroundServiceType = sa.getInt(
                    R.styleable.AndroidManifestService_foregroundServiceType,
                    ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE);

            service.flags |= flag(ServiceInfo.FLAG_STOP_WITH_TASK,
                    R.styleable.AndroidManifestService_stopWithTask, sa)
                    | flag(ServiceInfo.FLAG_ISOLATED_PROCESS,
                    R.styleable.AndroidManifestService_isolatedProcess, sa)
                    | flag(ServiceInfo.FLAG_EXTERNAL_SERVICE,
                    R.styleable.AndroidManifestService_externalService, sa)
                    | flag(ServiceInfo.FLAG_USE_APP_ZYGOTE,
                    R.styleable.AndroidManifestService_useAppZygote, sa)
                    | flag(ServiceInfo.FLAG_SINGLE_USER,
                    R.styleable.AndroidManifestService_singleUser, sa);

            visibleToEphemeral = sa.getBoolean(
                    R.styleable.AndroidManifestService_visibleToInstantApps, false);
            if (visibleToEphemeral) {
                service.flags |= ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP;
                pkg.setVisibleToInstantApps(true);
            }
        } finally {
            sa.recycle();
        }

        if (pkg.isCantSaveState()) {
            // A heavy-weight application can not have services in its main process
            // We can do direct compare because we intern all strings.
            if (Objects.equals(service.getProcessName(), packageName)) {
                return input.error("Heavy-weight applications can not have services "
                        + "in main process");
            }
        }
        final int depth = parser.getDepth();
        int type;
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG
                || parser.getDepth() > depth)) {
            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final ParseResult parseResult;
            switch (parser.getName()) {
                case "intent-filter":
                    ParseResult<ParsedIntentInfo> intentResult = ParsedMainComponentUtils
                            .parseIntentFilter(service, pkg, res, parser, visibleToEphemeral,
                                    true /*allowGlobs*/, false /*allowAutoVerify*/,
                                    false /*allowImplicitEphemeralVisibility*/,
                                    false /*failOnNoActions*/, input);
                    parseResult = intentResult;
                    if (intentResult.isSuccess()) {
                        ParsedIntentInfo intent = intentResult.getResult();
                        service.order = Math.max(intent.getOrder(), service.order);
                        service.addIntent(intent);
                    }
                    break;
                case "meta-data":
                    parseResult = ParsedComponentUtils.addMetaData(service, pkg, res, parser, input);
                    break;
                default:
                    parseResult = ParsingUtils.unknownTag(tag, pkg, parser, input);
                    break;
            }

            if (parseResult.isError()) {
                return input.error(parseResult);
            }
        }

        if (!setExported) {
            service.exported = service.getIntents().size() > 0;
        }

        return input.success(service);
    }
}
