xref: /aosp_15_r20/external/cronet/components/cronet/android/sample/src/org/chromium/cronet_sample_apk/Options.java (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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