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