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.plugin.platform; 6 7 import android.app.Activity; 8 import android.app.ActivityManager.TaskDescription; 9 import android.content.ClipData; 10 import android.content.ClipboardManager; 11 import android.content.Context; 12 import android.os.Build; 13 import android.support.annotation.NonNull; 14 import android.support.annotation.Nullable; 15 import android.view.HapticFeedbackConstants; 16 import android.view.SoundEffectConstants; 17 import android.view.View; 18 import android.view.Window; 19 20 import java.util.List; 21 22 import io.flutter.embedding.engine.systemchannels.PlatformChannel; 23 import io.flutter.plugin.common.ActivityLifecycleListener; 24 25 /** 26 * Android implementation of the platform plugin. 27 */ 28 public class PlatformPlugin { 29 public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE 30 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; 31 32 private final Activity activity; 33 private final PlatformChannel platformChannel; 34 private PlatformChannel.SystemChromeStyle currentTheme; 35 private int mEnabledOverlays; 36 37 private final PlatformChannel.PlatformMessageHandler mPlatformMessageHandler = new PlatformChannel.PlatformMessageHandler() { 38 @Override 39 public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) { 40 PlatformPlugin.this.playSystemSound(soundType); 41 } 42 43 @Override 44 public void vibrateHapticFeedback(@NonNull PlatformChannel.HapticFeedbackType feedbackType) { 45 PlatformPlugin.this.vibrateHapticFeedback(feedbackType); 46 } 47 48 @Override 49 public void setPreferredOrientations(int androidOrientation) { 50 setSystemChromePreferredOrientations(androidOrientation); 51 } 52 53 @Override 54 public void setApplicationSwitcherDescription(@NonNull PlatformChannel.AppSwitcherDescription description) { 55 setSystemChromeApplicationSwitcherDescription(description); 56 } 57 58 @Override 59 public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) { 60 setSystemChromeEnabledSystemUIOverlays(overlays); 61 } 62 63 @Override 64 public void restoreSystemUiOverlays() { 65 restoreSystemChromeSystemUIOverlays(); 66 } 67 68 @Override 69 public void setSystemUiOverlayStyle(@NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) { 70 setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle); 71 } 72 73 @Override 74 public void popSystemNavigator() { 75 PlatformPlugin.this.popSystemNavigator(); 76 } 77 78 @Override 79 public CharSequence getClipboardData(@Nullable PlatformChannel.ClipboardContentFormat format) { 80 return PlatformPlugin.this.getClipboardData(format); 81 } 82 83 @Override 84 public void setClipboardData(@NonNull String text) { 85 PlatformPlugin.this.setClipboardData(text); 86 } 87 }; 88 PlatformPlugin(Activity activity, PlatformChannel platformChannel)89 public PlatformPlugin(Activity activity, PlatformChannel platformChannel) { 90 this.activity = activity; 91 this.platformChannel = platformChannel; 92 this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler); 93 94 mEnabledOverlays = DEFAULT_SYSTEM_UI; 95 } 96 97 /** 98 * Releases all resources held by this {@code PlatformPlugin}. 99 * <p> 100 * Do not invoke any methods on a {@code PlatformPlugin} after invoking this method. 101 */ destroy()102 public void destroy() { 103 this.platformChannel.setPlatformMessageHandler(null); 104 } 105 playSystemSound(PlatformChannel.SoundType soundType)106 private void playSystemSound(PlatformChannel.SoundType soundType) { 107 if (soundType == PlatformChannel.SoundType.CLICK) { 108 View view = activity.getWindow().getDecorView(); 109 view.playSoundEffect(SoundEffectConstants.CLICK); 110 } 111 } 112 vibrateHapticFeedback(PlatformChannel.HapticFeedbackType feedbackType)113 private void vibrateHapticFeedback(PlatformChannel.HapticFeedbackType feedbackType) { 114 View view = activity.getWindow().getDecorView(); 115 switch (feedbackType) { 116 case STANDARD: 117 view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); 118 break; 119 case LIGHT_IMPACT: 120 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 121 break; 122 case MEDIUM_IMPACT: 123 view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); 124 break; 125 case HEAVY_IMPACT: 126 // HapticFeedbackConstants.CONTEXT_CLICK from API level 23. 127 view.performHapticFeedback(6); 128 break; 129 case SELECTION_CLICK: 130 view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); 131 break; 132 } 133 } 134 setSystemChromePreferredOrientations(int androidOrientation)135 private void setSystemChromePreferredOrientations(int androidOrientation) { 136 activity.setRequestedOrientation(androidOrientation); 137 } 138 139 @SuppressWarnings("deprecation") setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSwitcherDescription description)140 private void setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSwitcherDescription description) { 141 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { 142 return; 143 } 144 145 // Linter refuses to believe we're only executing this code in API 28 unless we use distinct if blocks and 146 // hardcode the API 28 constant. 147 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { 148 activity.setTaskDescription(new TaskDescription(description.label, /*icon=*/ null, description.color)); 149 } 150 if (Build.VERSION.SDK_INT >= 28) { 151 TaskDescription taskDescription = new TaskDescription(description.label, 0, description.color); 152 activity.setTaskDescription(taskDescription); 153 } 154 } 155 setSystemChromeEnabledSystemUIOverlays(List<PlatformChannel.SystemUiOverlay> overlaysToShow)156 private void setSystemChromeEnabledSystemUIOverlays(List<PlatformChannel.SystemUiOverlay> overlaysToShow) { 157 // Start by assuming we want to hide all system overlays (like an immersive game). 158 int enabledOverlays = DEFAULT_SYSTEM_UI 159 | View.SYSTEM_UI_FLAG_FULLSCREEN 160 | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 161 | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 162 163 if (overlaysToShow.size() == 0) { 164 enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; 165 } 166 167 // Re-add any desired system overlays. 168 for (int i = 0; i < overlaysToShow.size(); ++i) { 169 PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i); 170 switch (overlayToShow) { 171 case TOP_OVERLAYS: 172 enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; 173 break; 174 case BOTTOM_OVERLAYS: 175 enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; 176 enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; 177 break; 178 } 179 } 180 181 mEnabledOverlays = enabledOverlays; 182 updateSystemUiOverlays(); 183 } 184 185 /** 186 * Refreshes Android's window system UI (AKA system chrome) to match Flutter's desired 187 * {@link PlatformChannel.SystemChromeStyle}. 188 * <p> 189 * Updating the system UI Overlays is accomplished by altering the decor view of the 190 * {@link Window} associated with the {@link Activity} that was provided to this 191 * {@code PlatformPlugin}. 192 */ updateSystemUiOverlays()193 public void updateSystemUiOverlays(){ 194 activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays); 195 if (currentTheme != null) { 196 setSystemChromeSystemUIOverlayStyle(currentTheme); 197 } 198 } 199 restoreSystemChromeSystemUIOverlays()200 private void restoreSystemChromeSystemUIOverlays() { 201 updateSystemUiOverlays(); 202 } 203 setSystemChromeSystemUIOverlayStyle(PlatformChannel.SystemChromeStyle systemChromeStyle)204 private void setSystemChromeSystemUIOverlayStyle(PlatformChannel.SystemChromeStyle systemChromeStyle) { 205 Window window = activity.getWindow(); 206 View view = window.getDecorView(); 207 int flags = view.getSystemUiVisibility(); 208 // You can change the navigation bar color (including translucent colors) 209 // in Android, but you can't change the color of the navigation buttons until Android O. 210 // LIGHT vs DARK effectively isn't supported until then. 211 // Build.VERSION_CODES.O 212 if (Build.VERSION.SDK_INT >= 26) { 213 if (systemChromeStyle.systemNavigationBarIconBrightness != null) { 214 switch (systemChromeStyle.systemNavigationBarIconBrightness) { 215 case DARK: 216 //View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 217 flags |= 0x10; 218 break; 219 case LIGHT: 220 flags &= ~0x10; 221 break; 222 } 223 } 224 if (systemChromeStyle.systemNavigationBarColor != null) { 225 window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor); 226 } 227 } 228 // Build.VERSION_CODES.M 229 if (Build.VERSION.SDK_INT >= 23) { 230 if (systemChromeStyle.statusBarIconBrightness != null) { 231 switch (systemChromeStyle.statusBarIconBrightness) { 232 case DARK: 233 // View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 234 flags |= 0x2000; 235 break; 236 case LIGHT: 237 flags &= ~0x2000; 238 break; 239 } 240 } 241 if (systemChromeStyle.statusBarColor != null) { 242 window.setStatusBarColor(systemChromeStyle.statusBarColor); 243 } 244 } 245 if (systemChromeStyle.systemNavigationBarDividerColor != null) { 246 // Not available until Android P. 247 // window.setNavigationBarDividerColor(systemNavigationBarDividerColor); 248 } 249 view.setSystemUiVisibility(flags); 250 currentTheme = systemChromeStyle; 251 } 252 popSystemNavigator()253 private void popSystemNavigator() { 254 activity.finish(); 255 } 256 getClipboardData(PlatformChannel.ClipboardContentFormat format)257 private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) { 258 ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); 259 ClipData clip = clipboard.getPrimaryClip(); 260 if (clip == null) 261 return null; 262 263 if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) { 264 return clip.getItemAt(0).coerceToText(activity); 265 } 266 267 return null; 268 } 269 setClipboardData(String text)270 private void setClipboardData(String text) { 271 ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); 272 ClipData clip = ClipData.newPlainText("text label?", text); 273 clipboard.setPrimaryClip(clip); 274 } 275 } 276