From 698e6a737bed44fe181357b0f0f84f4fad0d5815 Mon Sep 17 00:00:00 2001 From: justin <noborder@qq.com> Date: Wed, 22 Apr 2020 17:18:09 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=A3=E5=86=B3=E6=B3=84=E6=BC=8F=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/build.gradle | 2 +- .../flutterboost/XPlatformPlugin.java | 311 ++++++++++++++++++ .../containers/BoostFlutterActivity.java | 9 +- .../FlutterActivityAndFragmentDelegate.java | 41 ++- .../containers/FlutterFragment.java | 10 +- 5 files changed, 342 insertions(+), 31 deletions(-) create mode 100644 android/src/main/java/com/idlefish/flutterboost/XPlatformPlugin.java diff --git a/android/build.gradle b/android/build.gradle index bd2e6c7..1821bbe 100755 --- a/android/build.gradle +++ b/android/build.gradle @@ -22,7 +22,7 @@ rootProject.allprojects { apply plugin: 'com.android.library' android { - compileSdkVersion 28 + compileSdkVersion 29 buildToolsVersion '28.0.3' defaultConfig { minSdkVersion 16 diff --git a/android/src/main/java/com/idlefish/flutterboost/XPlatformPlugin.java b/android/src/main/java/com/idlefish/flutterboost/XPlatformPlugin.java new file mode 100644 index 0000000..d4702a8 --- /dev/null +++ b/android/src/main/java/com/idlefish/flutterboost/XPlatformPlugin.java @@ -0,0 +1,311 @@ +package com.idlefish.flutterboost; + +import android.app.Activity; +import android.app.ActivityManager; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.graphics.Rect; +import android.os.Build; + + +import android.view.HapticFeedbackConstants; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.Window; + +import java.util.ArrayList; +import java.util.List; + + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.systemchannels.PlatformChannel; + +public class XPlatformPlugin { + + + public static final int DEFAULT_SYSTEM_UI = View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN; + + private Activity activity; + private PlatformChannel platformChannel; + private PlatformChannel.SystemChromeStyle currentTheme; + private int mEnabledOverlays; + + private PlatformChannel.PlatformMessageHandler mPlatformMessageHandler = new PlatformChannel.PlatformMessageHandler() { + @Override + public void playSystemSound(@NonNull PlatformChannel.SoundType soundType) { + XPlatformPlugin.this.playSystemSound(soundType); + } + + @Override + public void vibrateHapticFeedback(@NonNull PlatformChannel.HapticFeedbackType feedbackType) { + XPlatformPlugin.this.vibrateHapticFeedback(feedbackType); + } + + @Override + public void setPreferredOrientations(int androidOrientation) { + setSystemChromePreferredOrientations(androidOrientation); + } + + @Override + public void setApplicationSwitcherDescription(@NonNull PlatformChannel.AppSwitcherDescription description) { + setSystemChromeApplicationSwitcherDescription(description); + } + + @Override + public void showSystemOverlays(@NonNull List<PlatformChannel.SystemUiOverlay> overlays) { + setSystemChromeEnabledSystemUIOverlays(overlays); + } + + @Override + public void restoreSystemUiOverlays() { + restoreSystemChromeSystemUIOverlays(); + } + + @Override + public void setSystemUiOverlayStyle(@NonNull PlatformChannel.SystemChromeStyle systemUiOverlayStyle) { + setSystemChromeSystemUIOverlayStyle(systemUiOverlayStyle); + } + + @Override + public void popSystemNavigator() { + XPlatformPlugin.this.popSystemNavigator(); + } + + @Override + public CharSequence getClipboardData(@Nullable PlatformChannel.ClipboardContentFormat format) { + return XPlatformPlugin.this.getClipboardData(format); + } + + @Override + public void setClipboardData(@NonNull String text) { + XPlatformPlugin.this.setClipboardData(text); + } + + @Override + public List<Rect> getSystemGestureExclusionRects() { + return XPlatformPlugin.this.getSystemGestureExclusionRects(); + } + + @Override + public void setSystemGestureExclusionRects(@NonNull ArrayList<Rect> rects) { + XPlatformPlugin.this.setSystemGestureExclusionRects(rects); + } + }; + + public XPlatformPlugin( PlatformChannel platformChannel) { + + this.platformChannel = platformChannel; + + mEnabledOverlays = DEFAULT_SYSTEM_UI; + } + + public void attachToActivity(Activity activity ){ + this.activity = activity; + this.platformChannel.setPlatformMessageHandler(mPlatformMessageHandler); + + } + /** + * Releases all resources held by this {@code PlatformPlugin}. + * <p> + * Do not invoke any methods on a {@code PlatformPlugin} after invoking this method. + */ + public void detachActivity() { + this.activity=null; + this.mPlatformMessageHandler=null; + } + + private void playSystemSound(PlatformChannel.SoundType soundType) { + if (soundType == PlatformChannel.SoundType.CLICK) { + View view = activity.getWindow().getDecorView(); + view.playSoundEffect(SoundEffectConstants.CLICK); + } + } + + private void vibrateHapticFeedback(PlatformChannel.HapticFeedbackType feedbackType) { + View view = activity.getWindow().getDecorView(); + switch (feedbackType) { + case STANDARD: + view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + break; + case LIGHT_IMPACT: + view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + break; + case MEDIUM_IMPACT: + view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + break; + case HEAVY_IMPACT: + // HapticFeedbackConstants.CONTEXT_CLICK from API level 23. + view.performHapticFeedback(6); + break; + case SELECTION_CLICK: + view.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK); + break; + } + } + + private void setSystemChromePreferredOrientations(int androidOrientation) { + activity.setRequestedOrientation(androidOrientation); + } + + @SuppressWarnings("deprecation") + private void setSystemChromeApplicationSwitcherDescription(PlatformChannel.AppSwitcherDescription description) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + return; + } + + // Linter refuses to believe we're only executing this code in API 28 unless we use distinct if blocks and + // hardcode the API 28 constant. + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P && Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) { + activity.setTaskDescription(new ActivityManager.TaskDescription(description.label, /*icon=*/ null, description.color)); + } + if (Build.VERSION.SDK_INT >= 28) { + ActivityManager.TaskDescription taskDescription = new ActivityManager.TaskDescription(description.label, 0, description.color); + activity.setTaskDescription(taskDescription); + } + } + + private void setSystemChromeEnabledSystemUIOverlays(List<PlatformChannel.SystemUiOverlay> overlaysToShow) { + // Start by assuming we want to hide all system overlays (like an immersive game). + int enabledOverlays = DEFAULT_SYSTEM_UI + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + + if (overlaysToShow.size() == 0) { + enabledOverlays |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + } + + // Re-add any desired system overlays. + for (int i = 0; i < overlaysToShow.size(); ++i) { + PlatformChannel.SystemUiOverlay overlayToShow = overlaysToShow.get(i); + switch (overlayToShow) { + case TOP_OVERLAYS: + enabledOverlays &= ~View.SYSTEM_UI_FLAG_FULLSCREEN; + break; + case BOTTOM_OVERLAYS: + enabledOverlays &= ~View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION; + enabledOverlays &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + break; + } + } + + mEnabledOverlays = enabledOverlays; + updateSystemUiOverlays(); + } + + /** + * Refreshes Android's window system UI (AKA system chrome) to match Flutter's desired + * {@link PlatformChannel.SystemChromeStyle}. + * <p> + * Updating the system UI Overlays is accomplished by altering the decor view of the + * {@link Window} associated with the {@link Activity} that was provided to this + * {@code PlatformPlugin}. + */ + public void updateSystemUiOverlays(){ + activity.getWindow().getDecorView().setSystemUiVisibility(mEnabledOverlays); + if (currentTheme != null) { + setSystemChromeSystemUIOverlayStyle(currentTheme); + } + } + + private void restoreSystemChromeSystemUIOverlays() { + updateSystemUiOverlays(); + } + + private void setSystemChromeSystemUIOverlayStyle(PlatformChannel.SystemChromeStyle systemChromeStyle) { + Window window = activity.getWindow(); + View view = window.getDecorView(); + int flags = view.getSystemUiVisibility(); + // You can change the navigation bar color (including translucent colors) + // in Android, but you can't change the color of the navigation buttons until Android O. + // LIGHT vs DARK effectively isn't supported until then. + // Build.VERSION_CODES.O + if (Build.VERSION.SDK_INT >= 26) { + if (systemChromeStyle.systemNavigationBarIconBrightness != null) { + switch (systemChromeStyle.systemNavigationBarIconBrightness) { + case DARK: + //View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + flags |= 0x10; + break; + case LIGHT: + flags &= ~0x10; + break; + } + } + if (systemChromeStyle.systemNavigationBarColor != null) { + window.setNavigationBarColor(systemChromeStyle.systemNavigationBarColor); + } + } + // Build.VERSION_CODES.M + if (Build.VERSION.SDK_INT >= 23) { + if (systemChromeStyle.statusBarIconBrightness != null) { + switch (systemChromeStyle.statusBarIconBrightness) { + case DARK: + // View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + flags |= 0x2000; + break; + case LIGHT: + flags &= ~0x2000; + break; + } + } + if (systemChromeStyle.statusBarColor != null) { + window.setStatusBarColor(systemChromeStyle.statusBarColor); + } + } + if (systemChromeStyle.systemNavigationBarDividerColor != null) { + // Not available until Android P. + // window.setNavigationBarDividerColor(systemNavigationBarDividerColor); + } + view.setSystemUiVisibility(flags); + currentTheme = systemChromeStyle; + } + + private void popSystemNavigator() { + activity.finish(); + } + + private CharSequence getClipboardData(PlatformChannel.ClipboardContentFormat format) { + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = clipboard.getPrimaryClip(); + if (clip == null) + return null; + + if (format == null || format == PlatformChannel.ClipboardContentFormat.PLAIN_TEXT) { + return clip.getItemAt(0).coerceToText(activity); + } + + return null; + } + + private void setClipboardData(String text) { + ClipboardManager clipboard = (ClipboardManager) activity.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("text label?", text); + clipboard.setPrimaryClip(clip); + } + + + private List<Rect> getSystemGestureExclusionRects() { + if (Build.VERSION.SDK_INT >= 29) { + Window window = activity.getWindow(); + View view = window.getDecorView(); + return view.getSystemGestureExclusionRects(); + } + + return null; + } + + private void setSystemGestureExclusionRects(ArrayList<Rect> rects) { + if (Build.VERSION.SDK_INT < 29) { + return; + } + + Window window = activity.getWindow(); + View view = window.getDecorView(); + view.setSystemGestureExclusionRects(rects); + } + +} diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/BoostFlutterActivity.java b/android/src/main/java/com/idlefish/flutterboost/containers/BoostFlutterActivity.java index 14e85fb..91c2773 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/BoostFlutterActivity.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/BoostFlutterActivity.java @@ -21,6 +21,7 @@ import android.view.*; import android.widget.*; import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.XFlutterView; +import com.idlefish.flutterboost.XPlatformPlugin; import io.flutter.Log; import io.flutter.embedding.android.DrawableSplashScreen; import io.flutter.embedding.android.FlutterView; @@ -439,12 +440,8 @@ public class BoostFlutterActivity extends Activity @Nullable @Override - public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) { - if (activity != null) { - return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel()); - } else { - return null; - } + public XPlatformPlugin providePlatformPlugin( @NonNull FlutterEngine flutterEngine) { + return new XPlatformPlugin( flutterEngine.getPlatformChannel()); } /** diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java index 760892e..8b919c8 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterActivityAndFragmentDelegate.java @@ -23,6 +23,7 @@ import java.util.Map; import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.Utils; import com.idlefish.flutterboost.XFlutterView; +import com.idlefish.flutterboost.XPlatformPlugin; import com.idlefish.flutterboost.interfaces.IFlutterViewContainer; import com.idlefish.flutterboost.interfaces.IOperateSyncer; import io.flutter.Log; @@ -30,6 +31,7 @@ import io.flutter.app.FlutterActivity; import io.flutter.embedding.android.*; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.platform.PlatformPlugin; @@ -39,7 +41,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer private static final String TAG = "FlutterActivityAndFragmentDelegate"; - + private static int ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE=0; @NonNull private Host host; @Nullable @@ -49,7 +51,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer @Nullable private XFlutterView flutterView; @Nullable - private PlatformPlugin platformPlugin; + private XPlatformPlugin platformPlugin; private boolean isFlutterEngineFromHost; @@ -95,7 +97,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer // TODO(mattcarroll): the PlatformPlugin needs to be reimagined because it implicitly takes // control of the entire window. This is unacceptable for non-fullscreen // use-cases. - platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine); + platformPlugin = host.providePlatformPlugin(flutterEngine); host.configureFlutterEngine(flutterEngine); @@ -129,13 +131,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer Log.v(TAG, "Creating FlutterView."); - flutterEngine.getActivityControlSurface().attachToActivity( - host.getActivity(), - host.getLifecycle() - ); - - - mSyncer = FlutterBoost.instance().containerManager().generateSyncer(this); ensureAlive(); @@ -176,13 +171,19 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer Log.v(TAG, "onResume()"); ensureAlive(); flutterEngine.getLifecycleChannel().appIsResumed(); - - flutterEngine.getActivityControlSurface().attachToActivity( - host.getActivity(), - host.getLifecycle() - ); + if(ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE==0|| + ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE!=this.host.getActivity().hashCode()){ + flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges(); + flutterEngine.getActivityControlSurface().attachToActivity( + host.getActivity(), + host.getLifecycle() + ); + ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE=this.host.getActivity().hashCode(); + } + if(platformPlugin!=null) + platformPlugin.attachToActivity( host.getActivity()); } @@ -191,7 +192,6 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer public void onPostResume() { Log.v(TAG, "onPostResume()"); ensureAlive(); -// Utils.setStatusBarLightMode(host.getActivity(), true); } @@ -230,10 +230,15 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer // Null out the platformPlugin to avoid a possible retain cycle between the plugin, this Fragment, // and this Fragment's Activity. if (platformPlugin != null) { -// platformPlugin.destroy(); + platformPlugin.detachActivity(); platformPlugin = null; } + if(ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE!=0|| + ACTIVITY_CONTROL_SURFACE_ATTACH_TO_ACTVITY_HASH_CODE==this.host.getActivity().hashCode()){ + flutterEngine.getActivityControlSurface().detachFromActivityForConfigChanges(); + } + Utils.fixInputMethodManagerLeak(host.getActivity()); } @@ -461,7 +466,7 @@ public class FlutterActivityAndFragmentDelegate implements IFlutterViewContainer * Flutter experience should control system chrome. */ @Nullable - PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine); + XPlatformPlugin providePlatformPlugin( @NonNull FlutterEngine flutterEngine); /** * Hook for the host to configure the {@link FlutterEngine} as desired. diff --git a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterFragment.java b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterFragment.java index 35c38f2..dbe0799 100644 --- a/android/src/main/java/com/idlefish/flutterboost/containers/FlutterFragment.java +++ b/android/src/main/java/com/idlefish/flutterboost/containers/FlutterFragment.java @@ -16,6 +16,7 @@ import androidx.fragment.app.FragmentActivity; import com.idlefish.flutterboost.FlutterBoost; import com.idlefish.flutterboost.Utils; import com.idlefish.flutterboost.XFlutterView; +import com.idlefish.flutterboost.XPlatformPlugin; import io.flutter.embedding.android.*; import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.FlutterShellArgs; @@ -467,12 +468,9 @@ public class FlutterFragment extends Fragment implements FlutterActivityAndFragm @Nullable @Override - public PlatformPlugin providePlatformPlugin(@Nullable Activity activity, @NonNull FlutterEngine flutterEngine) { - if (activity != null) { - return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel()); - } else { - return null; - } + public XPlatformPlugin providePlatformPlugin( @NonNull FlutterEngine flutterEngine) { + return new XPlatformPlugin(flutterEngine.getPlatformChannel()); + } /** -- 2.26.2