• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter Authors. All rights reserved.
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 io.flutter.embedding.engine.systemchannels;
6 
7 import android.content.pm.ActivityInfo;
8 import android.support.annotation.NonNull;
9 import android.support.annotation.Nullable;
10 
11 import org.json.JSONArray;
12 import org.json.JSONException;
13 import org.json.JSONObject;
14 
15 import java.util.ArrayList;
16 import java.util.List;
17 
18 import io.flutter.Log;
19 import io.flutter.embedding.engine.dart.DartExecutor;
20 import io.flutter.plugin.common.JSONMethodCodec;
21 import io.flutter.plugin.common.MethodCall;
22 import io.flutter.plugin.common.MethodChannel;
23 
24 /**
25  * System channel that receives requests for host platform behavior, e.g., haptic and sound
26  * effects, system chrome configurations, and clipboard interaction.
27  */
28 public class PlatformChannel {
29   private static final String TAG = "PlatformChannel";
30 
31   @NonNull
32   public final MethodChannel channel;
33   @Nullable
34   private PlatformMessageHandler platformMessageHandler;
35 
36   private final MethodChannel.MethodCallHandler parsingMethodCallHandler = new MethodChannel.MethodCallHandler() {
37     @Override
38     public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) {
39       if (platformMessageHandler == null) {
40         // If no explicit PlatformMessageHandler has been registered then we don't
41         // need to forward this call to an API. Return.
42         return;
43       }
44 
45       String method = call.method;
46       Object arguments = call.arguments;
47       Log.v(TAG, "Received '" + method + "' message.");
48       try {
49         switch (method) {
50           case "SystemSound.play":
51             try {
52               SoundType soundType = SoundType.fromValue((String) arguments);
53               platformMessageHandler.playSystemSound(soundType);
54               result.success(null);
55             } catch (NoSuchFieldException exception) {
56               // The desired sound type does not exist.
57               result.error("error", exception.getMessage(), null);
58             }
59             break;
60           case "HapticFeedback.vibrate":
61             try {
62               HapticFeedbackType feedbackType = HapticFeedbackType.fromValue((String) arguments);
63               platformMessageHandler.vibrateHapticFeedback(feedbackType);
64               result.success(null);
65             } catch (NoSuchFieldException exception) {
66               // The desired feedback type does not exist.
67               result.error("error", exception.getMessage(), null);
68             }
69             break;
70           case "SystemChrome.setPreferredOrientations":
71             try {
72               int androidOrientation = decodeOrientations((JSONArray) arguments);
73               platformMessageHandler.setPreferredOrientations(androidOrientation);
74               result.success(null);
75             } catch (JSONException | NoSuchFieldException exception) {
76               // JSONException: One or more expected fields were either omitted or referenced an invalid type.
77               // NoSuchFieldException: One or more expected fields were either omitted or referenced an invalid type.
78               result.error("error", exception.getMessage(), null);
79             }
80             break;
81           case "SystemChrome.setApplicationSwitcherDescription":
82             try {
83               AppSwitcherDescription description = decodeAppSwitcherDescription((JSONObject) arguments);
84               platformMessageHandler.setApplicationSwitcherDescription(description);
85               result.success(null);
86             } catch (JSONException exception) {
87               // One or more expected fields were either omitted or referenced an invalid type.
88               result.error("error", exception.getMessage(), null);
89             }
90             break;
91           case "SystemChrome.setEnabledSystemUIOverlays":
92             try {
93               List<SystemUiOverlay> overlays = decodeSystemUiOverlays((JSONArray) arguments);
94               platformMessageHandler.showSystemOverlays(overlays);
95               result.success(null);
96             } catch (JSONException | NoSuchFieldException exception) {
97               // JSONException: One or more expected fields were either omitted or referenced an invalid type.
98               // NoSuchFieldException: One or more of the overlay names are invalid.
99               result.error("error", exception.getMessage(), null);
100             }
101             break;
102           case "SystemChrome.restoreSystemUIOverlays":
103             platformMessageHandler.restoreSystemUiOverlays();
104             result.success(null);
105             break;
106           case "SystemChrome.setSystemUIOverlayStyle":
107             try {
108               SystemChromeStyle systemChromeStyle = decodeSystemChromeStyle((JSONObject) arguments);
109               platformMessageHandler.setSystemUiOverlayStyle(systemChromeStyle);
110               result.success(null);
111             } catch (JSONException | NoSuchFieldException exception) {
112               // JSONException: One or more expected fields were either omitted or referenced an invalid type.
113               // NoSuchFieldException: One or more of the brightness names are invalid.
114               result.error("error", exception.getMessage(), null);
115             }
116             break;
117           case "SystemNavigator.pop":
118             platformMessageHandler.popSystemNavigator();
119             result.success(null);
120             break;
121           case "Clipboard.getData": {
122             String contentFormatName = (String) arguments;
123             ClipboardContentFormat clipboardFormat = null;
124             if (contentFormatName != null) {
125               try {
126                 clipboardFormat = ClipboardContentFormat.fromValue(contentFormatName);
127               } catch (NoSuchFieldException exception) {
128                 // An unsupported content format was requested. Return failure.
129                 result.error("error", "No such clipboard content format: " + contentFormatName, null);
130               }
131             }
132 
133             CharSequence clipboardContent = platformMessageHandler.getClipboardData(clipboardFormat);
134             if (clipboardContent != null) {
135               JSONObject response = new JSONObject();
136               response.put("text", clipboardContent);
137               result.success(response);
138             } else {
139               result.success(null);
140             }
141             break;
142           }
143           case "Clipboard.setData": {
144             String clipboardContent = ((JSONObject) arguments).getString("text");
145             platformMessageHandler.setClipboardData(clipboardContent);
146             result.success(null);
147             break;
148           }
149           default:
150             result.notImplemented();
151             break;
152         }
153       } catch (JSONException e) {
154         result.error("error", "JSON error: " + e.getMessage(), null);
155       }
156     }
157   };
158 
159   /**
160    * Constructs a {@code PlatformChannel} that connects Android to the Dart code
161    * running in {@code dartExecutor}.
162    *
163    * The given {@code dartExecutor} is permitted to be idle or executing code.
164    *
165    * See {@link DartExecutor}.
166    */
PlatformChannel(@onNull DartExecutor dartExecutor)167   public PlatformChannel(@NonNull DartExecutor dartExecutor) {
168     channel = new MethodChannel(dartExecutor, "flutter/platform", JSONMethodCodec.INSTANCE);
169     channel.setMethodCallHandler(parsingMethodCallHandler);
170   }
171 
172   /**
173    * Sets the {@link PlatformMessageHandler} which receives all events and requests
174    * that are parsed from the underlying platform channel.
175    */
setPlatformMessageHandler(@ullable PlatformMessageHandler platformMessageHandler)176   public void setPlatformMessageHandler(@Nullable PlatformMessageHandler platformMessageHandler) {
177     this.platformMessageHandler = platformMessageHandler;
178   }
179 
180   // TODO(mattcarroll): add support for IntDef annotations, then add @ScreenOrientation
181 
182   /**
183    * Decodes a series of orientations to an aggregate desired orientation.
184    *
185    * @throws JSONException if {@code encodedOrientations} does not contain expected keys and value types.
186    * @throws NoSuchFieldException if any given encoded orientation is not a valid orientation name.
187    */
decodeOrientations(@onNull JSONArray encodedOrientations)188   private int decodeOrientations(@NonNull JSONArray encodedOrientations) throws JSONException, NoSuchFieldException {
189     int requestedOrientation = 0x00;
190     int firstRequestedOrientation = 0x00;
191     for (int index = 0; index < encodedOrientations.length(); index += 1) {
192       String encodedOrientation = encodedOrientations.getString(index);
193       DeviceOrientation orientation = DeviceOrientation.fromValue(encodedOrientation);
194 
195       switch (orientation) {
196         case PORTRAIT_UP:
197           requestedOrientation |= 0x01;
198           break;
199         case PORTRAIT_DOWN:
200           requestedOrientation |= 0x04;
201           break;
202         case LANDSCAPE_LEFT:
203           requestedOrientation |= 0x02;
204           break;
205         case LANDSCAPE_RIGHT:
206           requestedOrientation |= 0x08;
207           break;
208       }
209 
210       if (firstRequestedOrientation == 0x00) {
211         firstRequestedOrientation = requestedOrientation;
212       }
213     }
214 
215     switch (requestedOrientation) {
216       case 0x00:
217         return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
218       case 0x01:
219         return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
220       case 0x02:
221         return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
222       case 0x04:
223         return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
224       case 0x05:
225         return ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT;
226       case 0x08:
227         return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
228       case 0x0a:
229         return ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE;
230       case 0x0b:
231         return ActivityInfo.SCREEN_ORIENTATION_USER;
232       case 0x0f:
233         return ActivityInfo.SCREEN_ORIENTATION_FULL_USER;
234       case 0x03: // portraitUp and landscapeLeft
235       case 0x06: // portraitDown and landscapeLeft
236       case 0x07: // portraitUp, portraitDown, and landscapeLeft
237       case 0x09: // portraitUp and landscapeRight
238       case 0x0c: // portraitDown and landscapeRight
239       case 0x0d: // portraitUp, portraitDown, and landscapeRight
240       case 0x0e: // portraitDown, landscapeLeft, and landscapeRight
241         // Android can't describe these cases, so just default to whatever the first
242         // specified value was.
243         switch (firstRequestedOrientation) {
244           case 0x01:
245             return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
246           case 0x02:
247             return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
248           case 0x04:
249             return ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
250           case 0x08:
251             return ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
252         }
253     }
254 
255     // Execution should never get this far, but if it does then we default
256     // to a portrait orientation.
257     return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
258   }
259 
260   @NonNull
decodeAppSwitcherDescription(@onNull JSONObject encodedDescription)261   private AppSwitcherDescription decodeAppSwitcherDescription(@NonNull JSONObject encodedDescription) throws JSONException {
262     int color = encodedDescription.getInt("primaryColor");
263     if (color != 0) { // 0 means color isn't set, use system default
264       color = color | 0xFF000000; // color must be opaque if set
265     }
266     String label = encodedDescription.getString("label");
267     return new AppSwitcherDescription(color, label);
268   }
269 
270   /**
271    * Decodes a list of JSON-encoded overlays to a list of {@link SystemUiOverlay}.
272    *
273    * @throws JSONException if {@code encodedSystemUiOverlay} does not contain expected keys and value types.
274    * @throws NoSuchFieldException if any of the given encoded overlay names are invalid.
275    */
276   @NonNull
decodeSystemUiOverlays(@onNull JSONArray encodedSystemUiOverlay)277   private List<SystemUiOverlay> decodeSystemUiOverlays(@NonNull JSONArray encodedSystemUiOverlay) throws JSONException, NoSuchFieldException {
278     List<SystemUiOverlay> overlays = new ArrayList<>();
279     for (int i = 0; i < encodedSystemUiOverlay.length(); ++i) {
280       String encodedOverlay = encodedSystemUiOverlay.getString(i);
281       SystemUiOverlay overlay = SystemUiOverlay.fromValue(encodedOverlay);
282       switch(overlay) {
283         case TOP_OVERLAYS:
284           overlays.add(SystemUiOverlay.TOP_OVERLAYS);
285           break;
286         case BOTTOM_OVERLAYS:
287           overlays.add(SystemUiOverlay.BOTTOM_OVERLAYS);
288           break;
289       }
290     }
291     return overlays;
292   }
293 
294   /**
295    * Decodes a JSON-encoded {@code encodedStyle} to a {@link SystemChromeStyle}.
296    *
297    * @throws JSONException if {@code encodedStyle} does not contain expected keys and value types.
298    * @throws NoSuchFieldException if any provided brightness name is invalid.
299    */
300   @NonNull
decodeSystemChromeStyle(@onNull JSONObject encodedStyle)301   private SystemChromeStyle decodeSystemChromeStyle(@NonNull JSONObject encodedStyle) throws JSONException, NoSuchFieldException {
302     Brightness systemNavigationBarIconBrightness = null;
303     // TODO(mattcarroll): add color annotation
304     Integer systemNavigationBarColor = null;
305     // TODO(mattcarroll): add color annotation
306     Integer systemNavigationBarDividerColor = null;
307     Brightness statusBarIconBrightness = null;
308     // TODO(mattcarroll): add color annotation
309     Integer statusBarColor = null;
310 
311     if (!encodedStyle.isNull("systemNavigationBarIconBrightness")) {
312       systemNavigationBarIconBrightness = Brightness.fromValue(encodedStyle.getString("systemNavigationBarIconBrightness"));
313     }
314 
315     if (!encodedStyle.isNull("systemNavigationBarColor")) {
316       systemNavigationBarColor = encodedStyle.getInt("systemNavigationBarColor");
317     }
318 
319     if (!encodedStyle.isNull("statusBarIconBrightness")) {
320       statusBarIconBrightness = Brightness.fromValue(encodedStyle.getString("statusBarIconBrightness"));
321     }
322 
323     if (!encodedStyle.isNull("statusBarColor")) {
324       statusBarColor = encodedStyle.getInt("statusBarColor");
325     }
326 
327     if (!encodedStyle.isNull("systemNavigationBarDividerColor")) {
328       systemNavigationBarDividerColor = encodedStyle.getInt("systemNavigationBarDividerColor");
329     }
330 
331     return new SystemChromeStyle(
332       statusBarColor,
333       statusBarIconBrightness,
334       systemNavigationBarColor,
335       systemNavigationBarIconBrightness,
336       systemNavigationBarDividerColor
337     );
338   }
339 
340   /**
341    * Handler that receives platform messages sent from Flutter to Android
342    * through a given {@link PlatformChannel}.
343    *
344    * To register a {@code PlatformMessageHandler} with a {@link PlatformChannel},
345    * see {@link PlatformChannel#setPlatformMessageHandler(PlatformMessageHandler)}.
346    */
347   public interface PlatformMessageHandler {
348     /**
349      * The Flutter application would like to play the given {@code soundType}.
350      */
playSystemSound(@onNull SoundType soundType)351     void playSystemSound(@NonNull SoundType soundType);
352 
353     /**
354      * The Flutter application would like to play the given haptic {@code feedbackType}.
355      */
vibrateHapticFeedback(@onNull HapticFeedbackType feedbackType)356     void vibrateHapticFeedback(@NonNull HapticFeedbackType feedbackType);
357 
358     /**
359      * The Flutter application would like to display in the given {@code androidOrientation}.
360      */
361     // TODO(mattcarroll): add @ScreenOrientation annotation
setPreferredOrientations(int androidOrientation)362     void setPreferredOrientations(int androidOrientation);
363 
364     /**
365      * The Flutter application would like to be displayed in Android's app switcher with
366      * the visual representation described in the given {@code description}.
367      * <p>
368      * See the related Android documentation:
369      * https://developer.android.com/guide/components/activities/recents
370      */
setApplicationSwitcherDescription(@onNull AppSwitcherDescription description)371     void setApplicationSwitcherDescription(@NonNull AppSwitcherDescription description);
372 
373     /**
374      * The Flutter application would like the Android system to display the given
375      * {@code overlays}.
376      * <p>
377      * {@link SystemUiOverlay#TOP_OVERLAYS} refers to system overlays such as the
378      * status bar, while {@link SystemUiOverlay#BOTTOM_OVERLAYS} refers to system
379      * overlays such as the back/home/recents navigation on the bottom of the screen.
380      * <p>
381      * An empty list of {@code overlays} should hide all system overlays.
382      */
showSystemOverlays(@onNull List<SystemUiOverlay> overlays)383     void showSystemOverlays(@NonNull List<SystemUiOverlay> overlays);
384 
385     /**
386      * The Flutter application would like to restore the visibility of system
387      * overlays to the last set of overlays sent via {@link #showSystemOverlays(List)}.
388      * <p>
389      * If {@link #showSystemOverlays(List)} has yet to be called, then a default
390      * system overlay appearance is desired:
391      * <p>
392      * {@code
393      * View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
394      * }
395      */
restoreSystemUiOverlays()396     void restoreSystemUiOverlays();
397 
398     /**
399      * The Flutter application would like the system chrome to present itself with
400      * the given {@code systemUiOverlayStyle}, i.e., the given status bar and
401      * navigation bar colors and brightness.
402      */
setSystemUiOverlayStyle(@onNull SystemChromeStyle systemUiOverlayStyle)403     void setSystemUiOverlayStyle(@NonNull SystemChromeStyle systemUiOverlayStyle);
404 
405     /**
406      * The Flutter application would like to pop the top item off of the Android
407      * app's navigation back stack.
408      */
popSystemNavigator()409     void popSystemNavigator();
410 
411     /**
412      * The Flutter application would like to receive the current data in the
413      * clipboard and have it returned in the given {@code format}.
414      */
415     @Nullable
getClipboardData(@ullable ClipboardContentFormat format)416     CharSequence getClipboardData(@Nullable ClipboardContentFormat format);
417 
418     /**
419      * The Flutter application would like to set the current data in the
420      * clipboard to the given {@code text}.
421      */
setClipboardData(@onNull String text)422     void setClipboardData(@NonNull String text);
423   }
424 
425   /**
426    * Types of sounds the Android OS can play on behalf of an application.
427    */
428   public enum SoundType {
429     CLICK("SystemSoundType.click");
430 
431     @NonNull
fromValue(@onNull String encodedName)432     static SoundType fromValue(@NonNull String encodedName) throws NoSuchFieldException {
433       for (SoundType soundType : SoundType.values()) {
434         if (soundType.encodedName.equals(encodedName)) {
435           return soundType;
436         }
437       }
438       throw new NoSuchFieldException("No such SoundType: " + encodedName);
439     }
440 
441     @NonNull
442     private final String encodedName;
443 
SoundType(@onNull String encodedName)444     SoundType(@NonNull String encodedName) {
445       this.encodedName = encodedName;
446     }
447   }
448 
449   /**
450    * The types of haptic feedback that the Android OS can generate on behalf
451    * of an application.
452    */
453   public enum HapticFeedbackType {
454     STANDARD(null),
455     LIGHT_IMPACT("HapticFeedbackType.lightImpact"),
456     MEDIUM_IMPACT("HapticFeedbackType.mediumImpact"),
457     HEAVY_IMPACT("HapticFeedbackType.heavyImpact"),
458     SELECTION_CLICK("HapticFeedbackType.selectionClick");
459 
460     @NonNull
fromValue(@ullable String encodedName)461     static HapticFeedbackType fromValue(@Nullable String encodedName) throws NoSuchFieldException {
462       for (HapticFeedbackType feedbackType : HapticFeedbackType.values()) {
463         if ((feedbackType.encodedName == null && encodedName == null)
464             || (feedbackType.encodedName != null && feedbackType.encodedName.equals(encodedName))) {
465           return feedbackType;
466         }
467       }
468       throw new NoSuchFieldException("No such HapticFeedbackType: " + encodedName);
469     }
470 
471     @Nullable
472     private final String encodedName;
473 
HapticFeedbackType(@ullable String encodedName)474     HapticFeedbackType(@Nullable String encodedName) {
475       this.encodedName = encodedName;
476     }
477   }
478 
479   /**
480    * The possible desired orientations of a Flutter application.
481    */
482   public enum DeviceOrientation {
483     PORTRAIT_UP("DeviceOrientation.portraitUp"),
484     PORTRAIT_DOWN("DeviceOrientation.portraitDown"),
485     LANDSCAPE_LEFT("DeviceOrientation.landscapeLeft"),
486     LANDSCAPE_RIGHT("DeviceOrientation.landscapeRight");
487 
488     @NonNull
fromValue(@onNull String encodedName)489     static DeviceOrientation fromValue(@NonNull String encodedName) throws NoSuchFieldException {
490       for (DeviceOrientation orientation : DeviceOrientation.values()) {
491         if (orientation.encodedName.equals(encodedName)) {
492           return orientation;
493         }
494       }
495       throw new NoSuchFieldException("No such DeviceOrientation: " + encodedName);
496     }
497 
498     @NonNull
499     private String encodedName;
500 
DeviceOrientation(@onNull String encodedName)501     DeviceOrientation(@NonNull String encodedName) {
502       this.encodedName = encodedName;
503     }
504   }
505 
506   /**
507    * The set of Android system UI overlays as perceived by the Flutter application.
508    * <p>
509    * Android includes many more overlay options and flags than what is provided by
510    * {@code SystemUiOverlay}. Flutter only requires control over a subset of the
511    * overlays and those overlays are represented by {@code SystemUiOverlay} values.
512    */
513   public enum SystemUiOverlay {
514     TOP_OVERLAYS("SystemUiOverlay.top"),
515     BOTTOM_OVERLAYS("SystemUiOverlay.bottom");
516 
517     @NonNull
fromValue(@onNull String encodedName)518     static SystemUiOverlay fromValue(@NonNull String encodedName) throws NoSuchFieldException {
519       for (SystemUiOverlay overlay : SystemUiOverlay.values()) {
520         if (overlay.encodedName.equals(encodedName)) {
521           return overlay;
522         }
523       }
524       throw new NoSuchFieldException("No such SystemUiOverlay: " + encodedName);
525     }
526 
527     @NonNull
528     private String encodedName;
529 
SystemUiOverlay(@onNull String encodedName)530     SystemUiOverlay(@NonNull String encodedName) {
531       this.encodedName = encodedName;
532     }
533   }
534 
535   /**
536    * The color and label of an application that appears in Android's app switcher, AKA
537    * recents screen.
538    */
539   public static class AppSwitcherDescription {
540     // TODO(mattcarroll): add color annotation
541     public final int color;
542     @NonNull
543     public final String label;
544 
AppSwitcherDescription(int color, @NonNull String label)545     public AppSwitcherDescription(int color, @NonNull String label) {
546       this.color = color;
547       this.label = label;
548     }
549   }
550 
551   /**
552    * The color and brightness of system chrome, e.g., status bar and system navigation bar.
553    */
554   public static class SystemChromeStyle {
555     // TODO(mattcarroll): add color annotation
556     @Nullable
557     public final Integer statusBarColor;
558     @Nullable
559     public final Brightness statusBarIconBrightness;
560     // TODO(mattcarroll): add color annotation
561     @Nullable
562     public final Integer systemNavigationBarColor;
563     @Nullable
564     public final Brightness systemNavigationBarIconBrightness;
565     // TODO(mattcarroll): add color annotation
566     @Nullable
567     public final Integer systemNavigationBarDividerColor;
568 
SystemChromeStyle( @ullable Integer statusBarColor, @Nullable Brightness statusBarIconBrightness, @Nullable Integer systemNavigationBarColor, @Nullable Brightness systemNavigationBarIconBrightness, @Nullable Integer systemNavigationBarDividerColor )569     public SystemChromeStyle(
570         @Nullable Integer statusBarColor,
571         @Nullable Brightness statusBarIconBrightness,
572         @Nullable Integer systemNavigationBarColor,
573         @Nullable Brightness systemNavigationBarIconBrightness,
574         @Nullable Integer systemNavigationBarDividerColor
575     ) {
576       this.statusBarColor = statusBarColor;
577       this.statusBarIconBrightness = statusBarIconBrightness;
578       this.systemNavigationBarColor = systemNavigationBarColor;
579       this.systemNavigationBarIconBrightness = systemNavigationBarIconBrightness;
580       this.systemNavigationBarDividerColor = systemNavigationBarDividerColor;
581     }
582   }
583 
584   public enum Brightness {
585     LIGHT("Brightness.light"),
586     DARK("Brightness.dark");
587 
588     @NonNull
fromValue(@onNull String encodedName)589     static Brightness fromValue(@NonNull String encodedName) throws NoSuchFieldException {
590       for (Brightness brightness : Brightness.values()) {
591         if (brightness.encodedName.equals(encodedName)) {
592           return brightness;
593         }
594       }
595       throw new NoSuchFieldException("No such Brightness: " + encodedName);
596     }
597 
598     @NonNull
599     private String encodedName;
600 
Brightness(@onNull String encodedName)601     Brightness(@NonNull String encodedName) {
602       this.encodedName = encodedName;
603     }
604   }
605 
606   /**
607    * Data formats of clipboard content.
608    */
609   public enum ClipboardContentFormat {
610     PLAIN_TEXT("text/plain");
611 
612     @NonNull
fromValue(@onNull String encodedName)613     static ClipboardContentFormat fromValue(@NonNull String encodedName) throws NoSuchFieldException {
614       for (ClipboardContentFormat format : ClipboardContentFormat.values()) {
615         if (format.encodedName.equals(encodedName)) {
616           return format;
617         }
618       }
619       throw new NoSuchFieldException("No such ClipboardContentFormat: " + encodedName);
620     }
621 
622     @NonNull
623     private String encodedName;
624 
ClipboardContentFormat(@onNull String encodedName)625     ClipboardContentFormat(@NonNull String encodedName) {
626       this.encodedName = encodedName;
627     }
628   }
629 }
630