// Copyright 2022 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.metrics;
import android.content.SharedPreferences;
import androidx.annotation.MainThread;
import org.jni_zero.CalledByNative;
import org.chromium.base.ContextUtils;
import org.chromium.base.ThreadUtils;
import java.util.Random;
/**
* Generates a new non-identifying entropy source used to seed persistent activities. Has a static
* cache so that the new low entropy source value will only be generated on first access.
* Low entropy source is queried by entropy_source.cc that caches it in prefs. On Android, it is
* generated in Java so that it can be used by FRE experiments when the native is not available yet.
*/
@MainThread
public class LowEntropySource {
// Should be equal to the value of EntropyState::kMaxLowEntropySize in C++.
public static final int MAX_LOW_ENTROPY_SIZE = 8000;
private static final class LazyHolder {
private static final int LOW_ENTROPY_SOURCE_STATIC_CACHE = generateInternal();
}
private static final class LazyHolderForPseudo {
private static final int PSEUDO_LOW_ENTROPY_SOURCE_STATIC_CACHE = generateInternal();
}
private static final String KEY_LOW_ENTROPY_SOURCE_FRE_COMPLETED =
"low_entropy_source_fre_completed";
/**
* Provides access to non-identifying entropy source for FRE trials.
* See {@link #generateLowEntropySource()} for details.
*
* WARNING: This should only be used on first run, as otherwise the value generated by this code
* will not correspond to what is used on the C++ side. Calling this method after FRE is
* completed will throw an exception.
*/
public static int generateLowEntropySourceForFirstRunTrial() {
ThreadUtils.assertOnUiThread();
SharedPreferences prefs = ContextUtils.getAppSharedPreferences();
if (prefs.getBoolean(KEY_LOW_ENTROPY_SOURCE_FRE_COMPLETED, false)) {
throw new IllegalStateException(
"LowEntropySource can't be used from Java after FRE has been completed!");
}
return generateLowEntropySource();
}
/**
* Should be invoked when the FRE is completed to notify LowEntropySource that
* {@link #generateLowEntropySourceForFirstRunTrial} can no longer be used.
*/
public static void markFirstRunComplete() {
ThreadUtils.assertOnUiThread();
SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit();
editor.putBoolean(KEY_LOW_ENTROPY_SOURCE_FRE_COMPLETED, true);
editor.apply();
}
/**
* Generates a new non-identifying entropy source. Has a static cache, so subsequent calls will
* return the same value during the process lifetime.
*/
@CalledByNative
private static int generateLowEntropySource() {
return LazyHolder.LOW_ENTROPY_SOURCE_STATIC_CACHE;
}
/**
* Generates a new non-identifying low entropy source using the same method that's used for the
* actual low entropy source. This one, however, is only used for statistical validation, and
* *not* for randomization or experiment assignment.
*/
@CalledByNative
private static int generatePseudoLowEntropySource() {
return LazyHolderForPseudo.PSEUDO_LOW_ENTROPY_SOURCE_STATIC_CACHE;
}
private static int generateInternal() {
return new Random().nextInt(MAX_LOW_ENTROPY_SIZE);
}
}