/*
 * Copyright (C) 2022 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 com.android.managedprovisioning.task;

import static com.android.internal.util.Preconditions.checkNotNull;

import static java.util.Objects.requireNonNull;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

import com.android.internal.annotations.VisibleForTesting;
import com.android.managedprovisioning.analytics.MetricsWriterFactory;
import com.android.managedprovisioning.analytics.ProvisioningAnalyticsTracker;
import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
import com.android.managedprovisioning.common.ProvisionLogger;
import com.android.managedprovisioning.common.SettingsFacade;
import com.android.managedprovisioning.common.Utils;
import com.android.managedprovisioning.model.PackageDownloadInfo;
import com.android.managedprovisioning.model.ProvisioningParams;

import java.io.File;

/**
 * Verifies the management app apk downloaded previously in {@link DownloadPackageTask}.
 *
 * <p>The first check verifies that a {@link android.app.admin.DeviceAdminReceiver} is present in
 * the apk and that it corresponds to the one provided via
 * {@link ProvisioningParams#deviceAdminComponentName}.</p>
 *
 * <p>The second check verifies that the package or signature checksum matches the ones given via
 * {@link PackageDownloadInfo#packageChecksum} or {@link PackageDownloadInfo#signatureChecksum}
 * respectively. The package checksum takes priority in case both are present.</p>
 */
public class VerifyAdminPackageTask extends AbstractProvisioningTask {
    public static final int ERROR_HASH_MISMATCH = 0;
    public static final int ERROR_DEVICE_ADMIN_MISSING = 1;

    private final Utils mUtils;
    private final PackageLocationProvider mDownloadLocationProvider;
    private final PackageManager mPackageManager;
    private final PackageDownloadInfo mPackageDownloadInfo;
    private final ChecksumUtils mChecksumUtils;

    public VerifyAdminPackageTask(
            PackageLocationProvider downloadLocationProvider,
            Context context,
            ProvisioningParams params,
            PackageDownloadInfo packageDownloadInfo,
            Callback callback) {
        this(new Utils(), downloadLocationProvider, context, params, packageDownloadInfo, callback,
                new ProvisioningAnalyticsTracker(
                        MetricsWriterFactory.getMetricsWriter(context, new SettingsFacade()),
                        new ManagedProvisioningSharedPreferences(context)),
                new ChecksumUtils(new Utils()));
    }

    @VisibleForTesting
    VerifyAdminPackageTask(
            Utils utils,
            PackageLocationProvider downloadLocationProvider,
            Context context,
            ProvisioningParams params,
            PackageDownloadInfo packageDownloadInfo,
            Callback callback,
            ProvisioningAnalyticsTracker provisioningAnalyticsTracker,
            ChecksumUtils checksumUtils) {
        super(context, params, callback, provisioningAnalyticsTracker);

        mUtils = checkNotNull(utils);
        mDownloadLocationProvider = checkNotNull(downloadLocationProvider);
        mPackageManager = mContext.getPackageManager();
        mPackageDownloadInfo = checkNotNull(packageDownloadInfo);
        mChecksumUtils = requireNonNull(checksumUtils);
    }

    @Override
    public void run(int userId) {
        final File packageLocation = mDownloadLocationProvider.getPackageLocation();
        if (packageLocation == null) {
            ProvisionLogger.logw("VerifyPackageTask invoked, but package is null");
            success();
            return;
        }
        ProvisionLogger.logi("Verifying package from location " + packageLocation.getAbsolutePath()
                + " for user " + userId);

        PackageInfo packageInfo = mPackageManager.getPackageArchiveInfo(
                packageLocation.getAbsolutePath(),
                PackageManager.GET_SIGNATURES | PackageManager.GET_RECEIVERS);
        String packageName = mProvisioningParams.inferDeviceAdminPackageName();
        // Device admin package name can't be null
        if (packageInfo == null || packageName == null) {
            ProvisionLogger.loge("Device admin package info or name is null");
            error(ERROR_DEVICE_ADMIN_MISSING);
            return;
        }

        if (mUtils.findDeviceAdminInPackageInfo(packageName,
                mProvisioningParams.deviceAdminComponentName, packageInfo) == null) {
            error(ERROR_DEVICE_ADMIN_MISSING);
            return;
        }

        if (mPackageDownloadInfo.packageChecksum.length > 0) {
            if (!mChecksumUtils.doesPackageHashMatch(
                    packageLocation.getAbsolutePath(), mPackageDownloadInfo.packageChecksum)) {
                error(ERROR_HASH_MISMATCH);
                return;
            }
        } else {
            if (!mChecksumUtils.doesASignatureHashMatch(
                    packageInfo, mPackageDownloadInfo.signatureChecksum)) {
                error(ERROR_HASH_MISMATCH);
                return;
            }
        }

        success();
    }
}