• 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.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