1 // Copyright 2022 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.components.metrics; 6 7 import android.content.SharedPreferences; 8 9 import androidx.annotation.MainThread; 10 11 import org.jni_zero.CalledByNative; 12 13 import org.chromium.base.ContextUtils; 14 import org.chromium.base.ThreadUtils; 15 16 import java.util.Random; 17 18 /** 19 * Generates a new non-identifying entropy source used to seed persistent activities. Has a static 20 * cache so that the new low entropy source value will only be generated on first access. 21 * Low entropy source is queried by entropy_source.cc that caches it in prefs. On Android, it is 22 * generated in Java so that it can be used by FRE experiments when the native is not available yet. 23 */ 24 @MainThread 25 public class LowEntropySource { 26 // Should be equal to the value of EntropyState::kMaxLowEntropySize in C++. 27 public static final int MAX_LOW_ENTROPY_SIZE = 8000; 28 29 private static final class LazyHolder { 30 private static final int LOW_ENTROPY_SOURCE_STATIC_CACHE = generateInternal(); 31 } 32 33 private static final class LazyHolderForPseudo { 34 private static final int PSEUDO_LOW_ENTROPY_SOURCE_STATIC_CACHE = generateInternal(); 35 } 36 37 private static final String KEY_LOW_ENTROPY_SOURCE_FRE_COMPLETED = 38 "low_entropy_source_fre_completed"; 39 40 /** 41 * Provides access to non-identifying entropy source for FRE trials. 42 * See {@link #generateLowEntropySource()} for details. 43 * 44 * WARNING: This should only be used on first run, as otherwise the value generated by this code 45 * will not correspond to what is used on the C++ side. Calling this method after FRE is 46 * completed will throw an exception. 47 */ generateLowEntropySourceForFirstRunTrial()48 public static int generateLowEntropySourceForFirstRunTrial() { 49 ThreadUtils.assertOnUiThread(); 50 SharedPreferences prefs = ContextUtils.getAppSharedPreferences(); 51 if (prefs.getBoolean(KEY_LOW_ENTROPY_SOURCE_FRE_COMPLETED, false)) { 52 throw new IllegalStateException( 53 "LowEntropySource can't be used from Java after FRE has been completed!"); 54 } 55 56 return generateLowEntropySource(); 57 } 58 59 /** 60 * Should be invoked when the FRE is completed to notify LowEntropySource that 61 * {@link #generateLowEntropySourceForFirstRunTrial} can no longer be used. 62 */ markFirstRunComplete()63 public static void markFirstRunComplete() { 64 ThreadUtils.assertOnUiThread(); 65 SharedPreferences.Editor editor = ContextUtils.getAppSharedPreferences().edit(); 66 editor.putBoolean(KEY_LOW_ENTROPY_SOURCE_FRE_COMPLETED, true); 67 editor.apply(); 68 } 69 70 /** 71 * Generates a new non-identifying entropy source. Has a static cache, so subsequent calls will 72 * return the same value during the process lifetime. 73 */ 74 @CalledByNative generateLowEntropySource()75 private static int generateLowEntropySource() { 76 return LazyHolder.LOW_ENTROPY_SOURCE_STATIC_CACHE; 77 } 78 79 /** 80 * Generates a new non-identifying low entropy source using the same method that's used for the 81 * actual low entropy source. This one, however, is only used for statistical validation, and 82 * *not* for randomization or experiment assignment. 83 */ 84 @CalledByNative generatePseudoLowEntropySource()85 private static int generatePseudoLowEntropySource() { 86 return LazyHolderForPseudo.PSEUDO_LOW_ENTROPY_SOURCE_STATIC_CACHE; 87 } 88 generateInternal()89 private static int generateInternal() { 90 return new Random().nextInt(MAX_LOW_ENTROPY_SIZE); 91 } 92 } 93