Flutter Engine
The Flutter Engine
SettingsChannel.java
Go to the documentation of this file.
1package io.flutter.embedding.engine.systemchannels;
2
3import static io.flutter.Build.API_LEVELS;
4
5import android.annotation.SuppressLint;
6import android.os.Build;
7import android.util.DisplayMetrics;
8import androidx.annotation.NonNull;
9import androidx.annotation.Nullable;
10import androidx.annotation.UiThread;
11import androidx.annotation.VisibleForTesting;
12import io.flutter.Log;
13import io.flutter.embedding.engine.dart.DartExecutor;
14import io.flutter.plugin.common.BasicMessageChannel;
15import io.flutter.plugin.common.JSONMessageCodec;
16import java.util.HashMap;
17import java.util.Map;
18import java.util.concurrent.ConcurrentLinkedQueue;
19
20public class SettingsChannel {
21 private static final String TAG = "SettingsChannel";
22
23 public static final String CHANNEL_NAME = "flutter/settings";
24 private static final String TEXT_SCALE_FACTOR = "textScaleFactor";
25 private static final String NATIVE_SPELL_CHECK_SERVICE_DEFINED = "nativeSpellCheckServiceDefined";
26 private static final String BRIEFLY_SHOW_PASSWORD = "brieflyShowPassword";
27 private static final String ALWAYS_USE_24_HOUR_FORMAT = "alwaysUse24HourFormat";
28 private static final String PLATFORM_BRIGHTNESS = "platformBrightness";
29 private static final String CONFIGURATION_ID = "configurationId";
30
31 // When hasNonlinearTextScalingSupport() returns false, this will not be initialized.
32 private static final ConfigurationQueue CONFIGURATION_QUEUE = new ConfigurationQueue();
33
34 @NonNull public final BasicMessageChannel<Object> channel;
35
36 public SettingsChannel(@NonNull DartExecutor dartExecutor) {
37 this.channel = new BasicMessageChannel<>(dartExecutor, CHANNEL_NAME, JSONMessageCodec.INSTANCE);
38 }
39
40 @SuppressLint("AnnotateVersionCheck")
41 public static boolean hasNonlinearTextScalingSupport() {
42 return Build.VERSION.SDK_INT >= API_LEVELS.API_34;
43 }
44
45 // This method will only be called on Flutter's UI thread.
46 public static DisplayMetrics getPastDisplayMetrics(int configId) {
48 final ConfigurationQueue.SentConfiguration configuration =
49 CONFIGURATION_QUEUE.getConfiguration(configId);
50 return configuration == null ? null : configuration.displayMetrics;
51 }
52
53 @NonNull
55 return new MessageBuilder(channel);
56 }
57
58 public static class MessageBuilder {
59 @NonNull private final BasicMessageChannel<Object> channel;
60 @NonNull private Map<String, Object> message = new HashMap<>();
61 @Nullable private DisplayMetrics displayMetrics;
62
64 this.channel = channel;
65 }
66
67 @NonNull
68 public MessageBuilder setDisplayMetrics(@NonNull DisplayMetrics displayMetrics) {
69 this.displayMetrics = displayMetrics;
70 return this;
71 }
72
73 @NonNull
74 public MessageBuilder setTextScaleFactor(float textScaleFactor) {
75 message.put(TEXT_SCALE_FACTOR, textScaleFactor);
76 return this;
77 }
78
79 @NonNull
81 boolean nativeSpellCheckServiceDefined) {
82 message.put(NATIVE_SPELL_CHECK_SERVICE_DEFINED, nativeSpellCheckServiceDefined);
83 return this;
84 }
85
86 @NonNull
87 public MessageBuilder setBrieflyShowPassword(@NonNull boolean brieflyShowPassword) {
88 message.put(BRIEFLY_SHOW_PASSWORD, brieflyShowPassword);
89 return this;
90 }
91
92 @NonNull
93 public MessageBuilder setUse24HourFormat(boolean use24HourFormat) {
94 message.put(ALWAYS_USE_24_HOUR_FORMAT, use24HourFormat);
95 return this;
96 }
97
98 @NonNull
100 message.put(PLATFORM_BRIGHTNESS, brightness.name);
101 return this;
102 }
103
104 public void send() {
105 Log.v(
106 TAG,
107 "Sending message: \n"
108 + "textScaleFactor: "
109 + message.get(TEXT_SCALE_FACTOR)
110 + "\n"
111 + "alwaysUse24HourFormat: "
112 + message.get(ALWAYS_USE_24_HOUR_FORMAT)
113 + "\n"
114 + "platformBrightness: "
115 + message.get(PLATFORM_BRIGHTNESS));
116 final DisplayMetrics metrics = this.displayMetrics;
117 if (!hasNonlinearTextScalingSupport() || metrics == null) {
118 channel.send(message);
119 return;
120 }
121 final ConfigurationQueue.SentConfiguration sentConfiguration =
123 final BasicMessageChannel.Reply deleteCallback =
124 CONFIGURATION_QUEUE.enqueueConfiguration(sentConfiguration);
125 message.put(CONFIGURATION_ID, sentConfiguration.generationNumber);
126 channel.send(message, deleteCallback);
127 }
128 }
129
130 /**
131 * The brightness mode of the host platform.
132 *
133 * <p>The {@code name} property is the serialized representation of each brightness mode when
134 * communicated via message channel.
135 */
136 public enum PlatformBrightness {
137 light("light"),
138 dark("dark");
139
140 @NonNull public String name;
141
142 PlatformBrightness(@NonNull String name) {
143 this.name = name;
144 }
145 }
146
147 /**
148 * A FIFO queue that maintains generations of configurations that are potentially used by the
149 * Flutter application.
150 *
151 * <p>Platform configurations needed by the Flutter app (for example, text scale factor) are
152 * retrived on the platform thread, serialized and sent to the Flutter application running on the
153 * Flutter UI thread. However, configurations exposed as functions that take parameters are
154 * typically not serializable. To allow the Flutter app to access these configurations, one
155 * possible solution is to create dart bindings that allow the Flutter framework to invoke these
156 * functions via JNI synchronously. To ensure the serialized configuration and these functions
157 * represent the same set of configurations at any given time, a "generation" id is used in these
158 * synchronous calls, to keep them consistent with the serialized configuration that the Flutter
159 * app most recently received and is currently using.
160 *
161 * <p>A unique generation identifier is generated by the {@link SettingsChannel} and associated
162 * with a configuration when it sends a serialized configuration to the Flutter framework. This
163 * queue keeps different generations of configurations that could be used by the Flutter
164 * framework, and cleans up old configurations that the Flutter framework no longer uses. When the
165 * Flutter framework invokes a function to access the configuration with a generation identifier,
166 * this queue finds the configuration with that identifier and also cleans up configurations that
167 * are no longer needed.
168 *
169 * <p>This mechanism is only needed because {@code TypedValue#applyDimension} does not take the
170 * current text scale factor as an input. Once the AndroidX API that allows us to query the scaled
171 * font size with a pure function is available, we can scrap this class and make the
172 * implementation much simpler.
173 */
174 @VisibleForTesting
175 public static class ConfigurationQueue {
176 private final ConcurrentLinkedQueue<SentConfiguration> sentQueue =
177 new ConcurrentLinkedQueue<>();
178
179 // The current SentConfiguration the Flutter application is using, according
180 // to the most recent getConfiguration call.
181 //
182 // This instance variable will only be accessed by getConfiguration, on
183 // Flutter's UI thread.
184 private SentConfiguration currentConfiguration;
185
186 /**
187 * Returns the {@link SentConfiguration} associated with the given {@code configGeneration}, and
188 * removes configurations older than the returned configurations from the queue as they are no
189 * longer needed.
190 */
191 public SentConfiguration getConfiguration(int configGeneration) {
192 if (currentConfiguration == null) {
193 currentConfiguration = sentQueue.poll();
194 }
195
196 // Remove the older entries, up to the entry associated with
197 // configGeneration. Here we assume the generationNumber never overflows.
198 while (currentConfiguration != null
199 && currentConfiguration.generationNumber < configGeneration) {
200 currentConfiguration = sentQueue.poll();
201 }
202
203 if (currentConfiguration == null) {
204 Log.e(
205 TAG,
206 "Cannot find config with generation: "
207 + String.valueOf(configGeneration)
208 + ", after exhausting the queue.");
209 return null;
210 } else if (currentConfiguration.generationNumber != configGeneration) {
211 Log.e(
212 TAG,
213 "Cannot find config with generation: "
214 + String.valueOf(configGeneration)
215 + ", the oldest config is now: "
216 + String.valueOf(currentConfiguration.generationNumber));
217 return null;
218 }
219 return currentConfiguration;
220 }
221
222 private SentConfiguration previousEnqueuedConfiguration;
223
224 /**
225 * Adds the most recently sent {@link SentConfiguration} to the queue.
226 *
227 * @return a {@link BasicMessageChannel.Reply} whose {@code reply} method must be called when
228 * the embedder receives the reply for the sent configuration, to properly clean up older
229 * configurations in the queue.
230 */
231 @UiThread
232 @Nullable
233 public BasicMessageChannel.Reply enqueueConfiguration(SentConfiguration config) {
234 sentQueue.add(config);
235 final SentConfiguration configurationToRemove = previousEnqueuedConfiguration;
236 previousEnqueuedConfiguration = config;
237 return configurationToRemove == null
238 ? null
239 : new BasicMessageChannel.Reply() {
240 @UiThread
241 @Override
242 public void reply(Object reply) {
243 // Removes the SentConfiguration sent right before `config`. Since
244 // platform messages are also FIFO older messages will be removed
245 // before newer ones.
246 sentQueue.remove(configurationToRemove);
247 if (!sentQueue.isEmpty()) {
248 Log.e(
249 TAG,
250 "The queue becomes empty after removing config generation "
251 + String.valueOf(configurationToRemove.generationNumber));
252 }
253 }
254 };
255 }
256
257 public static class SentConfiguration {
258 private static int nextConfigGeneration = Integer.MIN_VALUE;
259
260 @NonNull public final int generationNumber;
261 @NonNull private final DisplayMetrics displayMetrics;
262
263 public SentConfiguration(@NonNull DisplayMetrics displayMetrics) {
264 this.generationNumber = nextConfigGeneration++;
265 this.displayMetrics = displayMetrics;
266 }
267 }
268 }
269}
static final int API_34
Definition: Build.java:24
static void v(@NonNull String tag, @NonNull String message)
Definition: Log.java:40
static void e(@NonNull String tag, @NonNull String message)
Definition: Log.java:84
BasicMessageChannel.Reply enqueueConfiguration(SentConfiguration config)
MessageBuilder setBrieflyShowPassword(@NonNull boolean brieflyShowPassword)
MessageBuilder setDisplayMetrics(@NonNull DisplayMetrics displayMetrics)
MessageBuilder setNativeSpellCheckServiceDefined(boolean nativeSpellCheckServiceDefined)
MessageBuilder setPlatformBrightness(@NonNull PlatformBrightness brightness)
MessageBuilder(@NonNull BasicMessageChannel< Object > channel)
static DisplayMetrics getPastDisplayMetrics(int configId)
static final JSONMessageCodec INSTANCE
Win32Message message
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
#define TAG()