/*
 * Copyright (C) 2023 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.os.profiling;

import android.annotation.Nullable;
import android.os.Bundle;
import android.os.ProfilingManager;
import android.provider.DeviceConfig;

import perfetto.protos.DataSourceConfigOuterClass.DataSourceConfig;
import perfetto.protos.FtraceConfigOuterClass.FtraceConfig;
import perfetto.protos.HeapprofdConfigOuterClass.HeapprofdConfig;
import perfetto.protos.JavaHprofConfigOuterClass.JavaHprofConfig;
import perfetto.protos.PackagesListConfigOuterClass.PackagesListConfig;
import perfetto.protos.PerfEventConfigOuterClass.PerfEventConfig;
import perfetto.protos.PerfEventsOuterClass.PerfEvents;
import perfetto.protos.ProcessStatsConfigOuterClass.ProcessStatsConfig;
import perfetto.protos.TraceConfigOuterClass.TraceConfig;

public final class Configs {

    // Time to wait beyond trace timeout to ensure perfetto has time to finish writing output.
    private static final int FILE_PROCESSING_DELAY_MS = 2000;
    // Time used to account for any delay in starting up the underlying profiling process. This
    // value is used to calculate max profiling time.
    private static final int MAX_PROFILING_TIME_BUFFER_MS = 10 * 1000;

    private static final int FOUR_MB = 4096;

    private static final int ONE_DAY_MS = 24 * 60 * 60 * 1000;

    private static boolean sSystemTriggeredSystemTraceConfigsInitialized = false;
    private static boolean sSystemTraceConfigsInitialized = false;
    private static boolean sHeapProfileConfigsInitialized = false;
    private static boolean sJavaHeapDumpConfigsInitialized = false;
    private static boolean sStackSamplingConfigsInitialized = false;

    private static int sSystemTriggeredSystemTraceDurationMs;
    private static int sSystemTriggeredSystemTraceDiscardBufferSizeKb;
    private static int sSystemTriggeredSystemTraceRingBufferSizeKb;

    private static boolean sKillswitchSystemTrace;
    private static int sSystemTraceDurationMsDefault;
    private static int sSystemTraceDurationMsMin;
    private static int sSystemTraceDurationMsMax;
    private static int sSystemTraceSizeKbDefault;
    private static int sSystemTraceSizeKbMin;
    private static int sSystemTraceSizeKbMax;

    private static boolean sKillswitchHeapProfile;
    private static boolean sHeapProfileTrackJavaAllocationsDefault;
    private static int sHeapProfileFlushTimeoutMsDefault;
    private static int sHeapProfileDurationMsDefault;
    private static int sHeapProfileDurationMsMin;
    private static int sHeapProfileDurationMsMax;
    private static int sHeapProfileSizeKbDefault;
    private static int sHeapProfileSizeKbMin;
    private static int sHeapProfileSizeKbMax;
    private static long sHeapProfileSamplingIntervalBytesDefault;
    private static long sHeapProfileSamplingIntervalBytesMin;
    private static long sHeapProfileSamplingIntervalBytesMax;

    private static boolean sKillswitchJavaHeapDump;
    private static int sJavaHeapDumpDurationMsDefault;
    private static int sJavaHeapDumpDataSourceStopTimeoutMsDefault;
    private static int sJavaHeapDumpSizeKbDefault;
    private static int sJavaHeapDumpSizeKbMin;
    private static int sJavaHeapDumpSizeKbMax;

    private static boolean sKillswitchStackSampling;
    private static int sStackSamplingFlushTimeoutMsDefault;
    private static int sStackSamplingDurationMsDefault;
    private static int sStackSamplingDurationMsMin;
    private static int sStackSamplingDurationMsMax;
    private static int sStackSamplingSizeKbDefault;
    private static int sStackSamplingSizeKbMin;
    private static int sStackSamplingSizeKbMax;
    private static int sStackSamplingSamplingFrequencyDefault;
    private static int sStackSamplingSamplingFrequencyMin;
    private static int sStackSamplingSamplingFrequencyMax;

    /**
     * Initialize System Triggered System Trace related DeviceConfig values if they have not been
     * yet.
     */
    private static void initializeSystemTriggeredSystemTraceConfigsIfNecessary() {
        if (sSystemTriggeredSystemTraceConfigsInitialized) {
            return;
        }

        DeviceConfig.Properties properties =
                DeviceConfigHelper.getAllSystemTriggeredSystemTraceProperties();

        sSystemTriggeredSystemTraceDurationMs = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRIGGERED_SYSTEM_TRACE_DURATION_MS,
                30 * 60 * 1000 /* 30 minutes */);
        sSystemTriggeredSystemTraceDiscardBufferSizeKb = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRIGGERED_SYSTEM_TRACE_DISCARD_BUFFER_SIZE_KB, FOUR_MB);
        sSystemTriggeredSystemTraceRingBufferSizeKb = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRIGGERED_SYSTEM_TRACE_RING_BUFFER_SIZE_KB, 32768);

        sSystemTriggeredSystemTraceConfigsInitialized = true;
    }

    /** Initialize System Trace related DeviceConfig set values if they have not been yet. */
    private static void initializeSystemTraceConfigsIfNecessary() {
        if (sSystemTraceConfigsInitialized) {
            return;
        }

        DeviceConfig.Properties properties = DeviceConfigHelper.getAllSystemTraceProperties();

        sKillswitchSystemTrace = properties.getBoolean(
                DeviceConfigHelper.KILLSWITCH_SYSTEM_TRACE, false);
        sSystemTraceDurationMsDefault = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_DEFAULT, 300000);
        sSystemTraceDurationMsMin = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_MIN, 1000);
        sSystemTraceDurationMsMax = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_MAX, 600000);
        sSystemTraceSizeKbDefault = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRACE_SIZE_KB_DEFAULT, 32768);
        sSystemTraceSizeKbMin = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRACE_SIZE_KB_MIN, 64);
        sSystemTraceSizeKbMax = properties.getInt(
                DeviceConfigHelper.SYSTEM_TRACE_SIZE_KB_MAX, 32768);

        sSystemTraceConfigsInitialized = true;
    }

    /** Initialize Java Heap Dump related DeviceConfig set values if they have not been yet. */
    private static void initializeJavaHeapDumpConfigsIfNecessary() {
        if (sJavaHeapDumpConfigsInitialized) {
            return;
        }

        DeviceConfig.Properties properties = DeviceConfigHelper.getAllJavaHeapDumpProperties();

        sKillswitchJavaHeapDump = properties.getBoolean(
                DeviceConfigHelper.KILLSWITCH_JAVA_HEAP_DUMP, false);
        sJavaHeapDumpDurationMsDefault = properties.getInt(
                DeviceConfigHelper.JAVA_HEAP_DUMP_DURATION_MS_DEFAULT, 1000);
        sJavaHeapDumpDataSourceStopTimeoutMsDefault = properties.getInt(
                DeviceConfigHelper.JAVA_HEAP_DUMP_DATA_SOURCE_STOP_TIMEOUT_MS_DEFAULT, 100000);
        sJavaHeapDumpSizeKbDefault = properties.getInt(
                DeviceConfigHelper.JAVA_HEAP_DUMP_SIZE_KB_DEFAULT, 256000);
        sJavaHeapDumpSizeKbMin = properties.getInt(
                DeviceConfigHelper.JAVA_HEAP_DUMP_SIZE_KB_MIN, 8192 /* 8 MB */);
        sJavaHeapDumpSizeKbMax = properties.getInt(
                DeviceConfigHelper.JAVA_HEAP_DUMP_SIZE_KB_MAX, 256000);

        sJavaHeapDumpConfigsInitialized = true;
    }

    /** Initialize Heap Profile related DeviceConfig set values if they have not been yet. */
    private static void initializeHeapProfileConfigsIfNecessary() {
        if (sHeapProfileConfigsInitialized) {
            return;
        }

        DeviceConfig.Properties properties = DeviceConfigHelper.getAllHeapProfileProperties();

        sKillswitchHeapProfile = properties.getBoolean(
                DeviceConfigHelper.KILLSWITCH_HEAP_PROFILE, false);
        sHeapProfileTrackJavaAllocationsDefault = properties.getBoolean(
                DeviceConfigHelper.HEAP_PROFILE_TRACK_JAVA_ALLOCATIONS_DEFAULT, false);
        sHeapProfileFlushTimeoutMsDefault = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_FLUSH_TIMEOUT_MS_DEFAULT, 30000);
        sHeapProfileDurationMsDefault = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_DEFAULT, 120000);
        sHeapProfileDurationMsMin = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_MIN, 1000);
        sHeapProfileDurationMsMax = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_MAX, 300000);
        sHeapProfileSizeKbDefault = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_SIZE_KB_DEFAULT, 65536);
        sHeapProfileSizeKbMin = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_SIZE_KB_MIN, 256);
        sHeapProfileSizeKbMax = properties.getInt(
                DeviceConfigHelper.HEAP_PROFILE_SIZE_KB_MAX, 65536);
        sHeapProfileSamplingIntervalBytesDefault = properties.getLong(
                DeviceConfigHelper.HEAP_PROFILE_SAMPLING_INTERVAL_BYTES_DEFAULT, 4096L);
        sHeapProfileSamplingIntervalBytesMin = properties.getLong(
                DeviceConfigHelper.HEAP_PROFILE_SAMPLING_INTERVAL_BYTES_MIN, 1L);
        sHeapProfileSamplingIntervalBytesMax = properties.getLong(
                DeviceConfigHelper.HEAP_PROFILE_SAMPLING_INTERVAL_BYTES_MAX, 65536L);

        sHeapProfileConfigsInitialized = true;
    }

    /** Initialize Stack Sampling related DeviceConfig set values if they have not been yet. */
    private static void initializeStackSamplingConfigsIfNecessary() {
        if (sStackSamplingConfigsInitialized) {
            return;
        }

        DeviceConfig.Properties properties = DeviceConfigHelper.getAllStackSamplingProperties();

        sKillswitchStackSampling = properties.getBoolean(
                DeviceConfigHelper.KILLSWITCH_STACK_SAMPLING, false);
        sStackSamplingFlushTimeoutMsDefault = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_FLUSH_TIMEOUT_MS_DEFAULT, 30000);
        sStackSamplingDurationMsDefault = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_DEFAULT, 60000);
        sStackSamplingDurationMsMin = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_MIN, 1000);
        sStackSamplingDurationMsMax = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_MAX, 300000);
        sStackSamplingSizeKbDefault = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_SAMPLING_SIZE_KB_DEFAULT, 65536);
        sStackSamplingSizeKbMin = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_SAMPLING_SIZE_KB_MIN, 64);
        sStackSamplingSizeKbMax = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_SAMPLING_SIZE_KB_MAX, 65536);
        sStackSamplingSamplingFrequencyDefault = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_FREQUENCY_DEFAULT, 100);
        sStackSamplingSamplingFrequencyMin = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_FREQUENCY_MIN, 1);
        sStackSamplingSamplingFrequencyMax = properties.getInt(
                DeviceConfigHelper.STACK_SAMPLING_FREQUENCY_MAX, 200);

        sStackSamplingConfigsInitialized = true;
    }

    /**
     * Update DeviceConfig set configuration values if present in the provided properties, leaving
     * not present values unchanged.
     *
     * Will only update values that have already been initialized as initialization is required
     * before use and the changed values will be available under the normal access path.
     */
    public static void maybeUpdateConfigs(DeviceConfig.Properties properties) {
        // TODO(b/330940387): Revisit defaults before release

        if (sSystemTraceConfigsInitialized) {
            sKillswitchSystemTrace = properties.getBoolean(
                    DeviceConfigHelper.KILLSWITCH_SYSTEM_TRACE, sKillswitchSystemTrace);
            sSystemTraceDurationMsDefault = properties.getInt(
                    DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_DEFAULT,
                    sSystemTraceDurationMsDefault);
            sSystemTraceDurationMsMin = properties.getInt(
                    DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_MIN, sSystemTraceDurationMsMin);
            sSystemTraceDurationMsMax = properties.getInt(
                    DeviceConfigHelper.SYSTEM_TRACE_DURATION_MS_MAX, sSystemTraceDurationMsMax);
            sSystemTraceSizeKbDefault = properties.getInt(
                    DeviceConfigHelper.SYSTEM_TRACE_SIZE_KB_DEFAULT, sSystemTraceSizeKbDefault);
            sSystemTraceSizeKbMin = properties.getInt(
                    DeviceConfigHelper.SYSTEM_TRACE_SIZE_KB_MIN, sSystemTraceSizeKbMin);
            sSystemTraceSizeKbMax = properties.getInt(
                    DeviceConfigHelper.SYSTEM_TRACE_SIZE_KB_MAX, sSystemTraceSizeKbMax);
        }

        if (sHeapProfileConfigsInitialized) {
            sKillswitchHeapProfile = properties.getBoolean(
                    DeviceConfigHelper.KILLSWITCH_HEAP_PROFILE, sKillswitchHeapProfile);
            sHeapProfileTrackJavaAllocationsDefault = properties.getBoolean(
                    DeviceConfigHelper.HEAP_PROFILE_TRACK_JAVA_ALLOCATIONS_DEFAULT,
                    sHeapProfileTrackJavaAllocationsDefault);
            sHeapProfileFlushTimeoutMsDefault = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_FLUSH_TIMEOUT_MS_DEFAULT,
                    sHeapProfileFlushTimeoutMsDefault);
            sHeapProfileDurationMsDefault = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_DEFAULT,
                    sHeapProfileDurationMsDefault);
            sHeapProfileDurationMsMin = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_MIN, sHeapProfileDurationMsMin);
            sHeapProfileDurationMsMax = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_DURATION_MS_MAX, sHeapProfileDurationMsMax);
            sHeapProfileSizeKbDefault = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_SIZE_KB_DEFAULT, sHeapProfileSizeKbDefault);
            sHeapProfileSizeKbMin = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_SIZE_KB_MIN, sHeapProfileSizeKbMin);
            sHeapProfileSizeKbMax = properties.getInt(
                    DeviceConfigHelper.HEAP_PROFILE_SIZE_KB_MAX, sHeapProfileSizeKbMax);
            sHeapProfileSamplingIntervalBytesDefault = properties.getLong(
                    DeviceConfigHelper.HEAP_PROFILE_SAMPLING_INTERVAL_BYTES_DEFAULT,
                    sHeapProfileSamplingIntervalBytesDefault);
            sHeapProfileSamplingIntervalBytesMin = properties.getLong(
                    DeviceConfigHelper.HEAP_PROFILE_SAMPLING_INTERVAL_BYTES_MIN,
                    sHeapProfileSamplingIntervalBytesMin);
            sHeapProfileSamplingIntervalBytesMax = properties.getLong(
                    DeviceConfigHelper.HEAP_PROFILE_SAMPLING_INTERVAL_BYTES_MAX,
                    sHeapProfileSamplingIntervalBytesMax);
        }

        if (sJavaHeapDumpConfigsInitialized) {
            sKillswitchJavaHeapDump = properties.getBoolean(
                    DeviceConfigHelper.KILLSWITCH_JAVA_HEAP_DUMP, sKillswitchJavaHeapDump);
            sJavaHeapDumpDurationMsDefault = properties.getInt(
                    DeviceConfigHelper.JAVA_HEAP_DUMP_DURATION_MS_DEFAULT,
                    sJavaHeapDumpDurationMsDefault);
            sJavaHeapDumpDataSourceStopTimeoutMsDefault = properties.getInt(
                    DeviceConfigHelper.JAVA_HEAP_DUMP_DATA_SOURCE_STOP_TIMEOUT_MS_DEFAULT,
                    sJavaHeapDumpDataSourceStopTimeoutMsDefault);
            sJavaHeapDumpSizeKbDefault = properties.getInt(
                    DeviceConfigHelper.JAVA_HEAP_DUMP_SIZE_KB_DEFAULT, sJavaHeapDumpSizeKbDefault);
            sJavaHeapDumpSizeKbMin = properties.getInt(
                    DeviceConfigHelper.JAVA_HEAP_DUMP_SIZE_KB_MIN, sJavaHeapDumpSizeKbMin);
            sJavaHeapDumpSizeKbMax = properties.getInt(
                    DeviceConfigHelper.JAVA_HEAP_DUMP_SIZE_KB_MAX, sJavaHeapDumpSizeKbMax);
        }

        if (sStackSamplingConfigsInitialized) {
            sKillswitchStackSampling = properties.getBoolean(
                    DeviceConfigHelper.KILLSWITCH_STACK_SAMPLING, sKillswitchStackSampling);
            sStackSamplingFlushTimeoutMsDefault = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_FLUSH_TIMEOUT_MS_DEFAULT,
                    sStackSamplingFlushTimeoutMsDefault);
            sStackSamplingDurationMsDefault = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_DEFAULT,
                    sStackSamplingDurationMsDefault);
            sStackSamplingDurationMsMin = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_MIN, sStackSamplingDurationMsMin);
            sStackSamplingDurationMsMax = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_DURATION_MS_MAX, sStackSamplingDurationMsMax);
            sStackSamplingSizeKbDefault = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_SAMPLING_SIZE_KB_DEFAULT,
                    sStackSamplingSizeKbDefault);
            sStackSamplingSizeKbMin = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_SAMPLING_SIZE_KB_MIN,
                    sStackSamplingSizeKbMin);
            sStackSamplingSizeKbMax = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_SAMPLING_SIZE_KB_MAX,
                    sStackSamplingSizeKbMax);
            sStackSamplingSamplingFrequencyDefault = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_FREQUENCY_DEFAULT,
                    sStackSamplingSamplingFrequencyDefault);
            sStackSamplingSamplingFrequencyMin = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_FREQUENCY_MIN,
                    sStackSamplingSamplingFrequencyMin);
            sStackSamplingSamplingFrequencyMax = properties.getInt(
                    DeviceConfigHelper.STACK_SAMPLING_FREQUENCY_MAX,
                    sStackSamplingSamplingFrequencyMax);
        }
    }

    /** This method transforms a request into a useable config for perfetto. */
    public static byte[] generateConfigForRequest(int profilingType, final @Nullable Bundle params,
            String packageName) throws IllegalArgumentException {
        // Create a copy to modify. Entries will be removed from the copy as they're accessed to
        // ensure that no invalid parameters are present.
        Bundle paramsCopy = params == null ? null : new Bundle(params);

        switch (profilingType) {
            // Java heap dump
            case ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP:
                // This should be unnecessary, but make sure configs are initialized just in case.
                initializeJavaHeapDumpConfigsIfNecessary();

                if (sKillswitchJavaHeapDump) {
                    throw new IllegalArgumentException("Java heap dump is disabled");
                }

                int javaHeapDumpSizeKb = roundUpForBufferSize(getAndRemoveWithinBounds(
                        ProfilingManager.KEY_SIZE_KB,
                        sJavaHeapDumpSizeKbDefault,
                        sJavaHeapDumpSizeKbMin,
                        sJavaHeapDumpSizeKbMax,
                        paramsCopy));

                confirmEmptyOrThrow(paramsCopy);

                return generateJavaHeapDumpConfig(packageName, javaHeapDumpSizeKb);

            // Heap profile
            case ProfilingManager.PROFILING_TYPE_HEAP_PROFILE:
                // This should be unnecessary, but make sure configs are initialized just in case.
                initializeHeapProfileConfigsIfNecessary();

                if (sKillswitchHeapProfile) {
                    throw new IllegalArgumentException("Heap profile is disabled");
                }

                boolean trackJavaAllocations = getAndRemove(
                        ProfilingManager.KEY_TRACK_JAVA_ALLOCATIONS,
                        sHeapProfileTrackJavaAllocationsDefault, paramsCopy);
                long samplingIntervalBytes = getAndRemoveWithinBounds(
                        ProfilingManager.KEY_SAMPLING_INTERVAL_BYTES,
                        sHeapProfileSamplingIntervalBytesDefault,
                        sHeapProfileSamplingIntervalBytesMin,
                        sHeapProfileSamplingIntervalBytesMax,
                        paramsCopy);
                int heapProfileDurationMs = getAndRemoveWithinBounds(
                        ProfilingManager.KEY_DURATION_MS,
                        sHeapProfileDurationMsDefault,
                        sHeapProfileDurationMsMin,
                        sHeapProfileDurationMsMax,
                        paramsCopy);
                int heapProfileSizeKb = roundUpForBufferSize(getAndRemoveWithinBounds(
                        ProfilingManager.KEY_SIZE_KB,
                        sHeapProfileSizeKbDefault,
                        sHeapProfileSizeKbMin,
                        sHeapProfileSizeKbMax,
                        paramsCopy));

                confirmEmptyOrThrow(paramsCopy);

                return generateHeapProfileConfig(packageName, heapProfileSizeKb,
                        heapProfileDurationMs, samplingIntervalBytes, trackJavaAllocations);

            // Stack sampling
            case ProfilingManager.PROFILING_TYPE_STACK_SAMPLING:
                // This should be unnecessary, but make sure configs are initialized just in case.
                initializeStackSamplingConfigsIfNecessary();

                if (sKillswitchStackSampling) {
                    throw new IllegalArgumentException("Stack sampling is disabled");
                }

                long frequency = getAndRemoveWithinBounds(ProfilingManager.KEY_FREQUENCY_HZ,
                        sStackSamplingSamplingFrequencyDefault,
                        sStackSamplingSamplingFrequencyMin,
                        sStackSamplingSamplingFrequencyMax,
                        paramsCopy);
                int stackSamplingDurationMs = getAndRemoveWithinBounds(
                        ProfilingManager.KEY_DURATION_MS,
                        sStackSamplingDurationMsDefault,
                        sStackSamplingDurationMsMin,
                        sStackSamplingDurationMsMax,
                        paramsCopy);
                int stackSamplingSizeKb = roundUpForBufferSize(getAndRemoveWithinBounds(
                        ProfilingManager.KEY_SIZE_KB,
                        sStackSamplingSizeKbDefault,
                        sStackSamplingSizeKbMin,
                        sStackSamplingSizeKbMax,
                        paramsCopy));

                confirmEmptyOrThrow(paramsCopy);

                return generateStackSamplingConfig(packageName, stackSamplingSizeKb,
                        stackSamplingDurationMs, frequency);

            // System trace
            case ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE:
                // This should be unnecessary, but make sure configs are initialized just in case.
                initializeSystemTraceConfigsIfNecessary();

                if (sKillswitchSystemTrace) {
                    throw new IllegalArgumentException("System trace is disabled");
                }

                int systemTraceDurationMs = getAndRemoveWithinBounds(
                        ProfilingManager.KEY_DURATION_MS,
                        sSystemTraceDurationMsDefault,
                        sSystemTraceDurationMsMin,
                        sSystemTraceDurationMsMax,
                        paramsCopy);
                int systemTraceSizeKb = roundUpForBufferSize(getAndRemoveWithinBounds(
                        ProfilingManager.KEY_SIZE_KB,
                        sSystemTraceSizeKbDefault,
                        sSystemTraceSizeKbMin,
                        sSystemTraceSizeKbMax,
                        paramsCopy));
                TraceConfig.BufferConfig.FillPolicy systemTraceBufferFillPolicy =
                        getBufferFillPolicy(getAndRemove(ProfilingManager.KEY_BUFFER_FILL_POLICY,
                                ProfilingManager.VALUE_BUFFER_FILL_POLICY_RING_BUFFER, paramsCopy));

                confirmEmptyOrThrow(paramsCopy);

                return generateSystemTraceConfig(packageName, systemTraceSizeKb,
                        systemTraceDurationMs, systemTraceBufferFillPolicy);

            // Invalid type
            default:
                throw new IllegalArgumentException("Invalid profiling type");
        }
    }

    /**
     * This method returns how long in ms to wait initially before checking if profiling is complete
     * and rescheduling another check or post processing and cleaning up the result in the event
     * that it's not stopped manually.
     */
    public static int getInitialProfilingTimeMs(int profilingType,
            @Nullable Bundle params) {
        int duration;
        switch (profilingType) {
            case ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP:
                initializeJavaHeapDumpConfigsIfNecessary();
                duration = sJavaHeapDumpDurationMsDefault;
                break;

            case ProfilingManager.PROFILING_TYPE_HEAP_PROFILE:
                initializeHeapProfileConfigsIfNecessary();
                duration = getWithinBounds(ProfilingManager.KEY_DURATION_MS,
                        sHeapProfileDurationMsDefault, sHeapProfileDurationMsMin,
                        sHeapProfileDurationMsMax, params);
                break;

            case ProfilingManager.PROFILING_TYPE_STACK_SAMPLING:
                initializeStackSamplingConfigsIfNecessary();
                duration = getWithinBounds(ProfilingManager.KEY_DURATION_MS,
                        sStackSamplingDurationMsDefault, sStackSamplingDurationMsMin,
                        sStackSamplingDurationMsMax, params);
                break;

            case ProfilingManager.PROFILING_TYPE_SYSTEM_TRACE:
                initializeSystemTraceConfigsIfNecessary();
                duration = getWithinBounds(ProfilingManager.KEY_DURATION_MS,
                        sSystemTraceDurationMsDefault, sSystemTraceDurationMsMin,
                        sSystemTraceDurationMsMax, params);
                break;

            default:
                throw new IllegalArgumentException("Invalid profiling type");
        }
        return duration + FILE_PROCESSING_DELAY_MS;
    }

    /**
     * This method returns the maximum profiling time allowed for the different profiling types.
     */
    public static int getMaxProfilingTimeAllowedMs(int profilingType, @Nullable Bundle params) {
        // Get the initial delay
        int maxAllowedProcessingTime =
                getInitialProfilingTimeMs(profilingType, params);

        // Add the respective flush and data source timeouts for the types that have them.
        switch (profilingType) {
            case ProfilingManager.PROFILING_TYPE_HEAP_PROFILE:
                maxAllowedProcessingTime += sHeapProfileFlushTimeoutMsDefault;
                break;

            case ProfilingManager.PROFILING_TYPE_JAVA_HEAP_DUMP:
                maxAllowedProcessingTime += sJavaHeapDumpDataSourceStopTimeoutMsDefault;
                break;

            case ProfilingManager.PROFILING_TYPE_STACK_SAMPLING:
                maxAllowedProcessingTime += sStackSamplingFlushTimeoutMsDefault;
                break;
        }
        // Add extra buffer time to account for the time it may take to start the underlying
        // process.
        return maxAllowedProcessingTime + MAX_PROFILING_TIME_BUFFER_MS;
    }

    private static TraceConfig.BufferConfig.FillPolicy getBufferFillPolicy(int bufferFillPolicy)
            throws IllegalArgumentException {
        switch (bufferFillPolicy) {
            case ProfilingManager.VALUE_BUFFER_FILL_POLICY_DISCARD:
                return TraceConfig.BufferConfig.FillPolicy.DISCARD;
            case ProfilingManager.VALUE_BUFFER_FILL_POLICY_RING_BUFFER:
                return TraceConfig.BufferConfig.FillPolicy.RING_BUFFER;
            default:
                throw new IllegalArgumentException("Invalid buffer fill policy.");
        }
    }

    private static int getWithinBounds(String key, int defaultValue, int minValue,
            int maxValue, @Nullable Bundle params) {
        if (params == null) {
            return defaultValue;
        }
        int value = params.getInt(key, defaultValue);
        if (value < minValue) {
            return minValue;
        } else if (value > maxValue) {
            return maxValue;
        } else {
            return value;
        }
    }

    private static boolean getAndRemove(String key, boolean defaultValue, @Nullable Bundle bundle) {
        if (bundle == null) {
            return defaultValue;
        }
        if (bundle.containsKey(key)) {
            boolean value = bundle.getBoolean(key);
            bundle.remove(key);
            return value;
        }
        return defaultValue;
    }

    private static int getAndRemove(String key, int defaultValue, @Nullable Bundle bundle) {
        if (bundle == null) {
            return defaultValue;
        }
        if (bundle.containsKey(key)) {
            int value = bundle.getInt(key);
            bundle.remove(key);
            return value;
        }
        return defaultValue;
    }

    private static int getAndRemoveWithinBounds(String key, int defaultValue, int minValue,
            int maxValue, @Nullable Bundle bundle) {
        if (bundle == null) {
            return defaultValue;
        }
        if (bundle.containsKey(key)) {
            int value = bundle.getInt(key);
            bundle.remove(key);
            if (value < minValue) {
                value = minValue;
            } else if (value > maxValue) {
                value = maxValue;
            }
            return value;
        }
        return defaultValue;
    }

    private static long getAndRemoveWithinBounds(String key, long defaultValue, long minValue,
            long maxValue, @Nullable Bundle bundle) {
        if (bundle == null) {
            return defaultValue;
        }
        if (bundle.containsKey(key)) {
            long value = bundle.getLong(key);
            bundle.remove(key);
            if (value < minValue) {
                value = minValue;
            } else if (value > maxValue) {
                value = maxValue;
            }
            return value;
        }
        return defaultValue;
    }

    /** Buffer sizes are preferred to be multiples of 4kb, round up to next lowest 4 multiple. */
    private static int roundUpForBufferSize(int bufferSize) {
        return (bufferSize % 4 == 0) ? bufferSize : bufferSize + (4 - (bufferSize % 4));
    }

    private static void confirmEmptyOrThrow(@Nullable Bundle bundle)
            throws IllegalArgumentException {
        if (bundle != null && !bundle.isEmpty()) {
            throw new IllegalArgumentException(
                    "Bundle contains invalid or unsupported parameters");
        }
    }

    private static byte[] generateJavaHeapDumpConfig(String packageName, int bufferSizeKb) {
        TraceConfig.Builder builder = TraceConfig.newBuilder();

        // Add a buffer
        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig.newBuilder()
                .setSizeKb(bufferSizeKb)
                .setFillPolicy(TraceConfig.BufferConfig.FillPolicy.DISCARD)
                .build();
        builder.addBuffers(buffer);

        // Add data source
        JavaHprofConfig javaHprofConfig = JavaHprofConfig.newBuilder()
                .addProcessCmdline(packageName)
                .setDumpSmaps(true)
                .build();
        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
                .setName("android.java_hprof")
                .setJavaHprofConfig(javaHprofConfig)
                .build();
        TraceConfig.DataSource dataSource = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfig)
                .build();
        builder.addDataSources(dataSource);

        // Add duration and timeout
        builder.setDurationMs(sJavaHeapDumpDurationMsDefault);
        builder.setDataSourceStopTimeoutMs(sJavaHeapDumpDataSourceStopTimeoutMsDefault);

        return builder.build().toByteArray();
    }

    private static byte[] generateHeapProfileConfig(String packageName, int bufferSizeKb,
            int durationMs, long samplingIntervalBytes, boolean trackJavaAllocations) {
        TraceConfig.Builder builder = TraceConfig.newBuilder();

        // Add a buffer
        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig.newBuilder()
                .setSizeKb(bufferSizeKb)
                .setFillPolicy(TraceConfig.BufferConfig.FillPolicy.DISCARD)
                .build();
        builder.addBuffers(buffer);

        // Add data source
        HeapprofdConfig.Builder heapprofdConfigBuilder = HeapprofdConfig.newBuilder()
                .setShmemSizeBytes(8388608) //8MB
                .setSamplingIntervalBytes(samplingIntervalBytes)
                .addProcessCmdline(packageName);
        if (trackJavaAllocations) {
            heapprofdConfigBuilder.addHeaps("com.android.art");
        }
        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
                .setName("android.heapprofd")
                .setHeapprofdConfig(heapprofdConfigBuilder.build())
                .build();
        TraceConfig.DataSource dataSource = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfig)
                .build();
        builder.addDataSources(dataSource);

        // Add duration and timeout
        builder.setDurationMs(durationMs);
        builder.setFlushTimeoutMs(sHeapProfileFlushTimeoutMsDefault);

        return builder.build().toByteArray();
    }

    private static byte[] generateStackSamplingConfig(String packageName, int bufferSizeKb,
            int durationMs, long frequency) {
        TraceConfig.Builder builder = TraceConfig.newBuilder();

        // Add a buffer
        TraceConfig.BufferConfig buffer = TraceConfig.BufferConfig.newBuilder()
                .setSizeKb(bufferSizeKb)
                .setFillPolicy(TraceConfig.BufferConfig.FillPolicy.DISCARD)
                .build();
        builder.addBuffers(buffer);

        // Add data source
        PerfEvents.Timebase timebase = PerfEvents.Timebase.newBuilder()
                .setCounter(PerfEvents.Counter.SW_CPU_CLOCK)
                .setFrequency(frequency)
                .setTimestampClock(PerfEvents.PerfClock.PERF_CLOCK_MONOTONIC)
                .build();
        PerfEventConfig.Scope scope = PerfEventConfig.Scope.newBuilder()
                .addTargetCmdline(packageName)
                .build();
        PerfEventConfig.CallstackSampling callstackSampling = PerfEventConfig.CallstackSampling
                .newBuilder()
                .setScope(scope)
                .build();
        PerfEventConfig perfEventConfig = PerfEventConfig.newBuilder()
                .setTimebase(timebase)
                .setCallstackSampling(callstackSampling)
                .build();
        DataSourceConfig dataSourceConfig = DataSourceConfig.newBuilder()
                .setName("linux.perf")
                .setPerfEventConfig(perfEventConfig)
                .build();
        TraceConfig.DataSource dataSource = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfig)
                .build();
        builder.addDataSources(dataSource);

        // Add duration and timeout
        builder.setDurationMs(durationMs);
        builder.setFlushTimeoutMs(sStackSamplingFlushTimeoutMsDefault);

        return builder.build().toByteArray();
    }

    private static byte[] generateSystemTraceConfig(String packageName, int bufferSizeKb,
            int durationMs, TraceConfig.BufferConfig.FillPolicy bufferFillPolicy) {
        TraceConfig.Builder builder = TraceConfig.newBuilder();

        addSystemTraceGeneralConfigs(
                builder,
                new String[] {packageName},
                FOUR_MB,
                bufferSizeKb,
                durationMs,
                bufferFillPolicy);

        return builder.build().toByteArray();
    }

    /**
     * Generate config for system triggered background system trace.
     *
     * @param extraLong should only be set to true for testing.
     */
    public static byte[] generateSystemTriggeredTraceConfig(String uniqueSessionName,
            String[] packageNames, boolean extraLong) {
        // Make sure we have our config values set. This is the only config specific method which is
        // called directly and therefore needs to verify the config value initialization directly.
        initializeSystemTriggeredSystemTraceConfigsIfNecessary();

        TraceConfig.Builder builder = TraceConfig.newBuilder();

        addSystemTraceGeneralConfigs(
                builder,
                packageNames,
                sSystemTriggeredSystemTraceDiscardBufferSizeKb,
                sSystemTriggeredSystemTraceRingBufferSizeKb,
                extraLong
                        ? ONE_DAY_MS
                        : sSystemTriggeredSystemTraceDurationMs,
                TraceConfig.BufferConfig.FillPolicy.RING_BUFFER);

        builder.setUniqueSessionName(uniqueSessionName);

        return builder.build().toByteArray();
    }

    private static void addSystemTraceGeneralConfigs(TraceConfig.Builder builder,
            String[] packageNames, int bufferOneSizeKb, int bufferTwoSizeKb, int durationMs,
            TraceConfig.BufferConfig.FillPolicy bufferTwoFillPolicy) {
        // Add 2 buffers, discard for data sources dumped at beginning and caller set for all other
        // data sources.
        TraceConfig.BufferConfig buffer0 = TraceConfig.BufferConfig.newBuilder()
                .setSizeKb(bufferOneSizeKb)
                .setFillPolicy(TraceConfig.BufferConfig.FillPolicy.DISCARD)
                .build();
        builder.addBuffers(buffer0);
        TraceConfig.BufferConfig buffer1 = TraceConfig.BufferConfig.newBuilder()
                .setSizeKb(bufferTwoSizeKb)
                .setFillPolicy(bufferTwoFillPolicy)
                .build();
        builder.addBuffers(buffer1);

        // Add a whole bunch of data sources

        // Scan and dump all processes to buffer 0 when trace starts
        ProcessStatsConfig processStatsConfig = ProcessStatsConfig.newBuilder()
                .setScanAllProcessesOnStart(true)
                .build();
        DataSourceConfig dataSourceConfigProcessStats = DataSourceConfig.newBuilder()
                .setName("linux.process_stats")
                .setTargetBuffer(0)
                .setProcessStatsConfig(processStatsConfig)
                .build();
        TraceConfig.DataSource dataSourceProcessStats = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfigProcessStats)
                .build();
        builder.addDataSources(dataSourceProcessStats);

        // Initialize the builders that require package names so we only need to iterate through the
        // list once. These will be used in the following two sections.
        PackagesListConfig.Builder packagesListConfigBuilder = PackagesListConfig.newBuilder();
        FtraceConfig.Builder ftraceConfigBuilder = FtraceConfig.newBuilder();

        for (int i = 0; i < packageNames.length; i++) {
            String packageName = packageNames[i];

            // Enable atrace events for each app.
            ftraceConfigBuilder.addAtraceApps(packageName);

            // Add to package list config so data is kept by filter.
            packagesListConfigBuilder.addPackageNameFilter(packageName);
        }

        // Dump details about all listed packages to buffer 0. Redactor will filter out the ones
        // that should not end up in the finished output.
        DataSourceConfig dataSourceConfigPackagesList = DataSourceConfig.newBuilder()
                .setName("android.packages_list")
                .setTargetBuffer(0)
                .setPackagesListConfig(packagesListConfigBuilder.build())
                .build();
        TraceConfig.DataSource dataSourcePackagesList = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfigPackagesList)
                .build();
        builder.addDataSources(dataSourcePackagesList);

        // Dump select ftrace events to buffer 1
        FtraceConfig.CompactSchedConfig compactSchedConfig = FtraceConfig.CompactSchedConfig
                .newBuilder()
                .setEnabled(true)
                .build();
        ftraceConfigBuilder
                .setThrottleRssStat(true)
                .setDisableGenericEvents(true)
                .setCompactSched(compactSchedConfig)
                // RSS and ION buffer events:
                .addFtraceEvents("gpu_mem/gpu_mem_total")
                // Scheduling information & process tracking. Useful for:
                // - what is happening on each CPU at each moment
                // - why a thread was descheduled
                // - parent/child relationships between processes and threads
                .addFtraceEvents("power/suspend_resume")
                .addFtraceEvents("sched/sched_process_free")
                .addFtraceEvents("sched/sched_switch")
                .addFtraceEvents("task/task_newtask")
                .addFtraceEvents("task/task_rename")
                // Wakeup info. Allows you to compute how long a task was:
                .addFtraceEvents("sched/sched_waking")
                .addFtraceEvents("sched/sched_wakeup_new")
                // vmscan and mm_compaction events:
                .addFtraceEvents("vmscan/mm_vmscan_direct_reclaim_begin")
                .addFtraceEvents("vmscan/mm_vmscan_direct_reclaim_end")
                // Atrace activity manager:
                .addAtraceCategories("am")
                // Java and C:
                .addAtraceCategories("dalvik")
                // Bionic C library:
                .addAtraceCategories("bionic")
                // Binder kernel driver
                .addAtraceCategories("binder_driver")
                // View system:
                .addAtraceCategories("view")
                // Input:
                .addAtraceCategories("input")
                // Graphics:
                .addAtraceCategories("gfx");

        DataSourceConfig dataSourceConfigFtrace = DataSourceConfig.newBuilder()
                .setName("linux.ftrace")
                .setTargetBuffer(1)
                .setFtraceConfig(ftraceConfigBuilder.build())
                .build();
        TraceConfig.DataSource dataSourceFtrace = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfigFtrace)
                .build();
        builder.addDataSources(dataSourceFtrace);

        // Dump surfaceflinger frame timeline to buffer 1
        DataSourceConfig dataSourceConfigSurfaceFlinger = DataSourceConfig.newBuilder()
                .setName("android.surfaceflinger.frametimeline")
                .setTargetBuffer(1)
                .build();
        TraceConfig.DataSource dataSourceSurfaceFlinger = TraceConfig.DataSource.newBuilder()
                .setConfig(dataSourceConfigSurfaceFlinger)
                .build();
        builder.addDataSources(dataSourceSurfaceFlinger);

        // Clear incremental state
        TraceConfig.IncrementalStateConfig incrementalStateConfig = TraceConfig
                .IncrementalStateConfig.newBuilder()
                        .setClearPeriodMs(10000)
                        .build();
        builder.setIncrementalStateConfig(incrementalStateConfig);

        // Add duration
        builder.setDurationMs(durationMs);
    }

}
