Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
PlatformPlugin.java
Go to the documentation of this file.
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
5package io.flutter.plugin.platform;
6
7import static io.flutter.Build.API_LEVELS;
8
9import android.app.Activity;
10import android.app.ActivityManager.TaskDescription;
11import android.content.ClipData;
12import android.content.ClipDescription;
13import android.content.ClipboardManager;
14import android.content.Context;
15import android.content.Intent;
16import android.content.res.AssetFileDescriptor;
17import android.net.Uri;
18import android.os.Build;
19import android.view.HapticFeedbackConstants;
20import android.view.SoundEffectConstants;
21import android.view.View;
22import android.view.Window;
23import android.view.WindowManager;
24import androidx.activity.OnBackPressedDispatcherOwner;
25import androidx.annotation.NonNull;
26import androidx.annotation.Nullable;
27import androidx.annotation.VisibleForTesting;
28import androidx.core.view.WindowInsetsControllerCompat;
29import io.flutter.Log;
30import io.flutter.embedding.engine.systemchannels.PlatformChannel;
31import java.io.FileNotFoundException;
32import java.io.IOException;
33import java.util.List;
34
35/** Android implementation of the platform plugin. */
36public class PlatformPlugin {
37 public static final int DEFAULT_SYSTEM_UI =
38 View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
39
40 private final Activity activity;
41 private final PlatformChannel platformChannel;
42 @Nullable private final PlatformPluginDelegate platformPluginDelegate;
43 private PlatformChannel.SystemChromeStyle currentTheme;
44 private int mEnabledOverlays;
45 private static final String TAG = "PlatformPlugin";
46
47 /**
48 * The {@link PlatformPlugin} generally has default behaviors implemented for platform
49 * functionalities requested by the Flutter framework. However, functionalities exposed through
50 * this interface could be customized by the more public-facing APIs that implement this interface
51 * such as the {@link io.flutter.embedding.android.FlutterActivity} or the {@link
52 * io.flutter.embedding.android.FlutterFragment}.
53 */
54 public interface PlatformPluginDelegate {
55 /**
56 * Allow implementer to customize the behavior needed when the Flutter framework calls to pop
57 * the Android-side navigation stack.
58 *
59 * @return true if the implementation consumed the pop signal. If false, a default behavior of
60 * finishing the activity or sending the signal to {@link
61 * androidx.activity.OnBackPressedDispatcher} will be executed.
62 */
64
65 /**
66 * The Flutter application would or would not like to handle navigation pop events itself.
67 *
68 * <p>Relevant for registering and unregistering the app's OnBackInvokedCallback for the
69 * Predictive Back feature, for example as in {@link
70 * io.flutter.embedding.android.FlutterActivity}.
71 */
72 default void setFrameworkHandlesBack(boolean frameworkHandlesBack) {}
73 }
74
75 @VisibleForTesting
76 final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler =
77 new PlatformChannel.PlatformMessageHandler() {
78 @Override
79 public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
80 PlatformPlugin.this.playSystemSound(soundType);
81 }
82
83 @Override
84 public void vibrateHapticFeedback(
85 @NonNull PlatformChannel.HapticFeedbackType feedbackType) {
86 PlatformPlugin.this.vibrateHapticFeedback(feedbackType);
87 }
88
89 @Override
90 public void setPreferredOrientations(int androidOrientation) {
91 setSystemChromePreferredOrientations(androidOrientation);
92 }
93
94 @Override
95 public void setApplicationSwitcherDescription(
96 @NonNull PlatformChannel.AppSwitcherDescription description) {
97 setSystemChromeApplicationSwitcherDescription(description);
98 }
99
100 @Override
101 public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) {
102 setSystemChromeEnabledSystemUIOverlays(overlays);
103 }
104
105 @Override
106 public void showSystemUiMode(@NonNull PlatformChannel.SystemUiMode mode) {
107 setSystemChromeEnabledSystemUIMode(mode);
108 }
109
110 @Override
111 public void setSystemUiChangeListener() {
112 setSystemChromeChangeListener();
113 }
114
115 @Override
116 public void restoreSystemUiOverlays() {
117 restoreSystemChromeSystemUIOverlays();
118 }
119
120 @Override
121 public void setSystemUiOverlayStyle(
122 @NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) {
123 setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle);
124 }
125
126 @Override
127 public void setFrameworkHandlesBack(boolean frameworkHandlesBack) {
128 PlatformPlugin.this.setFrameworkHandlesBack(frameworkHandlesBack);
129 }
130
131 @Override
132 public void popSystemNavigator() {
133 PlatformPlugin.this.popSystemNavigator();
134 }
135
136 @Override
137 public CharSequence getClipboardData(
138 @Nullable PlatformChannel.ClipboardContentFormat format) {
139 return PlatformPlugin.this.getClipboardData(format);
140 }
141
142 @Override
143 public void setClipboardData(@NonNull String text) {
144 PlatformPlugin.this.setClipboardData(text);
145 }
146
147 @Override
148 public boolean clipboardHasStrings() {
149 return PlatformPlugin.this.clipboardHasStrings();
150 }
151
152 @Override
153 public void share(@NonNull String text) {
154 PlatformPlugin.this.share(text);
155 }
156 };
157
158 public PlatformPlugin(@NonNull Activity activity, @NonNull PlatformChannel platformChannel) {
159 this(activity, platformChannel, null);
160 }
161
163 @NonNull Activity activity,
164 @NonNull PlatformChannel platformChannel,
165 @Nullable PlatformPluginDelegate delegate) {
166 this.activity = activity;
167 this.platformChannel = platformChannel;
168 this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler);
169 this.platformPluginDelegate = delegate;
170
171 mEnabledOverlays = DEFAULT_SYSTEM_UI;
172 }
173
174 /**
175 * Releases all resources held by this {@code PlatformPlugin}.
176 *
177 * <p>Do not invoke any methods on a {@code PlatformPlugin} after invoking this method.
178 */
179 public void destroy() {
180 this.platformChannel.setPlatformMessageHandler(null);
181 }
182
183 private void playSystemSound(@NonNull PlatformChannel.SoundType soundType) {
184 if (soundType == PlatformChannel.SoundType.CLICK) {
185 View view = activity.getWindow().getDecorView();
186 view.playSoundEffect(SoundEffectConstants.CLICK);
187 }
188 }
189
190 @VisibleForTesting
191 /* package */ void vibrateHapticFeedback(
192 @NonNull PlatformChannel.HapticFeedbackType feedbackType) {
193 View view = activity.getWindow().getDecorView();
194 switch (feedbackType) {
195 case STANDARD:
196 view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
197 break;
198 case LIGHT_IMPACT:
199 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
200 break;
201 case MEDIUM_IMPACT:
202 view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP);
203 break;
204 case HEAVY_IMPACT:
205 if (Build.VERSION.SDK_INT >= API_LEVELS.API_23) {
206 view.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK);
207 }
208 break;
209 case SELECTION_CLICK:
210 view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
211 break;
212 }
213 }
214
215 private void setSystemChromePreferredOrientations(int androidOrientation) {
216 activity.setRequestedOrientation(androidOrientation);
217 }
218
219 @SuppressWarnings("deprecation")
220 private void setSystemChromeApplicationSwitcherDescription(
221 PlatformChannel.AppSwitcherDescription description) {
222 if (Build.VERSION.SDK_INT < API_LEVELS.API_28) {
223 activity.setTaskDescription(
224 new TaskDescription(description.label, /* icon= */ null, description.color));
225 } else {
226 TaskDescription taskDescription =
227 new TaskDescription(description.label, 0, description.color);
228 activity.setTaskDescription(taskDescription);
229 }
230 }
231
232 private void setSystemChromeChangeListener() {
233 // Set up a listener to notify the framework when the system ui has changed.
234 View decorView = activity.getWindow().getDecorView();
235 decorView.setOnSystemUiVisibilityChangeListener(
236 new View.OnSystemUiVisibilityChangeListener() {
237 @Override
238 public void onSystemUiVisibilityChange(int visibility) {
239 // `platformChannel.systemChromeChanged` may trigger a callback that eventually results
240 // in a call to `setSystemUiVisibility`.
241 // `setSystemUiVisibility` must not be called in the same frame as when
242 // `onSystemUiVisibilityChange` is received though.
243 //
244 // As such, post `platformChannel.systemChromeChanged` to the view handler to ensure
245 // that downstream callbacks are trigged on the next frame.
246 decorView.post(
247 () -> {
248 if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
249 // The system bars are visible. Make any desired adjustments to
250 // your UI, such as showing the action bar or other navigational
251 // controls. Another common action is to set a timer to dismiss
252 // the system bars and restore the fullscreen mode that was
253 // previously enabled.
254 platformChannel.systemChromeChanged(true);
255 } else {
256 // The system bars are NOT visible. Make any desired adjustments
257 // to your UI, such as hiding the action bar or other
258 // navigational controls.
259 platformChannel.systemChromeChanged(false);
260 }
261 });
262 }
263 });
264 }
265
266 private void setSystemChromeEnabledSystemUIMode(PlatformChannel.SystemUiMode systemUiMode) {
267 int enabledOverlays;
268
269 if (systemUiMode == PlatformChannel.SystemUiMode.LEAN_BACK) {
270 // LEAN BACK
271 // Available starting at SDK 16
272 // Should not show overlays, tap to reveal overlays, needs onChange callback
273 // When the overlays come in on tap, the app does not receive the gesture and does not know
274 // the system overlay has changed. The overlays cannot be dismissed, so adding the callback
275 // support will allow users to restore the system ui and dismiss the overlays.
276 // Not compatible with top/bottom overlays enabled.
277 enabledOverlays =
278 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
279 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
280 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
281 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
282 | View.SYSTEM_UI_FLAG_FULLSCREEN;
283 } else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE) {
284 // IMMERSIVE
285 // Available starting at 19
286 // Should not show overlays, swipe from edges to reveal overlays, needs onChange callback
287 // When the overlays come in on swipe, the app does not receive the gesture and does not know
288 // the system overlay has changed. The overlays cannot be dismissed, so adding callback
289 // support will allow users to restore the system ui and dismiss the overlays.
290 // Not compatible with top/bottom overlays enabled.
291 enabledOverlays =
292 View.SYSTEM_UI_FLAG_IMMERSIVE
293 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
294 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
295 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
296 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
297 | View.SYSTEM_UI_FLAG_FULLSCREEN;
298 } else if (systemUiMode == PlatformChannel.SystemUiMode.IMMERSIVE_STICKY) {
299 // STICKY IMMERSIVE
300 // Available starting at 19
301 // Should not show overlays, swipe from edges to reveal overlays. The app will also receive
302 // the swipe gesture. The overlays cannot be dismissed, so adding callback support will
303 // allow users to restore the system ui and dismiss the overlays.
304 // Not compatible with top/bottom overlays enabled.
305 enabledOverlays =
306 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
307 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
308 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
309 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
310 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
311 | View.SYSTEM_UI_FLAG_FULLSCREEN;
312 } else if (systemUiMode == PlatformChannel.SystemUiMode.EDGE_TO_EDGE
313 && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
314 // EDGE TO EDGE
315 // Available starting at 29
316 // SDK 29 and up will apply a translucent body scrim behind 2/3 button navigation bars
317 // to ensure contrast with buttons on the nav and status bars, unless the contrast is not
318 // enforced in the overlay styling.
319 enabledOverlays =
320 View.SYSTEM_UI_FLAG_LAYOUT_STABLE
321 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
322 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
323 } else {
324 // When none of the conditions are matched, return without updating the system UI overlays.
325 return;
326 }
327
328 mEnabledOverlays = enabledOverlays;
329 updateSystemUiOverlays();
330 }
331
332 private void setSystemChromeEnabledSystemUIOverlays(
334 // Start by assuming we want to hide all system overlays (like an immersive
335 // game).
336 int enabledOverlays =
337 DEFAULT_SYSTEM_UI
338 | View.SYSTEM_UI_FLAG_FULLSCREEN
339 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
340 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
341
342 if (overlaysToShow.size() == 0) {
343 enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
344 }
345
346 // Re-add any desired system overlays.
347 for (int i = 0; i < overlaysToShow.size(); ++i) {
348 PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i);
349 switch (overlayToShow) {
350 case TOP_OVERLAYS:
351 enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
352 break;
353 case BOTTOM_OVERLAYS:
354 enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
355 enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
356 break;
357 }
358 }
359
360 mEnabledOverlays = enabledOverlays;
361 updateSystemUiOverlays();
362 }
363
364 /**
365 * Refreshes Android's window system UI (AKA system chrome) to match Flutter's desired {@link
366 * PlatformChannel.SystemChromeStyle}.
367 *
368 * <p>Updating the system UI Overlays is accomplished by altering the decor view of the {@link
369 * Window} associated with the {@link android.app.Activity} that was provided to this {@code
370 * PlatformPlugin}.
371 */
373 activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays);
374 if (currentTheme != null) {
375 setSystemChromeSystemUIOverlayStyle(currentTheme);
376 }
377 }
378
379 private void restoreSystemChromeSystemUIOverlays() {
380 updateSystemUiOverlays();
381 }
382
383 @SuppressWarnings("deprecation")
384 private void setSystemChromeSystemUIOverlayStyle(
385 PlatformChannel.SystemChromeStyle systemChromeStyle) {
386 Window window = activity.getWindow();
387 View view = window.getDecorView();
388 WindowInsetsControllerCompat windowInsetsControllerCompat =
389 new WindowInsetsControllerCompat(window, view);
390
391 if (Build.VERSION.SDK_INT < API_LEVELS.API_30) {
392 // Flag set to specify that this window is responsible for drawing the background for the
393 // system bars. Must be set for all operations on API < 30 excluding enforcing system
394 // bar contrasts. Deprecated in API 30.
395 window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
396
397 // Flag set to dismiss any requests for translucent system bars to be provided in lieu of what
398 // is specified by systemChromeStyle. Must be set for all operations on API < 30 operations
399 // excluding enforcing system bar contrasts. Deprecated in API 30.
400 window.clearFlags(
401 WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
402 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
403 }
404
405 // SYSTEM STATUS BAR -------------------------------------------------------------------
406 // You can't change the color of the system status bar until SDK 21, and you can't change the
407 // color of the status icons until SDK 23. We only allow both starting at 23 to ensure buttons
408 // and icons can be visible when changing the background color.
409 // If transparent, SDK 29 and higher may apply a translucent scrim behind the bar to ensure
410 // proper contrast. This can be overridden with
411 // SystemChromeStyle.systemStatusBarContrastEnforced.
412 if (Build.VERSION.SDK_INT >= API_LEVELS.API_23) {
413 if (systemChromeStyle.statusBarIconBrightness != null) {
414 switch (systemChromeStyle.statusBarIconBrightness) {
415 case DARK:
416 // Dark status bar icon brightness.
417 // Light status bar appearance.
418 windowInsetsControllerCompat.setAppearanceLightStatusBars(true);
419 break;
420 case LIGHT:
421 // Light status bar icon brightness.
422 // Dark status bar appearance.
423 windowInsetsControllerCompat.setAppearanceLightStatusBars(false);
424 break;
425 }
426 }
427
428 if (systemChromeStyle.statusBarColor != null) {
429 window.setStatusBarColor(systemChromeStyle.statusBarColor);
430 }
431 }
432 // You can't override the enforced contrast for a transparent status bar until SDK 29.
433 // This overrides the translucent scrim that may be placed behind the bar on SDK 29+ to ensure
434 // contrast is appropriate when using full screen layout modes like Edge to Edge.
435 if (systemChromeStyle.systemStatusBarContrastEnforced != null
436 && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
437 window.setStatusBarContrastEnforced(systemChromeStyle.systemStatusBarContrastEnforced);
438 }
439
440 // SYSTEM NAVIGATION BAR --------------------------------------------------------------
441 // You can't change the color of the system navigation bar until SDK 21, and you can't change
442 // the color of the navigation buttons until SDK 26. We only allow both starting at 26 to
443 // ensure buttons can be visible when changing the background color.
444 // If transparent, SDK 29 and higher may apply a translucent scrim behind 2/3 button navigation
445 // bars to ensure proper contrast. This can be overridden with
446 // SystemChromeStyle.systemNavigationBarContrastEnforced.
447 if (Build.VERSION.SDK_INT >= API_LEVELS.API_26) {
448 if (systemChromeStyle.systemNavigationBarIconBrightness != null) {
449 switch (systemChromeStyle.systemNavigationBarIconBrightness) {
450 case DARK:
451 // Dark navigation bar icon brightness.
452 // Light navigation bar appearance.
453 windowInsetsControllerCompat.setAppearanceLightNavigationBars(true);
454 break;
455 case LIGHT:
456 // Light navigation bar icon brightness.
457 // Dark navigation bar appearance.
458 windowInsetsControllerCompat.setAppearanceLightNavigationBars(false);
459 break;
460 }
461 }
462
463 if (systemChromeStyle.systemNavigationBarColor != null) {
464 window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor);
465 }
466 }
467 // You can't change the color of the navigation bar divider color until SDK 28.
468 if (systemChromeStyle.systemNavigationBarDividerColor != null
469 && Build.VERSION.SDK_INT >= API_LEVELS.API_28) {
470 window.setNavigationBarDividerColor(systemChromeStyle.systemNavigationBarDividerColor);
471 }
472
473 // You can't override the enforced contrast for a transparent navigation bar until SDK 29.
474 // This overrides the translucent scrim that may be placed behind 2/3 button navigation bars on
475 // SDK 29+ to ensure contrast is appropriate when using full screen layout modes like
476 // Edge to Edge.
477 if (systemChromeStyle.systemNavigationBarContrastEnforced != null
478 && Build.VERSION.SDK_INT >= API_LEVELS.API_29) {
479 window.setNavigationBarContrastEnforced(
480 systemChromeStyle.systemNavigationBarContrastEnforced);
481 }
482
483 currentTheme = systemChromeStyle;
484 }
485
486 private void setFrameworkHandlesBack(boolean frameworkHandlesBack) {
487 if (platformPluginDelegate != null) {
488 platformPluginDelegate.setFrameworkHandlesBack(frameworkHandlesBack);
489 }
490 }
491
492 private void popSystemNavigator() {
493 if (platformPluginDelegate != null && platformPluginDelegate.popSystemNavigator()) {
494 // A custom behavior was executed by the delegate. Don't execute default behavior.
495 return;
496 }
497
498 if (activity instanceof OnBackPressedDispatcherOwner) {
499 ((OnBackPressedDispatcherOwner) activity).getOnBackPressedDispatcher().onBackPressed();
500 } else {
501 activity.finish();
502 }
503 }
504
505 private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) {
506 ClipboardManager clipboard =
507 (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
508
509 if (!clipboard.hasPrimaryClip()) return null;
510
511 CharSequence itemText = null;
512 try {
513 ClipData clip = clipboard.getPrimaryClip();
514 if (clip == null) return null;
515 if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) {
516 ClipData.Item item = clip.getItemAt(0);
517 // First, try getting clipboard data as text; no further processing
518 // required if so.
519 itemText = item.getText();
520 if (itemText == null) {
521 // Clipboard data does not contain text, so check whether or not it
522 // contains a URI to extract text from.
523 Uri itemUri = item.getUri();
524
525 if (itemUri == null) {
526 Log.w(
527 TAG, "Clipboard item contained no textual content nor a URI to retrieve it from.");
528 return null;
529 }
530
531 // Will only try to extract text from URI if it has the content scheme.
532 String uriScheme = itemUri.getScheme();
533
534 if (!uriScheme.equals("content")) {
535 Log.w(
536 TAG,
537 "Clipboard item contains a Uri with scheme '" + uriScheme + "'that is unhandled.");
538 return null;
539 }
540
541 AssetFileDescriptor assetFileDescriptor =
542 activity.getContentResolver().openTypedAssetFileDescriptor(itemUri, "text/*", null);
543
544 // Safely return clipboard data coerced into text; will return either
545 // itemText or text retrieved from its URI.
546 itemText = item.coerceToText(activity);
547 if (assetFileDescriptor != null) assetFileDescriptor.close();
548 }
549
550 return itemText;
551 }
552 } catch (SecurityException e) {
553 Log.w(
554 TAG,
555 "Attempted to get clipboard data that requires additional permission(s).\n"
556 + "See the exception details for which permission(s) are required, and consider adding them to your Android Manifest as described in:\n"
557 + "https://developer.android.com/guide/topics/permissions/overview",
558 e);
559 return null;
560 } catch (FileNotFoundException e) {
561 Log.w(TAG, "Clipboard text was unable to be received from content URI.");
562 return null;
563 } catch (IOException e) {
564 Log.w(TAG, "Failed to close AssetFileDescriptor while trying to read text from URI.", e);
565 return itemText;
566 }
567
568 return null;
569 }
570
571 private void setClipboardData(String text) {
572 ClipboardManager clipboard =
573 (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
574 ClipData clip = ClipData.newPlainText("text label?", text);
575 clipboard.setPrimaryClip(clip);
576 }
577
578 private boolean clipboardHasStrings() {
579 ClipboardManager clipboard =
580 (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE);
581 // Android 12 introduces a toast message that appears when an app reads the clipboard. To avoid
582 // unintended access, call the appropriate APIs to receive information about the current content
583 // that's on the clipboard (rather than the actual content itself).
584 if (!clipboard.hasPrimaryClip()) {
585 return false;
586 }
587 ClipDescription description = clipboard.getPrimaryClipDescription();
588 if (description == null) {
589 return false;
590 }
591 return description.hasMimeType("text/*");
592 }
593
594 private void share(@NonNull String text) {
595 Intent intent = new Intent();
596 intent.setAction(Intent.ACTION_SEND);
597 intent.setType("text/plain");
598 intent.putExtra(Intent.EXTRA_TEXT, text);
599
600 activity.startActivity(Intent.createChooser(intent, null));
601 }
602}
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition SkPath.cpp:3824
PlatformPlugin( @NonNull Activity activity, @NonNull PlatformChannel platformChannel, @Nullable PlatformPluginDelegate delegate)
void vibrateHapticFeedback( @NonNull PlatformChannel.HapticFeedbackType feedbackType)
final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler
PlatformPlugin(@NonNull Activity activity, @NonNull PlatformChannel platformChannel)
GLFWwindow * window
Definition main.cc:45
uint32_t uint32_t * format
default void setFrameworkHandlesBack(boolean frameworkHandlesBack)
std::u16string text
void Log(const char *format,...) SK_PRINTF_LIKE(1
Build(configs, env, options)
Definition build.py:232
#define TAG()