chromium/components/crash/android/java/src/org/chromium/components/crash/CrashKeys.java

// Copyright 2018 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;

import androidx.annotation.Nullable;

import org.jni_zero.CalledByNative;
import org.jni_zero.NativeMethods;

import org.chromium.base.ThreadUtils;

import java.util.concurrent.atomic.AtomicReferenceArray;

/**
 * This class allows setting crash keys from the Java side. The set of crash keys is defined at
 * build time. To add a new crash key, add a new entry to:
 *
 * <ol>
 *   <li>The CrashKeyIndex enum in {@code crash_keys_android.h}
 *   <li>The CrashKeyString array in {@code crash_keys_android.cc}
 *   <li>The {@link #KEYS} array in this class.
 * </ol>
 *
 * The crash keys will only be included in browser process crash reports.
 */
public class CrashKeys {
    private static final String[] KEYS =
            new String[] {
                "application_status",
                "installed_modules",
                "partner_customization_config",
                "first_run"
            };

    private final AtomicReferenceArray<String> mValues = new AtomicReferenceArray<>(KEYS.length);

    // Outside of assertions only accessed on the UI thread.
    private boolean mFlushed;

    private static class Holder {
        static final CrashKeys INSTANCE = new CrashKeys();
    }

    private CrashKeys() {
        assert CrashKeyIndex.NUM_ENTRIES == KEYS.length;
    }

    /** @return The shared instance of this class. */
    @CalledByNative
    public static CrashKeys getInstance() {
        return Holder.INSTANCE;
    }

    /**
     * @param keyIndex The index of a crash key.
     * @return The key for the given index.
     */
    public static String getKey(@CrashKeyIndex int keyIndex) {
        return KEYS[keyIndex];
    }

    /**
     * @return An atomic array of all the crash key values. This method should only be called before
     *         the values have been flushed to the native side.
     * @see #flushToNative
     */
    public AtomicReferenceArray<String> getValues() {
        assert !mFlushed : "Getting Java CrashKeys after the keys were flushed to native";
        return mValues;
    }

    /**
     * Sets a given crash key to the given value, or clears it. The value will either be stored in
     * Java (for use by pure-Java exception reporting), or forwarded to the native CrashKeys.
     * This method should only be called on the UI thread.
     * @param keyIndex The {@link CrashKeyIndex} of a crash key.
     * @param value The value for the given key, or null to clear it.
     */
    @CalledByNative
    public void set(@CrashKeyIndex int keyIndex, @Nullable String value) {
        ThreadUtils.assertOnUiThread();
        if (mFlushed) {
            CrashKeysJni.get().set(CrashKeys.this, keyIndex, value);
            return;
        }
        mValues.set(keyIndex, value);
    }

    /**
     * Flushes all crash keys to the native side. This method should be called on the UI thread when
     * pure-Java exception handling is disabled in favor of native crash reporting.
     */
    @CalledByNative
    public void flushToNative() {
        ThreadUtils.assertOnUiThread();

        assert !mFlushed : "Tried to flush to native twice";
        for (@CrashKeyIndex int i = 0; i < mValues.length(); i++) {
            CrashKeysJni.get().set(CrashKeys.this, i, mValues.getAndSet(i, null));
        }
        mFlushed = true;
    }

    @NativeMethods
    interface Natives {
        void set(CrashKeys caller, int key, String value);
    }
}