/*
 * Copyright 2016, 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.analytics;

import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.EXTRA_PROVISIONING_TRIGGER;
import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_QR_CODE;
import static android.app.admin.DevicePolicyManager.PROVISIONING_TRIGGER_UNSPECIFIED;
import static android.nfc.NfcAdapter.ACTION_NDEF_DISCOVERED;
import static android.stats.devicepolicy.DevicePolicyEnums.PROVISIONING_DPC_SETUP_COMPLETED;
import static android.stats.devicepolicy.DevicePolicyEnums.PROVISIONING_DPC_SETUP_STARTED;
import static android.stats.devicepolicy.DevicePolicyEnums.PROVISIONING_MANAGED_PROFILE_ON_FULLY_MANAGED_DEVICE;

import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ACTION;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_CANCELLED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_DPC_INSTALLED_BY_PACKAGE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_DPC_PACKAGE_NAME;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_NFC;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_QR_CODE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ENTRY_POINT_TRUSTED_SOURCE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_ERROR;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_EXTRA;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_SESSION_COMPLETED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_SESSION_STARTED;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_TERMS_COUNT;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.PROVISIONING_TERMS_READ;

import android.annotation.IntDef;
import android.app.admin.DevicePolicyEventLogger;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.stats.devicepolicy.DevicePolicyEnums;

import com.android.managedprovisioning.common.ManagedProvisioningSharedPreferences;
import com.android.managedprovisioning.common.SettingsFacade;
import com.android.managedprovisioning.model.ProvisioningParams;
import com.android.managedprovisioning.task.AbstractProvisioningTask;

import java.util.List;

/**
 * Utility class to log metrics.
 */
public class ProvisioningAnalyticsTracker {

    private final MetricsLoggerWrapper mMetricsLoggerWrapper = new MetricsLoggerWrapper();

    // Only add to the end of the list. Do not change or rearrange these values, that will break
    // historical data. Do not use negative numbers or zero, logger only handles positive
    // integers.
    public static final int CANCELLED_BEFORE_PROVISIONING = 1;
    public static final int CANCELLED_DURING_PROVISIONING = 2;
    public static final int CANCELLED_DURING_PROVISIONING_PREPARE = 3;
    private final ManagedProvisioningSharedPreferences mSharedPreferences;

    @IntDef({
        CANCELLED_BEFORE_PROVISIONING,
        CANCELLED_DURING_PROVISIONING,
        CANCELLED_DURING_PROVISIONING_PREPARE})
    public @interface CancelState {}

    private static final int PROVISIONING_FLOW_TYPE_ADMIN_INTEGRATED = 1;
    private static final int PROVISIONING_FLOW_TYPE_LEGACY = 2;

    private static final int DPC_SETUP_ACTION_UNKNOWN = 1;
    private static final int DPC_SETUP_ACTION_PROVISIONING_SUCCESSFUL = 2;
    private static final int DPC_SETUP_ACTION_ADMIN_POLICY_COMPLIANCE = 3;

    private final MetricsWriter mMetricsWriter;

    public ProvisioningAnalyticsTracker(MetricsWriter metricsWriter,
            ManagedProvisioningSharedPreferences prefs) {
        // Disables instantiation. Use getInstance() instead.
        mMetricsWriter = metricsWriter;
        mSharedPreferences = prefs;
    }

    /**
     * Logs some metrics when the provisioning starts.
     *
     * @param context Context passed to MetricsLogger
     * @param params Provisioning params
     */
    public void logProvisioningStarted(Context context, ProvisioningParams params) {
        logDpcPackageInformation(context, params.inferDeviceAdminPackageName());
        logNetworkType(context);
    }

    /**
     * Logs when provisioning is cancelled.
     *
     * @param context Context passed to MetricsLogger
     * @param cancelState State when provisioning was cancelled
     */
    public void logProvisioningCancelled(Context context, @CancelState int cancelState) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_CANCELLED, cancelState);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_CANCELLED)
                .setInt(cancelState)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs error during provisioning tasks.
     *
     * @param context Context passed to MetricsLogger
     * @param task Provisioning task which threw error
     * @param errorCode Code indicating the type of error that happened.
     */
    public void logProvisioningError(Context context, AbstractProvisioningTask task,
            int errorCode) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_ERROR,
                AnalyticsUtils.getErrorString(task, errorCode));
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_ERROR)
                .setStrings(AnalyticsUtils.getErrorString(task, errorCode))
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs error code, when provisioning is not allowed.
     *
     * @param context Context passed to MetricsLogger
     * @param provisioningErrorCode Code indicating why provisioning is not allowed.
     */
    public void logProvisioningNotAllowed(Context context, int provisioningErrorCode) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_ERROR, provisioningErrorCode);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_ERROR)
                .setStrings(String.valueOf(provisioningErrorCode))
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * logs when a provisioning session has started.
     *
     * @param context Context passed to MetricsLogger
     */
    public void logProvisioningSessionStarted(Context context) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_SESSION_STARTED);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_SESSION_STARTED)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * logs when a provisioning session has completed.
     *
     * @param context Context passed to MetricsLogger
     */
    public void logProvisioningSessionCompleted(Context context) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_SESSION_COMPLETED);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_SESSION_COMPLETED)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * logs number of terms displayed on the terms screen.
     *
     * @param context Context passed to MetricsLogger
     * @param count Number of terms displayed
     */
    public void logNumberOfTermsDisplayed(Context context, int count) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_TERMS_COUNT, count);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_TERMS_COUNT)
                .setInt(count)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * logs number of terms read on the terms screen.
     *
     * @param context Context passed to MetricsLogger
     * @param count Number of terms read
     */
    public void logNumberOfTermsRead(Context context, int count) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_TERMS_READ, count);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_TERMS_READ)
                .setInt(count)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs when the provisioning preparation has started.
     * <p>The preparation includes network setup, downloading, verifying and installing the
     * admin app.
     */
    public void logProvisioningPrepareStarted() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_PREPARE_STARTED)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs when the provisioning preparation has completed.
     * <p>The preparation includes network setup, downloading, verifying and installing the
     * admin app.
     */
    public void logProvisioningPrepareCompleted() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_PREPARE_COMPLETED)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logTimeLoggerEvent(int devicePolicyEvent, int time) {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(devicePolicyEvent)
                .setInt(time)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs the provisioning action.
     *  @param context Context passed to MetricsLogger
     * @param provisioningAction Action that triggered provisioning
     */
    public void logProvisioningAction(Context context, String provisioningAction) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_ACTION, provisioningAction);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_ACTION)
                .setStrings(provisioningAction)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
        maybeLogManagedProfileOnFullyManagedDevice(context, provisioningAction);
    }

    /**
     * Logs organization owned managed profile provisioning.
     */
    public void logOrganizationOwnedManagedProfileProvisioning() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_ORGANIZATION_OWNED_MANAGED_PROFILE)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs when the DPC is started, for the purpose of enterprise setup.  Note that in the admin-
     * integrated flow, this is when {@link DevicePolicyManager#ACTION_ADMIN_POLICY_COMPLIANCE} is
     * sent to the DPC, not {@link DevicePolicyManager#ACTION_GET_PROVISIONING_MODE}.
     *  @param context Context passed to MetricsLogger
     * @param intentAction Action that was sent to the DPC
     */
    public void logDpcSetupStarted(Context context, String intentAction) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_DPC_SETUP_STARTED);

        int intentActionCode;
        switch(intentAction) {
            case DevicePolicyManager.ACTION_PROVISIONING_SUCCESSFUL:
                intentActionCode = DPC_SETUP_ACTION_PROVISIONING_SUCCESSFUL;
                break;
            case DevicePolicyManager.ACTION_ADMIN_POLICY_COMPLIANCE:
                intentActionCode = DPC_SETUP_ACTION_ADMIN_POLICY_COMPLIANCE;
                break;
            default:
                intentActionCode = DPC_SETUP_ACTION_UNKNOWN;
                break;
        }

        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(PROVISIONING_DPC_SETUP_STARTED)
                .setInt(intentActionCode)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs when the DPC finishes with enterprise setup.  Note that this is only logged when setup
     * happens inside Setup Wizard; if it happens after Setup Wizard, we never find out when the
     * DPC finishes.
     *  @param context Context passed to MetricsLogger
     * @param resultCode The result code that is returned by the DPC
     */
    public void logDpcSetupCompleted(Context context, int resultCode) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_DPC_SETUP_COMPLETED);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(PROVISIONING_DPC_SETUP_COMPLETED)
                .setInt(resultCode)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs the type of provisioning flow if this is organization owned provisioning.
     * <p>It would be either admin integrated flow or legacy.
     *
     * @param params Used to extract whether this is the admin integrated flow
     */
    public void logProvisioningFlowType(ProvisioningParams params) {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_FLOW_TYPE)
                .setInt(params.flowType == ProvisioningParams.FLOW_TYPE_ADMIN_INTEGRATED
                        ? PROVISIONING_FLOW_TYPE_ADMIN_INTEGRATED
                        : PROVISIONING_FLOW_TYPE_LEGACY)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs whether an {@link android.app.Activity} is in landscape mode, along with its name.
     */
    public void logIsLandscape(boolean isLandscape, String activityName) {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_IS_LANDSCAPE)
                .setBoolean(isLandscape)
                .setStrings(activityName)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs whether the app is in night mode.
     */
    public void logIsNightMode(boolean isNightMode) {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_IS_NIGHT_MODE)
                .setBoolean(isNightMode)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs all the provisioning extras passed by the dpc.
     *
     * @param context Context passed to MetricsLogger
     * @param intent Intent that started provisioning
     */
    public void logProvisioningExtras(Context context, Intent intent) {
        final List<String> provisioningExtras = AnalyticsUtils.getAllProvisioningExtras(intent);
        for (String extra : provisioningExtras) {
            mMetricsLoggerWrapper.logAction(context, PROVISIONING_EXTRA, extra);
        }
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_EXTRAS)
                .setStrings(provisioningExtras.toArray(new String[0]))
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs some entry points to provisioning.
     *  @param context Context passed to MetricsLogger
     * @param intent Intent that started provisioning
     * @param settingsFacade
     */
    public void logEntryPoint(Context context, Intent intent,
            SettingsFacade settingsFacade) {
        if (intent == null || intent.getAction() == null) {
            return;
        }
        switch (intent.getAction()) {
            case ACTION_NDEF_DISCOVERED:
                mMetricsLoggerWrapper.logAction(context, PROVISIONING_ENTRY_POINT_NFC);
                mMetricsWriter.write(DevicePolicyEventLogger
                        .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_NFC)
                        .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
                break;
            case ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE:
                logProvisionedFromTrustedSource(context, intent, settingsFacade);
                break;
        }
    }

    public void logRoleHolderProvisioningStart() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.ROLE_HOLDER_PROVISIONING_START)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logRoleHolderProvisioningFinish() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.ROLE_HOLDER_PROVISIONING_FINISH)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logPlatformRoleHolderUpdateStart() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PLATFORM_ROLE_HOLDER_UPDATE_START)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logPlatformRoleHolderUpdateFailed() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PLATFORM_ROLE_HOLDER_UPDATE_FAILED)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logPlatformRoleHolderUpdateFinished(int resultCode) {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PLATFORM_ROLE_HOLDER_UPDATE_FINISHED)
                        .setInt(resultCode)
                        .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logRoleHolderUpdaterUpdateStart() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.ROLE_HOLDER_UPDATER_UPDATE_START)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logRoleHolderUpdaterUpdateFinish(int resultCode) {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.ROLE_HOLDER_UPDATER_UPDATE_FINISH)
                .setInt(resultCode)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logRoleHolderUpdaterUpdateRetry() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.ROLE_HOLDER_UPDATER_UPDATE_RETRY)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    public void logRoleHolderUpdaterUpdateFailed() {
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.ROLE_HOLDER_UPDATER_UPDATE_FAILED)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    private void logProvisionedFromTrustedSource(Context context, Intent intent,
            SettingsFacade settingsFacade) {
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_ENTRY_POINT_TRUSTED_SOURCE);
        final int provisioningTrigger = intent.getIntExtra(EXTRA_PROVISIONING_TRIGGER,
                PROVISIONING_TRIGGER_UNSPECIFIED);
        if (provisioningTrigger == PROVISIONING_TRIGGER_QR_CODE) {
            mMetricsLoggerWrapper.logAction(context, PROVISIONING_ENTRY_POINT_QR_CODE);
        }
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_ENTRY_POINT_TRUSTED_SOURCE)
                .setInt(provisioningTrigger)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences))
                .setBoolean(settingsFacade.isDuringSetupWizard(context)));
    }

    /**
     * Logs package information of the dpc.
     *
     * @param context Context passed to MetricsLogger
     * @param dpcPackageName Package name of the dpc
     */
    private void logDpcPackageInformation(Context context, String dpcPackageName) {
        // Logs package name of the dpc.
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_DPC_PACKAGE_NAME, dpcPackageName);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_DPC_PACKAGE_NAME)
                .setStrings(dpcPackageName)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));

        // Logs package name of the package which installed dpc.
        final String dpcInstallerPackage =
                AnalyticsUtils.getInstallerPackageName(context, dpcPackageName);
        mMetricsLoggerWrapper.logAction(context, PROVISIONING_DPC_INSTALLED_BY_PACKAGE,
                dpcInstallerPackage);
        mMetricsWriter.write(DevicePolicyEventLogger
                .createEvent(DevicePolicyEnums.PROVISIONING_DPC_INSTALLED_BY_PACKAGE)
                .setStrings(dpcInstallerPackage)
                .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
    }

    /**
     * Logs the network type to which the device is connected.
     *
     * @param context Context passed to MetricsLogger
     */
    private void logNetworkType(Context context) {
        NetworkTypeLogger networkTypeLogger = new NetworkTypeLogger(context);
        networkTypeLogger.log();
    }

    private void maybeLogManagedProfileOnFullyManagedDevice(Context context,
            String provisioningAction) {
        final DevicePolicyManager dpm =
                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
        final ComponentName currentDeviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
        if (currentDeviceOwner != null
                && ACTION_PROVISION_MANAGED_PROFILE.equals(provisioningAction)) {
            mMetricsWriter.write(DevicePolicyEventLogger
                    .createEvent(PROVISIONING_MANAGED_PROFILE_ON_FULLY_MANAGED_DEVICE)
                    .setTimePeriod(AnalyticsUtils.getProvisioningTime(mSharedPreferences)));
        }
    }
}
