diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 3d02635b70dc9..6cfa3cb2b17bc 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -44329,6 +44329,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/syst ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/RestorationChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/ScribeChannel.java + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SensitiveContentChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/systemchannels/SystemChannel.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/common/ActivityLifecycleListener.java + ../../../flutter/LICENSE @@ -44353,6 +44354,7 @@ ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ImeSyn ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/InputConnectionAdaptor.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ListenableEditingState.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/ScribePlugin.java + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/view/SensitiveContentPlugin.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/SpellCheckPlugin.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextEditingDelta.java + ../../../flutter/LICENSE ORIGIN: ../../../flutter/shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java + ../../../flutter/LICENSE diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 4b6ea453c09ed..d6fb9c4208dfd 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -286,6 +286,7 @@ android_java_sources = [ "io/flutter/embedding/engine/systemchannels/ProcessTextChannel.java", "io/flutter/embedding/engine/systemchannels/RestorationChannel.java", "io/flutter/embedding/engine/systemchannels/ScribeChannel.java", + "io/flutter/embedding/engine/systemchannels/SensitiveContentChannel.java", "io/flutter/embedding/engine/systemchannels/SettingsChannel.java", "io/flutter/embedding/engine/systemchannels/SpellCheckChannel.java", "io/flutter/embedding/engine/systemchannels/SystemChannel.java", @@ -338,6 +339,7 @@ android_java_sources = [ "io/flutter/plugin/platform/VirtualDisplayController.java", "io/flutter/plugin/platform/WindowManagerHandler.java", "io/flutter/plugin/text/ProcessTextPlugin.java", + "io/flutter/plugin/view/SensitiveContentPlugin.java", "io/flutter/util/HandlerCompat.java", "io/flutter/util/PathUtils.java", "io/flutter/util/Preconditions.java", diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java index f5794335b74f4..f406a3374a7cd 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivity.java @@ -54,6 +54,7 @@ import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; import io.flutter.embedding.engine.plugins.util.GeneratedPluginRegister; import io.flutter.plugin.platform.PlatformPlugin; +import io.flutter.plugin.view.SensitiveContentPlugin; import java.util.ArrayList; import java.util.List; @@ -220,7 +221,7 @@ public class FlutterActivity extends Activity *

This ID can be used to lookup {@code FlutterView} in the Android view hierarchy. For more, * see {@link android.view.View#findViewById}. */ - public static final int FLUTTER_VIEW_ID = View.generateViewId(); + public static final int FLUTTER_VIEW_ID = 0; /** * Creates an {@link Intent} that launches a {@code FlutterActivity}, which creates a {@link @@ -1312,6 +1313,13 @@ public PlatformPlugin providePlatformPlugin( return new PlatformPlugin(getActivity(), flutterEngine.getPlatformChannel(), this); } + @Nullable + @Override + public SensitiveContentPlugin provideSensitiveContentPlugin( + @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) { + return new SensitiveContentPlugin(getActivity(), flutterEngine.getSensitiveContentChannel()); + } + /** * Hook for subclasses to easily configure a {@code FlutterEngine}. * diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java index c576eacda8c0c..5dec91176c540 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterActivityAndFragmentDelegate.java @@ -36,6 +36,7 @@ import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.plugin.platform.PlatformPlugin; +import io.flutter.plugin.view.SensitiveContentPlugin; import java.util.Arrays; import java.util.List; @@ -89,6 +90,7 @@ public interface DelegateFactory { @Nullable private FlutterEngine flutterEngine; @VisibleForTesting @Nullable FlutterView flutterView; @Nullable private PlatformPlugin platformPlugin; + @Nullable private SensitiveContentPlugin sensitiveContentPlugin; @VisibleForTesting @Nullable OnPreDrawListener activePreDrawListener; private boolean isFlutterEngineFromHost; private boolean isFlutterUiDisplayed; @@ -140,6 +142,7 @@ void release() { this.flutterEngine = null; this.flutterView = null; this.platformPlugin = null; + this.sensitiveContentPlugin = null; } /** @@ -215,6 +218,7 @@ void onAttach(@NonNull Context context) { // control of the entire window. This is unacceptable for non-fullscreen // use-cases. platformPlugin = host.providePlatformPlugin(host.getActivity(), flutterEngine); + sensitiveContentPlugin = host.provideSensitiveContentPlugin(host.getActivity(), flutterEngine); host.configureFlutterEngine(flutterEngine); isAttached = true; @@ -756,6 +760,11 @@ void onDetach() { platformPlugin = null; } + if (sensitiveContentPlugin != null) { + sensitiveContentPlugin.destroy(); + sensitiveContentPlugin = null; + } + if (host.shouldDispatchAppLifecycleState() && flutterEngine != null) { flutterEngine.getLifecycleChannel().appIsDetached(); } @@ -1184,6 +1193,14 @@ private void ensureAlive() { PlatformPlugin providePlatformPlugin( @Nullable Activity activity, @NonNull FlutterEngine flutterEngine); + /** + * Hook for host to create/provide a {@link SensitiveContentPlugin} if the associated Flutter + * experience should set content sensitivity. + */ + @Nullable + SensitiveContentPlugin provideSensitiveContentPlugin( + @Nullable Activity activity, @NonNull FlutterEngine flutterEngine); + /** * Hook for the host to configure the {@link io.flutter.embedding.engine.FlutterEngine} as * desired. diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java index e26d13e80f74a..5976ba22bd79b 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterFragment.java @@ -25,6 +25,7 @@ import io.flutter.embedding.engine.FlutterShellArgs; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.plugin.platform.PlatformPlugin; +import io.flutter.plugin.view.SensitiveContentPlugin; import java.util.ArrayList; import java.util.List; @@ -104,7 +105,8 @@ public class FlutterFragment extends Fragment *

This ID can be used to lookup {@code FlutterView} in the Android view hierarchy. For more, * see {@link android.view.View#findViewById}. */ - public static final int FLUTTER_VIEW_ID = View.generateViewId(); + // TODO(camsim99): Determine if we should involve FlutterFragment. + public static final int FLUTTER_VIEW_ID = 1; private static final String TAG = "FlutterFragment"; @@ -1501,6 +1503,17 @@ public PlatformPlugin providePlatformPlugin( } } + @Nullable + @Override + public SensitiveContentPlugin provideSensitiveContentPlugin( + @Nullable Activity activity, @NonNull FlutterEngine flutterEngine) { + if (activity != null) { + return new SensitiveContentPlugin(getActivity(), flutterEngine.getSensitiveContentChannel()); + } else { + return null; + } + } + /** * Configures a {@link io.flutter.embedding.engine.FlutterEngine} after its creation. * diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 683dfeace7937..43916b46c6a7b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -35,6 +35,7 @@ import io.flutter.embedding.engine.systemchannels.ProcessTextChannel; import io.flutter.embedding.engine.systemchannels.RestorationChannel; import io.flutter.embedding.engine.systemchannels.ScribeChannel; +import io.flutter.embedding.engine.systemchannels.SensitiveContentChannel; import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.embedding.engine.systemchannels.SpellCheckChannel; import io.flutter.embedding.engine.systemchannels.SystemChannel; @@ -102,6 +103,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater { @NonNull private final PlatformChannel platformChannel; @NonNull private final ProcessTextChannel processTextChannel; @NonNull private final ScribeChannel scribeChannel; + @NonNull private final SensitiveContentChannel sensitiveContentChannel; @NonNull private final SettingsChannel settingsChannel; @NonNull private final SpellCheckChannel spellCheckChannel; @NonNull private final SystemChannel systemChannel; @@ -340,6 +342,7 @@ public FlutterEngine( processTextChannel = new ProcessTextChannel(dartExecutor, context.getPackageManager()); restorationChannel = new RestorationChannel(dartExecutor, waitForRestorationData); scribeChannel = new ScribeChannel(dartExecutor); + sensitiveContentChannel = new SensitiveContentChannel(dartExecutor); settingsChannel = new SettingsChannel(dartExecutor); spellCheckChannel = new SpellCheckChannel(dartExecutor); systemChannel = new SystemChannel(dartExecutor); @@ -619,6 +622,12 @@ public ScribeChannel getScribeChannel() { return scribeChannel; } + /** System channel that handles setting content sensitivity. */ + @NonNull + public SensitiveContentChannel getSensitiveContentChannel() { + return sensitiveContentChannel; + } + /** System channel that sends and receives spell check requests and results. */ @NonNull public SpellCheckChannel getSpellCheckChannel() { diff --git a/shell/platform/android/io/flutter/embedding/engine/systemchannels/SensitiveContentChannel.java b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SensitiveContentChannel.java new file mode 100644 index 0000000000000..63a07adde3899 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/systemchannels/SensitiveContentChannel.java @@ -0,0 +1,84 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine.systemchannels; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.Log; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.StandardMethodCodec; +import java.util.ArrayList; + +/** + * {@link SensitiveContentChannel} is a platform channel that is used by the framework to set the + * content sensitivity of native Flutter Android {@code View}s. + */ +public class SensitiveContentChannel { + private static final String TAG = "SensitiveContentChannel"; + + public final MethodChannel channel; + private SensitiveContentMethodHandler sensitiveContentMethodHandler; + + @NonNull + public final MethodChannel.MethodCallHandler parsingMethodHandler = + new MethodChannel.MethodCallHandler() { + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result result) { + if (sensitiveContentMethodHandler == null) { + Log.v( + TAG, + "No SensitiveContentChannel registered, call not forwarded to sensitive content API."); + return; + } + String method = call.method; + Object args = call.arguments; + Log.v(TAG, "Received '" + method + "' message."); + switch (method) { + case "SensitiveContent.setContentSensitivity": + final ArrayList argumentList = (ArrayList) args; + final int flutterViewId = (int) argumentList.get(0); + final int contentSensitivityLevel = (int) argumentList.get(1); + try { + sensitiveContentMethodHandler.setContentSensitivity( + flutterViewId, contentSensitivityLevel, result); + } catch (IllegalStateException exception) { + result.error("error", exception.getMessage(), null); + } + break; + default: + result.notImplemented(); + break; + } + } + }; + + public SensitiveContentChannel(@NonNull DartExecutor dartExecutor) { + channel = + new MethodChannel(dartExecutor, "flutter/sensitivecontent", StandardMethodCodec.INSTANCE); + channel.setMethodCallHandler(parsingMethodHandler); + } + + /** + * Sets the {@link SensitiveContentMethodHandler} which receives all requests to set a particular + * content sensitivty level sent through this channel. + */ + public void setSensitiveContentMethodHandler( + @Nullable SensitiveContentMethodHandler sensitiveContentMethodHandler) { + this.sensitiveContentMethodHandler = sensitiveContentMethodHandler; + } + + public interface SensitiveContentMethodHandler { + /** + * Requests that content being marked with the requested {@code contentSensitivity} mode for the + * native Flutter Android {@code View} whose ID matches {@code flutterViewId}. + */ + void setContentSensitivity( + @NonNull int flutterViewId, + @NonNull int contentSensitivity, + @NonNull MethodChannel.Result result); + } +} diff --git a/shell/platform/android/io/flutter/plugin/view/SensitiveContentPlugin.java b/shell/platform/android/io/flutter/plugin/view/SensitiveContentPlugin.java new file mode 100644 index 0000000000000..d8ad768360a55 --- /dev/null +++ b/shell/platform/android/io/flutter/plugin/view/SensitiveContentPlugin.java @@ -0,0 +1,71 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugin.view; + +import android.app.Activity; +import android.view.View; +import androidx.annotation.NonNull; +import io.flutter.embedding.engine.systemchannels.SensitiveContentChannel; +import io.flutter.plugin.common.MethodChannel; + +/** + * {@link SensitiveContentPlugin} is the implementation of all functionality needed to set content + * sensitive on a native Flutter Android {@code View}. + * + *

The plugin handles requests for setting content sensitivity sent by the {@link + * io.flutter.embedding.engine.systemchannels.SensitiveContentChannel} via making a call to the + * relevant {@code View}. + */ +public class SensitiveContentPlugin + implements SensitiveContentChannel.SensitiveContentMethodHandler { + + private final Activity mflutterActivity; + private final SensitiveContentChannel mSensitiveContentChannel; + + public SensitiveContentPlugin( + @NonNull Activity flutterActivity, @NonNull SensitiveContentChannel sensitiveContentChannel) { + mflutterActivity = flutterActivity; + mSensitiveContentChannel = sensitiveContentChannel; + + mSensitiveContentChannel.setSensitiveContentMethodHandler(this); + } + + int i = 0; + /** + * Sets content sensitivity level of the Android {@code View} with the specified {@code + * flutterViewId} to the level specified by {@contentSensitivity}. + */ + @Override + public void setContentSensitivity( + @NonNull int flutterViewId, + @NonNull int contentSensitivity, + @NonNull MethodChannel.Result result) { + final View flutterView = mflutterActivity.findViewById(flutterViewId); + if (flutterView == null) { + result.error("error", "Requested Flutter View to set content sensitivty of not found.", null); + } + + final int currentContentSensitivity = flutterView.getContentSensitivity(); + flutterView.setContentSensitivity(contentSensitivity); + + final boolean shouldInvalidateView = + currentContentSensitivity == View.CONTENT_SENSITIVITY_SENSITIVE + && contentSensitivity != View.CONTENT_SENSITIVITY_SENSITIVE; + if (shouldInvalidateView) { + flutterView.invalidate(); + } + + result.success(null); + } + + /** + * Releases all resources held by this {@code SensitiveContentPlugin}. + * + *

Do not invoke any methods on a {@code SensitiveContentPlugin} after invoking this method. + */ + public void destroy() { + this.mSensitiveContentChannel.setSensitiveContentMethodHandler(null); + } +}