1 // Copyright 2023 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.cronet_sample_apk; 6 7 import androidx.annotation.OptIn; 8 9 import org.chromium.net.ConnectionMigrationOptions; 10 11 import java.util.Collections; 12 import java.util.LinkedHashMap; 13 import java.util.List; 14 import java.util.Map; 15 16 /** 17 * Adding an option here will make it show up in the list of available options. 18 * Each {@link Option} has the following attributes: 19 * <ul> 20 * <li>A short name which appears in bold on the options list.</li> 21 * <li>A description which provides a thorough explanation of what this option does.</li> 22 * <li>An {@link Action} which is applied on CronetEngine's Builder each time the user hits "Reset 23 * Engine". </li> <li>A default value, every option must have a default value.</li> 24 * </ul> 25 * <b>NOTE</b>: Each option must map to one {@link OptionsIdentifier OptionsIdentifier}. This is 26 * necessary to provide custom implementation for options that does not configure the builders. See 27 * {@link OptionsIdentifier#SLOW_DOWNLOAD} as an example. 28 * 29 * <p> To add a new option, do the following: 30 * <ol> 31 * <li> Add a new optionIdentifier {@link OptionsIdentifier} </li> 32 * <li> Inject a new Option instance into your optionIdentifier enum value. </li> 33 * <li> Implement the logic for the new option within a new {@link Action}. </li> 34 * <li> If the {@link Action} interface is not enough to satisfy the use-case. Feel free to add 35 * custom logic, See {@link OptionsIdentifier#SLOW_DOWNLOAD} as an example.</li> 36 * <li> Restart the APK and verify that your option is working as intended. </li> 37 * </ol> 38 */ 39 public class Options { 40 public enum OptionsIdentifier { 41 MIGRATE_SESSIONS_ON_NETWORK_CHANGE_V2( 42 new BooleanOption( 43 "migrate_sessions_on_network_change_v2", 44 "Enable QUIC connection migration. This only occurs when a network has " 45 + "completed" 46 + " disconnected and no longer reachable. QUIC will try to migrate " 47 + "the current session(maybe idle? depending on another option) to " 48 + "another network.", 49 new Action<>() { 50 @Override 51 public void configureBuilder(ActionData data, Boolean value) { 52 data.getMigrationBuilder().enableDefaultNetworkMigration(value); 53 } 54 }, 55 false)), 56 MIGRATION_SESSION_EARLY_V2( 57 new BooleanOption( 58 "migrate_sessions_early_v2", 59 "Enable QUIC early session migration. This will make quic send probing" 60 + " packets when the network is degrading, QUIC will migrate the " 61 + "sessions to a different network even before the original network " 62 + "has disconnected.", 63 new Action<Boolean>() { 64 @Override 65 @OptIn(markerClass = ConnectionMigrationOptions.Experimental.class) 66 public void configureBuilder(ActionData data, Boolean value) { 67 data.getMigrationBuilder().enablePathDegradationMigration(value); 68 data.getMigrationBuilder().allowNonDefaultNetworkUsage(value); 69 } 70 }, 71 false)), 72 SLOW_DOWNLOAD( 73 new BooleanOption( 74 "Slow Download (10s)", 75 "Hang the onReadCompleted for 10s before proceeding. This should simulate slow connection.", 76 new Action<>() {}, 77 false)); 78 79 private final Option<?> mOption; 80 OptionsIdentifier(Option<?> option)81 OptionsIdentifier(Option<?> option) { 82 this.mOption = option; 83 } 84 getOption()85 public Option<?> getOption() { 86 return mOption; 87 } 88 } 89 90 private static final Map<OptionsIdentifier, Option> OPTIONS = 91 Collections.unmodifiableMap(createOptionsMap()); 92 private static final List<Option> OPTION_LIST = List.copyOf(OPTIONS.values()); 93 createOptionsMap()94 private static Map<OptionsIdentifier, Option> createOptionsMap() { 95 Map<OptionsIdentifier, Option> optionsMap = new LinkedHashMap<>(); 96 for (OptionsIdentifier optionIdentifier : OptionsIdentifier.values()) { 97 optionsMap.put(optionIdentifier, optionIdentifier.getOption()); 98 } 99 return optionsMap; 100 } 101 102 public abstract static class Option<T> { 103 private final String mOptionName; 104 private final String mOptionDescription; 105 private final Action<T> mAction; 106 private T mOptionValue; 107 Option( String optionName, String optionDescription, Action<T> action, T defaultValue)108 private Option( 109 String optionName, String optionDescription, Action<T> action, T defaultValue) { 110 this.mOptionName = optionName; 111 this.mOptionDescription = optionDescription; 112 this.mAction = action; 113 this.mOptionValue = defaultValue; 114 } 115 getShortName()116 public String getShortName() { 117 return mOptionName; 118 } 119 getDescription()120 public String getDescription() { 121 return mOptionDescription; 122 } 123 configure(ActionData data)124 void configure(ActionData data) { 125 mAction.configureBuilder(data, getValue()); 126 } 127 getInputType()128 abstract Class<T> getInputType(); 129 getValue()130 T getValue() { 131 return mOptionValue; 132 } 133 setValue(T newValue)134 void setValue(T newValue) { 135 mOptionValue = newValue; 136 } 137 } 138 139 public static class BooleanOption extends Option<Boolean> { BooleanOption( String optionName, String optionDescription, Action<Boolean> action, Boolean defaultValue)140 private BooleanOption( 141 String optionName, 142 String optionDescription, 143 Action<Boolean> action, 144 Boolean defaultValue) { 145 super(optionName, optionDescription, action, defaultValue); 146 } 147 148 @Override getInputType()149 Class<Boolean> getInputType() { 150 return Boolean.class; 151 } 152 } 153 Options()154 private Options() {} 155 getOptions()156 public static List<Option> getOptions() { 157 return OPTION_LIST; 158 } 159 isBooleanOptionOn(OptionsIdentifier identifier)160 public static boolean isBooleanOptionOn(OptionsIdentifier identifier) { 161 if (!OPTIONS.containsKey(identifier)) { 162 throw new IllegalArgumentException( 163 "The provided identifier does not map to any option"); 164 } 165 if (!OPTIONS.get(identifier).getInputType().equals(Boolean.class)) { 166 throw new IllegalStateException("The provided identifier maps to a non-boolean value"); 167 } 168 return (boolean) OPTIONS.get(identifier).getValue(); 169 } 170 } 171