// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.components.crash.browser;

import android.app.ActivityManager;
import android.app.ApplicationExitInfo;
import android.content.Context;
import android.os.Build;

import androidx.annotation.IntDef;
import androidx.annotation.RequiresApi;

import org.chromium.base.ContextUtils;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.metrics.RecordHistogram;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;

/**
 * Wrapper to get the process exit reason of a dead process with same UID as current from
 * ActivityManager, and record to UMA.
 */
public class ProcessExitReasonFromSystem {
    /**
     * Get the exit reason of the most recent chrome process that died and had |pid| as the process
     * ID. Only available on R+ devices, returns -1 otherwise.
     * @return ApplicationExitInfo.Reason
     */
    @RequiresApi(Build.VERSION_CODES.R)
    public static int getExitReason(int pid) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
            return -1;
        }
        ActivityManager am =
                (ActivityManager) ContextUtils.getApplicationContext().getSystemService(
                        Context.ACTIVITY_SERVICE);
        // Set maxNum to 1 since we want the latest reason with the pid.
        List<ApplicationExitInfo> reasons =
                am.getHistoricalProcessExitReasons(/*package_name=*/null, pid, /*maxNum=*/1);
        if (reasons.isEmpty() || reasons.get(0) == null || reasons.get(0).getPid() != pid) {
            return -1;
        }
        return reasons.get(0).getReason();
    }

    // These values are persisted to logs. Entries should not be renumbered and
    // numeric values should never be reused.
    @IntDef({ExitReason.REASON_ANR, ExitReason.REASON_CRASH, ExitReason.REASON_CRASH_NATIVE,
            ExitReason.REASON_DEPENDENCY_DIED, ExitReason.REASON_EXCESSIVE_RESOURCE_USAGE,
            ExitReason.REASON_EXIT_SELF, ExitReason.REASON_INITIALIZATION_FAILURE,
            ExitReason.REASON_LOW_MEMORY, ExitReason.REASON_OTHER,
            ExitReason.REASON_PERMISSION_CHANGE, ExitReason.REASON_SIGNALED,
            ExitReason.REASON_UNKNOWN, ExitReason.REASON_USER_REQUESTED,
            ExitReason.REASON_USER_STOPPED})
    @Retention(RetentionPolicy.SOURCE)
    @interface ExitReason {
        int REASON_ANR = 0;
        int REASON_CRASH = 1;
        int REASON_CRASH_NATIVE = 2;
        int REASON_DEPENDENCY_DIED = 3;
        int REASON_EXCESSIVE_RESOURCE_USAGE = 4;
        int REASON_EXIT_SELF = 5;
        int REASON_INITIALIZATION_FAILURE = 6;
        int REASON_LOW_MEMORY = 7;
        int REASON_OTHER = 8;
        int REASON_PERMISSION_CHANGE = 9;
        int REASON_SIGNALED = 10;
        int REASON_UNKNOWN = 11;
        int REASON_USER_REQUESTED = 12;
        int REASON_USER_STOPPED = 13;
        int NUM_ENTRIES = 14;
    }

    @CalledByNative
    private static void recordExitReasonToUma(int pid, String umaName) {
        recordAsEnumHistogram(umaName, getExitReason(pid));
    }

    /**
     * Records the given |systemReason| (given by #getExitReason) to UMA with the given |umaName|.
     * @see #getExitReason
     */
    public static void recordAsEnumHistogram(String umaName, int systemReason) {
        @ExitReason
        int reason;
        switch (systemReason) {
            case ApplicationExitInfo.REASON_ANR:
                reason = ExitReason.REASON_ANR;
                break;
            case ApplicationExitInfo.REASON_CRASH:
                reason = ExitReason.REASON_CRASH;
                break;
            case ApplicationExitInfo.REASON_CRASH_NATIVE:
                reason = ExitReason.REASON_CRASH_NATIVE;
                break;
            case ApplicationExitInfo.REASON_DEPENDENCY_DIED:
                reason = ExitReason.REASON_DEPENDENCY_DIED;
                break;
            case ApplicationExitInfo.REASON_EXCESSIVE_RESOURCE_USAGE:
                reason = ExitReason.REASON_EXCESSIVE_RESOURCE_USAGE;
                break;
            case ApplicationExitInfo.REASON_EXIT_SELF:
                reason = ExitReason.REASON_EXIT_SELF;
                break;
            case ApplicationExitInfo.REASON_INITIALIZATION_FAILURE:
                reason = ExitReason.REASON_INITIALIZATION_FAILURE;
                break;
            case ApplicationExitInfo.REASON_LOW_MEMORY:
                reason = ExitReason.REASON_LOW_MEMORY;
                break;
            case ApplicationExitInfo.REASON_OTHER:
                reason = ExitReason.REASON_OTHER;
                break;
            case ApplicationExitInfo.REASON_PERMISSION_CHANGE:
                reason = ExitReason.REASON_PERMISSION_CHANGE;
                break;
            case ApplicationExitInfo.REASON_SIGNALED:
                reason = ExitReason.REASON_SIGNALED;
                break;
            case ApplicationExitInfo.REASON_UNKNOWN:
                reason = ExitReason.REASON_UNKNOWN;
                break;
            case ApplicationExitInfo.REASON_USER_REQUESTED:
                reason = ExitReason.REASON_USER_REQUESTED;
                break;
            case ApplicationExitInfo.REASON_USER_STOPPED:
                reason = ExitReason.REASON_USER_STOPPED;
                break;
            default:
                // Reason is unavailable in current platform, so skip recording.
                return;
        }
        RecordHistogram.recordEnumeratedHistogram(umaName, reason, ExitReason.NUM_ENTRIES);
    }
};
