diff --git a/.ci/Dockerfile b/.ci/Dockerfile index a69f9cb67526..bae957da08eb 100644 --- a/.ci/Dockerfile +++ b/.ci/Dockerfile @@ -1,5 +1,20 @@ FROM cirrusci/flutter:stable +RUN sudo apt-get update -y + +RUN sudo apt-get install -y --no-install-recommends gnupg + +# Add repo for gcloud sdk and install it +RUN echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" | \ + sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list + +RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | \ + sudo apt-key --keyring /usr/share/keyrings/cloud.google.gpg add - + +RUN sudo apt-get update && sudo apt-get install -y google-cloud-sdk && \ + gcloud config set core/disable_usage_reporting true && \ + gcloud config set component_manager/disable_update_check true + RUN yes | sdkmanager \ "platforms;android-27" \ "build-tools;27.0.3" \ diff --git a/.cirrus.yml b/.cirrus.yml index 0caa1a226807..a3fc1382cc04 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -5,32 +5,59 @@ task: cpu: 8 memory: 16G upgrade_script: + - flutter channel stable + - flutter upgrade - flutter channel master - flutter upgrade - git fetch origin master activate_script: pub global activate flutter_plugin_tools matrix: - name: publishable - script: ./script/check_publish.sh - - name: test+format + script: + - flutter channel stable + - ./script/check_publish.sh + - name: format install_script: - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - - sudo apt-add-repository "deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-7 main" - sudo apt-get update - sudo apt-get install -y --allow-unauthenticated clang-format-7 format_script: ./script/incremental_build.sh format --travis --clang-format=clang-format-7 - test_script: ./script/incremental_build.sh test + - name: test + env: + matrix: + CHANNEL: "master" + CHANNEL: "stable" + test_script: + # TODO(jackson): Allow web plugins once supported on stable + # https://github.com/flutter/flutter/issues/42864 + - if [[ "$CHANNEL" -eq "stable" ]]; then find . | grep _web$ | xargs rm -rf; fi + - flutter channel $CHANNEL + - ./script/incremental_build.sh test - name: analyze script: ./script/incremental_build.sh analyze - name: build_all_plugins_apk - script: ./script/build_all_plugins_app.sh apk - - name: build-apks+java-test+drive-examples + script: + # TODO(jackson): Allow web plugins once supported on stable + # https://github.com/flutter/flutter/issues/42864 + - if [[ "$CHANNEL" -eq "stable" ]]; then find . | grep _web$ | xargs rm -rf; fi + - flutter channel $CHANNEL + - ./script/build_all_plugins_app.sh apk + - name: build-apks+java-test+firebase-test-lab env: matrix: PLUGIN_SHARDING: "--shardIndex 0 --shardCount 2" PLUGIN_SHARDING: "--shardIndex 1 --shardCount 2" + matrix: + CHANNEL: "master" + CHANNEL: "stable" MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] + GCLOUD_FIREBASE_TESTLAB_KEY: ENCRYPTED[07586610af1fdfc894e5969f70ef2458341b9b7e9c3b7c4225a663b4a48732b7208a4d91c3b7d45305a6b55fa2a37fc4] script: + # TODO(jackson): Allow web plugins once supported on stable + # https://github.com/flutter/flutter/issues/42864 + - if [[ "$CHANNEL" -eq "stable" ]]; then find . | grep _web$ | xargs rm -rf; fi + - flutter channel $CHANNEL # Unsetting CIRRUS_CHANGE_MESSAGE and CIRRUS_COMMIT_MESSAGE as they # might include non-ASCII characters which makes Gradle crash. # See: https://github.com/flutter/flutter/issues/24935 @@ -43,6 +70,12 @@ task: - export CIRRUS_COMMIT_MESSAGE="" - ./script/incremental_build.sh build-examples --apk - ./script/incremental_build.sh java-test # must come after apk build + - if [[ $GCLOUD_FIREBASE_TESTLAB_KEY == ENCRYPTED* ]]; then + - echo "This user does not have permission to run Firebase Test Lab tests." + - else + - echo $GCLOUD_FIREBASE_TESTLAB_KEY > ${HOME}/gcloud-service-key.json + - ./script/incremental_build.sh firebase-test-lab + - fi - export CIRRUS_CHANGE_MESSAGE=`cat /tmp/cirrus_change_message.txt` - export CIRRUS_COMMIT_MESSAGE=`cat /tmp/cirrus_commit_message.txt` @@ -53,17 +86,23 @@ task: setup_script: - pod repo update upgrade_script: + - flutter channel stable + - flutter upgrade - flutter channel master - flutter upgrade - git fetch origin master - activate_script: - - pub global activate flutter_plugin_tools + activate_script: pub global activate flutter_plugin_tools create_simulator_script: - xcrun simctl list - xcrun simctl create Flutter-iPhone com.apple.CoreSimulator.SimDeviceType.iPhone-X com.apple.CoreSimulator.SimRuntime.iOS-12-2 | xargs xcrun simctl boot matrix: - name: build_all_plugins_ipa - script: ./script/build_all_plugins_app.sh ios --no-codesign + script: + # TODO(jackson): Allow web plugins once supported on stable + # https://github.com/flutter/flutter/issues/42864 + - if [[ "$CHANNEL" -eq "stable" ]]; then find . | grep _web$ | xargs rm -rf; fi + - flutter channel $CHANNEL + - ./script/build_all_plugins_app.sh ios --no-codesign - name: lint_darwin_plugins script: ./script/lint_darwin_plugins.sh - name: build-ipas+drive-examples @@ -74,7 +113,14 @@ task: PLUGIN_SHARDING: "--shardIndex 1 --shardCount 4" PLUGIN_SHARDING: "--shardIndex 2 --shardCount 4" PLUGIN_SHARDING: "--shardIndex 3 --shardCount 4" + matrix: + CHANNEL: "master" + CHANNEL: "stable" SIMCTL_CHILD_MAPS_API_KEY: ENCRYPTED[596a9f6bca436694625ac50851dc5da6b4d34cba8025f7db5bc9465142e8cd44e15f69e3507787753accebfc4910d550] build_script: + # TODO(jackson): Allow web plugins once supported on stable + # https://github.com/flutter/flutter/issues/42864 + - if [[ "$CHANNEL" -eq "stable" ]]; then find . | grep _web$ | xargs rm -rf; fi + - flutter channel $CHANNEL - ./script/incremental_build.sh build-examples --ipa - ./script/incremental_build.sh drive-examples diff --git a/packages/android_alarm_manager/CHANGELOG.md b/packages/android_alarm_manager/CHANGELOG.md index cf70ea752d8c..1506fd304cf7 100644 --- a/packages/android_alarm_manager/CHANGELOG.md +++ b/packages/android_alarm_manager/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.4.4+2 + +* Remove AndroidX warning. + ## 0.4.4+1 * Update and migrate iOS example project. diff --git a/packages/android_alarm_manager/android/build.gradle b/packages/android_alarm_manager/android/build.gradle index 11c6be26861a..d711772c2bb8 100644 --- a/packages/android_alarm_manager/android/build.gradle +++ b/packages/android_alarm_manager/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "android_alarm_manager"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.androidalarmmanager' version '1.0-SNAPSHOT' diff --git a/packages/android_alarm_manager/pubspec.yaml b/packages/android_alarm_manager/pubspec.yaml index acdaad5e488f..6fe4ed944d55 100644 --- a/packages/android_alarm_manager/pubspec.yaml +++ b/packages/android_alarm_manager/pubspec.yaml @@ -1,7 +1,7 @@ name: android_alarm_manager description: Flutter plugin for accessing the Android AlarmManager service, and running Dart code in the background when alarms fire. -version: 0.4.4+1 +version: 0.4.4+2 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_alarm_manager diff --git a/packages/android_intent/CHANGELOG.md b/packages/android_intent/CHANGELOG.md index 26090115d466..a71b5e943ffa 100644 --- a/packages/android_intent/CHANGELOG.md +++ b/packages/android_intent/CHANGELOG.md @@ -1,3 +1,33 @@ +## 0.3.4+5 + +* Remove AndroidX warning. + +## 0.3.4+4 + +* Include lifecycle dependency as a compileOnly one on Android to resolve + potential version conflicts with other transitive libraries. + +## 0.3.4+3 + +* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. + +## 0.3.4+2 + +* Fix resolveActivity not respecting the provided componentName. + +## 0.3.4+1 + +* Fix minor lints in the Java platform code. +* Add smoke e2e tests for the V2 embedding. +* Fully migrate the example app to AndroidX. + +## 0.3.4 + +* Migrate the plugin to use the V2 Android engine embedding. This shouldn't + affect existing functionality. Plugin authors who use the V2 embedding can now + instantiate the plugin and expect that it correctly responds to app lifecycle + changes. + ## 0.3.3+3 * Define clang module for iOS. @@ -8,11 +38,11 @@ ## 0.3.3+1 -* Added "action_application_details_settings" action to open application info settings . +* Added "action_application_details_settings" action to open application info settings . ## 0.3.3 -* Added "flags" option to call intent.addFlags(int) in native. +* Added "flags" option to call intent.addFlags(int) in native. ## 0.3.2 diff --git a/packages/android_intent/android/build.gradle b/packages/android_intent/android/build.gradle index 8b21464f4b19..008b49ff7719 100644 --- a/packages/android_intent/android/build.gradle +++ b/packages/android_intent/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "android_intent"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.androidintent' version '1.0-SNAPSHOT' @@ -44,4 +31,41 @@ android { lintOptions { disable 'InvalidPackage' } + testOptions { + unitTests.includeAndroidResources = true + } +} + +dependencies { + compileOnly 'androidx.annotation:annotation:1.0.0' + testImplementation 'junit:junit:4.12' + testImplementation 'org.mockito:mockito-core:1.10.19' + testImplementation 'androidx.test:core:1.0.0' + testImplementation 'org.robolectric:robolectric:4.3' +} + +// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } } diff --git a/packages/android_intent/android/gradle.properties b/packages/android_intent/android/gradle.properties index 53ae0ae470eb..8bd86f680510 100644 --- a/packages/android_intent/android/gradle.properties +++ b/packages/android_intent/android/gradle.properties @@ -1,3 +1 @@ -android.enableJetifier=true -android.useAndroidX=true org.gradle.jvmargs=-Xmx1536M diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java index b6d3c81b1a8c..10c807979162 100644 --- a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java +++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/AndroidIntentPlugin.java @@ -1,161 +1,74 @@ -// Copyright 2017 The Chromium 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.plugins.androidintent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Settings; -import android.util.Log; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; +import androidx.annotation.NonNull; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.util.ArrayList; -import java.util.Map; -/** AndroidIntentPlugin */ -@SuppressWarnings("unchecked") -public class AndroidIntentPlugin implements MethodCallHandler { - private static final String TAG = AndroidIntentPlugin.class.getCanonicalName(); - private final Registrar mRegistrar; +/** + * Plugin implementation that uses the new {@code io.flutter.embedding} package. + * + *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. + */ +public final class AndroidIntentPlugin implements FlutterPlugin, ActivityAware { + private final IntentSender sender; + private final MethodCallHandlerImpl impl; - /** Plugin registration. */ - public static void registerWith(Registrar registrar) { - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/android_intent"); - channel.setMethodCallHandler(new AndroidIntentPlugin(registrar)); + /** + * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. + * + *

See {@code io.flutter.plugins.androidintentexample.MainActivity} for an example. + */ + public AndroidIntentPlugin() { + sender = new IntentSender(/*activity=*/ null, /*applicationContext=*/ null); + impl = new MethodCallHandlerImpl(sender); } - private AndroidIntentPlugin(Registrar registrar) { - this.mRegistrar = registrar; + /** + * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} + * package. + * + *

Calling this automatically initializes the plugin. However plugins initialized this way + * won't react to changes in activity or context, unlike {@link AndroidIntentPlugin}. + */ + public static void registerWith(Registrar registrar) { + IntentSender sender = new IntentSender(registrar.activity(), registrar.context()); + MethodCallHandlerImpl impl = new MethodCallHandlerImpl(sender); + impl.startListening(registrar.messenger()); } - private String convertAction(String action) { - switch (action) { - case "action_view": - return Intent.ACTION_VIEW; - case "action_voice": - return Intent.ACTION_VOICE_COMMAND; - case "settings": - return Settings.ACTION_SETTINGS; - case "action_location_source_settings": - return Settings.ACTION_LOCATION_SOURCE_SETTINGS; - case "action_application_details_settings": - return Settings.ACTION_APPLICATION_DETAILS_SETTINGS; - default: - return action; - } + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + sender.setApplicationContext(binding.getApplicationContext()); + sender.setActivity(null); + impl.startListening(binding.getFlutterEngine().getDartExecutor()); } - private Bundle convertArguments(Map arguments) { - Bundle bundle = new Bundle(); - for (String key : arguments.keySet()) { - Object value = arguments.get(key); - if (value instanceof Integer) { - bundle.putInt(key, (Integer) value); - } else if (value instanceof String) { - bundle.putString(key, (String) value); - } else if (value instanceof Boolean) { - bundle.putBoolean(key, (Boolean) value); - } else if (value instanceof Double) { - bundle.putDouble(key, (Double) value); - } else if (value instanceof Long) { - bundle.putLong(key, (Long) value); - } else if (value instanceof byte[]) { - bundle.putByteArray(key, (byte[]) value); - } else if (value instanceof int[]) { - bundle.putIntArray(key, (int[]) value); - } else if (value instanceof long[]) { - bundle.putLongArray(key, (long[]) value); - } else if (value instanceof double[]) { - bundle.putDoubleArray(key, (double[]) value); - } else if (isTypedArrayList(value, Integer.class)) { - bundle.putIntegerArrayList(key, (ArrayList) value); - } else if (isTypedArrayList(value, String.class)) { - bundle.putStringArrayList(key, (ArrayList) value); - } else if (isStringKeyedMap(value)) { - bundle.putBundle(key, convertArguments((Map) value)); - } else { - throw new UnsupportedOperationException("Unsupported type " + value); - } - } - return bundle; + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + sender.setApplicationContext(null); + sender.setActivity(null); + impl.stopListening(); } - private boolean isTypedArrayList(Object value, Class type) { - if (!(value instanceof ArrayList)) { - return false; - } - ArrayList list = (ArrayList) value; - for (Object o : list) { - if (!(o == null || type.isInstance(o))) { - return false; - } - } - return true; + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + sender.setActivity(binding.getActivity()); } - private boolean isStringKeyedMap(Object value) { - if (!(value instanceof Map)) { - return false; - } - Map map = (Map) value; - for (Object key : map.keySet()) { - if (!(key == null || key instanceof String)) { - return false; - } - } - return true; + @Override + public void onDetachedFromActivity() { + sender.setActivity(null); } - private Context getActiveContext() { - return (mRegistrar.activity() != null) ? mRegistrar.activity() : mRegistrar.context(); + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } @Override - public void onMethodCall(MethodCall call, Result result) { - Context context = getActiveContext(); - String action = convertAction((String) call.argument("action")); - - // Build intent - Intent intent = new Intent(action); - if (mRegistrar.activity() == null) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } - if (call.argument("flag") != null) { - intent.addFlags((Integer) call.argument("flags")); - } - if (call.argument("category") != null) { - intent.addCategory((String) call.argument("category")); - } - if (call.argument("data") != null) { - intent.setData(Uri.parse((String) call.argument("data"))); - } - if (call.argument("arguments") != null) { - intent.putExtras(convertArguments((Map) call.argument("arguments"))); - } - if (call.argument("package") != null) { - String packageName = (String) call.argument("package"); - intent.setPackage(packageName); - if (call.argument("componentName") != null) { - intent.setComponent( - new ComponentName(packageName, (String) call.argument("componentName"))); - } - if (intent.resolveActivity(context.getPackageManager()) == null) { - Log.i(TAG, "Cannot resolve explicit intent - ignoring package"); - intent.setPackage(null); - } - } - - Log.i(TAG, "Sending intent " + intent); - context.startActivity(intent); - - result.success(null); + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); } } diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java new file mode 100644 index 000000000000..d34ef1c2a734 --- /dev/null +++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/IntentSender.java @@ -0,0 +1,110 @@ +package io.flutter.plugins.androidintent; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.text.TextUtils; +import android.util.Log; +import androidx.annotation.Nullable; + +/** Forms and launches intents. */ +public final class IntentSender { + private static final String TAG = "IntentSender"; + + @Nullable private Activity activity; + @Nullable private Context applicationContext; + + /** + * Caches the given {@code activity} and {@code applicationContext} to use for sending intents + * later. + * + *

Either may be null initially, but at least {@code applicationContext} should be set before + * calling {@link #send}. + * + *

See also {@link #setActivity}, {@link #setApplicationContext}, and {@link #send}. + */ + public IntentSender(@Nullable Activity activity, @Nullable Context applicationContext) { + this.activity = activity; + this.applicationContext = applicationContext; + } + + /** + * Creates and launches an intent with the given params using the cached {@link Activity} and + * {@link Context}. + * + *

This will fail to create and send the intent if {@code applicationContext} hasn't been set + * at the time of calling. + * + *

This uses {@code activity} to start the intent whenever it's not null. Otherwise it falls + * back to {@code applicationContext} and adds {@link Intent#FLAG_ACTIVITY_NEW_TASK} to the intent + * before launching it. + * + * @param action the Intent action, such as {@code ACTION_VIEW}. + * @param flags forwarded to {@link Intent#addFlags(int)} if non-null. + * @param category forwarded to {@link Intent#addCategory(String)} if non-null. + * @param data forwarded to {@link Intent#setData(Uri)} if non-null. + * @param arguments forwarded to {@link Intent#putExtras(Bundle)} if non-null. + * @param packageName forwarded to {@link Intent#setPackage(String)} if non-null. This is forced + * to null if it can't be resolved. + * @param componentName forwarded to {@link Intent#setComponent(ComponentName)} if non-null. + */ + void send( + String action, + @Nullable Integer flags, + @Nullable String category, + @Nullable Uri data, + @Nullable Bundle arguments, + @Nullable String packageName, + @Nullable ComponentName componentName) { + if (applicationContext == null) { + Log.wtf(TAG, "Trying to send an intent before the applicationContext was initialized."); + return; + } + + Intent intent = new Intent(action); + + if (flags != null) { + intent.addFlags(flags); + } + if (!TextUtils.isEmpty(category)) { + intent.addCategory(category); + } + if (data != null) { + intent.setData(data); + } + if (arguments != null) { + intent.putExtras(arguments); + } + if (!TextUtils.isEmpty(packageName)) { + intent.setPackage(packageName); + if (componentName != null) { + intent.setComponent(componentName); + } + if (intent.resolveActivity(applicationContext.getPackageManager()) == null) { + Log.i(TAG, "Cannot resolve explicit intent - ignoring package"); + intent.setPackage(null); + } + } + + Log.v(TAG, "Sending intent " + intent); + if (activity != null) { + activity.startActivity(intent); + } else { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + applicationContext.startActivity(intent); + } + } + + /** Caches the given {@code activity} to use for {@link #send}. */ + void setActivity(@Nullable Activity activity) { + this.activity = activity; + } + + /** Caches the given {@code applicationContext} to use for {@link #send}. */ + void setApplicationContext(@Nullable Context applicationContext) { + this.applicationContext = applicationContext; + } +} diff --git a/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..c9c55791adc9 --- /dev/null +++ b/packages/android_intent/android/src/main/java/io/flutter/plugins/androidintent/MethodCallHandlerImpl.java @@ -0,0 +1,172 @@ +package io.flutter.plugins.androidintent; + +import android.content.ComponentName; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import java.util.ArrayList; +import java.util.Map; + +/** Forwards incoming {@link MethodCall}s to {@link IntentSender#send}. */ +public final class MethodCallHandlerImpl implements MethodCallHandler { + private static final String TAG = "MethodCallHandlerImpl"; + private final IntentSender sender; + @Nullable private MethodChannel methodChannel; + + /** + * Uses the given {@code sender} for all incoming calls. + * + *

This assumes that the sender's context and activity state are managed elsewhere and + * correctly initialized before being sent here. + */ + MethodCallHandlerImpl(IntentSender sender) { + this.sender = sender; + } + + /** + * Registers this instance as a method call handler on the given {@code messenger}. + * + *

Stops any previously started and unstopped calls. + * + *

This should be cleaned with {@link #stopListening} once the messenger is disposed of. + */ + void startListening(BinaryMessenger messenger) { + if (methodChannel != null) { + Log.wtf(TAG, "Setting a method call handler before the last was disposed."); + stopListening(); + } + + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/android_intent"); + methodChannel.setMethodCallHandler(this); + } + + /** + * Clears this instance from listening to method calls. + * + *

Does nothing is {@link #startListening} hasn't been called, or if we're already stopped. + */ + void stopListening() { + if (methodChannel == null) { + Log.d(TAG, "Tried to stop listening when no methodChannel had been initialized."); + return; + } + + methodChannel.setMethodCallHandler(null); + methodChannel = null; + } + + /** + * Parses the incoming call and forwards it to the cached {@link IntentSender}. + * + *

Always calls {@code result#success}. + */ + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { + String action = convertAction((String) call.argument("action")); + Integer flags = call.argument("flags"); + String category = call.argument("category"); + Uri data = call.argument("data") != null ? Uri.parse((String) call.argument("data")) : null; + Bundle arguments = convertArguments((Map) call.argument("arguments")); + String packageName = call.argument("package"); + ComponentName componentName = + (!TextUtils.isEmpty(packageName) + && !TextUtils.isEmpty((String) call.argument("componentName"))) + ? new ComponentName(packageName, (String) call.argument("componentName")) + : null; + + sender.send(action, flags, category, data, arguments, packageName, componentName); + + result.success(null); + } + + private static String convertAction(String action) { + switch (action) { + case "action_view": + return Intent.ACTION_VIEW; + case "action_voice": + return Intent.ACTION_VOICE_COMMAND; + case "settings": + return Settings.ACTION_SETTINGS; + case "action_location_source_settings": + return Settings.ACTION_LOCATION_SOURCE_SETTINGS; + case "action_application_details_settings": + return Settings.ACTION_APPLICATION_DETAILS_SETTINGS; + default: + return action; + } + } + + private static Bundle convertArguments(Map arguments) { + Bundle bundle = new Bundle(); + if (arguments == null) { + return bundle; + } + for (String key : arguments.keySet()) { + Object value = arguments.get(key); + if (value instanceof Integer) { + bundle.putInt(key, (Integer) value); + } else if (value instanceof String) { + bundle.putString(key, (String) value); + } else if (value instanceof Boolean) { + bundle.putBoolean(key, (Boolean) value); + } else if (value instanceof Double) { + bundle.putDouble(key, (Double) value); + } else if (value instanceof Long) { + bundle.putLong(key, (Long) value); + } else if (value instanceof byte[]) { + bundle.putByteArray(key, (byte[]) value); + } else if (value instanceof int[]) { + bundle.putIntArray(key, (int[]) value); + } else if (value instanceof long[]) { + bundle.putLongArray(key, (long[]) value); + } else if (value instanceof double[]) { + bundle.putDoubleArray(key, (double[]) value); + } else if (isTypedArrayList(value, Integer.class)) { + bundle.putIntegerArrayList(key, (ArrayList) value); + } else if (isTypedArrayList(value, String.class)) { + bundle.putStringArrayList(key, (ArrayList) value); + } else if (isStringKeyedMap(value)) { + bundle.putBundle(key, convertArguments((Map) value)); + } else { + throw new UnsupportedOperationException("Unsupported type " + value); + } + } + return bundle; + } + + private static boolean isTypedArrayList(Object value, Class type) { + if (!(value instanceof ArrayList)) { + return false; + } + ArrayList list = (ArrayList) value; + for (Object o : list) { + if (!(o == null || type.isInstance(o))) { + return false; + } + } + return true; + } + + private static boolean isStringKeyedMap(Object value) { + if (!(value instanceof Map)) { + return false; + } + Map map = (Map) value; + for (Object key : map.keySet()) { + if (!(key == null || key instanceof String)) { + return false; + } + } + return true; + } +} diff --git a/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java new file mode 100644 index 000000000000..de02d548f47f --- /dev/null +++ b/packages/android_intent/android/src/test/java/io/flutter/plugins/androidintent/MethodCallHandlerImplTest.java @@ -0,0 +1,223 @@ +package io.flutter.plugins.androidintent; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.robolectric.Shadows.shadowOf; + +import android.app.Application; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import androidx.test.core.app.ApplicationProvider; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.BinaryMessenger.BinaryMessageHandler; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel.Result; +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.shadows.ShadowPackageManager; + +@RunWith(RobolectricTestRunner.class) +public class MethodCallHandlerImplTest { + private static final String CHANNEL_NAME = "plugins.flutter.io/android_intent"; + private Context context; + private IntentSender sender; + private MethodCallHandlerImpl methodCallHandler; + + @Before + public void setUp() { + context = ApplicationProvider.getApplicationContext(); + sender = new IntentSender(null, null); + methodCallHandler = new MethodCallHandlerImpl(sender); + } + + @Test + public void startListening_registersChannel() { + BinaryMessenger messenger = mock(BinaryMessenger.class); + + methodCallHandler.startListening(messenger); + + verify(messenger, times(1)) + .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class)); + } + + @Test + public void startListening_unregistersExistingChannel() { + BinaryMessenger firstMessenger = mock(BinaryMessenger.class); + BinaryMessenger secondMessenger = mock(BinaryMessenger.class); + methodCallHandler.startListening(firstMessenger); + + methodCallHandler.startListening(secondMessenger); + + // Unregisters the first and then registers the second. + verify(firstMessenger, times(1)).setMessageHandler(CHANNEL_NAME, null); + verify(secondMessenger, times(1)) + .setMessageHandler(eq(CHANNEL_NAME), any(BinaryMessageHandler.class)); + } + + @Test + public void stopListening_unregistersExistingChannel() { + BinaryMessenger messenger = mock(BinaryMessenger.class); + methodCallHandler.startListening(messenger); + + methodCallHandler.stopListening(); + + verify(messenger, times(1)).setMessageHandler(CHANNEL_NAME, null); + } + + @Test + public void stopListening_doesNothingWhenUnset() { + BinaryMessenger messenger = mock(BinaryMessenger.class); + + methodCallHandler.stopListening(); + + verify(messenger, never()).setMessageHandler(CHANNEL_NAME, null); + } + + @Test + public void onMethodCall_doesNothingWhenContextIsNull() { + Result result = mock(Result.class); + Map args = new HashMap<>(); + args.put("action", "foo"); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + // No matter what, should always succeed. + verify(result, times(1)).success(null); + assertNull(shadowOf((Application) context).getNextStartedActivity()); + } + + @Test + public void onMethodCall_setsAction() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + args.put("action", "foo"); + Result result = mock(Result.class); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertEquals("foo", intent.getAction()); + } + + @Test + public void onMethodCall_setsNewTaskFlagWithApplicationContext() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + args.put("action", "foo"); + Result result = mock(Result.class); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, intent.getFlags()); + } + + @Test + public void onMethodCall_addsFlags() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + args.put("action", "foo"); + Integer requestFlags = Intent.FLAG_FROM_BACKGROUND; + args.put("flags", requestFlags); + Result result = mock(Result.class); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK | requestFlags, intent.getFlags()); + } + + @Test + public void onMethodCall_addsCategory() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + args.put("action", "foo"); + String category = "bar"; + args.put("category", category); + Result result = mock(Result.class); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertTrue(intent.getCategories().contains(category)); + } + + @Test + public void onMethodCall_setsData() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + args.put("action", "foo"); + Uri data = Uri.parse("http://flutter.dev"); + args.put("data", data.toString()); + Result result = mock(Result.class); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertEquals(data, intent.getData()); + } + + @Test + public void onMethodCall_clearsInvalidPackageNames() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + args.put("action", "foo"); + args.put("packageName", "invalid"); + Result result = mock(Result.class); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertNull(intent.getPackage()); + } + + @Test + public void onMethodCall_setsComponentName() { + sender.setApplicationContext(context); + Map args = new HashMap<>(); + ComponentName expectedComponent = + new ComponentName("io.flutter.plugins.androidintent", "MainActivity"); + args.put("action", "foo"); + args.put("package", expectedComponent.getPackageName()); + args.put("componentName", expectedComponent.getClassName()); + Result result = mock(Result.class); + ShadowPackageManager shadowPm = + shadowOf(ApplicationProvider.getApplicationContext().getPackageManager()); + shadowPm.addActivityIfNotPresent(expectedComponent); + + methodCallHandler.onMethodCall(new MethodCall("launch", args), result); + + verify(result, times(1)).success(null); + Intent intent = shadowOf((Application) context).getNextStartedActivity(); + assertNotNull(intent); + assertNotNull(intent.getComponent()); + assertEquals(expectedComponent.getPackageName(), intent.getPackage()); + assertEquals(expectedComponent.flattenToString(), intent.getComponent().flattenToString()); + } +} diff --git a/packages/android_intent/example/android/app/gradle.properties b/packages/android_intent/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/android_intent/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/android_intent/example/android/app/src/androidTestDebug/java/io/flutter/plugins/androidintentexample/EmbeddingV1ActivityTest.java b/packages/android_intent/example/android/app/src/androidTestDebug/java/io/flutter/plugins/androidintentexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..db000f0f995c --- /dev/null +++ b/packages/android_intent/example/android/app/src/androidTestDebug/java/io/flutter/plugins/androidintentexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.androidintentexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/instrumentation_adapter/example/android/app/src/androidTest/java/com/example/instrumentation_adapter_example/MainActivityTest.java b/packages/android_intent/example/android/app/src/androidTestDebug/java/io/flutter/plugins/androidintentexample/MainActivityTest.java similarity index 69% rename from packages/instrumentation_adapter/example/android/app/src/androidTest/java/com/example/instrumentation_adapter_example/MainActivityTest.java rename to packages/android_intent/example/android/app/src/androidTestDebug/java/io/flutter/plugins/androidintentexample/MainActivityTest.java index bb489bf57942..d390dcd74997 100644 --- a/packages/instrumentation_adapter/example/android/app/src/androidTest/java/com/example/instrumentation_adapter_example/MainActivityTest.java +++ b/packages/android_intent/example/android/app/src/androidTestDebug/java/io/flutter/plugins/androidintentexample/MainActivityTest.java @@ -1,7 +1,7 @@ -package com.example.instrumentation_adapter_example; +package io.flutter.plugins.androidintentexample; import androidx.test.rule.ActivityTestRule; -import dev.flutter.plugins.instrumentationadapter.FlutterRunner; +import dev.flutter.plugins.e2e.FlutterRunner; import org.junit.Rule; import org.junit.runner.RunWith; diff --git a/packages/android_intent/example/android/app/src/main/AndroidManifest.xml b/packages/android_intent/example/android/app/src/main/AndroidManifest.xml index ce2fbe4a64a8..7d6fcd44834f 100644 --- a/packages/android_intent/example/android/app/src/main/AndroidManifest.xml +++ b/packages/android_intent/example/android/app/src/main/AndroidManifest.xml @@ -1,29 +1,41 @@ + package="io.flutter.plugins.androidintentexample"> - - - + + + + + + + + + + + - - - - - - - - - + + diff --git a/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/EmbeddingV1Activity.java b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..95dc41a02ef7 --- /dev/null +++ b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/EmbeddingV1Activity.java @@ -0,0 +1,17 @@ +// Copyright 2017 The Chromium 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.plugins.androidintentexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java index 4af83acdf1cb..56e0bab207d4 100644 --- a/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java +++ b/packages/android_intent/example/android/app/src/main/java/io/flutter/plugins/androidintentexample/MainActivity.java @@ -1,17 +1,12 @@ -// Copyright 2017 The Chromium 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.plugins.androidintentexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.androidintent.AndroidIntentPlugin; public class MainActivity extends FlutterActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new AndroidIntentPlugin()); } } diff --git a/packages/android_intent/example/android/gradle.properties b/packages/android_intent/example/android/gradle.properties index 8bd86f680510..d2032bce8be6 100644 --- a/packages/android_intent/example/android/gradle.properties +++ b/packages/android_intent/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.enableJetifier=true +android.useAndroidX=true +android.enableR8=true diff --git a/packages/android_intent/example/pubspec.yaml b/packages/android_intent/example/pubspec.yaml index 79c1cea0a8cd..172daa446b41 100644 --- a/packages/android_intent/example/pubspec.yaml +++ b/packages/android_intent/example/pubspec.yaml @@ -7,6 +7,14 @@ dependencies: android_intent: path: ../ +dev_dependencies: + e2e: "^0.2.1" + flutter_driver: + sdk: flutter + # The following section is specific to Flutter. flutter: uses-material-design: true + +environment: + flutter: ">=1.9.1+hotfix.2 <2.0.0" \ No newline at end of file diff --git a/packages/android_intent/example/test_driver/android_intent_e2e.dart b/packages/android_intent/example/test_driver/android_intent_e2e.dart new file mode 100644 index 000000000000..8df8146f52be --- /dev/null +++ b/packages/android_intent/example/test_driver/android_intent_e2e.dart @@ -0,0 +1,53 @@ +import 'dart:io'; + +import 'package:android_intent_example/main.dart'; +import 'package:e2e/e2e.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../lib/android_intent.dart'; + +/// This is a smoke test that verifies that the example app builds and loads. +/// Because this plugin works by launching Android platform UIs it's not +/// possible to meaningfully test it through its Dart interface currently. There +/// are more useful unit tests for the platform logic under android/src/test/. +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + testWidgets('Embedding example app loads', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that the new embedding example app builds + if (Platform.isAndroid) { + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && widget.data.startsWith('Tap here'), + ), + findsNWidgets(2), + ); + } else { + expect( + find.byWidgetPredicate( + (Widget widget) => + widget is Text && + widget.data.startsWith('This plugin only works with Android'), + ), + findsOneWidget, + ); + } + }); + + testWidgets('#launch throws when no Activity is found', + (WidgetTester tester) async { + // We can't test that any of this is really working, this is mostly just + // checking that the plugin API is registered. Only works on Android. + const AndroidIntent intent = + AndroidIntent(action: 'LAUNCH', package: 'foobar'); + await expectLater(() async => await intent.launch(), throwsA((Exception e) { + return e is PlatformException && + e.message.contains('No Activity found to handle Intent'); + })); + }, skip: !Platform.isAndroid); +} diff --git a/packages/instrumentation_adapter/example/test_driver/widget_test.dart b/packages/android_intent/example/test_driver/android_intent_e2e_test.dart similarity index 55% rename from packages/instrumentation_adapter/example/test_driver/widget_test.dart rename to packages/android_intent/example/test_driver/android_intent_e2e_test.dart index 88e53d1c1f05..4f38746ce76c 100644 --- a/packages/instrumentation_adapter/example/test_driver/widget_test.dart +++ b/packages/android_intent/example/test_driver/android_intent_e2e_test.dart @@ -1,9 +1,12 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); driver.close(); + exit(result == 'pass' ? 0 : 1); } diff --git a/packages/android_intent/pubspec.yaml b/packages/android_intent/pubspec.yaml index 2bdf002f3ea8..b0248ad2a5cd 100644 --- a/packages/android_intent/pubspec.yaml +++ b/packages/android_intent/pubspec.yaml @@ -2,7 +2,7 @@ name: android_intent description: Flutter plugin for launching Android Intents. Not supported on iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/android_intent -version: 0.3.3+3 +version: 0.3.4+5 flutter: plugin: @@ -22,4 +22,4 @@ dev_dependencies: sdk: flutter environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.2.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0" diff --git a/packages/battery/CHANGELOG.md b/packages/battery/CHANGELOG.md index 6777b98c4ab5..c6381ec0282e 100644 --- a/packages/battery/CHANGELOG.md +++ b/packages/battery/CHANGELOG.md @@ -1,3 +1,24 @@ +## 0.3.1+3 + +* Remove AndroidX warning. + +## 0.3.1+2 + +* Include lifecycle dependency as a compileOnly one on Android to resolve + potential version conflicts with other transitive libraries. + +## 0.3.1+1 + +* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. + +## 0.3.1 + +* Support the v2 Android embedder. + +## 0.3.0+6 + +* Define clang module for iOS. + ## 0.3.0+5 * Fix Gradle version. diff --git a/packages/battery/android/build.gradle b/packages/battery/android/build.gradle index ed302d2969e2..ecd2e1cbb2c8 100644 --- a/packages/battery/android/build.gradle +++ b/packages/battery/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "battery"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.battery' version '1.0-SNAPSHOT' @@ -45,3 +32,29 @@ android { disable 'InvalidPackage' } } + +// TODO(amirh): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} diff --git a/packages/battery/android/src/main/java/io/flutter/plugins/battery/BatteryPlugin.java b/packages/battery/android/src/main/java/io/flutter/plugins/battery/BatteryPlugin.java index b6d3e799e5c7..1bf3c249552d 100644 --- a/packages/battery/android/src/main/java/io/flutter/plugins/battery/BatteryPlugin.java +++ b/packages/battery/android/src/main/java/io/flutter/plugins/battery/BatteryPlugin.java @@ -12,6 +12,8 @@ import android.os.BatteryManager; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; import io.flutter.plugin.common.EventChannel.EventSink; import io.flutter.plugin.common.EventChannel.StreamHandler; @@ -22,25 +24,41 @@ import io.flutter.plugin.common.PluginRegistry; /** BatteryPlugin */ -public class BatteryPlugin implements MethodCallHandler, StreamHandler { +public class BatteryPlugin implements MethodCallHandler, StreamHandler, FlutterPlugin { + + private Context applicationContext; + private BroadcastReceiver chargingStateChangeReceiver; + private MethodChannel methodChannel; + private EventChannel eventChannel; /** Plugin registration. */ public static void registerWith(PluginRegistry.Registrar registrar) { - final MethodChannel methodChannel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/battery"); - final EventChannel eventChannel = - new EventChannel(registrar.messenger(), "plugins.flutter.io/charging"); - final BatteryPlugin instance = new BatteryPlugin(registrar); - eventChannel.setStreamHandler(instance); - methodChannel.setMethodCallHandler(instance); + final BatteryPlugin instance = new BatteryPlugin(); + instance.onAttachedToEngine(registrar.context(), registrar.messenger()); } - BatteryPlugin(PluginRegistry.Registrar registrar) { - this.registrar = registrar; + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + onAttachedToEngine( + binding.getApplicationContext(), binding.getFlutterEngine().getDartExecutor()); } - private final PluginRegistry.Registrar registrar; - private BroadcastReceiver chargingStateChangeReceiver; + private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { + this.applicationContext = applicationContext; + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/battery"); + eventChannel = new EventChannel(messenger, "plugins.flutter.io/charging"); + eventChannel.setStreamHandler(this); + methodChannel.setMethodCallHandler(this); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + applicationContext = null; + methodChannel.setMethodCallHandler(null); + methodChannel = null; + eventChannel.setStreamHandler(null); + eventChannel = null; + } @Override public void onMethodCall(MethodCall call, Result result) { @@ -60,28 +78,25 @@ public void onMethodCall(MethodCall call, Result result) { @Override public void onListen(Object arguments, EventSink events) { chargingStateChangeReceiver = createChargingStateChangeReceiver(events); - registrar - .context() - .registerReceiver( - chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + applicationContext.registerReceiver( + chargingStateChangeReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); } @Override public void onCancel(Object arguments) { - registrar.context().unregisterReceiver(chargingStateChangeReceiver); + applicationContext.unregisterReceiver(chargingStateChangeReceiver); chargingStateChangeReceiver = null; } private int getBatteryLevel() { int batteryLevel = -1; - Context context = registrar.context(); if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { BatteryManager batteryManager = - (BatteryManager) context.getSystemService(context.BATTERY_SERVICE); + (BatteryManager) applicationContext.getSystemService(applicationContext.BATTERY_SERVICE); batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); } else { Intent intent = - new ContextWrapper(context) + new ContextWrapper(applicationContext) .registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) diff --git a/packages/battery/example/android/app/gradle.properties b/packages/battery/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/battery/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/battery/example/android/app/src/androidTest/java/io/flutter/plugins/battery/EmbedderV1ActivityTest.java b/packages/battery/example/android/app/src/androidTest/java/io/flutter/plugins/battery/EmbedderV1ActivityTest.java new file mode 100644 index 000000000000..ef6e5d9fe246 --- /dev/null +++ b/packages/battery/example/android/app/src/androidTest/java/io/flutter/plugins/battery/EmbedderV1ActivityTest.java @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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.plugins.batteryexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbedderV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbedderV1Activity.class); +} diff --git a/packages/battery/example/android/app/src/androidTest/java/io/flutter/plugins/battery/MainActivityTest.java b/packages/battery/example/android/app/src/androidTest/java/io/flutter/plugins/battery/MainActivityTest.java new file mode 100644 index 000000000000..16165ad2e66f --- /dev/null +++ b/packages/battery/example/android/app/src/androidTest/java/io/flutter/plugins/battery/MainActivityTest.java @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium 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.plugins.batteryexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/battery/example/android/app/src/main/AndroidManifest.xml b/packages/battery/example/android/app/src/main/AndroidManifest.xml index 6b07de4e5d3d..e76af9c8dbfe 100644 --- a/packages/battery/example/android/app/src/main/AndroidManifest.xml +++ b/packages/battery/example/android/app/src/main/AndroidManifest.xml @@ -15,5 +15,12 @@ + + diff --git a/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/EmbedderV1Activity.java b/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/EmbedderV1Activity.java new file mode 100644 index 000000000000..f04a2e2ca2f9 --- /dev/null +++ b/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/EmbedderV1Activity.java @@ -0,0 +1,17 @@ +// Copyright 2017 The Chromium 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.plugins.batteryexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbedderV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/MainActivity.java b/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/MainActivity.java index 320226f9b6d8..26ae8ecc2091 100644 --- a/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/MainActivity.java +++ b/packages/battery/example/android/app/src/main/java/io/flutter/plugins/batteryexample/MainActivity.java @@ -1,17 +1,16 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2019 The Chromium 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.plugins.batteryexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.battery.BatteryPlugin; public class MainActivity extends FlutterActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new BatteryPlugin()); } } diff --git a/packages/battery/example/android/gradle.properties b/packages/battery/example/android/gradle.properties index 8bd86f680510..38c8d4544ff1 100644 --- a/packages/battery/example/android/gradle.properties +++ b/packages/battery/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/battery/example/pubspec.yaml b/packages/battery/example/pubspec.yaml index 1fde3d25dd2d..8092cd303647 100644 --- a/packages/battery/example/pubspec.yaml +++ b/packages/battery/example/pubspec.yaml @@ -7,5 +7,10 @@ dependencies: battery: path: ../ +dev_dependencies: + flutter_driver: + sdk: flutter + e2e: ^0.2.1 + flutter: uses-material-design: true diff --git a/packages/battery/ios/battery.podspec b/packages/battery/ios/battery.podspec index fc58888b7bf8..86db7c2b9dec 100644 --- a/packages/battery/ios/battery.podspec +++ b/packages/battery/ios/battery.podspec @@ -15,6 +15,7 @@ Flutter plugin for accessing information about the battery. s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - - s.ios.deployment_target = '8.0' + + s.platform = :ios, '8.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } end diff --git a/packages/battery/pubspec.yaml b/packages/battery/pubspec.yaml index 56a70b18f552..6c51deb0678b 100644 --- a/packages/battery/pubspec.yaml +++ b/packages/battery/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for accessing information about the battery state (full, charging, discharging) on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/battery -version: 0.3.0+5 +version: 0.3.1+3 flutter: plugin: @@ -22,7 +22,8 @@ dev_dependencies: mockito: 3.0.0 flutter_test: sdk: flutter + e2e: ^0.2.1 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.2.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0" diff --git a/packages/battery/test/battery_e2e.dart b/packages/battery/test/battery_e2e.dart new file mode 100644 index 000000000000..6ffc7e6541fb --- /dev/null +++ b/packages/battery/test/battery_e2e.dart @@ -0,0 +1,17 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:battery/battery.dart'; +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('Can get battery level', (WidgetTester tester) async { + final Battery battery = Battery(); + final int batteryLevel = await battery.batteryLevel; + expect(batteryLevel, isNotNull); + }); +} diff --git a/packages/camera/CHANGELOG.md b/packages/camera/CHANGELOG.md index 2fe0e44fa6c2..8a3f248433eb 100644 --- a/packages/camera/CHANGELOG.md +++ b/packages/camera/CHANGELOG.md @@ -1,3 +1,25 @@ +## 0.5.6+4 + +* Android: Use CameraDevice.TEMPLATE_RECORD to improve image streaming. + +## 0.5.6+3 + +* Remove AndroidX warning. + +## 0.5.6+2 + +* Include lifecycle dependency as a compileOnly one on Android to resolve + potential version conflicts with other transitive libraries. + +## 0.5.6+1 + +* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. + +## 0.5.6 + +* Add support for the v2 Android embedding. This shouldn't affect existing + functionality. + ## 0.5.5+1 * Fix event type check diff --git a/packages/camera/android/build.gradle b/packages/camera/android/build.gradle index ab2fc8fd89f0..d5d86805074d 100644 --- a/packages/camera/android/build.gradle +++ b/packages/camera/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "camera"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.camera' version '1.0-SNAPSHOT' @@ -60,3 +47,29 @@ android { dependencies { testImplementation 'junit:junit:4.12' } + +// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java index 754a157a8b71..0fcda278d836 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/Camera.java @@ -426,7 +426,7 @@ public void startPreview() throws CameraAccessException { public void startPreviewWithImageStream(EventChannel imageStreamChannel) throws CameraAccessException { - createCaptureSession(CameraDevice.TEMPLATE_STILL_CAPTURE, imageStreamReader.getSurface()); + createCaptureSession(CameraDevice.TEMPLATE_RECORD, imageStreamReader.getSurface()); imageStreamChannel.setStreamHandler( new EventChannel.StreamHandler() { diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java index e45fb1e5a594..3c86ce0d9816 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPermissions.java @@ -7,20 +7,30 @@ import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import io.flutter.plugin.common.PluginRegistry; -import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener; + +final class CameraPermissions { + interface PermissionsRegistry { + void addListener(RequestPermissionsResultListener handler); + } + + interface ResultCallback { + void onResult(String errorCode, String errorDescription); + } -public class CameraPermissions { private static final int CAMERA_REQUEST_ID = 9796; private boolean ongoing = false; - public void requestPermissions( - Registrar registrar, boolean enableAudio, ResultCallback callback) { + void requestPermissions( + Activity activity, + PermissionsRegistry permissionsRegistry, + boolean enableAudio, + ResultCallback callback) { if (ongoing) { callback.onResult("cameraPermission", "Camera permission request ongoing"); } - Activity activity = registrar.activity(); if (!hasCameraPermission(activity) || (enableAudio && !hasAudioPermission(activity))) { - registrar.addRequestPermissionsResultListener( + permissionsRegistry.addListener( new CameraRequestPermissionsListener( (String errorCode, String errorDescription) -> { ongoing = false; @@ -51,6 +61,7 @@ private boolean hasAudioPermission(Activity activity) { private static class CameraRequestPermissionsListener implements PluginRegistry.RequestPermissionsResultListener { + final ResultCallback callback; private CameraRequestPermissionsListener(ResultCallback callback) { @@ -73,8 +84,4 @@ public boolean onRequestPermissionsResult(int id, String[] permissions, int[] gr return false; } } - - interface ResultCallback { - void onResult(String errorCode, String errorDescription); - } } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java index b504f039e326..9bd34e17aa02 100644 --- a/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/CameraPlugin.java @@ -4,172 +4,108 @@ package io.flutter.plugins.camera; -import android.hardware.camera2.CameraAccessException; +import android.app.Activity; import android.os.Build; import androidx.annotation.NonNull; -import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry.Registrar; -import io.flutter.view.FlutterView; +import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; import io.flutter.view.TextureRegistry; -public class CameraPlugin implements MethodCallHandler { +/** + * Platform implementation of the camera_plugin. + * + *

Instantiate this in an add to app scenario to gracefully handle activity and context changes. + * See {@code io.flutter.plugins.camera.MainActivity} for an example. + * + *

Call {@link #registerWith(Registrar)} to register an implementation of this that uses the + * stable {@code io.flutter.plugin.common} package. + */ +public final class CameraPlugin implements FlutterPlugin, ActivityAware { - private final CameraPermissions cameraPermissions = new CameraPermissions(); - private final FlutterView view; - private final Registrar registrar; - private final EventChannel imageStreamChannel; - private Camera camera; + private static final String TAG = "CameraPlugin"; + private @Nullable FlutterPluginBinding flutterPluginBinding; + private @Nullable MethodCallHandlerImpl methodCallHandler; - private CameraPlugin(Registrar registrar) { - this.registrar = registrar; - this.view = registrar.view(); - this.imageStreamChannel = - new EventChannel(registrar.messenger(), "plugins.flutter.io/camera/imageStream"); - } + /** + * Initialize this within the {@code #configureFlutterEngine} of a Flutter activity or fragment. + * + *

See {@code io.flutter.plugins.camera.MainActivity} for an example. + */ + public CameraPlugin() {} + /** + * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} + * package. + * + *

Calling this automatically initializes the plugin. However plugins initialized this way + * won't react to changes in activity or context, unlike {@link CameraPlugin}. + */ public static void registerWith(Registrar registrar) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - // When a background flutter view tries to register the plugin, the registrar has no activity. - // We stop the registration process as this plugin is foreground only. Also, if the sdk is - // less than 21 (min sdk for Camera2) we don't register the plugin. - return; - } + CameraPlugin plugin = new CameraPlugin(); + plugin.maybeStartListening( + registrar.activity(), + registrar.messenger(), + registrar::addRequestPermissionsResultListener, + registrar.view()); + } - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/camera"); + @Override + public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { + this.flutterPluginBinding = binding; + } - channel.setMethodCallHandler(new CameraPlugin(registrar)); + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + this.flutterPluginBinding = null; } - private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException { - String cameraName = call.argument("cameraName"); - String resolutionPreset = call.argument("resolutionPreset"); - boolean enableAudio = call.argument("enableAudio"); - TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = view.createSurfaceTexture(); - DartMessenger dartMessenger = - new DartMessenger(registrar.messenger(), flutterSurfaceTexture.id()); - camera = - new Camera( - registrar.activity(), - flutterSurfaceTexture, - dartMessenger, - cameraName, - resolutionPreset, - enableAudio); + @Override + public void onAttachedToActivity(@NonNull ActivityPluginBinding binding) { + maybeStartListening( + binding.getActivity(), + flutterPluginBinding.getFlutterEngine().getDartExecutor(), + binding::addRequestPermissionsResultListener, + flutterPluginBinding.getFlutterEngine().getRenderer()); + } - camera.open(result); + @Override + public void onDetachedFromActivity() { + if (methodCallHandler == null) { + // Could be on too low of an SDK to have started listening originally. + return; + } + + methodCallHandler.stopListening(); + methodCallHandler = null; } @Override - public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { - switch (call.method) { - case "availableCameras": - try { - result.success(CameraUtils.getAvailableCameras(registrar.activity())); - } catch (Exception e) { - handleException(e, result); - } - break; - case "initialize": - { - if (camera != null) { - camera.close(); - } - cameraPermissions.requestPermissions( - registrar, - call.argument("enableAudio"), - (String errCode, String errDesc) -> { - if (errCode == null) { - try { - instantiateCamera(call, result); - } catch (Exception e) { - handleException(e, result); - } - } else { - result.error(errCode, errDesc, null); - } - }); + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + onAttachedToActivity(binding); + } - break; - } - case "takePicture": - { - camera.takePicture(call.argument("path"), result); - break; - } - case "prepareForVideoRecording": - { - // This optimization is not required for Android. - result.success(null); - break; - } - case "startVideoRecording": - { - camera.startVideoRecording(call.argument("filePath"), result); - break; - } - case "stopVideoRecording": - { - camera.stopVideoRecording(result); - break; - } - case "pauseVideoRecording": - { - camera.pauseVideoRecording(result); - break; - } - case "resumeVideoRecording": - { - camera.resumeVideoRecording(result); - break; - } - case "startImageStream": - { - try { - camera.startPreviewWithImageStream(imageStreamChannel); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "stopImageStream": - { - try { - camera.startPreview(); - result.success(null); - } catch (Exception e) { - handleException(e, result); - } - break; - } - case "dispose": - { - if (camera != null) { - camera.dispose(); - } - result.success(null); - break; - } - default: - result.notImplemented(); - break; - } + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } - // We move catching CameraAccessException out of onMethodCall because it causes a crash - // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to - // to be able to compile with <21 sdks for apps that want the camera and support earlier version. - @SuppressWarnings("ConstantConditions") - private void handleException(Exception exception, Result result) { - if (exception instanceof CameraAccessException) { - result.error("CameraAccess", exception.getMessage(), null); + private void maybeStartListening( + Activity activity, + BinaryMessenger messenger, + PermissionsRegistry permissionsRegistry, + TextureRegistry textureRegistry) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + // If the sdk is less than 21 (min sdk for Camera2) we don't register the plugin. + return; } - throw (RuntimeException) exception; + methodCallHandler = + new MethodCallHandlerImpl( + activity, messenger, new CameraPermissions(), permissionsRegistry, textureRegistry); } } diff --git a/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..cb58d19a9a02 --- /dev/null +++ b/packages/camera/android/src/main/java/io/flutter/plugins/camera/MethodCallHandlerImpl.java @@ -0,0 +1,174 @@ +package io.flutter.plugins.camera; + +import android.app.Activity; +import android.hardware.camera2.CameraAccessException; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.EventChannel; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugins.camera.CameraPermissions.PermissionsRegistry; +import io.flutter.view.TextureRegistry; + +final class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + private final Activity activity; + private final BinaryMessenger messenger; + private final CameraPermissions cameraPermissions; + private final PermissionsRegistry permissionsRegistry; + private final TextureRegistry textureRegistry; + private final MethodChannel methodChannel; + private final EventChannel imageStreamChannel; + private @Nullable Camera camera; + + MethodCallHandlerImpl( + Activity activity, + BinaryMessenger messenger, + CameraPermissions cameraPermissions, + PermissionsRegistry permissionsAdder, + TextureRegistry textureRegistry) { + this.activity = activity; + this.messenger = messenger; + this.cameraPermissions = cameraPermissions; + this.permissionsRegistry = permissionsAdder; + this.textureRegistry = textureRegistry; + + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/camera"); + imageStreamChannel = new EventChannel(messenger, "plugins.flutter.io/camera/imageStream"); + methodChannel.setMethodCallHandler(this); + } + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull final Result result) { + switch (call.method) { + case "availableCameras": + try { + result.success(CameraUtils.getAvailableCameras(activity)); + } catch (Exception e) { + handleException(e, result); + } + break; + case "initialize": + { + if (camera != null) { + camera.close(); + } + cameraPermissions.requestPermissions( + activity, + permissionsRegistry, + call.argument("enableAudio"), + (String errCode, String errDesc) -> { + if (errCode == null) { + try { + instantiateCamera(call, result); + } catch (Exception e) { + handleException(e, result); + } + } else { + result.error(errCode, errDesc, null); + } + }); + + break; + } + case "takePicture": + { + camera.takePicture(call.argument("path"), result); + break; + } + case "prepareForVideoRecording": + { + // This optimization is not required for Android. + result.success(null); + break; + } + case "startVideoRecording": + { + camera.startVideoRecording(call.argument("filePath"), result); + break; + } + case "stopVideoRecording": + { + camera.stopVideoRecording(result); + break; + } + case "pauseVideoRecording": + { + camera.pauseVideoRecording(result); + break; + } + case "resumeVideoRecording": + { + camera.resumeVideoRecording(result); + break; + } + case "startImageStream": + { + try { + camera.startPreviewWithImageStream(imageStreamChannel); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "stopImageStream": + { + try { + camera.startPreview(); + result.success(null); + } catch (Exception e) { + handleException(e, result); + } + break; + } + case "dispose": + { + if (camera != null) { + camera.dispose(); + } + result.success(null); + break; + } + default: + result.notImplemented(); + break; + } + } + + void stopListening() { + methodChannel.setMethodCallHandler(null); + } + + private void instantiateCamera(MethodCall call, Result result) throws CameraAccessException { + String cameraName = call.argument("cameraName"); + String resolutionPreset = call.argument("resolutionPreset"); + boolean enableAudio = call.argument("enableAudio"); + TextureRegistry.SurfaceTextureEntry flutterSurfaceTexture = + textureRegistry.createSurfaceTexture(); + DartMessenger dartMessenger = new DartMessenger(messenger, flutterSurfaceTexture.id()); + camera = + new Camera( + activity, + flutterSurfaceTexture, + dartMessenger, + cameraName, + resolutionPreset, + enableAudio); + + camera.open(result); + } + + // We move catching CameraAccessException out of onMethodCall because it causes a crash + // on plugin registration for sdks incompatible with Camera2 (< 21). We want this plugin to + // to be able to compile with <21 sdks for apps that want the camera and support earlier version. + @SuppressWarnings("ConstantConditions") + private void handleException(Exception exception, Result result) { + if (exception instanceof CameraAccessException) { + result.error("CameraAccess", exception.getMessage(), null); + } + + throw (RuntimeException) exception; + } +} diff --git a/packages/camera/example/android/app/build.gradle b/packages/camera/example/android/app/build.gradle index 39003759e4a3..e47b6db5e21e 100644 --- a/packages/camera/example/android/app/build.gradle +++ b/packages/camera/example/android/app/build.gradle @@ -58,6 +58,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/packages/camera/example/android/app/gradle.properties b/packages/camera/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/camera/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..95b5f4373b62 --- /dev/null +++ b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.cameraexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/MainActivityTest.java b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/MainActivityTest.java new file mode 100644 index 000000000000..5d1b95578dc0 --- /dev/null +++ b/packages/camera/example/android/app/src/androidTestDebug/java/io/flutter/plugins/cameraexample/MainActivityTest.java @@ -0,0 +1,11 @@ +package io.flutter.plugins.cameraexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/camera/example/android/app/src/main/AndroidManifest.xml b/packages/camera/example/android/app/src/main/AndroidManifest.xml index 15f6087e4ebe..aad8d98bfa27 100644 --- a/packages/camera/example/android/app/src/main/AndroidManifest.xml +++ b/packages/camera/example/android/app/src/main/AndroidManifest.xml @@ -1,30 +1,39 @@ + package="io.flutter.plugins.cameraexample"> - + + + + + + + + + + + - + - - - - - - - - - + diff --git a/packages/instrumentation_adapter/example/android/app/src/main/java/com/example/instrumentation_adapter_example/MainActivity.java b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java similarity index 72% rename from packages/instrumentation_adapter/example/android/app/src/main/java/com/example/instrumentation_adapter_example/MainActivity.java rename to packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java index 21cb8e80d9c9..9e86560d3ff4 100644 --- a/packages/instrumentation_adapter/example/android/app/src/main/java/com/example/instrumentation_adapter_example/MainActivity.java +++ b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/EmbeddingV1Activity.java @@ -1,10 +1,10 @@ -package com.example.instrumentation_adapter_example; +package io.flutter.plugins.cameraexample; import android.os.Bundle; import io.flutter.app.FlutterActivity; import io.flutter.plugins.GeneratedPluginRegistrant; -public class MainActivity extends FlutterActivity { +public class EmbeddingV1Activity extends FlutterActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java index 8692b845f947..bbe9e45be2db 100644 --- a/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java +++ b/packages/camera/example/android/app/src/main/java/io/flutter/plugins/cameraexample/MainActivity.java @@ -1,13 +1,22 @@ package io.flutter.plugins.cameraexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import androidx.annotation.NonNull; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugins.camera.CameraPlugin; +import io.flutter.plugins.pathprovider.PathProviderPlugin; +import io.flutter.plugins.videoplayer.VideoPlayerPlugin; public class MainActivity extends FlutterActivity { @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new CameraPlugin()); + + ShimPluginRegistry shimPluginRegistry = new ShimPluginRegistry(flutterEngine); + PathProviderPlugin.registerWith( + shimPluginRegistry.registrarFor("io.flutter.plugins.pathprovider.PathProviderPlugin")); + VideoPlayerPlugin.registerWith( + shimPluginRegistry.registrarFor("io.flutter.plugins.videoplayer.VideoPlayerPlugin")); } } diff --git a/packages/camera/example/android/gradle.properties b/packages/camera/example/android/gradle.properties index 8bd86f680510..a6738207fd15 100644 --- a/packages/camera/example/android/gradle.properties +++ b/packages/camera/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/camera/example/pubspec.yaml b/packages/camera/example/pubspec.yaml index 59f3821abe21..0f76a09fed3b 100644 --- a/packages/camera/example/pubspec.yaml +++ b/packages/camera/example/pubspec.yaml @@ -9,6 +9,7 @@ dependencies: flutter: sdk: flutter video_player: ^0.10.0 + e2e: "^0.2.0" dev_dependencies: flutter_test: @@ -18,3 +19,7 @@ dev_dependencies: flutter: uses-material-design: true + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/camera/example/test_driver/camera.dart b/packages/camera/example/test_driver/camera_e2e.dart similarity index 85% rename from packages/camera/example/test_driver/camera.dart rename to packages/camera/example/test_driver/camera_e2e.dart index d68b8c5ba1fc..151339942f15 100644 --- a/packages/camera/example/test_driver/camera.dart +++ b/packages/camera/example/test_driver/camera_e2e.dart @@ -3,16 +3,16 @@ import 'dart:io'; import 'dart:ui'; import 'package:flutter/painting.dart'; -import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:camera/camera.dart'; import 'package:path_provider/path_provider.dart'; import 'package:video_player/video_player.dart'; +import 'package:e2e/e2e.dart'; void main() { - final Completer completer = Completer(); Directory testDir; - enableFlutterDriverExtension(handler: (_) => completer.future); + + E2EWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { final Directory extDir = await getTemporaryDirectory(); @@ -21,7 +21,6 @@ void main() { tearDownAll(() async { await testDir.delete(recursive: true); - completer.complete(null); }); final Map presetExpectedSizes = @@ -70,7 +69,8 @@ void main() { expectedSize, Size(image.height.toDouble(), image.width.toDouble())); } - test('Capture specific image resolutions', () async { + testWidgets('Capture specific image resolutions', + (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; @@ -90,7 +90,7 @@ void main() { await controller.dispose(); } } - }); + }, skip: !Platform.isAndroid); // This tests that the capture is no bigger than the preset, since we have // automatic code to fall back to smaller sizes when we need to. Returns @@ -121,7 +121,8 @@ void main() { expectedSize, Size(video.height, video.width)); } - test('Capture specific video resolutions', () async { + testWidgets('Capture specific video resolutions', + (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; @@ -142,9 +143,9 @@ void main() { await controller.dispose(); } } - }); + }, skip: !Platform.isAndroid); - test('Pause and resume video recording', () async { + testWidgets('Pause and resume video recording', (WidgetTester tester) async { final List cameras = await availableCameras(); if (cameras.isEmpty) { return; @@ -198,5 +199,40 @@ void main() { await videoController.dispose(); expect(duration, lessThan(recordingTime - timePaused)); - }); + }, skip: !Platform.isAndroid); + + testWidgets( + 'Android image streaming', + (WidgetTester tester) async { + final List cameras = await availableCameras(); + if (cameras.isEmpty) { + return; + } + + final CameraController controller = CameraController( + cameras[0], + ResolutionPreset.low, + enableAudio: false, + ); + + await controller.initialize(); + bool _isDetecting = false; + + await controller.startImageStream((CameraImage image) { + if (_isDetecting) return; + + _isDetecting = true; + + expectLater(image, isNotNull).whenComplete(() => _isDetecting = false); + }); + + expect(controller.value.isStreamingImages, true); + + sleep(const Duration(milliseconds: 500)); + + await controller.stopImageStream(); + controller.dispose(); + }, + skip: !Platform.isAndroid, + ); } diff --git a/packages/camera/example/test_driver/camera_e2e_test.dart b/packages/camera/example/test_driver/camera_e2e_test.dart new file mode 100644 index 000000000000..e3e089a81fc9 --- /dev/null +++ b/packages/camera/example/test_driver/camera_e2e_test.dart @@ -0,0 +1,55 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +const String _examplePackage = 'io.flutter.plugins.cameraexample'; + +Future main() async { + if (!(Platform.isLinux || Platform.isMacOS)) { + print('This test must be run on a POSIX host. Skipping...'); + exit(0); + } + final bool adbExists = + Process.runSync('which', ['adb']).exitCode == 0; + if (!adbExists) { + print('This test needs ADB to exist on the \$PATH. Skipping...'); + exit(0); + } + print('Granting camera permissions...'); + Process.runSync('adb', [ + 'shell', + 'pm', + 'grant', + _examplePackage, + 'android.permission.CAMERA' + ]); + Process.runSync('adb', [ + 'shell', + 'pm', + 'grant', + _examplePackage, + 'android.permission.RECORD_AUDIO' + ]); + print('Starting test.'); + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + print('Test finished. Revoking camera permissions...'); + Process.runSync('adb', [ + 'shell', + 'pm', + 'revoke', + _examplePackage, + 'android.permission.CAMERA' + ]); + Process.runSync('adb', [ + 'shell', + 'pm', + 'revoke', + _examplePackage, + 'android.permission.RECORD_AUDIO' + ]); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/camera/example/test_driver/camera_test.dart b/packages/camera/example/test_driver/camera_test.dart deleted file mode 100644 index 38fe6c447e05..000000000000 --- a/packages/camera/example/test_driver/camera_test.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; - -Future main() async { - final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); -} diff --git a/packages/camera/pubspec.yaml b/packages/camera/pubspec.yaml index 0906f175fb8d..aae2d6e9112b 100644 --- a/packages/camera/pubspec.yaml +++ b/packages/camera/pubspec.yaml @@ -2,7 +2,7 @@ name: camera description: A Flutter plugin for getting information about and controlling the camera on Android and iOS. Supports previewing the camera feed, capturing images, capturing video, and streaming image buffers to dart. -version: 0.5.5+1 +version: 0.5.6+3 authors: - Flutter Team @@ -32,4 +32,4 @@ flutter: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.2.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0" diff --git a/packages/connectivity/CHANGELOG.md b/packages/connectivity/CHANGELOG.md index b52e629fd516..5a7b5f7f4e05 100644 --- a/packages/connectivity/CHANGELOG.md +++ b/packages/connectivity/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.4.5+3 + +* Remove AndroidX warnings. + +## 0.4.5+2 + +* Include lifecycle dependency as a compileOnly one on Android to resolve + potential version conflicts with other transitive libraries. + +## 0.4.5+1 + +* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. + +## 0.4.5 + +* Support the v2 Android embedder. + ## 0.4.4+1 * Update and migrate iOS example project. diff --git a/packages/connectivity/android/build.gradle b/packages/connectivity/android/build.gradle index 681eb0438b75..26ca50c78652 100644 --- a/packages/connectivity/android/build.gradle +++ b/packages/connectivity/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "connectivity"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.connectivity' version '1.0-SNAPSHOT' @@ -45,3 +32,29 @@ android { disable 'InvalidPackage' } } + +// TODO(amirh): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java new file mode 100644 index 000000000000..2d07641a42c1 --- /dev/null +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/Connectivity.java @@ -0,0 +1,103 @@ +// Copyright 2019 The Chromium 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.plugins.connectivity; + +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; + +/** Reports connectivity related information such as connectivity type and wifi information. */ +class Connectivity { + private ConnectivityManager connectivityManager; + private WifiManager wifiManager; + + Connectivity(ConnectivityManager connectivityManager, WifiManager wifiManager) { + this.connectivityManager = connectivityManager; + this.wifiManager = wifiManager; + } + + String getNetworkType() { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Network network = connectivityManager.getActiveNetwork(); + NetworkCapabilities capabilities = connectivityManager.getNetworkCapabilities(network); + if (capabilities == null) { + return "none"; + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) + || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { + return "wifi"; + } + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + return "mobile"; + } + } + + return getNetworkTypeLegacy(); + } + + String getWifiName() { + WifiInfo wifiInfo = getWifiInfo(); + String ssid = null; + if (wifiInfo != null) ssid = wifiInfo.getSSID(); + if (ssid != null) ssid = ssid.replaceAll("\"", ""); // Android returns "SSID" + return ssid; + } + + String getWifiBSSID() { + WifiInfo wifiInfo = getWifiInfo(); + String bssid = null; + if (wifiInfo != null) { + bssid = wifiInfo.getBSSID(); + } + return bssid; + } + + String getWifiIPAddress() { + WifiInfo wifiInfo = null; + if (wifiManager != null) wifiInfo = wifiManager.getConnectionInfo(); + + String ip = null; + int i_ip = 0; + if (wifiInfo != null) i_ip = wifiInfo.getIpAddress(); + + if (i_ip != 0) + ip = + String.format( + "%d.%d.%d.%d", + (i_ip & 0xff), (i_ip >> 8 & 0xff), (i_ip >> 16 & 0xff), (i_ip >> 24 & 0xff)); + + return ip; + } + + private WifiInfo getWifiInfo() { + return wifiManager == null ? null : wifiManager.getConnectionInfo(); + } + + @SuppressWarnings("deprecation") + private String getNetworkTypeLegacy() { + // handle type for Android versions less than Android 9 + NetworkInfo info = connectivityManager.getActiveNetworkInfo(); + if (info == null || !info.isConnected()) { + return "none"; + } + int type = info.getType(); + switch (type) { + case ConnectivityManager.TYPE_ETHERNET: + case ConnectivityManager.TYPE_WIFI: + case ConnectivityManager.TYPE_WIMAX: + return "wifi"; + case ConnectivityManager.TYPE_MOBILE: + case ConnectivityManager.TYPE_MOBILE_DUN: + case ConnectivityManager.TYPE_MOBILE_HIPRI: + return "mobile"; + default: + return "none"; + } + } +} diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java new file mode 100644 index 000000000000..be8b47eff944 --- /dev/null +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityBroadcastReceiver.java @@ -0,0 +1,50 @@ +// Copyright 2019 The Chromium 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.plugins.connectivity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import io.flutter.plugin.common.EventChannel; + +/** + * The ConnectivityBroadcastReceiver receives the connectivity updates and send them to the UIThread + * through an {@link EventChannel.EventSink} + * + *

Use {@link + * io.flutter.plugin.common.EventChannel#setStreamHandler(io.flutter.plugin.common.EventChannel.StreamHandler)} + * to set up the receiver. + */ +class ConnectivityBroadcastReceiver extends BroadcastReceiver + implements EventChannel.StreamHandler { + private Context context; + private Connectivity connectivity; + private EventChannel.EventSink events; + + ConnectivityBroadcastReceiver(Context context, Connectivity connectivity) { + this.context = context; + this.connectivity = connectivity; + } + + @Override + public void onListen(Object arguments, EventChannel.EventSink events) { + this.events = events; + context.registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + } + + @Override + public void onCancel(Object arguments) { + context.unregisterReceiver(this); + } + + @Override + public void onReceive(Context context, Intent intent) { + if (events != null) { + events.success(connectivity.getNetworkType()); + } + } +} diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java new file mode 100644 index 000000000000..931b702d442a --- /dev/null +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityMethodChannelHandler.java @@ -0,0 +1,48 @@ +// Copyright 2019 The Chromium 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.plugins.connectivity; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; + +/** + * The handler receives {@link MethodCall}s from the UIThread, gets the related information from + * a @{@link Connectivity}, and then send the result back to the UIThread through the {@link + * MethodChannel.Result}. + */ +class ConnectivityMethodChannelHandler implements MethodChannel.MethodCallHandler { + + private Connectivity connectivity; + + /** + * Construct the ConnectivityMethodChannelHandler with a {@code connectivity}. The {@code + * connectivity} must not be null. + */ + ConnectivityMethodChannelHandler(Connectivity connectivity) { + assert (connectivity != null); + this.connectivity = connectivity; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case "check": + result.success(connectivity.getNetworkType()); + break; + case "wifiName": + result.success(connectivity.getWifiName()); + break; + case "wifiBSSID": + result.success(connectivity.getWifiBSSID()); + break; + case "wifiIPAddress": + result.success(connectivity.getWifiIPAddress()); + break; + default: + result.notImplemented(); + break; + } + } +} diff --git a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java index dac720b0450c..ef8f7861d8e0 100644 --- a/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java +++ b/packages/connectivity/android/src/main/java/io/flutter/plugins/connectivity/ConnectivityPlugin.java @@ -4,186 +4,60 @@ package io.flutter.plugins.connectivity; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.net.ConnectivityManager; -import android.net.Network; -import android.net.NetworkCapabilities; -import android.net.NetworkInfo; -import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; -import android.os.Build; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.EventChannel; -import io.flutter.plugin.common.EventChannel.EventSink; -import io.flutter.plugin.common.EventChannel.StreamHandler; -import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; /** ConnectivityPlugin */ -public class ConnectivityPlugin implements MethodCallHandler, StreamHandler { - private final Registrar registrar; - private final ConnectivityManager manager; - private BroadcastReceiver receiver; +public class ConnectivityPlugin implements FlutterPlugin { + + private MethodChannel methodChannel; + private EventChannel eventChannel; /** Plugin registration. */ public static void registerWith(Registrar registrar) { - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/connectivity"); - final EventChannel eventChannel = - new EventChannel(registrar.messenger(), "plugins.flutter.io/connectivity_status"); - ConnectivityPlugin instance = new ConnectivityPlugin(registrar); - channel.setMethodCallHandler(instance); - eventChannel.setStreamHandler(instance); - } - private ConnectivityPlugin(Registrar registrar) { - this.registrar = registrar; - this.manager = - (ConnectivityManager) - registrar - .context() - .getApplicationContext() - .getSystemService(Context.CONNECTIVITY_SERVICE); + ConnectivityPlugin plugin = new ConnectivityPlugin(); + plugin.setupChannels(registrar.messenger(), registrar.context()); } @Override - public void onListen(Object arguments, EventSink events) { - receiver = createReceiver(events); - registrar - .context() - .registerReceiver(receiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); + public void onAttachedToEngine(FlutterPluginBinding binding) { + setupChannels(binding.getFlutterEngine().getDartExecutor(), binding.getApplicationContext()); } @Override - public void onCancel(Object arguments) { - registrar.context().unregisterReceiver(receiver); - receiver = null; - } - - private String getNetworkType(ConnectivityManager manager) { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - Network network = manager.getActiveNetwork(); - NetworkCapabilities capabilities = manager.getNetworkCapabilities(network); - if (capabilities == null) { - return "none"; - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) - || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) { - return "wifi"; - } - if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { - return "mobile"; - } - } - - return getNetworkTypeLegacy(manager); + public void onDetachedFromEngine(FlutterPluginBinding binding) { + teardownChannels(); } - @SuppressWarnings("deprecation") - private String getNetworkTypeLegacy(ConnectivityManager manager) { - // handle type for Android versions less than Android 9 - NetworkInfo info = manager.getActiveNetworkInfo(); - if (info == null || !info.isConnected()) { - return "none"; - } - int type = info.getType(); - switch (type) { - case ConnectivityManager.TYPE_ETHERNET: - case ConnectivityManager.TYPE_WIFI: - case ConnectivityManager.TYPE_WIMAX: - return "wifi"; - case ConnectivityManager.TYPE_MOBILE: - case ConnectivityManager.TYPE_MOBILE_DUN: - case ConnectivityManager.TYPE_MOBILE_HIPRI: - return "mobile"; - default: - return "none"; - } - } - - @Override - public void onMethodCall(MethodCall call, Result result) { - switch (call.method) { - case "check": - handleCheck(call, result); - break; - case "wifiName": - handleWifiName(call, result); - break; - case "wifiBSSID": - handleBSSID(call, result); - break; - case "wifiIPAddress": - handleWifiIPAddress(call, result); - break; - default: - result.notImplemented(); - break; - } - } - - private void handleCheck(MethodCall call, final Result result) { - result.success(checkNetworkType()); - } - - private String checkNetworkType() { - return getNetworkType(manager); - } - - private WifiInfo getWifiInfo() { - WifiManager wifiManager = - (WifiManager) - registrar.context().getApplicationContext().getSystemService(Context.WIFI_SERVICE); - return wifiManager == null ? null : wifiManager.getConnectionInfo(); - } - - private void handleWifiName(MethodCall call, final Result result) { - WifiInfo wifiInfo = getWifiInfo(); - String ssid = null; - if (wifiInfo != null) ssid = wifiInfo.getSSID(); - if (ssid != null) ssid = ssid.replaceAll("\"", ""); // Android returns "SSID" - result.success(ssid); - } - - private void handleBSSID(MethodCall call, MethodChannel.Result result) { - WifiInfo wifiInfo = getWifiInfo(); - String bssid = null; - if (wifiInfo != null) bssid = wifiInfo.getBSSID(); - result.success(bssid); - } - - private void handleWifiIPAddress(MethodCall call, final Result result) { - WifiManager wifiManager = - (WifiManager) - registrar.context().getApplicationContext().getSystemService(Context.WIFI_SERVICE); - - WifiInfo wifiInfo = null; - if (wifiManager != null) wifiInfo = wifiManager.getConnectionInfo(); + private void setupChannels(BinaryMessenger messenger, Context context) { + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/connectivity"); + eventChannel = new EventChannel(messenger, "plugins.flutter.io/connectivity_status"); + ConnectivityManager connectivityManager = + (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); - String ip = null; - int i_ip = 0; - if (wifiInfo != null) i_ip = wifiInfo.getIpAddress(); + Connectivity connectivity = new Connectivity(connectivityManager, wifiManager); - if (i_ip != 0) - ip = - String.format( - "%d.%d.%d.%d", - (i_ip & 0xff), (i_ip >> 8 & 0xff), (i_ip >> 16 & 0xff), (i_ip >> 24 & 0xff)); + ConnectivityMethodChannelHandler methodChannelHandler = + new ConnectivityMethodChannelHandler(connectivity); + ConnectivityBroadcastReceiver receiver = + new ConnectivityBroadcastReceiver(context, connectivity); - result.success(ip); + methodChannel.setMethodCallHandler(methodChannelHandler); + eventChannel.setStreamHandler(receiver); } - private BroadcastReceiver createReceiver(final EventSink events) { - return new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - events.success(checkNetworkType()); - } - }; + private void teardownChannels() { + methodChannel.setMethodCallHandler(null); + eventChannel.setStreamHandler(null); + methodChannel = null; + eventChannel = null; } } diff --git a/packages/connectivity/example/android/app/gradle.properties b/packages/connectivity/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/connectivity/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/connectivity/example/android/app/src/main/AndroidManifest.xml b/packages/connectivity/example/android/app/src/main/AndroidManifest.xml index bf36efe1a689..3bf2ca03bc33 100644 --- a/packages/connectivity/example/android/app/src/main/AndroidManifest.xml +++ b/packages/connectivity/example/android/app/src/main/AndroidManifest.xml @@ -4,12 +4,20 @@ + + + android:theme="@android:style/Theme.Black.NoTitleBar" + android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection" + android:hardwareAccelerated="true" + android:windowSoftInputMode="adjustResize"> + diff --git a/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivity_example/MainActivity.java b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1Activity.java similarity index 89% rename from packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivity_example/MainActivity.java rename to packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1Activity.java index 6d76bcc24ac5..587b623c049d 100644 --- a/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivity_example/MainActivity.java +++ b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1Activity.java @@ -8,7 +8,7 @@ import io.flutter.app.FlutterActivity; import io.flutter.plugins.GeneratedPluginRegistrant; -public class MainActivity extends FlutterActivity { +public class EmbeddingV1Activity extends FlutterActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1ActivityTest.java b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..a34755399117 --- /dev/null +++ b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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.plugins.connectivityexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivity.java b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivity.java new file mode 100644 index 000000000000..b0deb61bfd28 --- /dev/null +++ b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivity.java @@ -0,0 +1,18 @@ +// Copyright 2019 The Chromium 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.plugins.connectivityexample; + +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.connectivity.ConnectivityPlugin; + +public class MainActivity extends FlutterActivity { + + @Override + public void configureFlutterEngine(FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + flutterEngine.getPlugins().add(new ConnectivityPlugin()); + } +} diff --git a/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivityTest.java b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivityTest.java new file mode 100644 index 000000000000..0c33d6a92f46 --- /dev/null +++ b/packages/connectivity/example/android/app/src/main/java/io/flutter/plugins/connectivityexample/MainActivityTest.java @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium 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.plugins.connectivityexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/connectivity/example/android/gradle.properties b/packages/connectivity/example/android/gradle.properties index 8bd86f680510..a6738207fd15 100644 --- a/packages/connectivity/example/android/gradle.properties +++ b/packages/connectivity/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/connectivity/example/pubspec.yaml b/packages/connectivity/example/pubspec.yaml index c364782e786c..2f2b6eb68fca 100644 --- a/packages/connectivity/example/pubspec.yaml +++ b/packages/connectivity/example/pubspec.yaml @@ -11,6 +11,7 @@ dev_dependencies: flutter_driver: sdk: flutter test: any + e2e: ^0.2.0 flutter: uses-material-design: true diff --git a/packages/connectivity/example/test_driver/connectivity_test.dart b/packages/connectivity/example/test_driver/connectivity_test.dart deleted file mode 100644 index 2b89c8f2f7bb..000000000000 --- a/packages/connectivity/example/test_driver/connectivity_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:test/test.dart'; - -void main() { - test('connectivity', () async { - final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); - driver.close(); - }); -} diff --git a/packages/connectivity/example/test_driver/test/connectivity_e2e_test.dart b/packages/connectivity/example/test_driver/test/connectivity_e2e_test.dart new file mode 100644 index 000000000000..d4586bdc7127 --- /dev/null +++ b/packages/connectivity/example/test_driver/test/connectivity_e2e_test.dart @@ -0,0 +1,14 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/connectivity/pubspec.yaml b/packages/connectivity/pubspec.yaml index 10a01988db5c..4466c7ef6054 100644 --- a/packages/connectivity/pubspec.yaml +++ b/packages/connectivity/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for discovering the state of the network (WiFi & mobile/cellular) connectivity on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/connectivity -version: 0.4.4+1 +version: 0.4.5+3 flutter: plugin: @@ -22,7 +22,8 @@ dev_dependencies: flutter_driver: sdk: flutter test: any + e2e: ^0.2.0 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.2.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0" diff --git a/packages/connectivity/example/test_driver/connectivity.dart b/packages/connectivity/test/connectivity_e2e.dart similarity index 68% rename from packages/connectivity/example/test_driver/connectivity.dart rename to packages/connectivity/test/connectivity_e2e.dart index 685f69efb1c8..10c4bda34e0d 100644 --- a/packages/connectivity/example/test_driver/connectivity.dart +++ b/packages/connectivity/test/connectivity_e2e.dart @@ -1,13 +1,14 @@ -import 'dart:async'; +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + import 'dart:io'; -import 'package:flutter_driver/driver_extension.dart'; +import 'package:e2e/e2e.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:connectivity/connectivity.dart'; void main() { - final Completer completer = Completer(); - enableFlutterDriverExtension(handler: (_) => completer.future); - tearDownAll(() => completer.complete(null)); + E2EWidgetsFlutterBinding.ensureInitialized(); group('Connectivity test driver', () { Connectivity _connectivity; @@ -16,7 +17,7 @@ void main() { _connectivity = Connectivity(); }); - test('test connectivity result', () async { + testWidgets('test connectivity result', (WidgetTester tester) async { final ConnectivityResult result = await _connectivity.checkConnectivity(); expect(result, isNotNull); switch (result) { @@ -30,8 +31,7 @@ void main() { } }); - test('test location methods, iOS only', () async { - print(Platform.isIOS); + testWidgets('test location methods, iOS only', (WidgetTester tester) async { if (Platform.isIOS) { expect((await _connectivity.getLocationServiceAuthorization()), LocationAuthorizationStatus.notDetermined); diff --git a/packages/device_info/CHANGELOG.md b/packages/device_info/CHANGELOG.md index 62d4960dc73b..e500051ff604 100644 --- a/packages/device_info/CHANGELOG.md +++ b/packages/device_info/CHANGELOG.md @@ -1,3 +1,20 @@ +## 0.4.1+2 + +* Remove AndroidX warning. + +## 0.4.1+1 + +* Include lifecycle dependency as a compileOnly one on Android to resolve + potential version conflicts with other transitive libraries. + +## 0.4.1 + +* Support the v2 Android embedding. +* Update to AndroidX. +* Migrate to using the new e2e test binding. +* Add a e2e test. + + ## 0.4.0+4 * Define clang module for iOS. diff --git a/packages/device_info/android/build.gradle b/packages/device_info/android/build.gradle index df08280c0b1b..d1dd790732f1 100644 --- a/packages/device_info/android/build.gradle +++ b/packages/device_info/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "device_info"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.deviceinfo' version '1.0-SNAPSHOT' @@ -45,3 +32,29 @@ android { disable 'InvalidPackage' } } + +// TODO(cyanglaz): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} diff --git a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java index a22009f09ba7..8ad0f5db1851 100644 --- a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java +++ b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/DeviceInfoPlugin.java @@ -4,120 +4,43 @@ package io.flutter.plugins.deviceinfo; -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Build; -import android.os.Build.VERSION; -import android.os.Build.VERSION_CODES; -import android.provider.Settings; -import io.flutter.plugin.common.MethodCall; +import android.content.ContentResolver; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; /** DeviceInfoPlugin */ -public class DeviceInfoPlugin implements MethodCallHandler { - private final Context context; +public class DeviceInfoPlugin implements FlutterPlugin { - /** Substitute for missing values. */ - private static final String[] EMPTY_STRING_LIST = new String[] {}; + MethodChannel channel; /** Plugin registration. */ public static void registerWith(Registrar registrar) { - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/device_info"); - channel.setMethodCallHandler(new DeviceInfoPlugin(registrar.context())); + DeviceInfoPlugin plugin = new DeviceInfoPlugin(); + plugin.setupMethodChannel(registrar.messenger(), registrar.context().getContentResolver()); } - /** Do not allow direct instantiation. */ - private DeviceInfoPlugin(Context context) { - this.context = context; + @Override + public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) { + setupMethodChannel( + binding.getFlutterEngine().getDartExecutor(), + binding.getApplicationContext().getContentResolver()); } @Override - public void onMethodCall(MethodCall call, Result result) { - if (call.method.equals("getAndroidDeviceInfo")) { - Map build = new HashMap<>(); - build.put("board", Build.BOARD); - build.put("bootloader", Build.BOOTLOADER); - build.put("brand", Build.BRAND); - build.put("device", Build.DEVICE); - build.put("display", Build.DISPLAY); - build.put("fingerprint", Build.FINGERPRINT); - build.put("hardware", Build.HARDWARE); - build.put("host", Build.HOST); - build.put("id", Build.ID); - build.put("manufacturer", Build.MANUFACTURER); - build.put("model", Build.MODEL); - build.put("product", Build.PRODUCT); - if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) { - build.put("supported32BitAbis", Arrays.asList(Build.SUPPORTED_32_BIT_ABIS)); - build.put("supported64BitAbis", Arrays.asList(Build.SUPPORTED_64_BIT_ABIS)); - build.put("supportedAbis", Arrays.asList(Build.SUPPORTED_ABIS)); - } else { - build.put("supported32BitAbis", Arrays.asList(EMPTY_STRING_LIST)); - build.put("supported64BitAbis", Arrays.asList(EMPTY_STRING_LIST)); - build.put("supportedAbis", Arrays.asList(EMPTY_STRING_LIST)); - } - build.put("tags", Build.TAGS); - build.put("type", Build.TYPE); - build.put("isPhysicalDevice", !isEmulator()); - build.put("androidId", getAndroidId()); - - Map version = new HashMap<>(); - if (VERSION.SDK_INT >= VERSION_CODES.M) { - version.put("baseOS", VERSION.BASE_OS); - version.put("previewSdkInt", VERSION.PREVIEW_SDK_INT); - version.put("securityPatch", VERSION.SECURITY_PATCH); - } - version.put("codename", VERSION.CODENAME); - version.put("incremental", VERSION.INCREMENTAL); - version.put("release", VERSION.RELEASE); - version.put("sdkInt", VERSION.SDK_INT); - build.put("version", version); - - result.success(build); - } else { - result.notImplemented(); - } + public void onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding binding) { + tearDownChannel(); } - /** - * Returns the Android hardware device ID that is unique between the device + user and app - * signing. This key will change if the app is uninstalled or its data is cleared. Device factory - * reset will also result in a value change. - * - * @return The android ID - */ - @SuppressLint("HardwareIds") - private String getAndroidId() { - return Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); + private void setupMethodChannel(BinaryMessenger messenger, ContentResolver contentResolver) { + channel = new MethodChannel(messenger, "plugins.flutter.io/device_info"); + final MethodCallHandlerImpl handler = new MethodCallHandlerImpl(contentResolver); + channel.setMethodCallHandler(handler); } - /** - * A simple emulator-detection based on the flutter tools detection logic and a couple of legacy - * detection systems - */ - private boolean isEmulator() { - return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) - || Build.FINGERPRINT.startsWith("generic") - || Build.FINGERPRINT.startsWith("unknown") - || Build.HARDWARE.contains("goldfish") - || Build.HARDWARE.contains("ranchu") - || Build.MODEL.contains("google_sdk") - || Build.MODEL.contains("Emulator") - || Build.MODEL.contains("Android SDK built for x86") - || Build.MANUFACTURER.contains("Genymotion") - || Build.PRODUCT.contains("sdk_google") - || Build.PRODUCT.contains("google_sdk") - || Build.PRODUCT.contains("sdk") - || Build.PRODUCT.contains("sdk_x86") - || Build.PRODUCT.contains("vbox86p") - || Build.PRODUCT.contains("emulator") - || Build.PRODUCT.contains("simulator"); + private void tearDownChannel() { + channel.setMethodCallHandler(null); + channel = null; } } diff --git a/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..22ea1f0ec85e --- /dev/null +++ b/packages/device_info/android/src/main/java/io/flutter/plugins/deviceinfo/MethodCallHandlerImpl.java @@ -0,0 +1,115 @@ +// Copyright 2017 The Chromium 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.plugins.deviceinfo; + +import android.annotation.SuppressLint; +import android.content.ContentResolver; +import android.os.Build; +import android.provider.Settings; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * The implementation of {@link MethodChannel.MethodCallHandler} for the plugin. Responsible for + * receiving method calls from method channel. + */ +class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + + private ContentResolver contentResolver; + + /** Substitute for missing values. */ + private static final String[] EMPTY_STRING_LIST = new String[] {}; + + /** Constructs DeviceInfo. The {@code contentResolver} must not be null. */ + MethodCallHandlerImpl(ContentResolver contentResolver) { + this.contentResolver = contentResolver; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + if (call.method.equals("getAndroidDeviceInfo")) { + Map build = new HashMap<>(); + build.put("board", Build.BOARD); + build.put("bootloader", Build.BOOTLOADER); + build.put("brand", Build.BRAND); + build.put("device", Build.DEVICE); + build.put("display", Build.DISPLAY); + build.put("fingerprint", Build.FINGERPRINT); + build.put("hardware", Build.HARDWARE); + build.put("host", Build.HOST); + build.put("id", Build.ID); + build.put("manufacturer", Build.MANUFACTURER); + build.put("model", Build.MODEL); + build.put("product", Build.PRODUCT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + build.put("supported32BitAbis", Arrays.asList(Build.SUPPORTED_32_BIT_ABIS)); + build.put("supported64BitAbis", Arrays.asList(Build.SUPPORTED_64_BIT_ABIS)); + build.put("supportedAbis", Arrays.asList(Build.SUPPORTED_ABIS)); + } else { + build.put("supported32BitAbis", Arrays.asList(EMPTY_STRING_LIST)); + build.put("supported64BitAbis", Arrays.asList(EMPTY_STRING_LIST)); + build.put("supportedAbis", Arrays.asList(EMPTY_STRING_LIST)); + } + build.put("tags", Build.TAGS); + build.put("type", Build.TYPE); + build.put("isPhysicalDevice", !isEmulator()); + build.put("androidId", getAndroidId()); + + Map version = new HashMap<>(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + version.put("baseOS", Build.VERSION.BASE_OS); + version.put("previewSdkInt", Build.VERSION.PREVIEW_SDK_INT); + version.put("securityPatch", Build.VERSION.SECURITY_PATCH); + } + version.put("codename", Build.VERSION.CODENAME); + version.put("incremental", Build.VERSION.INCREMENTAL); + version.put("release", Build.VERSION.RELEASE); + version.put("sdkInt", Build.VERSION.SDK_INT); + build.put("version", version); + + result.success(build); + } else { + result.notImplemented(); + } + } + + /** + * Returns the Android hardware device ID that is unique between the device + user and app + * signing. This key will change if the app is uninstalled or its data is cleared. Device factory + * reset will also result in a value change. + * + * @return The android ID + */ + @SuppressLint("HardwareIds") + private String getAndroidId() { + return Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID); + } + + /** + * A simple emulator-detection based on the flutter tools detection logic and a couple of legacy + * detection systems + */ + private boolean isEmulator() { + return (Build.BRAND.startsWith("generic") && Build.DEVICE.startsWith("generic")) + || Build.FINGERPRINT.startsWith("generic") + || Build.FINGERPRINT.startsWith("unknown") + || Build.HARDWARE.contains("goldfish") + || Build.HARDWARE.contains("ranchu") + || Build.MODEL.contains("google_sdk") + || Build.MODEL.contains("Emulator") + || Build.MODEL.contains("Android SDK built for x86") + || Build.MANUFACTURER.contains("Genymotion") + || Build.PRODUCT.contains("sdk_google") + || Build.PRODUCT.contains("google_sdk") + || Build.PRODUCT.contains("sdk") + || Build.PRODUCT.contains("sdk_x86") + || Build.PRODUCT.contains("vbox86p") + || Build.PRODUCT.contains("emulator") + || Build.PRODUCT.contains("simulator"); + } +} diff --git a/packages/device_info/example/android/app/gradle.properties b/packages/device_info/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/device_info/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/device_info/example/android/app/src/main/AndroidManifest.xml b/packages/device_info/example/android/app/src/main/AndroidManifest.xml index b46ebe843677..45242ab08b69 100644 --- a/packages/device_info/example/android/app/src/main/AndroidManifest.xml +++ b/packages/device_info/example/android/app/src/main/AndroidManifest.xml @@ -4,12 +4,19 @@ - + + diff --git a/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/EmbeddingV1Activity.java b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..0bfaee848dbf --- /dev/null +++ b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/EmbeddingV1Activity.java @@ -0,0 +1,17 @@ +// Copyright 2017 The Chromium 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.plugins.deviceinfoexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/EmbeddingV1ActivityTest.java b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..2bec9fb8e254 --- /dev/null +++ b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.deviceinfoexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivity.java b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivity.java index 06a3172875a7..b820542706e0 100644 --- a/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivity.java +++ b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivity.java @@ -1,17 +1,20 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. +// Copyright 2019 The Chromium 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.plugins.deviceinfoexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.deviceinfo.DeviceInfoPlugin; public class MainActivity extends FlutterActivity { + + // TODO(cyanglaz): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. + // https://github.com/flutter/flutter/issues/42694 @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + super.configureFlutterEngine(flutterEngine); + flutterEngine.getPlugins().add(new DeviceInfoPlugin()); } } diff --git a/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivityTest.java b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivityTest.java new file mode 100644 index 000000000000..36967ebf4564 --- /dev/null +++ b/packages/device_info/example/android/app/src/main/java/io/flutter/plugins/deviceinfoexample/MainActivityTest.java @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium 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.plugins.deviceinfoexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/device_info/example/android/gradle.properties b/packages/device_info/example/android/gradle.properties index 8bd86f680510..38c8d4544ff1 100644 --- a/packages/device_info/example/android/gradle.properties +++ b/packages/device_info/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/device_info/example/pubspec.yaml b/packages/device_info/example/pubspec.yaml index 65b7bf8a4ef6..148b7b9ed286 100644 --- a/packages/device_info/example/pubspec.yaml +++ b/packages/device_info/example/pubspec.yaml @@ -7,5 +7,15 @@ dependencies: device_info: path: ../ +dev_dependencies: + flutter_driver: + sdk: flutter + e2e: ^0.2.0 + flutter: uses-material-design: true + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.2 <2.0.0" + diff --git a/packages/device_info/example/test_driver/device_info_e2e.dart b/packages/device_info/example/test_driver/device_info_e2e.dart new file mode 100644 index 000000000000..db8a73f579c1 --- /dev/null +++ b/packages/device_info/example/test_driver/device_info_e2e.dart @@ -0,0 +1,32 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:device_info/device_info.dart'; +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + IosDeviceInfo iosInfo; + AndroidDeviceInfo androidInfo; + + setUpAll(() async { + final DeviceInfoPlugin deviceInfoPlugin = DeviceInfoPlugin(); + if (Platform.isIOS) { + iosInfo = await deviceInfoPlugin.iosInfo; + } else if (Platform.isAndroid) { + androidInfo = await deviceInfoPlugin.androidInfo; + } + }); + + testWidgets('Can get non-null device model', (WidgetTester tester) async { + if (Platform.isIOS) { + expect(iosInfo.model, isNotNull); + } else if (Platform.isAndroid) { + expect(androidInfo.model, isNotNull); + } + }); +} diff --git a/packages/device_info/example/test_driver/device_info_e2e_test.dart b/packages/device_info/example/test_driver/device_info_e2e_test.dart new file mode 100644 index 000000000000..ff6e9ce74ad9 --- /dev/null +++ b/packages/device_info/example/test_driver/device_info_e2e_test.dart @@ -0,0 +1,15 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/device_info/pubspec.yaml b/packages/device_info/pubspec.yaml index bc192a87c96e..7e43f13a70a3 100644 --- a/packages/device_info/pubspec.yaml +++ b/packages/device_info/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin providing detailed information about the device (make, model, etc.), and Android or iOS version the app is running on. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/device_info -version: 0.4.0+4 +version: 0.4.1+2 flutter: plugin: @@ -15,6 +15,12 @@ dependencies: flutter: sdk: flutter +dev_dependencies: + test: ^1.3.0 + flutter_test: + sdk: flutter + e2e: ^0.2.0 + environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.6.7 <2.0.0" diff --git a/packages/instrumentation_adapter/.gitignore b/packages/e2e/.gitignore similarity index 100% rename from packages/instrumentation_adapter/.gitignore rename to packages/e2e/.gitignore diff --git a/packages/instrumentation_adapter/.metadata b/packages/e2e/.metadata similarity index 100% rename from packages/instrumentation_adapter/.metadata rename to packages/e2e/.metadata diff --git a/packages/instrumentation_adapter/CHANGELOG.md b/packages/e2e/CHANGELOG.md similarity index 54% rename from packages/instrumentation_adapter/CHANGELOG.md rename to packages/e2e/CHANGELOG.md index 8b9b8a725a77..d398373d3865 100644 --- a/packages/instrumentation_adapter/CHANGELOG.md +++ b/packages/e2e/CHANGELOG.md @@ -1,3 +1,26 @@ +## 0.2.1+1 + +* Updated README. + +## 0.2.1 + +* Support the v2 Android embedder. +* Print a warning if the plugin is not registered. +* Updated method channel name. +* Set a Flutter minimum SDK version. + +## 0.2.0+1 + +* Updated README. + +## 0.2.0 + +* Renamed package from instrumentation_adapter to e2e. +* Refactored example app test. +* **Breaking change**. Renamed `InstrumentationAdapterFlutterBinding` to + `E2EWidgetsFlutterBinding`. +* Updated README. + ## 0.1.4 * Migrate example to AndroidX. diff --git a/packages/instrumentation_adapter/LICENSE b/packages/e2e/LICENSE similarity index 100% rename from packages/instrumentation_adapter/LICENSE rename to packages/e2e/LICENSE diff --git a/packages/e2e/README.md b/packages/e2e/README.md new file mode 100644 index 000000000000..faeadf3b4df7 --- /dev/null +++ b/packages/e2e/README.md @@ -0,0 +1,154 @@ +# e2e + +This package enables self-driving testing of Flutter code on devices and emulators. +It adapts flutter_test results into a format that is compatible with `flutter drive` +and native Android instrumentation testing. + +iOS support is not available yet, but is planned in the future. + +## Usage + +Add a dependency on the `e2e` package in the +`dev_dependencies` section of pubspec.yaml. For plugins, do this in the +pubspec.yaml of the example app. + +Invoke `E2EWidgetsFlutterBinding.ensureInitialized()` at the start +of a test file, e.g. + +```dart +import 'package:e2e/e2e.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + testWidgets("failing test example", (WidgetTester tester) async { + expect(2 + 2, equals(5)); + }); + exit(result == 'pass' ? 0 : 1); +} +``` + +## Test locations + +It is recommended to put e2e tests in the `test/` folder of the app or package. +For example apps, if the e2e test references example app code, it should go in +`example/test/`. It is also acceptable to put e2e tests in `test_driver/` folder +so that they're alongside the runner app (see below). + +## Using Flutter driver to run tests + +`E2EWidgetsTestBinding` supports launching the on-device tests with `flutter drive`. +Note that the tests don't use the `FlutterDriver` API, they use `testWidgets` instead. + +Put the a file named `_e2e_test.dart` in the app' `test_driver` directory: + +``` +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} +``` + +To run a example app test with Flutter driver: + +``` +cd example +flutter drive test/_e2e.dart +``` + +To test plugin APIs using Flutter driver: + +``` +cd example +flutter drive --driver=test_driver/_test.dart test/_e2e.dart +``` + +## Android device testing + +Create an instrumentation test file in your application's +**android/app/src/androidTest/java/com/example/myapp/** directory (replacing +com, example, and myapp with values from your app's package name). You can name +this test file MainActivityTest.java or another name of your choice. + +``` +package com.example.myapp; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule + public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} +``` + +Update your application's **myapp/android/app/build.gradle** to make sure it +uses androidx's version of AndroidJUnitRunner and has androidx libraries as a +dependency. + +``` +android { + ... + defaultConfig { + ... + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } +} + +dependencies { + testImplementation 'junit:junit:4.12' + + // https://developer.android.com/jetpack/androidx/releases/test/#1.2.0 + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' +} +``` + +To e2e test on a local Android device (emulated or physical): + +``` +./gradlew app:connectedAndroidTest -Ptarget=`pwd`/../test_driver/_e2e.dart +``` + +## Firebase Test Lab + +If this is you first time testing with Firebase Test Lab, +you'll need to follow the guides in the +[Firebase test lab documentation](https://firebase.google.com/docs/test-lab/?gclid=EAIaIQobChMIs5qVwqW25QIV8iCtBh3DrwyUEAAYASAAEgLFU_D_BwE) +to set up a project. + +To run an e2e test on Android devices using Firebase Test Lab, use gradle commands to build an +instrumentation test for Android. + +``` +pushd android +./gradlew app:assembleAndroidTest +./gradlew app:assembleDebug -Ptarget=.dart +popd +``` + +Upload the build apks Firebase Test Lab, making sure to replace , +, , and with your values. + +``` +gcloud auth activate-service-account --key-file= +gcloud --quiet config set project +gcloud firebase test android run --type instrumentation \ + --app build/app/outputs/apk/debug/app-debug.apk \ + --test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk\ + --timeout 2m \ + --results-bucket= \ + --results-dir= +``` + +You can pass additional parameters on the command line, such as the +devices you want to test on. See +[gcloud firebase test android run](https://cloud.google.com/sdk/gcloud/reference/firebase/test/android/run). + +iOS support for Firebase Test Lab is not yet available, but is planned. diff --git a/packages/instrumentation_adapter/android/.gitignore b/packages/e2e/android/.gitignore similarity index 100% rename from packages/instrumentation_adapter/android/.gitignore rename to packages/e2e/android/.gitignore diff --git a/packages/e2e/android/build.gradle b/packages/e2e/android/build.gradle new file mode 100644 index 000000000000..08486c8c800c --- /dev/null +++ b/packages/e2e/android/build.gradle @@ -0,0 +1,67 @@ +group 'com.example.e2e' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.2.1' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + dependencies { + api 'junit:junit:4.12' + + // https://developer.android.com/jetpack/androidx/releases/test/#1.2.0 + api 'androidx.test:runner:1.2.0' + api 'androidx.test:rules:1.2.0' + api 'androidx.test.espresso:espresso-core:3.2.0' + } +} + +// TODO(amirh): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "2.1.0" + api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" + } + } + } +} \ No newline at end of file diff --git a/packages/instrumentation_adapter/android/gradle.properties b/packages/e2e/android/gradle.properties similarity index 100% rename from packages/instrumentation_adapter/android/gradle.properties rename to packages/e2e/android/gradle.properties diff --git a/packages/e2e/android/settings.gradle b/packages/e2e/android/settings.gradle new file mode 100644 index 000000000000..e5d17d080b60 --- /dev/null +++ b/packages/e2e/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'e2e' diff --git a/packages/instrumentation_adapter/android/src/main/AndroidManifest.xml b/packages/e2e/android/src/main/AndroidManifest.xml similarity index 62% rename from packages/instrumentation_adapter/android/src/main/AndroidManifest.xml rename to packages/e2e/android/src/main/AndroidManifest.xml index 3b424b6fad67..33fdf86052ab 100644 --- a/packages/instrumentation_adapter/android/src/main/AndroidManifest.xml +++ b/packages/e2e/android/src/main/AndroidManifest.xml @@ -1,3 +1,3 @@ + package="dev.flutter.e2e"> diff --git a/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java b/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java new file mode 100644 index 000000000000..ec536408c3fa --- /dev/null +++ b/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/E2EPlugin.java @@ -0,0 +1,59 @@ +// Copyright 2019 The Chromium 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 dev.flutter.plugins.e2e; + +import android.content.Context; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** E2EPlugin */ +public class E2EPlugin implements MethodCallHandler, FlutterPlugin { + private MethodChannel methodChannel; + + public static CompletableFuture> testResults = new CompletableFuture<>(); + + private static final String CHANNEL = "plugins.flutter.io/e2e"; + + /** Plugin registration. */ + public static void registerWith(Registrar registrar) { + final E2EPlugin instance = new E2EPlugin(); + instance.onAttachedToEngine(registrar.context(), registrar.messenger()); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + onAttachedToEngine( + binding.getApplicationContext(), binding.getFlutterEngine().getDartExecutor()); + } + + private void onAttachedToEngine(Context applicationContext, BinaryMessenger messenger) { + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/e2e"); + methodChannel.setMethodCallHandler(this); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + methodChannel.setMethodCallHandler(null); + methodChannel = null; + } + + @Override + public void onMethodCall(MethodCall call, Result result) { + if (call.method.equals("allTestsFinished")) { + Map results = call.argument("results"); + testResults.complete(results); + result.success(null); + } else { + result.notImplemented(); + } + } +} diff --git a/packages/instrumentation_adapter/android/src/main/java/dev/flutter/instrumentationadapter/FlutterRunner.java b/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/FlutterRunner.java similarity index 95% rename from packages/instrumentation_adapter/android/src/main/java/dev/flutter/instrumentationadapter/FlutterRunner.java rename to packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/FlutterRunner.java index c823306e022c..31f3e8431cad 100644 --- a/packages/instrumentation_adapter/android/src/main/java/dev/flutter/instrumentationadapter/FlutterRunner.java +++ b/packages/e2e/android/src/main/java/dev/flutter/plugins/e2e/FlutterRunner.java @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package dev.flutter.plugins.instrumentationadapter; +package dev.flutter.plugins.e2e; import android.app.Activity; import androidx.test.rule.ActivityTestRule; @@ -49,7 +49,7 @@ public Description getDescription() { public void run(RunNotifier notifier) { Map results = null; try { - results = InstrumentationAdapterPlugin.testResults.get(); + results = E2EPlugin.testResults.get(); } catch (ExecutionException | InterruptedException e) { throw new IllegalThreadStateException("Unable to get test results"); } diff --git a/packages/instrumentation_adapter/example/.gitignore b/packages/e2e/example/.gitignore similarity index 100% rename from packages/instrumentation_adapter/example/.gitignore rename to packages/e2e/example/.gitignore diff --git a/packages/instrumentation_adapter/example/.metadata b/packages/e2e/example/.metadata similarity index 100% rename from packages/instrumentation_adapter/example/.metadata rename to packages/e2e/example/.metadata diff --git a/packages/instrumentation_adapter/example/README.md b/packages/e2e/example/README.md similarity index 84% rename from packages/instrumentation_adapter/example/README.md rename to packages/e2e/example/README.md index f6030a4080b2..64a5e8780bc2 100644 --- a/packages/instrumentation_adapter/example/README.md +++ b/packages/e2e/example/README.md @@ -1,6 +1,6 @@ -# instrumentation_adapter_example +# e2e_example -Demonstrates how to use the instrumentation_adapter plugin. +Demonstrates how to use the e2e plugin. ## Getting Started diff --git a/packages/instrumentation_adapter/example/android/app/build.gradle b/packages/e2e/example/android/app/build.gradle similarity index 96% rename from packages/instrumentation_adapter/example/android/app/build.gradle rename to packages/e2e/example/android/app/build.gradle index 500e9ea951c6..527ed2dd38e0 100644 --- a/packages/instrumentation_adapter/example/android/app/build.gradle +++ b/packages/e2e/example/android/app/build.gradle @@ -33,7 +33,7 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.example.instrumentation_adapter_example" + applicationId "com.example.e2e_example" minSdkVersion 16 targetSdkVersion 28 versionCode flutterVersionCode.toInteger() diff --git a/packages/e2e/example/android/app/src/androidTest/java/com/example/e2e_example/EmbedderV1ActivityTest.java b/packages/e2e/example/android/app/src/androidTest/java/com/example/e2e_example/EmbedderV1ActivityTest.java new file mode 100644 index 000000000000..a526c1c8422e --- /dev/null +++ b/packages/e2e/example/android/app/src/androidTest/java/com/example/e2e_example/EmbedderV1ActivityTest.java @@ -0,0 +1,13 @@ +package com.example.e2e_example; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbedderV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbedderV1Activity.class); +} diff --git a/packages/e2e/example/android/app/src/androidTest/java/com/example/e2e_example/MainActivityTest.java b/packages/e2e/example/android/app/src/androidTest/java/com/example/e2e_example/MainActivityTest.java new file mode 100644 index 000000000000..b61c056e176d --- /dev/null +++ b/packages/e2e/example/android/app/src/androidTest/java/com/example/e2e_example/MainActivityTest.java @@ -0,0 +1,11 @@ +package com.example.e2e_example; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/instrumentation_adapter/example/android/app/src/debug/AndroidManifest.xml b/packages/e2e/example/android/app/src/debug/AndroidManifest.xml similarity index 83% rename from packages/instrumentation_adapter/example/android/app/src/debug/AndroidManifest.xml rename to packages/e2e/example/android/app/src/debug/AndroidManifest.xml index 87cc33d27c03..5d4aea26b1dd 100644 --- a/packages/instrumentation_adapter/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/e2e/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.e2e_example"> diff --git a/packages/e2e/example/android/app/src/main/AndroidManifest.xml b/packages/e2e/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..ae1a11dbcb34 --- /dev/null +++ b/packages/e2e/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + diff --git a/packages/e2e/example/android/app/src/main/java/com/example/e2e_example/EmbedderV1Activity.java b/packages/e2e/example/android/app/src/main/java/com/example/e2e_example/EmbedderV1Activity.java new file mode 100644 index 000000000000..4056569eed8c --- /dev/null +++ b/packages/e2e/example/android/app/src/main/java/com/example/e2e_example/EmbedderV1Activity.java @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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 com.example.e2e_example; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbedderV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/e2e/example/android/app/src/main/java/com/example/e2e_example/MainActivity.java b/packages/e2e/example/android/app/src/main/java/com/example/e2e_example/MainActivity.java new file mode 100644 index 000000000000..a868db8d9d1c --- /dev/null +++ b/packages/e2e/example/android/app/src/main/java/com/example/e2e_example/MainActivity.java @@ -0,0 +1,16 @@ +// Copyright 2019 The Chromium 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 com.example.e2e_example; + +import dev.flutter.plugins.e2e.E2EPlugin; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; + +public class MainActivity extends FlutterActivity { + @Override + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new E2EPlugin()); + } +} diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/drawable/launch_background.xml b/packages/e2e/example/android/app/src/main/res/drawable/launch_background.xml similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/drawable/launch_background.xml rename to packages/e2e/example/android/app/src/main/res/drawable/launch_background.xml diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/e2e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/e2e/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/e2e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/e2e/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/e2e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/e2e/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/e2e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/e2e/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/e2e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/e2e/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/instrumentation_adapter/example/android/app/src/main/res/values/styles.xml b/packages/e2e/example/android/app/src/main/res/values/styles.xml similarity index 100% rename from packages/instrumentation_adapter/example/android/app/src/main/res/values/styles.xml rename to packages/e2e/example/android/app/src/main/res/values/styles.xml diff --git a/packages/instrumentation_adapter/example/android/app/src/profile/AndroidManifest.xml b/packages/e2e/example/android/app/src/profile/AndroidManifest.xml similarity index 83% rename from packages/instrumentation_adapter/example/android/app/src/profile/AndroidManifest.xml rename to packages/e2e/example/android/app/src/profile/AndroidManifest.xml index 87cc33d27c03..5d4aea26b1dd 100644 --- a/packages/instrumentation_adapter/example/android/app/src/profile/AndroidManifest.xml +++ b/packages/e2e/example/android/app/src/profile/AndroidManifest.xml @@ -1,5 +1,5 @@ + package="com.example.e2e_example"> diff --git a/packages/instrumentation_adapter/example/android/build.gradle b/packages/e2e/example/android/build.gradle similarity index 100% rename from packages/instrumentation_adapter/example/android/build.gradle rename to packages/e2e/example/android/build.gradle diff --git a/packages/instrumentation_adapter/example/android/gradle.properties b/packages/e2e/example/android/gradle.properties similarity index 100% rename from packages/instrumentation_adapter/example/android/gradle.properties rename to packages/e2e/example/android/gradle.properties diff --git a/packages/instrumentation_adapter/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/e2e/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 100% rename from packages/instrumentation_adapter/example/android/gradle/wrapper/gradle-wrapper.properties rename to packages/e2e/example/android/gradle/wrapper/gradle-wrapper.properties diff --git a/packages/instrumentation_adapter/example/android/settings.gradle b/packages/e2e/example/android/settings.gradle similarity index 100% rename from packages/instrumentation_adapter/example/android/settings.gradle rename to packages/e2e/example/android/settings.gradle diff --git a/packages/instrumentation_adapter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/e2e/example/ios/Flutter/AppFrameworkInfo.plist similarity index 100% rename from packages/instrumentation_adapter/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/e2e/example/ios/Flutter/AppFrameworkInfo.plist diff --git a/packages/instrumentation_adapter/example/ios/Flutter/Debug.xcconfig b/packages/e2e/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/instrumentation_adapter/example/ios/Flutter/Debug.xcconfig rename to packages/e2e/example/ios/Flutter/Debug.xcconfig diff --git a/packages/instrumentation_adapter/example/ios/Flutter/Release.xcconfig b/packages/e2e/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/instrumentation_adapter/example/ios/Flutter/Release.xcconfig rename to packages/e2e/example/ios/Flutter/Release.xcconfig diff --git a/packages/instrumentation_adapter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/e2e/example/ios/Runner.xcodeproj/project.pbxproj similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/e2e/example/ios/Runner.xcodeproj/project.pbxproj diff --git a/packages/instrumentation_adapter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/e2e/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/e2e/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme diff --git a/packages/instrumentation_adapter/example/ios/Runner/AppDelegate.h b/packages/e2e/example/ios/Runner/AppDelegate.h similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/AppDelegate.h rename to packages/e2e/example/ios/Runner/AppDelegate.h diff --git a/packages/instrumentation_adapter/example/ios/Runner/AppDelegate.m b/packages/e2e/example/ios/Runner/AppDelegate.m similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/AppDelegate.m rename to packages/e2e/example/ios/Runner/AppDelegate.m diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json rename to packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png rename to packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png diff --git a/packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md rename to packages/e2e/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md diff --git a/packages/instrumentation_adapter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/e2e/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/e2e/example/ios/Runner/Base.lproj/LaunchScreen.storyboard diff --git a/packages/instrumentation_adapter/example/ios/Runner/Base.lproj/Main.storyboard b/packages/e2e/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/e2e/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/instrumentation_adapter/example/ios/Runner/Info.plist b/packages/e2e/example/ios/Runner/Info.plist similarity index 96% rename from packages/instrumentation_adapter/example/ios/Runner/Info.plist rename to packages/e2e/example/ios/Runner/Info.plist index 613203aaeb06..62f6fbb5c02c 100644 --- a/packages/instrumentation_adapter/example/ios/Runner/Info.plist +++ b/packages/e2e/example/ios/Runner/Info.plist @@ -11,7 +11,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - instrumentation_adapter_example + e2e_example CFBundlePackageType APPL CFBundleShortVersionString diff --git a/packages/instrumentation_adapter/example/ios/Runner/main.m b/packages/e2e/example/ios/Runner/main.m similarity index 100% rename from packages/instrumentation_adapter/example/ios/Runner/main.m rename to packages/e2e/example/ios/Runner/main.m diff --git a/packages/instrumentation_adapter/example/lib/main.dart b/packages/e2e/example/lib/main.dart similarity index 100% rename from packages/instrumentation_adapter/example/lib/main.dart rename to packages/e2e/example/lib/main.dart diff --git a/packages/instrumentation_adapter/example/pubspec.yaml b/packages/e2e/example/pubspec.yaml similarity index 73% rename from packages/instrumentation_adapter/example/pubspec.yaml rename to packages/e2e/example/pubspec.yaml index 55d547736b24..456a62f2b36a 100644 --- a/packages/instrumentation_adapter/example/pubspec.yaml +++ b/packages/e2e/example/pubspec.yaml @@ -1,9 +1,10 @@ -name: instrumentation_adapter_example -description: Demonstrates how to use the instrumentation_adapter plugin. +name: e2e_example +description: Demonstrates how to use the e2e plugin. publish_to: 'none' environment: sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.6.7 <2.0.0" dependencies: flutter: @@ -16,7 +17,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - instrumentation_adapter: + e2e: path: ../ # For information on the generic Dart part of this file, see the diff --git a/packages/instrumentation_adapter/example/test_driver/widget.dart b/packages/e2e/example/test_driver/example_e2e.dart similarity index 82% rename from packages/instrumentation_adapter/example/test_driver/widget.dart rename to packages/e2e/example/test_driver/example_e2e.dart index 109002c86790..e91dd4d0ce9f 100644 --- a/packages/instrumentation_adapter/example/test_driver/widget.dart +++ b/packages/e2e/example/test_driver/example_e2e.dart @@ -8,12 +8,12 @@ import 'dart:io' show Platform; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:instrumentation_adapter/instrumentation_adapter.dart'; +import 'package:e2e/e2e.dart'; -import 'package:instrumentation_adapter_example/main.dart'; +import 'package:e2e_example/main.dart'; void main() { - InstrumentationAdapterFlutterBinding.ensureInitialized(); + E2EWidgetsFlutterBinding.ensureInitialized(); testWidgets('verify text', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(MyApp()); diff --git a/packages/e2e/example/test_driver/example_e2e_test.dart b/packages/e2e/example/test_driver/example_e2e_test.dart new file mode 100644 index 000000000000..4f38746ce76c --- /dev/null +++ b/packages/e2e/example/test_driver/example_e2e_test.dart @@ -0,0 +1,12 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/instrumentation_adapter/ios/.gitignore b/packages/e2e/ios/.gitignore similarity index 100% rename from packages/instrumentation_adapter/ios/.gitignore rename to packages/e2e/ios/.gitignore diff --git a/packages/instrumentation_adapter/ios/Assets/.gitkeep b/packages/e2e/ios/Assets/.gitkeep similarity index 100% rename from packages/instrumentation_adapter/ios/Assets/.gitkeep rename to packages/e2e/ios/Assets/.gitkeep diff --git a/packages/e2e/ios/Classes/E2EPlugin.h b/packages/e2e/ios/Classes/E2EPlugin.h new file mode 100644 index 000000000000..1411dce3f1da --- /dev/null +++ b/packages/e2e/ios/Classes/E2EPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface E2EPlugin : NSObject +@end diff --git a/packages/instrumentation_adapter/ios/Classes/InstrumentationAdapterPlugin.m b/packages/e2e/ios/Classes/E2EPlugin.m similarity index 50% rename from packages/instrumentation_adapter/ios/Classes/InstrumentationAdapterPlugin.m rename to packages/e2e/ios/Classes/E2EPlugin.m index 704a5b05e031..4f19f3a2f961 100644 --- a/packages/instrumentation_adapter/ios/Classes/InstrumentationAdapterPlugin.m +++ b/packages/e2e/ios/Classes/E2EPlugin.m @@ -1,11 +1,11 @@ -#import "InstrumentationAdapterPlugin.h" +#import "E2EPlugin.h" -@implementation InstrumentationAdapterPlugin +@implementation E2EPlugin + (void)registerWithRegistrar:(NSObject*)registrar { - FlutterMethodChannel* channel = [FlutterMethodChannel - methodChannelWithName:@"dev.flutter/InstrumentationAdapterFlutterBinding" - binaryMessenger:[registrar messenger]]; - InstrumentationAdapterPlugin* instance = [[InstrumentationAdapterPlugin alloc] init]; + FlutterMethodChannel* channel = + [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.dev/e2e" + binaryMessenger:[registrar messenger]]; + E2EPlugin* instance = [[E2EPlugin alloc] init]; [registrar addMethodCallDelegate:instance channel:channel]; } diff --git a/packages/instrumentation_adapter/ios/instrumentation_adapter.podspec b/packages/e2e/ios/e2e.podspec similarity index 83% rename from packages/instrumentation_adapter/ios/instrumentation_adapter.podspec rename to packages/e2e/ios/e2e.podspec index 45edadad4cab..cb0f3bcc4088 100644 --- a/packages/instrumentation_adapter/ios/instrumentation_adapter.podspec +++ b/packages/e2e/ios/e2e.podspec @@ -2,13 +2,13 @@ # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html # Pod::Spec.new do |s| - s.name = 'instrumentation_adapter' + s.name = 'e2e' s.version = '0.0.1' - s.summary = 'Instrumentation adapter.' + s.summary = 'Adapter for e2e tests.' s.description = <<-DESC Runs tests that use the flutter_test API as integration tests. DESC - s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/instrumentation_adapter' + s.homepage = 'https://github.com/flutter/plugins/tree/master/packages/e2e' s.license = { :file => '../LICENSE' } s.author = { 'Flutter Team' => 'flutter-dev@googlegroups.com' } s.source = { :path => '.' } diff --git a/packages/instrumentation_adapter/lib/instrumentation_adapter.dart b/packages/e2e/lib/e2e.dart similarity index 86% rename from packages/instrumentation_adapter/lib/instrumentation_adapter.dart rename to packages/e2e/lib/e2e.dart index 9999452234cd..2f8dc0ebac90 100644 --- a/packages/instrumentation_adapter/lib/instrumentation_adapter.dart +++ b/packages/e2e/lib/e2e.dart @@ -10,16 +10,15 @@ import 'package:flutter/widgets.dart'; /// A subclass of [LiveTestWidgetsFlutterBinding] that reports tests results /// on a channel to adapt them to native instrumentation test format. -class InstrumentationAdapterFlutterBinding - extends LiveTestWidgetsFlutterBinding { - InstrumentationAdapterFlutterBinding() { +class E2EWidgetsFlutterBinding extends LiveTestWidgetsFlutterBinding { + E2EWidgetsFlutterBinding() { // TODO(jackson): Report test results as they arrive tearDownAll(() async { try { await _channel.invokeMethod( 'allTestsFinished', {'results': _results}); } on MissingPluginException { - // Tests were run on the host rather than a device. + print('Warning: E2E test plugin was not detected.'); } if (!_allTestsPassed.isCompleted) _allTestsPassed.complete(true); }); @@ -29,14 +28,13 @@ class InstrumentationAdapterFlutterBinding static WidgetsBinding ensureInitialized() { if (WidgetsBinding.instance == null) { - InstrumentationAdapterFlutterBinding(); + E2EWidgetsFlutterBinding(); } - assert(WidgetsBinding.instance is InstrumentationAdapterFlutterBinding); + assert(WidgetsBinding.instance is E2EWidgetsFlutterBinding); return WidgetsBinding.instance; } - static const MethodChannel _channel = - MethodChannel('dev.flutter/InstrumentationAdapterFlutterBinding'); + static const MethodChannel _channel = MethodChannel('plugins.flutter.io/e2e'); static Map _results = {}; diff --git a/packages/instrumentation_adapter/pubspec.yaml b/packages/e2e/pubspec.yaml similarity index 64% rename from packages/instrumentation_adapter/pubspec.yaml rename to packages/e2e/pubspec.yaml index 1d8c258d8097..b3287d732014 100644 --- a/packages/instrumentation_adapter/pubspec.yaml +++ b/packages/e2e/pubspec.yaml @@ -1,11 +1,12 @@ -name: instrumentation_adapter +name: e2e description: Runs tests that use the flutter_test API as integration tests. -version: 0.1.4 +version: 0.2.1+1 author: Flutter Team -homepage: https://github.com/flutter/plugins/tree/master/packages/instrumentation_adapter +homepage: https://github.com/flutter/plugins/tree/master/packages/e2e environment: sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.6.7 <2.0.0" dependencies: flutter: @@ -15,5 +16,5 @@ dependencies: flutter: plugin: - androidPackage: dev.flutter.plugins.instrumentationadapter - pluginClass: InstrumentationAdapterPlugin + androidPackage: dev.flutter.plugins.e2e + pluginClass: E2EPlugin diff --git a/packages/flutter_plugin_android_lifecycle/.gitignore b/packages/flutter_plugin_android_lifecycle/.gitignore new file mode 100644 index 000000000000..e9dc58d3d6e2 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/.gitignore @@ -0,0 +1,7 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ diff --git a/packages/flutter_plugin_android_lifecycle/.metadata b/packages/flutter_plugin_android_lifecycle/.metadata new file mode 100644 index 000000000000..c360d84244ac --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 0e605cc4dd83137f785769dea5e8ae7da1afb361 + channel: master + +project_type: plugin diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md new file mode 100644 index 000000000000..fb3d8d83cb62 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md @@ -0,0 +1,11 @@ +## 1.0.2 + +* Adapt to the embedding API changes in https://github.com/flutter/engine/pull/13280 (only supports Activity Lifecycle). + +## 1.0.1 +* Register the E2E plugin in the example app. + +## 1.0.0 + +* Introduces a `FlutterLifecycleAdapter`, which can be used by other plugins to obtain a `Lifecycle` + reference from a `FlutterPluginBinding`. diff --git a/packages/flutter_plugin_android_lifecycle/LICENSE b/packages/flutter_plugin_android_lifecycle/LICENSE new file mode 100644 index 000000000000..0c382ce171cc --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/flutter_plugin_android_lifecycle/README.md b/packages/flutter_plugin_android_lifecycle/README.md new file mode 100644 index 000000000000..25f4d9efd056 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/README.md @@ -0,0 +1,41 @@ +# Flutter Android Lifecycle Plugin + +[![pub package](https://img.shields.io/pub/v/flutter_plugin_android_lifecycle.svg)](https://pub.dartlang.org/packages/flutter_plugin_android_lifecycle) + +A Flutter plugin for Android to allow other Flutter plugins to access Android `Lifecycle` objects +in the plugin's binding. + +The purpose of having this plugin instead of exposing an Android `Lifecycle` object in the engine's +Android embedding plugins API is to force plugins to have a pub constraint that signifies the +major version of the Android `Lifecycle` API they expect. + +## Installation + +Add `flutter_plugin_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.io/using-packages/). + +## Example + +Use a `FlutterLifecycleAdapter` within another Flutter plugin's Android implementation, as shown +below: + +```java +import androidx.lifecycle.Lifecycle; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding; +import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; + +public class MyPlugin implements FlutterPlugin, ActivityAware { + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + Lifecycle lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); + // Use lifecycle as desired. + } + + //... +} +``` + +[Feedback welcome](https://github.com/flutter/flutter/issues) and +[Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome! diff --git a/packages/flutter_plugin_android_lifecycle/android/.gitignore b/packages/flutter_plugin_android_lifecycle/android/.gitignore new file mode 100644 index 000000000000..c6cbe562a427 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/packages/flutter_plugin_android_lifecycle/android/build.gradle b/packages/flutter_plugin_android_lifecycle/android/build.gradle new file mode 100644 index 000000000000..71b5ee29a112 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/build.gradle @@ -0,0 +1,63 @@ +group 'io.flutter.plugins.flutter_plugin_android_lifecycle' +version '1.0' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 28 + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + + dependencies { + implementation "androidx.annotation:annotation:1.1.0" + } +} + +// TODO(amirh): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "2.1.0" + api "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + api "androidx.lifecycle:lifecycle-runtime:$lifecycle_version" + } + } + } +} diff --git a/packages/flutter_plugin_android_lifecycle/android/gradle.properties b/packages/flutter_plugin_android_lifecycle/android/gradle.properties new file mode 100644 index 000000000000..38c8d4544ff1 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/flutter_plugin_android_lifecycle/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_plugin_android_lifecycle/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..019065d1d650 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip diff --git a/packages/flutter_plugin_android_lifecycle/android/settings.gradle b/packages/flutter_plugin_android_lifecycle/android/settings.gradle new file mode 100644 index 000000000000..70836e6e7200 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'flutter_plugin_android_lifecycle' diff --git a/packages/flutter_plugin_android_lifecycle/android/src/main/AndroidManifest.xml b/packages/flutter_plugin_android_lifecycle/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..aece929d2458 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/embedding/engine/plugins/lifecycle/FlutterLifecycleAdapter.java b/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/embedding/engine/plugins/lifecycle/FlutterLifecycleAdapter.java new file mode 100644 index 000000000000..c55c35c6fa0c --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/embedding/engine/plugins/lifecycle/FlutterLifecycleAdapter.java @@ -0,0 +1,69 @@ +// Copyright 2019 The Chromium 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.plugins.lifecycle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; +import io.flutter.Log; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** Provides a static method for extracting lifecycle objects from Flutter plugin bindings. */ +public class FlutterLifecycleAdapter { + private static final String TAG = "FlutterLifecycleAdapter"; + + /** + * Returns the lifecycle object for the activity a plugin is bound to. + * + *

Returns null if the Flutter engine version does not include the lifecycle extraction code. + * (this probably means the Flutter engine version is too old). + */ + @Nullable + public static Lifecycle getActivityLifecycle( + @NonNull ActivityPluginBinding activityPluginBinding) { + try { + Method getLifecycle = ActivityPluginBinding.class.getMethod("getLifecycle"); + Object hiddenLifecycle = getLifecycle.invoke(activityPluginBinding); + return getHiddenLifecycle(hiddenLifecycle); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalAccessException + | InvocationTargetException e) { + Log.w( + TAG, + "You are attempting to use Flutter plugins that are newer than your" + + " version of Flutter. Plugins may not work as expected."); + } + return null; + } + + // TODO(amirh): add a getter for a Service lifecycle. + // https://github.com/flutter/flutter/issues/43741 + + /** + * Returns the lifecycle object for the given Flutter plugin binding. + * + *

Returns null if the Flutter engine version does not include the lifecycle extraction code. + * (this probably means the Flutter engine version is too old). + */ + @NonNull + private static Lifecycle getHiddenLifecycle(@NonNull Object reference) + throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, + ClassNotFoundException { + Class hiddenLifecycleClass = + Class.forName("io.flutter.embedding.engine.plugins.lifecycle.HiddenLifecycleReference"); + + if (!reference.getClass().equals(hiddenLifecycleClass)) { + throw new IllegalArgumentException( + "The reference argument must be of type HiddenLifecycleReference. Was actually " + + reference); + } + + Method getLifecycle = reference.getClass().getMethod("getLifecycle"); + return (Lifecycle) getLifecycle.invoke(reference); + } +} diff --git a/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java b/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java new file mode 100644 index 000000000000..7abf1d67667c --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/android/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle/FlutterAndroidLifecyclePlugin.java @@ -0,0 +1,19 @@ +// Copyright 2019 The Chromium 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.plugins.flutter_plugin_android_lifecycle; + +import io.flutter.plugin.common.PluginRegistry.Registrar; + +/** + * Plugin class that exists because the Flutter tool expects such a class to exist for every Android + * plugin. + * + *

DO NOT USE THIS CLASS. + */ +public class FlutterAndroidLifecyclePlugin { + public static void registerWith(Registrar registrar) { + // no-op + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/.gitignore b/packages/flutter_plugin_android_lifecycle/example/.gitignore new file mode 100644 index 000000000000..437cb45872e1 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/.gitignore @@ -0,0 +1,36 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/packages/flutter_plugin_android_lifecycle/example/.metadata b/packages/flutter_plugin_android_lifecycle/example/.metadata new file mode 100644 index 000000000000..2962833dfb9c --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 0e605cc4dd83137f785769dea5e8ae7da1afb361 + channel: master + +project_type: app diff --git a/packages/flutter_plugin_android_lifecycle/example/android/.gitignore b/packages/flutter_plugin_android_lifecycle/example/android/.gitignore new file mode 100644 index 000000000000..bc2100d8f75e --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/.gitignore @@ -0,0 +1,7 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/build.gradle b/packages/flutter_plugin_android_lifecycle/example/android/app/build.gradle new file mode 100644 index 000000000000..b2f6139734bd --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/build.gradle @@ -0,0 +1,61 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "io.flutter.plugins.flutter_plugin_android_lifecycle_example" + minSdkVersion 16 + targetSdkVersion 28 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test:runner:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' +} diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/EmbeddingV1ActivityTest.java b/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..a5ad2176b4d0 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/EmbeddingV1ActivityTest.java @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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.plugins.flutter_plugin_android_lifecycle_example; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/MainActivityTest.java b/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/MainActivityTest.java new file mode 100644 index 000000000000..05057c1a697e --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/androidTest/java/io/flutter/plugins/flutter_plugin_android_lifecycle/MainActivityTest.java @@ -0,0 +1,15 @@ +// Copyright 2019 The Chromium 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.plugins.flutter_plugin_android_lifecycle_example; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/debug/AndroidManifest.xml b/packages/flutter_plugin_android_lifecycle/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000000..d7586285fc1e --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/instrumentation_adapter/example/android/app/src/main/AndroidManifest.xml b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/AndroidManifest.xml similarity index 68% rename from packages/instrumentation_adapter/example/android/app/src/main/AndroidManifest.xml rename to packages/flutter_plugin_android_lifecycle/example/android/app/src/main/AndroidManifest.xml index 653fa39a669d..eddf84b10d4c 100644 --- a/packages/instrumentation_adapter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,5 @@ - + package="io.flutter.plugins.flutter_plugin_android_lifecycle_example"> - - + + diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/EmbeddingV1Activity.java b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/EmbeddingV1Activity.java new file mode 100644 index 000000000000..a18c01b779ff --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/EmbeddingV1Activity.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.flutter_plugin_android_lifecycle_example; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java new file mode 100644 index 000000000000..2ea33493eb3c --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/java/io/flutter/plugins/flutter_plugin_android_lifecycle_example/MainActivity.java @@ -0,0 +1,59 @@ +// Copyright 2019 The Chromium 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.plugins.flutter_plugin_android_lifecycle_example; + +import android.util.Log; +import androidx.lifecycle.Lifecycle; +import dev.flutter.plugins.e2e.E2EPlugin; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; + +public class MainActivity extends FlutterActivity { + private static final String TAG = "MainActivity"; + + @Override + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new TestPlugin()); + flutterEngine.getPlugins().add(new E2EPlugin()); + } + + private static class TestPlugin implements FlutterPlugin, ActivityAware { + + @Override + public void onAttachedToEngine(FlutterPluginBinding flutterPluginBinding) {} + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) {} + + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + Lifecycle lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); + + if (lifecycle == null) { + Log.d(TAG, "Couldn't obtained Lifecycle!"); + return; + // TODO(amirh): make this throw once the lifecycle API is available on stable. + // https://github.com/flutter/flutter/issues/42875 + // throw new RuntimeException( + // "The FlutterLifecycleAdapter did not correctly provide a Lifecycle instance. Source reference: " + // + flutterPluginBinding.getLifecycle()); + } + Log.d(TAG, "Successfully obtained Lifecycle: " + lifecycle); + } + + @Override + public void onDetachedFromActivity() {} + + @Override + public void onDetachedFromActivityForConfigChanges() {} + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) {} + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/drawable/launch_background.xml b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000000..304732f88420 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000000..db77bb4b7b09 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000000..17987b79bb8a Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000000..09d4391482be Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000000..d5f1c8d34e7a Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000000..4d6372eebdb2 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/values/styles.xml b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..00fa4417cfbe --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ + + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/android/app/src/profile/AndroidManifest.xml b/packages/flutter_plugin_android_lifecycle/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000000..d7586285fc1e --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/android/build.gradle b/packages/flutter_plugin_android_lifecycle/example/android/build.gradle new file mode 100644 index 000000000000..e0d7ae2c11af --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties b/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties new file mode 100644 index 000000000000..38c8d4544ff1 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/flutter_plugin_android_lifecycle/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/flutter_plugin_android_lifecycle/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..296b146b7318 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle b/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle new file mode 100644 index 000000000000..5a2f14fb18f6 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/android/settings.gradle @@ -0,0 +1,15 @@ +include ':app' + +def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + +def plugins = new Properties() +def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') +if (pluginsFile.exists()) { + pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } +} + +plugins.each { name, path -> + def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() + include ":$name" + project(":$name").projectDir = pluginDirectory +} diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/.gitignore b/packages/flutter_plugin_android_lifecycle/example/ios/.gitignore new file mode 100644 index 000000000000..f78c1480b6dd --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/.gitignore @@ -0,0 +1,31 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/AppFrameworkInfo.plist b/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 000000000000..6b4c0f78a785 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/Debug.xcconfig b/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 000000000000..e8efba114687 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/Release.xcconfig b/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/Release.xcconfig new file mode 100644 index 000000000000..399e9340e6f6 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner.xcodeproj/project.pbxproj b/packages/flutter_plugin_android_lifecycle/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..f42fc07b1c19 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,576 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 68BFFC9A2252F6377926CCB6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D97B2D435F77384E1832544A /* libPods-Runner.a */; }; + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; + 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, + 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; + 48B2B2D61E102CB7FCA66327 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; + 8A495AA36DFBF39C3BD5D917 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9E27BB0D8AE008E9718C1EC3 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + D97B2D435F77384E1832544A /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, + 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, + 68BFFC9A2252F6377926CCB6 /* libPods-Runner.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 29946A38AEAEDCD95716766D /* Pods */ = { + isa = PBXGroup; + children = ( + 8A495AA36DFBF39C3BD5D917 /* Pods-Runner.debug.xcconfig */, + 9E27BB0D8AE008E9718C1EC3 /* Pods-Runner.release.xcconfig */, + 48B2B2D61E102CB7FCA66327 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B80C3931E831B6300D905FE /* App.framework */, + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEBA1CF902C7004384FC /* Flutter.framework */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 29946A38AEAEDCD95716766D /* Pods */, + C6B60E52AC0C0C398A9D6E3E /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, + 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + ); + path = Runner; + sourceTree = ""; + }; + 97C146F11CF9000F007C117D /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 97C146F21CF9000F007C117D /* main.m */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + C6B60E52AC0C0C398A9D6E3E /* Frameworks */ = { + isa = PBXGroup; + children = ( + D97B2D435F77384E1832544A /* libPods-Runner.a */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + B0349D7BFB658C43C3407041 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 2D345E120F865FCD8BCE231E /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = "The Chromium Authors"; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 2D345E120F865FCD8BCE231E /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + B0349D7BFB658C43C3407041 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, + 97C146F31CF9000F007C117D /* main.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.flutterAndroidLifecycleExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.flutterAndroidLifecycleExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.flutterAndroidLifecycleExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/flutter_plugin_android_lifecycle/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..a28140cfdb3f --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/AppDelegate.h b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/AppDelegate.h new file mode 100644 index 000000000000..36e21bbf9cf4 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/AppDelegate.m b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/AppDelegate.m new file mode 100644 index 000000000000..59a72e90be12 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#include "AppDelegate.h" +#include "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..d36b1fab2d9d --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 000000000000..dc9ada4725e9 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 000000000000..28c6bf03016f Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 000000000000..2ccbfd967d96 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 000000000000..f091b6b0bca8 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 000000000000..4cde12118dda Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 000000000000..d0ef06e7edb8 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 000000000000..dcdc2306c285 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 000000000000..2ccbfd967d96 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 000000000000..c8f9ed8f5cee Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 000000000000..a6d6b8609df0 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 000000000000..a6d6b8609df0 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 000000000000..75b2d164a5a9 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 000000000000..c4df70d39da7 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 000000000000..6a84f41e14e2 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 000000000000..d0e1f5853602 Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000000..89c2725b70f1 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 000000000000..f2e259c7c939 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Base.lproj/Main.storyboard b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 000000000000..f3c28516fb38 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Info.plist b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Info.plist new file mode 100644 index 000000000000..8526e1f7226c --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + flutter_plugin_android_lifecycle_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/flutter_plugin_android_lifecycle/example/ios/Runner/main.m b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/main.m new file mode 100644 index 000000000000..dff6597e4513 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/ios/Runner/main.m @@ -0,0 +1,9 @@ +#import +#import +#import "AppDelegate.h" + +int main(int argc, char* argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/lib/main.dart b/packages/flutter_plugin_android_lifecycle/example/lib/main.dart new file mode 100644 index 000000000000..12339516b156 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/lib/main.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +void main() => runApp(MyApp()); + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Sample flutter_plugin_android_lifecycle usage'), + ), + ), + ); + } +} diff --git a/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml new file mode 100644 index 000000000000..a3fc63311cc6 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/pubspec.yaml @@ -0,0 +1,21 @@ +name: flutter_plugin_android_lifecycle_example +description: Demonstrates how to use the flutter_plugin_android_lifecycle plugin. +publish_to: 'none' + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + e2e: "^0.2.1" + +dev_dependencies: + flutter_test: + sdk: flutter + + flutter_plugin_android_lifecycle: + path: ../ + +flutter: + uses-material-design: true diff --git a/packages/flutter_plugin_android_lifecycle/example/test_driver/flutter_plugin_android_lifecycle_e2e.dart b/packages/flutter_plugin_android_lifecycle/example/test_driver/flutter_plugin_android_lifecycle_e2e.dart new file mode 100644 index 000000000000..5abead07d132 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/example/test_driver/flutter_plugin_android_lifecycle_e2e.dart @@ -0,0 +1,15 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:e2e/e2e.dart'; +import 'package:flutter_plugin_android_lifecycle_example/main.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + testWidgets('loads', (WidgetTester tester) async { + await tester.pumpWidget(MyApp()); + }); +} diff --git a/packages/flutter_plugin_android_lifecycle/ios/.gitignore b/packages/flutter_plugin_android_lifecycle/ios/.gitignore new file mode 100644 index 000000000000..aa479fd3ce8a --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/packages/flutter_plugin_android_lifecycle/ios/Assets/.gitkeep b/packages/flutter_plugin_android_lifecycle/ios/Assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/flutter_plugin_android_lifecycle/ios/Classes/FlutterAndroidLifecyclePlugin.h b/packages/flutter_plugin_android_lifecycle/ios/Classes/FlutterAndroidLifecyclePlugin.h new file mode 100644 index 000000000000..a554ce0500c6 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/ios/Classes/FlutterAndroidLifecyclePlugin.h @@ -0,0 +1,8 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import + +@interface FlutterAndroidLifecyclePlugin : NSObject +@end diff --git a/packages/flutter_plugin_android_lifecycle/ios/Classes/FlutterAndroidLifecyclePlugin.m b/packages/flutter_plugin_android_lifecycle/ios/Classes/FlutterAndroidLifecyclePlugin.m new file mode 100644 index 000000000000..38cffd362da7 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/ios/Classes/FlutterAndroidLifecyclePlugin.m @@ -0,0 +1,10 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "FlutterAndroidLifecyclePlugin.h" + +@implementation FlutterAndroidLifecyclePlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { +} +@end diff --git a/packages/flutter_plugin_android_lifecycle/ios/flutter_plugin_android_lifecycle.podspec b/packages/flutter_plugin_android_lifecycle/ios/flutter_plugin_android_lifecycle.podspec new file mode 100644 index 000000000000..9e17e46d263b --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/ios/flutter_plugin_android_lifecycle.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint flutter_plugin_android_lifecycle.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flutter_plugin_android_lifecycle' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.public_header_files = 'Classes/**/*.h' + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } +end diff --git a/packages/flutter_plugin_android_lifecycle/lib/flutter_plugin_android_lifecycle.dart b/packages/flutter_plugin_android_lifecycle/lib/flutter_plugin_android_lifecycle.dart new file mode 100644 index 000000000000..4352552e3eda --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/lib/flutter_plugin_android_lifecycle.dart @@ -0,0 +1,2 @@ +// The flutter_plugin_android_lifecycle plugin only provides a Java API +// for use by Android plugins. This plugin has no Dart code. diff --git a/packages/flutter_plugin_android_lifecycle/pubspec.yaml b/packages/flutter_plugin_android_lifecycle/pubspec.yaml new file mode 100644 index 000000000000..b934e8af3113 --- /dev/null +++ b/packages/flutter_plugin_android_lifecycle/pubspec.yaml @@ -0,0 +1,23 @@ +name: flutter_plugin_android_lifecycle +description: Flutter plugin for accessing an Android Lifecycle within other plugins. +version: 1.0.2 +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/flutter_plugin_android_lifecycle + +environment: + sdk: ">=2.1.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +flutter: + plugin: + platforms: + android: + package: io.flutter.plugins.flutter_plugin_android_lifecycle + pluginClass: FlutterAndroidLifecyclePlugin diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md index 45ecfbeb3e3d..2c83c6449da9 100644 --- a/packages/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,15 @@ +## 0.5.21+10 + +* Cast error.code to unsigned long to avoid using NSInteger as %ld format warnings. + +## 0.5.21+9 + +* Remove AndroidX warnings. + +## 0.5.21+8 + +* Add NS_ASSUME_NONNULL_* macro to reduce iOS compiler warnings. + ## 0.5.21+7 * Create a clone of cached elements in GoogleMap (Polyline, Polygon, etc.) to detect modifications diff --git a/packages/google_maps_flutter/android/build.gradle b/packages/google_maps_flutter/android/build.gradle index e7bc80c42c52..f13de0b79a18 100644 --- a/packages/google_maps_flutter/android/build.gradle +++ b/packages/google_maps_flutter/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "google_maps_flutter"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.googlemaps' version '1.0-SNAPSHOT' diff --git a/packages/google_maps_flutter/example/android/app/gradle.properties b/packages/google_maps_flutter/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/google_maps_flutter/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/google_maps_flutter/example/android/gradle.properties b/packages/google_maps_flutter/example/android/gradle.properties index 7be3d8b46841..38c8d4544ff1 100644 --- a/packages/google_maps_flutter/example/android/gradle.properties +++ b/packages/google_maps_flutter/example/android/gradle.properties @@ -1,2 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.h b/packages/google_maps_flutter/ios/Classes/GoogleMapController.h index 02f444504a6a..339f789c1a89 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.h +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.h @@ -9,6 +9,8 @@ #import "GoogleMapPolygonController.h" #import "GoogleMapPolylineController.h" +NS_ASSUME_NONNULL_BEGIN + // Defines map UI options writable from Flutter. @protocol FLTGoogleMapOptionsSink - (void)setCameraTargetBounds:(GMSCoordinateBounds *)bounds; @@ -46,3 +48,5 @@ @interface FLTGoogleMapFactory : NSObject - (instancetype)initWithRegistrar:(NSObject *)registrar; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h index 7b8bccd7b462..24d42532ce7d 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h @@ -6,6 +6,8 @@ #import #import "GoogleMapController.h" +NS_ASSUME_NONNULL_BEGIN + // Defines marker UI options writable from Flutter. @protocol FLTGoogleMapMarkerOptionsSink - (void)setAlpha:(float)alpha; @@ -43,3 +45,5 @@ - (void)onMarkerDragEnd:(NSString*)markerId coordinate:(CLLocationCoordinate2D)coordinate; - (void)onInfoWindowTap:(NSString*)markerId; @end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m index 91b4e7bce2b7..2f0d4a989b9d 100644 --- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m +++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m @@ -189,7 +189,7 @@ static void InterpretInfoWindow(id sink, NSDictio } else { NSString* error = [NSString stringWithFormat:@"'fromAssetImage' should have exactly 3 arguments. Got: %lu", - iconData.count]; + (unsigned long)iconData.count]; NSException* exception = [NSException exceptionWithName:@"InvalidBitmapDescriptor" reason:error userInfo:nil]; @@ -209,7 +209,7 @@ static void InterpretInfoWindow(id sink, NSDictio } else { NSString* error = [NSString stringWithFormat:@"fromBytes should have exactly one argument, the bytes. Got: %lu", - iconData.count]; + (unsigned long)iconData.count]; NSException* exception = [NSException exceptionWithName:@"InvalidByteDescriptor" reason:error userInfo:nil]; diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml index 5e8e92eb6740..3e8b80283e6e 100644 --- a/packages/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter -version: 0.5.21+7 +version: 0.5.21+9 dependencies: flutter: diff --git a/packages/google_sign_in/CHANGELOG.md b/packages/google_sign_in/CHANGELOG.md index b46be247f317..300eba20968e 100644 --- a/packages/google_sign_in/CHANGELOG.md +++ b/packages/google_sign_in/CHANGELOG.md @@ -1,6 +1,15 @@ +## 4.0.11 + +* Update iOS CocoaPod dependency to 5.0 to fix deprecated API usage issue. + +## 4.0.10 + +* Remove AndroidX warning. + ## 4.0.9 * Update and migrate iOS example project. +* Define clang module for iOS. ## 4.0.8 diff --git a/packages/google_sign_in/android/build.gradle b/packages/google_sign_in/android/build.gradle index cb7227abc3f7..144739559b5c 100755 --- a/packages/google_sign_in/android/build.gradle +++ b/packages/google_sign_in/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "google_sign_in"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.googlesignin' version '1.0-SNAPSHOT' diff --git a/packages/google_sign_in/example/android/app/gradle.properties b/packages/google_sign_in/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/google_sign_in/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/google_sign_in/ios/Classes/GoogleSignInPlugin.m b/packages/google_sign_in/ios/Classes/GoogleSignInPlugin.m index 483bc5c6e81c..716eb36585c4 100644 --- a/packages/google_sign_in/ios/Classes/GoogleSignInPlugin.m +++ b/packages/google_sign_in/ios/Classes/GoogleSignInPlugin.m @@ -29,7 +29,7 @@ details:error.localizedDescription]; } -@interface FLTGoogleSignInPlugin () +@interface FLTGoogleSignInPlugin () @end @implementation FLTGoogleSignInPlugin { @@ -49,7 +49,6 @@ - (instancetype)init { self = [super init]; if (self) { [GIDSignIn sharedInstance].delegate = self; - [GIDSignIn sharedInstance].uiDelegate = self; // On the iOS simulator, we get "Broken pipe" errors after sign-in for some // unknown reason. We can avoid crashing the app by ignoring them. @@ -84,11 +83,13 @@ - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result } } else if ([call.method isEqualToString:@"signInSilently"]) { if ([self setAccountRequest:result]) { - [[GIDSignIn sharedInstance] signInSilently]; + [[GIDSignIn sharedInstance] restorePreviousSignIn]; } } else if ([call.method isEqualToString:@"isSignedIn"]) { - result(@([[GIDSignIn sharedInstance] hasAuthInKeychain])); + result(@([[GIDSignIn sharedInstance] hasPreviousSignIn])); } else if ([call.method isEqualToString:@"signIn"]) { + [GIDSignIn sharedInstance].presentingViewController = [self topViewController]; + if ([self setAccountRequest:result]) { @try { [[GIDSignIn sharedInstance] signIn]; @@ -135,10 +136,7 @@ - (BOOL)setAccountRequest:(FlutterResult)request { - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { NSString *sourceApplication = options[UIApplicationOpenURLOptionsSourceApplicationKey]; - id annotation = options[UIApplicationOpenURLOptionsAnnotationKey]; - return [[GIDSignIn sharedInstance] handleURL:url - sourceApplication:sourceApplication - annotation:annotation]; + return [[GIDSignIn sharedInstance] handleURL:url]; } #pragma mark - protocol @@ -192,4 +190,36 @@ - (void)respondWithAccount:(id)account error:(NSError *)error { result(error != nil ? getFlutterError(error) : account); } +- (UIViewController *)topViewController { + return [self topViewControllerFromViewController:[UIApplication sharedApplication] + .keyWindow.rootViewController]; +} + +/** + * This method recursively iterate through the view hierarchy + * to return the top most view controller. + * + * It supports the following scenarios: + * + * - The view controller is presenting another view. + * - The view controller is a UINavigationController. + * - The view controller is a UITabBarController. + * + * @return The top most view controller. + */ +- (UIViewController *)topViewControllerFromViewController:(UIViewController *)viewController { + if ([viewController isKindOfClass:[UINavigationController class]]) { + UINavigationController *navigationController = (UINavigationController *)viewController; + return [self + topViewControllerFromViewController:[navigationController.viewControllers lastObject]]; + } + if ([viewController isKindOfClass:[UITabBarController class]]) { + UITabBarController *tabController = (UITabBarController *)viewController; + return [self topViewControllerFromViewController:tabController.selectedViewController]; + } + if (viewController.presentedViewController) { + return [self topViewControllerFromViewController:viewController.presentedViewController]; + } + return viewController; +} @end diff --git a/packages/google_sign_in/ios/google_sign_in.podspec b/packages/google_sign_in/ios/google_sign_in.podspec index 673341edfcf9..73533c6a0db6 100755 --- a/packages/google_sign_in/ios/google_sign_in.podspec +++ b/packages/google_sign_in/ios/google_sign_in.podspec @@ -15,6 +15,9 @@ Enables Google Sign-In in Flutter apps. s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.dependency 'GoogleSignIn', '~> 4.0' + s.dependency 'GoogleSignIn', '~> 5.0' s.static_framework = true + + s.platform = :ios, '8.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } end diff --git a/packages/google_sign_in/pubspec.yaml b/packages/google_sign_in/pubspec.yaml index c5adc53f218d..e239e01c3b44 100644 --- a/packages/google_sign_in/pubspec.yaml +++ b/packages/google_sign_in/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for Google Sign-In, a secure authentication system for signing in with a Google account on Android and iOS. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/google_sign_in -version: 4.0.9 +version: 4.0.11 flutter: plugin: diff --git a/packages/image_picker/CHANGELOG.md b/packages/image_picker/CHANGELOG.md index 6a8a75a4b65f..418d3a0a57d6 100644 --- a/packages/image_picker/CHANGELOG.md +++ b/packages/image_picker/CHANGELOG.md @@ -1,3 +1,19 @@ +## 0.6.1+10 + +* iOS: Fix image orientation problems when scaling images. + +## 0.6.1+9 + +* Remove AndroidX warning. + +## 0.6.1+8 + +* Fix iOS build and analyzer warnings. + +## 0.6.1+7 + +* Android: Fix ImagePickerPlugin#onCreate casting context which causes exception. + ## 0.6.1+6 * Define clang module for iOS diff --git a/packages/image_picker/android/build.gradle b/packages/image_picker/android/build.gradle index d192a890e3cc..19f14a286a61 100755 --- a/packages/image_picker/android/build.gradle +++ b/packages/image_picker/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "image_picker"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.imagepicker' version '1.0-SNAPSHOT' diff --git a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java index 05e3d883e5e2..b495a8e1a33f 100644 --- a/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java +++ b/packages/image_picker/android/src/main/java/io/flutter/plugins/imagepicker/ImagePickerPlugin.java @@ -81,8 +81,11 @@ public void onActivitySaveInstanceState(Activity activity, Bundle outState) { @Override public void onActivityDestroyed(Activity activity) { - if (activity == registrar.activity()) { - ((Application) registrar.context()).unregisterActivityLifecycleCallbacks(this); + if (activity == registrar.activity() + && registrar.activity().getApplicationContext() != null) { + ((Application) registrar.activity().getApplicationContext()) + .unregisterActivityLifecycleCallbacks( + this); // Use getApplicationContext() to avoid casting failures } } @@ -90,9 +93,13 @@ public void onActivityDestroyed(Activity activity) { public void onActivityStopped(Activity activity) {} }; - if (this.registrar != null) { - ((Application) this.registrar.context()) - .registerActivityLifecycleCallbacks(this.activityLifecycleCallbacks); + if (this.registrar != null + && this.registrar.context() != null + && this.registrar.context().getApplicationContext() != null) { + ((Application) this.registrar.context().getApplicationContext()) + .registerActivityLifecycleCallbacks( + this + .activityLifecycleCallbacks); // Use getApplicationContext() to avoid casting failures. } } diff --git a/packages/image_picker/example/android/app/gradle.properties b/packages/image_picker/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/image_picker/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java index e37fceb7fdea..94a81d3d3eac 100644 --- a/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java +++ b/packages/image_picker/example/android/app/src/test/java/io/flutter/plugins/imagepicker/ImagePickerPluginTest.java @@ -109,6 +109,14 @@ public void onResiter_WhenAcitivityIsNull_ShouldNotCrash() { "No exception thrown when ImagePickerPlugin.registerWith ran with activity = null", true); } + @Test + public void onConstructor_WhenContextTypeIsActivity_ShouldNotCrash() { + when(mockRegistrar.context()).thenReturn(mockActivity); + new ImagePickerPlugin(mockRegistrar, mockImagePickerDelegate); + assertTrue( + "No exception thrown when ImagePickerPlugin() ran with context instanceof Activity", true); + } + private MethodCall buildMethodCall(final int source) { final Map arguments = new HashMap<>(); arguments.put("source", source); diff --git a/packages/image_picker/example/android/gradle.properties b/packages/image_picker/example/android/gradle.properties index 8bd86f680510..38c8d4544ff1 100755 --- a/packages/image_picker/example/android/gradle.properties +++ b/packages/image_picker/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m b/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m index 241db72a68cf..ab765208d0bc 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m +++ b/packages/image_picker/ios/Classes/FLTImagePickerImageUtil.m @@ -76,6 +76,20 @@ + (UIImage *)scaledImage:(UIImage *)image scale:1 orientation:UIImageOrientationUp]; + // The image orientation is manually set to UIImageOrientationUp which swapped the aspect ratio in + // some scenarios. For example, when the original image has orientation left, the horizontal + // pixels should be scaled to `width` and the vertical pixels should be scaled to `height`. After + // setting the orientation to up, we end up scaling the horizontal pixels to `height` and vertical + // to `width`. Below swap will solve this issue. + if ([image imageOrientation] == UIImageOrientationLeft || + [image imageOrientation] == UIImageOrientationRight || + [image imageOrientation] == UIImageOrientationLeftMirrored || + [image imageOrientation] == UIImageOrientationRightMirrored) { + double temp = width; + width = height; + height = temp; + } + UIGraphicsBeginImageContextWithOptions(CGSizeMake(width, height), NO, 1.0); [imageToScale drawInRect:CGRectMake(0, 0, width, height)]; diff --git a/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m b/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m index 67b6efa8a8a8..9786f61e1e67 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m +++ b/packages/image_picker/ios/Classes/FLTImagePickerMetaDataUtil.m @@ -45,6 +45,7 @@ + (NSDictionary *)getMetaDataFromImageData:(NSData *)imageData { CGImageSourceRef source = CGImageSourceCreateWithData((CFDataRef)imageData, NULL); NSDictionary *metadata = (NSDictionary *)CFBridgingRelease(CGImageSourceCopyPropertiesAtIndex(source, 0, NULL)); + CFRelease(source); return metadata; } diff --git a/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h b/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h index 709b6ca65143..1e6fda2cf786 100644 --- a/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h +++ b/packages/image_picker/ios/Classes/FLTImagePickerPhotoAssetUtil.h @@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN // Save image with correct meta data and extention copied from image picker result info. + (NSString *)saveImageWithPickerInfo:(nullable NSDictionary *)info image:(UIImage *)image - imageQuality:(NSNumber *)imageQuality; + imageQuality:(nullable NSNumber *)imageQuality; @end diff --git a/packages/image_picker/pubspec.yaml b/packages/image_picker/pubspec.yaml index 1779edcad0fc..93379d32fe7e 100755 --- a/packages/image_picker/pubspec.yaml +++ b/packages/image_picker/pubspec.yaml @@ -5,7 +5,7 @@ authors: - Flutter Team - Rhodes Davis Jr. homepage: https://github.com/flutter/plugins/tree/master/packages/image_picker -version: 0.6.1+6 +version: 0.6.1+10 flutter: plugin: diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 940909b10d0d..2624cadd785d 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,6 +1,23 @@ +## 0.2.2+2 + +* Include lifecycle dependency as a compileOnly one on Android to resolve + potential version conflicts with other transitive libraries. + +## 0.2.2+1 + +* Android: Use android.arch.lifecycle instead of androidx.lifecycle:lifecycle in `build.gradle` to support apps that has not been migrated to AndroidX. + +## 0.2.2 + +* Support the v2 Android embedder. +* Update to AndroidX. +* Migrate to using the new e2e test binding. +* Add a e2e test. + ## 0.2.1+5 * Define clang module for iOS. +* Fix iOS build warning. ## 0.2.1+4 diff --git a/packages/in_app_purchase/android/build.gradle b/packages/in_app_purchase/android/build.gradle index 6be1b770312a..54bd5e183713 100644 --- a/packages/in_app_purchase/android/build.gradle +++ b/packages/in_app_purchase/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "in_app_purchase"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.inapppurchase' version '1.0-SNAPSHOT' @@ -54,3 +41,29 @@ dependencies { androidTestImplementation 'androidx.test:runner:1.1.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' } + +// TODO(cyanglaz): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + compileOnly "android.arch.lifecycle:common:$lifecycle_version" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + } + } + } +} diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java index 078986c04c86..b9ec9f6395a3 100644 --- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactory.java @@ -1,19 +1,23 @@ +// Copyright 2019 The Chromium 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.plugins.inapppurchase; import android.content.Context; +import androidx.annotation.NonNull; import com.android.billingclient.api.BillingClient; import io.flutter.plugin.common.MethodChannel; +/** Responsible for creating a {@link BillingClient} object. */ interface BillingClientFactory { - BillingClient createBillingClient(Context context, MethodChannel channel); -} - -final class BillingClientFactoryImpl implements BillingClientFactory { - @Override - public BillingClient createBillingClient(Context context, MethodChannel channel) { - return BillingClient.newBuilder(context) - .setListener(new PluginPurchaseListener(channel)) - .build(); - } + /** + * Creates and returns a {@link BillingClient}. + * + * @param context The context used to create the {@link BillingClient}. + * @param channel The method channel used to create the {@link BillingClient}. + * @return The {@link BillingClient} object that is created. + */ + BillingClient createBillingClient(@NonNull Context context, @NonNull MethodChannel channel); } diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java new file mode 100644 index 000000000000..383fcabbb3c5 --- /dev/null +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/BillingClientFactoryImpl.java @@ -0,0 +1,20 @@ +// Copyright 2019 The Chromium 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.plugins.inapppurchase; + +import android.content.Context; +import com.android.billingclient.api.BillingClient; +import io.flutter.plugin.common.MethodChannel; + +/** The implementation for {@link BillingClientFactory} for the plugin. */ +final class BillingClientFactoryImpl implements BillingClientFactory { + + @Override + public BillingClient createBillingClient(Context context, MethodChannel channel) { + return BillingClient.newBuilder(context) + .setListener(new PluginPurchaseListener(channel)) + .build(); + } +} diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java index 99f68842d1c0..33910eea9122 100644 --- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java @@ -4,42 +4,19 @@ package io.flutter.plugins.inapppurchase; -import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; -import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesResult; -import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList; - import android.app.Activity; import android.content.Context; -import android.util.Log; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; -import com.android.billingclient.api.BillingClientStateListener; -import com.android.billingclient.api.BillingFlowParams; -import com.android.billingclient.api.ConsumeResponseListener; -import com.android.billingclient.api.Purchase; -import com.android.billingclient.api.PurchaseHistoryResponseListener; -import com.android.billingclient.api.SkuDetails; -import com.android.billingclient.api.SkuDetailsParams; -import com.android.billingclient.api.SkuDetailsResponseListener; -import io.flutter.plugin.common.MethodCall; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.lang.Override; -import java.util.HashMap; -import java.util.List; -import java.util.Map; /** Wraps a {@link BillingClient} instance and responds to Dart calls for it. */ -public class InAppPurchasePlugin implements MethodCallHandler { - private static final String TAG = "InAppPurchasePlugin"; - private @Nullable BillingClient billingClient; - private final BillingClientFactory factory; - private final Registrar registrar; - private final Context applicationContext; - private final MethodChannel channel; +public class InAppPurchasePlugin implements FlutterPlugin, ActivityAware { @VisibleForTesting static final class MethodNames { @@ -63,219 +40,58 @@ static final class MethodNames { private MethodNames() {}; } - private HashMap cachedSkus = new HashMap<>(); + private MethodChannel methodChannel; + private MethodCallHandlerImpl methodCallHandler; /** Plugin registration. */ public static void registerWith(Registrar registrar) { - final MethodChannel channel = - new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase"); - - final BillingClientFactory factory = new BillingClientFactoryImpl(); - final InAppPurchasePlugin plugin = new InAppPurchasePlugin(factory, registrar, channel); - channel.setMethodCallHandler(plugin); - } - - public InAppPurchasePlugin( - BillingClientFactory factory, Registrar registrar, MethodChannel channel) { - this.applicationContext = registrar.context(); - this.registrar = registrar; - this.factory = factory; - this.channel = channel; + InAppPurchasePlugin plugin = new InAppPurchasePlugin(); + plugin.setupMethodChannel(registrar.activity(), registrar.messenger(), registrar.context()); } @Override - public void onMethodCall(MethodCall call, Result result) { - switch (call.method) { - case MethodNames.IS_READY: - isReady(result); - break; - case MethodNames.START_CONNECTION: - startConnection((int) call.argument("handle"), result); - break; - case MethodNames.END_CONNECTION: - endConnection(result); - break; - case MethodNames.QUERY_SKU_DETAILS: - querySkuDetailsAsync( - (String) call.argument("skuType"), (List) call.argument("skusList"), result); - break; - case MethodNames.LAUNCH_BILLING_FLOW: - launchBillingFlow( - (String) call.argument("sku"), (String) call.argument("accountId"), result); - break; - case MethodNames.QUERY_PURCHASES: - queryPurchases((String) call.argument("skuType"), result); - break; - case MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: - queryPurchaseHistoryAsync((String) call.argument("skuType"), result); - break; - case MethodNames.CONSUME_PURCHASE_ASYNC: - consumeAsync((String) call.argument("purchaseToken"), result); - break; - default: - result.notImplemented(); - } - } - - private void startConnection(final int handle, final Result result) { - if (billingClient == null) { - billingClient = factory.createBillingClient(applicationContext, channel); - } - - billingClient.startConnection( - new BillingClientStateListener() { - private boolean alreadyFinished = false; - - @Override - public void onBillingSetupFinished(int responseCode) { - if (alreadyFinished) { - Log.d(TAG, "Tried to call onBilllingSetupFinished multiple times."); - return; - } - alreadyFinished = true; - // Consider the fact that we've finished a success, leave it to the Dart side to validate the responseCode. - result.success(responseCode); - } - - @Override - public void onBillingServiceDisconnected() { - final Map arguments = new HashMap<>(); - arguments.put("handle", handle); - channel.invokeMethod(MethodNames.ON_DISCONNECT, arguments); - } - }); - } - - private void endConnection(final Result result) { - if (billingClient != null) { - billingClient.endConnection(); - billingClient = null; - } - result.success(null); - } - - private void isReady(Result result) { - if (billingClientError(result)) { - return; - } - - result.success(billingClient.isReady()); + public void onAttachedToEngine(FlutterPlugin.FlutterPluginBinding binding) { + setupMethodChannel( + /*activity=*/ null, + binding.getFlutterEngine().getDartExecutor(), + binding.getApplicationContext()); } - private void querySkuDetailsAsync( - final String skuType, final List skusList, final Result result) { - if (billingClientError(result)) { - return; - } - - SkuDetailsParams params = - SkuDetailsParams.newBuilder().setType(skuType).setSkusList(skusList).build(); - billingClient.querySkuDetailsAsync( - params, - new SkuDetailsResponseListener() { - public void onSkuDetailsResponse( - int responseCode, @Nullable List skuDetailsList) { - updateCachedSkus(skuDetailsList); - final Map skuDetailsResponse = new HashMap<>(); - skuDetailsResponse.put("responseCode", responseCode); - skuDetailsResponse.put("skuDetailsList", fromSkuDetailsList(skuDetailsList)); - result.success(skuDetailsResponse); - } - }); + @Override + public void onDetachedFromEngine(FlutterPlugin.FlutterPluginBinding binding) { + teardownMethodChannel(); } - private void launchBillingFlow(String sku, @Nullable String accountId, Result result) { - if (billingClientError(result)) { - return; - } - - SkuDetails skuDetails = cachedSkus.get(sku); - if (skuDetails == null) { - result.error( - "NOT_FOUND", - "Details for sku " + sku + " are not available. Has this ID already been fetched?", - null); - return; - } - final Activity activity = registrar.activity(); - - if (activity == null) { - result.error( - "ACTIVITY_UNAVAILABLE", - "Details for sku " - + sku - + " are not available. This method must be run with the app in foreground.", - null); - return; - } - - BillingFlowParams.Builder paramsBuilder = - BillingFlowParams.newBuilder().setSkuDetails(skuDetails); - if (accountId != null && !accountId.isEmpty()) { - paramsBuilder.setAccountId(accountId); - } - result.success(billingClient.launchBillingFlow(activity, paramsBuilder.build())); + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + methodCallHandler.setActivity(binding.getActivity()); } - private void consumeAsync(String purchaseToken, final Result result) { - if (billingClientError(result)) { - return; - } - - ConsumeResponseListener listener = - new ConsumeResponseListener() { - @Override - public void onConsumeResponse( - @BillingClient.BillingResponse int responseCode, String outToken) { - result.success(responseCode); - } - }; - billingClient.consumeAsync(purchaseToken, listener); + @Override + public void onDetachedFromActivity() { + methodCallHandler.setActivity(null); } - private void queryPurchases(String skuType, Result result) { - if (billingClientError(result)) { - return; - } - - // Like in our connect call, consider the billing client responding a "success" here regardless of status code. - result.success(fromPurchasesResult(billingClient.queryPurchases(skuType))); + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + onAttachedToActivity(binding); } - private void queryPurchaseHistoryAsync(String skuType, final Result result) { - if (billingClientError(result)) { - return; - } - - billingClient.queryPurchaseHistoryAsync( - skuType, - new PurchaseHistoryResponseListener() { - @Override - public void onPurchaseHistoryResponse(int responseCode, List purchasesList) { - final Map serialized = new HashMap<>(); - serialized.put("responseCode", responseCode); - serialized.put("purchasesList", fromPurchasesList(purchasesList)); - result.success(serialized); - } - }); + @Override + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } - private void updateCachedSkus(@Nullable List skuDetailsList) { - if (skuDetailsList == null) { - return; - } - - for (SkuDetails skuDetails : skuDetailsList) { - cachedSkus.put(skuDetails.getSku(), skuDetails); - } + private void setupMethodChannel(Activity activity, BinaryMessenger messenger, Context context) { + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/in_app_purchase"); + methodCallHandler = + new MethodCallHandlerImpl(activity, context, methodChannel, new BillingClientFactoryImpl()); + methodChannel.setMethodCallHandler(methodCallHandler); } - private boolean billingClientError(Result result) { - if (billingClient != null) { - return false; - } - - result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null); - return true; + private void teardownMethodChannel() { + methodChannel.setMethodCallHandler(null); + methodChannel = null; + methodCallHandler = null; } } diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java new file mode 100644 index 000000000000..2abcc4b5f634 --- /dev/null +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/MethodCallHandlerImpl.java @@ -0,0 +1,260 @@ +// Copyright 2019 The Chromium 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.plugins.inapppurchase; + +import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; +import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesResult; +import static io.flutter.plugins.inapppurchase.Translator.fromSkuDetailsList; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingClientStateListener; +import com.android.billingclient.api.BillingFlowParams; +import com.android.billingclient.api.ConsumeResponseListener; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.PurchaseHistoryResponseListener; +import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.SkuDetailsParams; +import com.android.billingclient.api.SkuDetailsResponseListener; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Handles method channel for the plugin. */ +class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + + private static final String TAG = "InAppPurchasePlugin"; + + @Nullable private BillingClient billingClient; + private final BillingClientFactory billingClientFactory; + + @Nullable private Activity activity; + private final Context applicationContext; + private final MethodChannel methodChannel; + + private HashMap cachedSkus = new HashMap<>(); + + /** Constructs the MethodCallHandlerImpl */ + MethodCallHandlerImpl( + @Nullable Activity activity, + @NonNull Context applicationContext, + @NonNull MethodChannel methodChannel, + @NonNull BillingClientFactory billingClientFactory) { + this.billingClientFactory = billingClientFactory; + this.applicationContext = applicationContext; + this.activity = activity; + this.methodChannel = methodChannel; + } + + /** + * Sets the activity. Should be called as soon as the the activity is available. When the activity + * becomes unavailable, call this method again with {@code null}. + */ + void setActivity(@Nullable Activity activity) { + this.activity = activity; + } + + @Override + public void onMethodCall(MethodCall call, MethodChannel.Result result) { + switch (call.method) { + case InAppPurchasePlugin.MethodNames.IS_READY: + isReady(result); + break; + case InAppPurchasePlugin.MethodNames.START_CONNECTION: + startConnection((int) call.argument("handle"), result); + break; + case InAppPurchasePlugin.MethodNames.END_CONNECTION: + endConnection(result); + break; + case InAppPurchasePlugin.MethodNames.QUERY_SKU_DETAILS: + querySkuDetailsAsync( + (String) call.argument("skuType"), (List) call.argument("skusList"), result); + break; + case InAppPurchasePlugin.MethodNames.LAUNCH_BILLING_FLOW: + launchBillingFlow( + (String) call.argument("sku"), (String) call.argument("accountId"), result); + break; + case InAppPurchasePlugin.MethodNames.QUERY_PURCHASES: + queryPurchases((String) call.argument("skuType"), result); + break; + case InAppPurchasePlugin.MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: + queryPurchaseHistoryAsync((String) call.argument("skuType"), result); + break; + case InAppPurchasePlugin.MethodNames.CONSUME_PURCHASE_ASYNC: + consumeAsync((String) call.argument("purchaseToken"), result); + break; + default: + result.notImplemented(); + } + } + + private void endConnection(final MethodChannel.Result result) { + if (billingClient != null) { + billingClient.endConnection(); + billingClient = null; + } + result.success(null); + } + + private void isReady(MethodChannel.Result result) { + if (billingClientError(result)) { + return; + } + + result.success(billingClient.isReady()); + } + + private void querySkuDetailsAsync( + final String skuType, final List skusList, final MethodChannel.Result result) { + if (billingClientError(result)) { + return; + } + + SkuDetailsParams params = + SkuDetailsParams.newBuilder().setType(skuType).setSkusList(skusList).build(); + billingClient.querySkuDetailsAsync( + params, + new SkuDetailsResponseListener() { + public void onSkuDetailsResponse( + int responseCode, @Nullable List skuDetailsList) { + updateCachedSkus(skuDetailsList); + final Map skuDetailsResponse = new HashMap<>(); + skuDetailsResponse.put("responseCode", responseCode); + skuDetailsResponse.put("skuDetailsList", fromSkuDetailsList(skuDetailsList)); + result.success(skuDetailsResponse); + } + }); + } + + private void launchBillingFlow( + String sku, @Nullable String accountId, MethodChannel.Result result) { + if (billingClientError(result)) { + return; + } + + SkuDetails skuDetails = cachedSkus.get(sku); + if (skuDetails == null) { + result.error( + "NOT_FOUND", + "Details for sku " + sku + " are not available. Has this ID already been fetched?", + null); + return; + } + + if (activity == null) { + result.error( + "ACTIVITY_UNAVAILABLE", + "Details for sku " + + sku + + " are not available. This method must be run with the app in foreground.", + null); + return; + } + + BillingFlowParams.Builder paramsBuilder = + BillingFlowParams.newBuilder().setSkuDetails(skuDetails); + if (accountId != null && !accountId.isEmpty()) { + paramsBuilder.setAccountId(accountId); + } + result.success(billingClient.launchBillingFlow(activity, paramsBuilder.build())); + } + + private void consumeAsync(String purchaseToken, final MethodChannel.Result result) { + if (billingClientError(result)) { + return; + } + + ConsumeResponseListener listener = + new ConsumeResponseListener() { + @Override + public void onConsumeResponse( + @BillingClient.BillingResponse int responseCode, String outToken) { + result.success(responseCode); + } + }; + billingClient.consumeAsync(purchaseToken, listener); + } + + private void queryPurchases(String skuType, MethodChannel.Result result) { + if (billingClientError(result)) { + return; + } + + // Like in our connect call, consider the billing client responding a "success" here regardless of status code. + result.success(fromPurchasesResult(billingClient.queryPurchases(skuType))); + } + + private void queryPurchaseHistoryAsync(String skuType, final MethodChannel.Result result) { + if (billingClientError(result)) { + return; + } + + billingClient.queryPurchaseHistoryAsync( + skuType, + new PurchaseHistoryResponseListener() { + @Override + public void onPurchaseHistoryResponse(int responseCode, List purchasesList) { + final Map serialized = new HashMap<>(); + serialized.put("responseCode", responseCode); + serialized.put("purchasesList", fromPurchasesList(purchasesList)); + result.success(serialized); + } + }); + } + + private void startConnection(final int handle, final MethodChannel.Result result) { + if (billingClient == null) { + billingClient = billingClientFactory.createBillingClient(applicationContext, methodChannel); + } + + billingClient.startConnection( + new BillingClientStateListener() { + private boolean alreadyFinished = false; + + @Override + public void onBillingSetupFinished(int responseCode) { + if (alreadyFinished) { + Log.d(TAG, "Tried to call onBilllingSetupFinished multiple times."); + return; + } + alreadyFinished = true; + // Consider the fact that we've finished a success, leave it to the Dart side to validate the responseCode. + result.success(responseCode); + } + + @Override + public void onBillingServiceDisconnected() { + final Map arguments = new HashMap<>(); + arguments.put("handle", handle); + methodChannel.invokeMethod(InAppPurchasePlugin.MethodNames.ON_DISCONNECT, arguments); + } + }); + } + + private void updateCachedSkus(@Nullable List skuDetailsList) { + if (skuDetailsList == null) { + return; + } + + for (SkuDetails skuDetails : skuDetailsList) { + cachedSkus.put(skuDetails.getSku(), skuDetails); + } + } + + private boolean billingClientError(MethodChannel.Result result) { + if (billingClient != null) { + return false; + } + + result.error("UNAVAILABLE", "BillingClient is unset. Try reconnecting.", null); + return true; + } +} diff --git a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java index f1de27eaacc8..db3260cb5a0c 100644 --- a/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java +++ b/packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/PluginPurchaseListener.java @@ -1,3 +1,7 @@ +// Copyright 2019 The Chromium 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.plugins.inapppurchase; import static io.flutter.plugins.inapppurchase.Translator.fromPurchasesList; diff --git a/packages/in_app_purchase/example/android/app/gradle.properties b/packages/in_app_purchase/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/in_app_purchase/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/in_app_purchase/example/android/app/src/main/AndroidManifest.xml b/packages/in_app_purchase/example/android/app/src/main/AndroidManifest.xml index 4edc914fd19b..5ad1aa42bf98 100644 --- a/packages/in_app_purchase/example/android/app/src/main/AndroidManifest.xml +++ b/packages/in_app_purchase/example/android/app/src/main/AndroidManifest.xml @@ -17,11 +17,12 @@ android:label="in_app_purchase_example" android:icon="@mipmap/ic_launcher"> - + + + + + + + + + + + - - - - - - - - - + + diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/EmbeddingV1Activity.java b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..e52ccfc18b47 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/EmbeddingV1Activity.java @@ -0,0 +1,18 @@ +// Copyright 2017 The Chromium 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.plugins.urllauncherexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java index 87478bfa27df..76f7df752842 100644 --- a/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java +++ b/packages/url_launcher/url_launcher/example/android/app/src/main/java/io/flutter/plugins/urllauncherexample/MainActivity.java @@ -1,18 +1,12 @@ -// Copyright 2017 The Chromium 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.plugins.urllauncherexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.urllauncher.UrlLauncherPlugin; public class MainActivity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new UrlLauncherPlugin()); } } diff --git a/packages/url_launcher/url_launcher/example/android/gradle.properties b/packages/url_launcher/url_launcher/example/android/gradle.properties index 8bd86f680510..a6738207fd15 100644 --- a/packages/url_launcher/url_launcher/example/android/gradle.properties +++ b/packages/url_launcher/url_launcher/example/android/gradle.properties @@ -1 +1,4 @@ org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/url_launcher/url_launcher/example/pubspec.yaml b/packages/url_launcher/url_launcher/example/pubspec.yaml index 99aaf27cff78..4d9d01aa9b5e 100644 --- a/packages/url_launcher/url_launcher/example/pubspec.yaml +++ b/packages/url_launcher/url_launcher/example/pubspec.yaml @@ -7,5 +7,10 @@ dependencies: url_launcher: path: ../ +dev_dependencies: + e2e: "^0.2.0" + flutter_driver: + sdk: flutter + flutter: uses-material-design: true diff --git a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart new file mode 100644 index 000000000000..e1d75f93b326 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e.dart @@ -0,0 +1,24 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:e2e/e2e.dart'; +import 'package:url_launcher/url_launcher.dart'; + +void main() { + E2EWidgetsFlutterBinding.ensureInitialized(); + + test('canLaunch', () async { + expect(await canLaunch('randomstring'), false); + + // Generally all devices should have some default browser. + expect(await canLaunch('http://flutter.dev'), true); + + // Generally all devices should have some default SMS app. + expect(await canLaunch('sms:5555555555'), true); + + // tel: and mailto: links may not be openable on every device. iOS + // simulators notably can't open these link types. + }); +} diff --git a/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart new file mode 100644 index 000000000000..ac4ea11482e2 --- /dev/null +++ b/packages/url_launcher/url_launcher/example/test_driver/url_launcher_e2e_test.dart @@ -0,0 +1,15 @@ +// Copyright 2019, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:io'; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + final FlutterDriver driver = await FlutterDriver.connect(); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); + driver.close(); + exit(result == 'pass' ? 0 : 1); +} diff --git a/packages/url_launcher/url_launcher/ios/url_launcher.podspec b/packages/url_launcher/url_launcher/ios/url_launcher.podspec index d8436c1c1e93..d3be4d12f778 100644 --- a/packages/url_launcher/url_launcher/ios/url_launcher.podspec +++ b/packages/url_launcher/url_launcher/ios/url_launcher.podspec @@ -16,6 +16,7 @@ A Flutter plugin for making the underlying platform (Android or iOS) launch a UR s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.ios.deployment_target = '8.0' + s.platform = :ios, '8.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } end diff --git a/packages/url_launcher/url_launcher/lib/url_launcher.dart b/packages/url_launcher/url_launcher/lib/url_launcher.dart index 045448fbde97..97af77143936 100644 --- a/packages/url_launcher/url_launcher/lib/url_launcher.dart +++ b/packages/url_launcher/url_launcher/lib/url_launcher.dart @@ -7,8 +7,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; - -const MethodChannel _channel = MethodChannel('plugins.flutter.io/url_launcher'); +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; /// Parses the specified URL string and delegates handling of it to the /// underlying platform. @@ -84,17 +83,14 @@ Future launch( ? SystemUiOverlayStyle.dark : SystemUiOverlayStyle.light); } - final bool result = await _channel.invokeMethod( - 'launch', - { - 'url': urlString, - 'useSafariVC': forceSafariVC ?? isWebURL, - 'useWebView': forceWebView ?? false, - 'enableJavaScript': enableJavaScript ?? false, - 'enableDomStorage': enableDomStorage ?? false, - 'universalLinksOnly': universalLinksOnly ?? false, - 'headers': headers ?? {}, - }, + final bool result = await UrlLauncherPlatform.instance.launch( + urlString, + useSafariVC: forceSafariVC ?? isWebURL, + useWebView: forceWebView ?? false, + enableJavaScript: enableJavaScript ?? false, + enableDomStorage: enableDomStorage ?? false, + universalLinksOnly: universalLinksOnly ?? false, + headers: headers ?? {}, ); if (statusBarBrightness != null) { WidgetsBinding.instance.renderView.automaticSystemUiAdjustment = @@ -109,10 +105,7 @@ Future canLaunch(String urlString) async { if (urlString == null) { return false; } - return await _channel.invokeMethod( - 'canLaunch', - {'url': urlString}, - ); + return await UrlLauncherPlatform.instance.canLaunch(urlString); } /// Closes the current WebView, if one was previously opened via a call to [launch]. @@ -127,5 +120,5 @@ Future canLaunch(String urlString) async { /// SafariViewController is only available on IOS version >= 9.0, this method does not do anything /// on IOS version below 9.0 Future closeWebView() async { - return await _channel.invokeMethod('closeWebView'); + return await UrlLauncherPlatform.instance.closeWebView(); } diff --git a/packages/url_launcher/url_launcher/pubspec.yaml b/packages/url_launcher/url_launcher/pubspec.yaml index a48f90b6eacb..2477e118586e 100644 --- a/packages/url_launcher/url_launcher/pubspec.yaml +++ b/packages/url_launcher/url_launcher/pubspec.yaml @@ -3,7 +3,7 @@ description: Flutter plugin for launching a URL on Android and iOS. Supports web, phone, SMS, and email schemes. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher -version: 5.1.5 +version: 5.2.6 flutter: plugin: @@ -14,11 +14,14 @@ flutter: dependencies: flutter: sdk: flutter + url_launcher_platform_interface: ^1.0.1 dev_dependencies: flutter_test: sdk: flutter + test: ^1.3.0 + mockito: ^4.1.1 environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/url_launcher/url_launcher/test/url_launcher_test.dart b/packages/url_launcher/url_launcher/test/url_launcher_test.dart index a70392810f86..e7150bebde05 100644 --- a/packages/url_launcher/url_launcher/test/url_launcher_test.dart +++ b/packages/url_launcher/url_launcher/test/url_launcher_test.dart @@ -2,50 +2,44 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:test/test.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; +import 'package:flutter/services.dart' show PlatformException; void main() { - TestWidgetsFlutterBinding.ensureInitialized(); + final MockUrlLauncher mock = MockUrlLauncher(); + when(mock.isMock).thenReturn(true); - const MethodChannel channel = - MethodChannel('plugins.flutter.io/url_launcher'); - final List log = []; - channel.setMockMethodCallHandler((MethodCall methodCall) async { - log.add(methodCall); - }); - - tearDown(() { - log.clear(); - }); + UrlLauncherPlatform.instance = mock; test('canLaunch', () async { await canLaunch('http://example.com/'); - expect( - log, - [ - isMethodCall('canLaunch', arguments: { - 'url': 'http://example.com/', - }) - ], - ); + expect(verify(mock.canLaunch(captureAny)).captured.single, + 'http://example.com/'); }); test('launch default behavior', () async { await launch('http://example.com/'); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': true, - 'useWebView': false, - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) + verify(mock.launch( + captureAny, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: captureAnyNamed('enableJavaScript'), + enableDomStorage: captureAnyNamed('enableDomStorage'), + universalLinksOnly: captureAnyNamed('universalLinksOnly'), + headers: captureAnyNamed('headers'), + )).captured, + [ + 'http://example.com/', + true, + false, + false, + false, + false, + {}, ], ); }); @@ -56,36 +50,32 @@ void main() { headers: {'key': 'value'}, ); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': true, - 'useWebView': false, - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {'key': 'value'}, - }) - ], + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: captureAnyNamed('headers'), + )).captured.single, + {'key': 'value'}, ); }); test('launch force SafariVC', () async { await launch('http://example.com/', forceSafariVC: true); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': true, - 'useWebView': false, - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], + verify(mock.launch( + any, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured.single, + true, ); }); @@ -93,36 +83,32 @@ void main() { await launch('http://example.com/', forceSafariVC: false, universalLinksOnly: true); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': false, - 'useWebView': false, - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': true, - 'headers': {}, - }) - ], + verify(mock.launch( + any, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: captureAnyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured, + [false, true], ); }); test('launch force WebView', () async { await launch('http://example.com/', forceWebView: true); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': true, - 'useWebView': true, - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured.single, + true, ); }); @@ -130,18 +116,16 @@ void main() { await launch('http://example.com/', forceWebView: true, enableJavaScript: true); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': true, - 'useWebView': true, - 'enableJavaScript': true, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: captureAnyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured, + [true, true], ); }); @@ -149,49 +133,45 @@ void main() { await launch('http://example.com/', forceWebView: true, enableDomStorage: true); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': true, - 'useWebView': true, - 'enableJavaScript': false, - 'enableDomStorage': true, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], + verify(mock.launch( + any, + useSafariVC: anyNamed('useSafariVC'), + useWebView: captureAnyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: captureAnyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured, + [true, true], ); }); test('launch force SafariVC to false', () async { await launch('http://example.com/', forceSafariVC: false); expect( - log, - [ - isMethodCall('launch', arguments: { - 'url': 'http://example.com/', - 'useSafariVC': false, - 'useWebView': false, - 'enableJavaScript': false, - 'enableDomStorage': false, - 'universalLinksOnly': false, - 'headers': {}, - }) - ], + // ignore: missing_required_param + verify(mock.launch( + any, + useSafariVC: captureAnyNamed('useSafariVC'), + useWebView: anyNamed('useWebView'), + enableJavaScript: anyNamed('enableJavaScript'), + enableDomStorage: anyNamed('enableDomStorage'), + universalLinksOnly: anyNamed('universalLinksOnly'), + headers: anyNamed('headers'), + )).captured.single, + false, ); }); test('cannot launch a non-web in webview', () async { expect(() async => await launch('tel:555-555-5555', forceWebView: true), - throwsA(isInstanceOf())); + throwsA(isA())); }); test('closeWebView default behavior', () async { await closeWebView(); - expect( - log, - [isMethodCall('closeWebView', arguments: null)], - ); + verify(mock.closeWebView()); }); } + +class MockUrlLauncher extends Mock implements UrlLauncherPlatform {} diff --git a/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..7aa8ee011524 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/CHANGELOG.md @@ -0,0 +1,11 @@ +## 1.0.2 + +* Use package URI in test directory to import code from lib. + +## 1.0.1 + +* Enforce that UrlLauncherPlatform isn't implemented with `implements`. + +## 1.0.0 + +* Initial release. diff --git a/packages/url_launcher/url_launcher_platform_interface/LICENSE b/packages/url_launcher/url_launcher_platform_interface/LICENSE new file mode 100644 index 000000000000..c89293372cf3 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/LICENSE @@ -0,0 +1,27 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/url_launcher/url_launcher_platform_interface/README.md b/packages/url_launcher/url_launcher_platform_interface/README.md new file mode 100644 index 000000000000..3fd6b02cfdf5 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/README.md @@ -0,0 +1,26 @@ +# url_launcher_platform_interface + +A common platform interface for the [`url_launcher`][1] plugin. + +This interface allows platform-specific implementations of the `url_launcher` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +# Usage + +To implement a new platform-specific implementation of `url_launcher`, extend +[`UrlLauncherPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`UrlLauncherPlatform` by calling +`UrlLauncherPlatform.instance = MyPlatformUrlLauncher()`. + +# Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../url_launcher +[2]: lib/url_launcher_platform_interface.dart diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart b/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart new file mode 100644 index 000000000000..3fbd2ee01843 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/lib/method_channel_url_launcher.dart @@ -0,0 +1,52 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:meta/meta.dart' show required; + +import 'url_launcher_platform_interface.dart'; + +const MethodChannel _channel = MethodChannel('plugins.flutter.io/url_launcher'); + +/// An implementation of [UrlLauncherPlatform] that uses method channels. +class MethodChannelUrlLauncher extends UrlLauncherPlatform { + @override + Future canLaunch(String url) { + return _channel.invokeMethod( + 'canLaunch', + {'url': url}, + ); + } + + @override + Future closeWebView() { + return _channel.invokeMethod('closeWebView'); + } + + @override + Future launch( + String url, { + @required bool useSafariVC, + @required bool useWebView, + @required bool enableJavaScript, + @required bool enableDomStorage, + @required bool universalLinksOnly, + @required Map headers, + }) { + return _channel.invokeMethod( + 'launch', + { + 'url': url, + 'useSafariVC': useSafariVC, + 'useWebView': useWebView, + 'enableJavaScript': enableJavaScript, + 'enableDomStorage': enableDomStorage, + 'universalLinksOnly': universalLinksOnly, + 'headers': headers, + }, + ); + } +} diff --git a/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart new file mode 100644 index 000000000000..a17aa0626126 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/lib/url_launcher_platform_interface.dart @@ -0,0 +1,85 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:meta/meta.dart' show required, visibleForTesting; + +import 'method_channel_url_launcher.dart'; + +/// The interface that implementations of url_launcher must implement. +/// +/// Platform implementations should extend this class rather than implement it as `url_launcher` +/// does not consider newly added methods to be breaking changes. Extending this class +/// (using `extends`) ensures that the subclass will get the default implementation, while +/// platform implementations that `implements` this interface will be broken by newly added +/// [UrlLauncherPlatform] methods. +abstract class UrlLauncherPlatform { + /// Only mock implementations should set this to true. + /// + /// Mockito mocks are implementing this class with `implements` which is forbidden for anything + /// other than mocks (see class docs). This property provides a backdoor for mockito mocks to + /// skip the verification that the class isn't implemented with `implements`. + @visibleForTesting + bool get isMock => false; + + /// The default instance of [UrlLauncherPlatform] to use. + /// + /// Platform-specific plugins should override this with their own + /// platform-specific class that extends [UrlLauncherPlatform] when they + /// register themselves. + /// + /// Defaults to [MethodChannelUrlLauncher]. + static UrlLauncherPlatform _instance = MethodChannelUrlLauncher(); + + static UrlLauncherPlatform get instance => _instance; + + // TODO(amirh): Extract common platform interface logic. + // https://github.com/flutter/flutter/issues/43368 + static set instance(UrlLauncherPlatform instance) { + if (!instance.isMock) { + try { + instance._verifyProvidesDefaultImplementations(); + } on NoSuchMethodError catch (_) { + throw AssertionError( + 'Platform interfaces must not be implemented with `implements`'); + } + } + _instance = instance; + } + + /// Returns `true` if this platform is able to launch [url]. + Future canLaunch(String url) { + throw UnimplementedError('canLaunch() has not been implemented.'); + } + + /// Returns `true` if the given [url] was successfully launched. + /// + /// For documentation on the other arguments, see the `launch` documentation + /// in `package:url_launcher/url_launcher.dart`. + Future launch( + String url, { + @required bool useSafariVC, + @required bool useWebView, + @required bool enableJavaScript, + @required bool enableDomStorage, + @required bool universalLinksOnly, + @required Map headers, + }) { + throw UnimplementedError('launch() has not been implemented.'); + } + + /// Closes the WebView, if one was opened earlier by [launch]. + Future closeWebView() { + throw UnimplementedError('closeWebView() has not been implemented.'); + } + + // This method makes sure that UrlLauncher isn't implemented with `implements`. + // + // See class doc for more details on why implementing this class is forbidden. + // + // This private method is called by the instance setter, which fails if the class is + // implemented with `implements`. + void _verifyProvidesDefaultImplementations() {} +} diff --git a/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..192fbe7e8066 --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/pubspec.yaml @@ -0,0 +1,21 @@ +name: url_launcher_platform_interface +description: A common platform interface for the url_launcher plugin. +author: Flutter Team +homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_platform_interface +# NOTE: We strongly prefer non-breaking changes, even at the expense of a +# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes +version: 1.0.2 + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + +dev_dependencies: + flutter_test: + sdk: flutter + mockito: ^4.1.1 + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=1.9.1+hotfix.4 <2.0.0" diff --git a/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart new file mode 100644 index 000000000000..1d6b0c9f3f8c --- /dev/null +++ b/packages/url_launcher/url_launcher_platform_interface/test/method_channel_url_launcher_test.dart @@ -0,0 +1,286 @@ +// Copyright 2017 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:mockito/mockito.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:url_launcher_platform_interface/method_channel_url_launcher.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$UrlLauncherPlatform', () { + test('$MethodChannelUrlLauncher() is the default instance', () { + expect(UrlLauncherPlatform.instance, + isInstanceOf()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + UrlLauncherPlatform.instance = ImplementsUrlLauncherPlatform(); + }, throwsA(isInstanceOf())); + }); + + test('Can be mocked with `implements`', () { + final ImplementsUrlLauncherPlatform mock = + ImplementsUrlLauncherPlatform(); + when(mock.isMock).thenReturn(true); + UrlLauncherPlatform.instance = mock; + }); + + test('Can be extended', () { + UrlLauncherPlatform.instance = ExtendsUrlLauncherPlatform(); + }); + }); + + group('$MethodChannelUrlLauncher', () { + const MethodChannel channel = + MethodChannel('plugins.flutter.io/url_launcher'); + final List log = []; + channel.setMockMethodCallHandler((MethodCall methodCall) async { + log.add(methodCall); + }); + + final MethodChannelUrlLauncher launcher = MethodChannelUrlLauncher(); + + tearDown(() { + log.clear(); + }); + + test('canLaunch', () async { + await launcher.canLaunch('http://example.com/'); + expect( + log, + [ + isMethodCall('canLaunch', arguments: { + 'url': 'http://example.com/', + }) + ], + ); + }); + + test('launch', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': true, + 'useWebView': false, + 'enableJavaScript': false, + 'enableDomStorage': false, + 'universalLinksOnly': false, + 'headers': {}, + }) + ], + ); + }); + + test('launch with headers', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {'key': 'value'}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': true, + 'useWebView': false, + 'enableJavaScript': false, + 'enableDomStorage': false, + 'universalLinksOnly': false, + 'headers': {'key': 'value'}, + }) + ], + ); + }); + + test('launch force SafariVC', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': true, + 'useWebView': false, + 'enableJavaScript': false, + 'enableDomStorage': false, + 'universalLinksOnly': false, + 'headers': {}, + }) + ], + ); + }); + + test('launch universal links only', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: true, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': false, + 'useWebView': false, + 'enableJavaScript': false, + 'enableDomStorage': false, + 'universalLinksOnly': true, + 'headers': {}, + }) + ], + ); + }); + + test('launch force WebView', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': true, + 'useWebView': true, + 'enableJavaScript': false, + 'enableDomStorage': false, + 'universalLinksOnly': false, + 'headers': {}, + }) + ], + ); + }); + + test('launch force WebView enable javascript', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: true, + enableJavaScript: true, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': true, + 'useWebView': true, + 'enableJavaScript': true, + 'enableDomStorage': false, + 'universalLinksOnly': false, + 'headers': {}, + }) + ], + ); + }); + + test('launch force WebView enable DOM storage', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: true, + useWebView: true, + enableJavaScript: false, + enableDomStorage: true, + universalLinksOnly: false, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': true, + 'useWebView': true, + 'enableJavaScript': false, + 'enableDomStorage': true, + 'universalLinksOnly': false, + 'headers': {}, + }) + ], + ); + }); + + test('launch force SafariVC to false', () async { + await launcher.launch( + 'http://example.com/', + useSafariVC: false, + useWebView: false, + enableJavaScript: false, + enableDomStorage: false, + universalLinksOnly: false, + headers: const {}, + ); + expect( + log, + [ + isMethodCall('launch', arguments: { + 'url': 'http://example.com/', + 'useSafariVC': false, + 'useWebView': false, + 'enableJavaScript': false, + 'enableDomStorage': false, + 'universalLinksOnly': false, + 'headers': {}, + }) + ], + ); + }); + + test('closeWebView default behavior', () async { + await launcher.closeWebView(); + expect( + log, + [isMethodCall('closeWebView', arguments: null)], + ); + }); + }); +} + +class ImplementsUrlLauncherPlatform extends Mock + implements UrlLauncherPlatform {} + +class ExtendsUrlLauncherPlatform extends UrlLauncherPlatform {} diff --git a/packages/url_launcher/url_launcher_web/CHANGELOG.md b/packages/url_launcher/url_launcher_web/CHANGELOG.md index a6ea2c9a5d5a..e20f5fbbb636 100644 --- a/packages/url_launcher/url_launcher_web/CHANGELOG.md +++ b/packages/url_launcher/url_launcher_web/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.0.2 + +- Switch to using `url_launcher_platform_interface`. + # 0.0.1 - Initial open-source release. diff --git a/packages/url_launcher/url_launcher_web/README.md b/packages/url_launcher/url_launcher_web/README.md index 35b3fa7e17d7..66a586c57872 100644 --- a/packages/url_launcher/url_launcher_web/README.md +++ b/packages/url_launcher/url_launcher_web/README.md @@ -14,8 +14,9 @@ on `package:url_launcher`. dependencies: url_launcher: ^5.1.4 url_launcher_web: - git: git@github.com:flutter/plugins.git - path: packages/url_launcher/url_launcher_web + git: + url: git://github.com/flutter/plugins.git + path: packages/url_launcher/url_launcher_web ``` Once you have the `url_launcher_web` dependency in your pubspec, you should diff --git a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart index e000c1fc9bea..8882303a92a5 100644 --- a/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart +++ b/packages/url_launcher/url_launcher_web/lib/url_launcher_web.dart @@ -1,49 +1,46 @@ import 'dart:async'; import 'dart:html' as html; -import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:meta/meta.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; -class UrlLauncherPlugin { +/// The web implementation of [UrlLauncherPlatform]. +/// +/// This class implements the `package:url_launcher` functionality for the web. +class UrlLauncherPlugin extends UrlLauncherPlatform { + /// Registers this class as the default instance of [UrlLauncherPlatform]. static void registerWith(Registrar registrar) { - final MethodChannel channel = MethodChannel( - 'plugins.flutter.io/url_launcher', - const StandardMethodCodec(), - registrar.messenger); - final UrlLauncherPlugin instance = UrlLauncherPlugin(); - channel.setMethodCallHandler(instance.handleMethodCall); + UrlLauncherPlatform.instance = UrlLauncherPlugin(); } - Future handleMethodCall(MethodCall call) async { - switch (call.method) { - case 'canLaunch': - final String url = call.arguments['url']; - return _canLaunch(url); - case 'launch': - final String url = call.arguments['url']; - return _launch(url); - default: - throw PlatformException( - code: 'Unimplemented', - details: "The url_launcher plugin for web doesn't implement " - "the method '${call.method}'"); - } + /// Opens the given [url] in a new window. + /// + /// Returns the newly created window. + @visibleForTesting + html.WindowBase openNewWindow(String url) { + return html.window.open(url, ''); } - bool _canLaunch(String url) { + @override + Future canLaunch(String url) { final Uri parsedUrl = Uri.tryParse(url); - if (parsedUrl == null) return false; - - return parsedUrl.isScheme('http') || parsedUrl.isScheme('https'); - } + if (parsedUrl == null) return Future.value(false); - bool _launch(String url) { - return openNewWindow(url) != null; + return Future.value( + parsedUrl.isScheme('http') || parsedUrl.isScheme('https')); } - @visibleForTesting - html.WindowBase openNewWindow(String url) { - return html.window.open(url, ''); + @override + Future launch( + String url, { + @required bool useSafariVC, + @required bool useWebView, + @required bool enableJavaScript, + @required bool enableDomStorage, + @required bool universalLinksOnly, + @required Map headers, + }) { + return Future.value(openNewWindow(url) != null); } } diff --git a/packages/url_launcher/url_launcher_web/pubspec.yaml b/packages/url_launcher/url_launcher_web/pubspec.yaml index b76aea1da6ab..e94a4d0d81c0 100644 --- a/packages/url_launcher/url_launcher_web/pubspec.yaml +++ b/packages/url_launcher/url_launcher_web/pubspec.yaml @@ -2,7 +2,7 @@ name: url_launcher_web description: Web platform implementation of url_launcher author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/url_launcher/url_launcher_web -version: 0.0.1 +version: 0.0.2 flutter: plugin: @@ -17,6 +17,7 @@ dependencies: flutter_web_plugins: sdk: flutter meta: ^1.1.7 + url_launcher_platform_interface: ^1.0.1 dev_dependencies: flutter_test: diff --git a/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart b/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart index 0bf9678cfe16..6109ea7a80d3 100644 --- a/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart +++ b/packages/url_launcher/url_launcher_web/test/url_launcher_web_test.dart @@ -7,18 +7,18 @@ import 'dart:html' as html; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_web_plugins/flutter_web_plugins.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher_web/url_launcher_web.dart'; +import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart'; void main() { group('URL Launcher for Web', () { setUp(() { - TestWidgetsFlutterBinding.ensureInitialized(); - webPluginRegistry.registerMessageHandler(); - final Registrar registrar = - webPluginRegistry.registrarFor(UrlLauncherPlugin); - UrlLauncherPlugin.registerWith(registrar); + UrlLauncherPlatform.instance = UrlLauncherPlugin(); + }); + + test('$UrlLauncherPlugin is the live instance', () { + expect(UrlLauncherPlatform.instance, isA()); }); test('can launch "http" URLs', () { @@ -45,5 +45,9 @@ void main() { expect(newWindow, isNot(equals(html.window))); expect(newWindow.opener, equals(html.window)); }); + + test('does not implement closeWebView()', () { + expect(closeWebView(), throwsUnimplementedError); + }); }); } diff --git a/packages/video_player/CHANGELOG.md b/packages/video_player/CHANGELOG.md index ca3a82880b10..d4882496320f 100644 --- a/packages/video_player/CHANGELOG.md +++ b/packages/video_player/CHANGELOG.md @@ -1,3 +1,19 @@ +## 0.10.2+6 + +* Remove AndroidX warnings. + +## 0.10.2+5 + +* Update unit test for compatibility with Flutter stable branch. + +## 0.10.2+4 + +* Define clang module for iOS. + +## 0.10.2+3 + +* Fix bug where formatHint was not being pass down to network sources. + ## 0.10.2+2 * Update and migrate iOS example project. diff --git a/packages/video_player/android/build.gradle b/packages/video_player/android/build.gradle index ec60461e1900..edbb4c7acce4 100644 --- a/packages/video_player/android/build.gradle +++ b/packages/video_player/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "video_player"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.videoplayer' version '1.0-SNAPSHOT' diff --git a/packages/video_player/example/android/app/gradle.properties b/packages/video_player/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/video_player/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/video_player/ios/video_player.podspec b/packages/video_player/ios/video_player.podspec index 0c817478f39f..c3268377f1d4 100644 --- a/packages/video_player/ios/video_player.podspec +++ b/packages/video_player/ios/video_player.podspec @@ -16,6 +16,7 @@ A new flutter plugin project. s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.ios.deployment_target = '8.0' + s.platform = :ios, '8.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } end diff --git a/packages/video_player/lib/video_player.dart b/packages/video_player/lib/video_player.dart index 059799b017b3..f1b0e7c9791d 100644 --- a/packages/video_player/lib/video_player.dart +++ b/packages/video_player/lib/video_player.dart @@ -207,13 +207,14 @@ class VideoPlayerController extends ValueNotifier { }; break; case DataSourceType.network: - dataSourceDescription = {'uri': dataSource}; - break; - case DataSourceType.file: dataSourceDescription = { 'uri': dataSource, 'formatHint': _videoFormatStringMap[formatHint] }; + break; + case DataSourceType.file: + dataSourceDescription = {'uri': dataSource}; + break; } final Map response = await _channel.invokeMapMethod( diff --git a/packages/video_player/pubspec.yaml b/packages/video_player/pubspec.yaml index ee47cd813add..1b0805be1764 100644 --- a/packages/video_player/pubspec.yaml +++ b/packages/video_player/pubspec.yaml @@ -2,7 +2,7 @@ name: video_player description: Flutter plugin for displaying inline video with other Flutter widgets on Android and iOS. author: Flutter Team -version: 0.10.2+2 +version: 0.10.2+6 homepage: https://github.com/flutter/plugins/tree/master/packages/video_player flutter: diff --git a/packages/video_player/test/video_player_test.dart b/packages/video_player/test/video_player_test.dart index f482f0b63cd3..9211f0c3a87c 100644 --- a/packages/video_player/test/video_player_test.dart +++ b/packages/video_player/test/video_player_test.dart @@ -3,7 +3,9 @@ // found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:video_player/video_player.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -80,4 +82,156 @@ void main() { ), findsOneWidget); }); + + group('VideoPlayerController', () { + FakeVideoPlayerPlatform fakeVideoPlayerPlatform; + + setUp(() { + fakeVideoPlayerPlatform = FakeVideoPlayerPlatform(); + }); + + test('initialize asset', () async { + final VideoPlayerController controller = VideoPlayerController.asset( + 'a.avi', + ); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], { + 'asset': 'a.avi', + 'package': null, + }); + }); + + test('initialize network', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + ); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], { + 'uri': 'https://127.0.0.1', + 'formatHint': null, + }); + }); + + test('initialize network with hint', () async { + final VideoPlayerController controller = VideoPlayerController.network( + 'https://127.0.0.1', + formatHint: VideoFormat.dash); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], { + 'uri': 'https://127.0.0.1', + 'formatHint': 'dash', + }); + }); + + test('initialize file', () async { + final VideoPlayerController controller = + VideoPlayerController.file(File('a.avi')); + await controller.initialize(); + + expect( + fakeVideoPlayerPlatform.dataSourceDescriptions[0], { + 'uri': 'file://a.avi', + }); + }); + }); +} + +class FakeVideoPlayerPlatform { + FakeVideoPlayerPlatform() { + _channel.setMockMethodCallHandler(onMethodCall); + } + + final MethodChannel _channel = const MethodChannel('flutter.io/videoPlayer'); + + Completer initialized = Completer(); + List> dataSourceDescriptions = >[]; + int nextTextureId = 0; + + Future onMethodCall(MethodCall call) { + switch (call.method) { + case 'init': + initialized.complete(true); + break; + case 'create': + FakeVideoEventStream( + nextTextureId, 100, 100, const Duration(seconds: 1)); + final Map dataSource = call.arguments; + dataSourceDescriptions.add(dataSource.cast()); + return Future>.sync(() { + return { + 'textureId': nextTextureId++, + }; + }); + break; + case 'setLooping': + break; + case 'setVolume': + break; + case 'pause': + break; + default: + throw UnimplementedError( + '${call.method} is not implemented by the FakeVideoPlayerPlatform'); + } + return Future.sync(() {}); + } +} + +class FakeVideoEventStream { + FakeVideoEventStream(this.textureId, this.width, this.height, this.duration) { + eventsChannel = FakeEventsChannel( + 'flutter.io/videoPlayer/videoEvents$textureId', onListen); + } + + int textureId; + int width; + int height; + Duration duration; + FakeEventsChannel eventsChannel; + + void onListen() { + final Map initializedEvent = { + 'event': 'initialized', + 'duration': duration.inMilliseconds, + 'width': width, + 'height': height, + }; + eventsChannel.sendEvent(initializedEvent); + } +} + +class FakeEventsChannel { + FakeEventsChannel(String name, this.onListen) { + eventsMethodChannel = MethodChannel(name); + eventsMethodChannel.setMockMethodCallHandler(onMethodCall); + } + + MethodChannel eventsMethodChannel; + VoidCallback onListen; + + Future onMethodCall(MethodCall call) { + switch (call.method) { + case 'listen': + onListen(); + break; + } + return Future.sync(() {}); + } + + void sendEvent(dynamic event) { + // TODO(jackson): This has been deprecated and should be replaced + // with `ServicesBinding.instance.defaultBinaryMessenger` when it's + // available on all the versions of Flutter that we test. + // ignore: deprecated_member_use + defaultBinaryMessenger.handlePlatformMessage( + eventsMethodChannel.name, + const StandardMethodCodec().encodeSuccessEnvelope(event), + (ByteData data) {}); + } } diff --git a/packages/webview_flutter/CHANGELOG.md b/packages/webview_flutter/CHANGELOG.md index 98e312afd3a3..17a4eb0b0916 100644 --- a/packages/webview_flutter/CHANGELOG.md +++ b/packages/webview_flutter/CHANGELOG.md @@ -1,3 +1,30 @@ +## 0.3.15+3 + +* Re-land support for the v2 Android embedding. This correctly sets the minimum + SDK to the latest stable and avoid any compile errors. *WARNING:* the V2 + embedding itself still requires the current Flutter master channel + (flutter/flutter@1d4d63a) for text input to work properly on all Android + versions. + +## 0.3.15+2 + +* Remove AndroidX warnings. + +## 0.3.15+1 + +* Revert the prior embedding support add since it requires an API that hasn't + rolled to stable. + +## 0.3.15 + +* Add support for the v2 Android embedding. This shouldn't affect existing + functionality. Plugin authors who use the V2 embedding can now register the + plugin and expect that it correctly responds to app lifecycle changes. + +## 0.3.14+2 + +* Define clang module for iOS. + ## 0.3.14+1 * Allow underscores anywhere for Javascript Channel name. @@ -9,7 +36,7 @@ ## 0.3.13 * Add an optional `userAgent` property to set a custom User Agent. - + ## 0.3.12+1 * Temporarily revert getTitle (doing this as a patch bump shortly after publishing). diff --git a/packages/webview_flutter/android/build.gradle b/packages/webview_flutter/android/build.gradle index 4fe7629b5f76..2d725840e4c6 100644 --- a/packages/webview_flutter/android/build.gradle +++ b/packages/webview_flutter/android/build.gradle @@ -1,16 +1,3 @@ -def PLUGIN = "webview_flutter"; -def ANDROIDX_WARNING = "flutterPluginsAndroidXWarning"; -gradle.buildFinished { buildResult -> - if (buildResult.failure && !rootProject.ext.has(ANDROIDX_WARNING)) { - println ' *********************************************************' - println 'WARNING: This version of ' + PLUGIN + ' will break your Android build if it or its dependencies aren\'t compatible with AndroidX.' - println ' See https://goo.gl/CP92wY for more information on the problem and how to fix it.' - println ' This warning prints for all Android build failures. The real root cause of the error may be unrelated.' - println ' *********************************************************' - rootProject.ext.set(ANDROIDX_WARNING, true); - } -} - group 'io.flutter.plugins.webviewflutter' version '1.0-SNAPSHOT' @@ -50,3 +37,28 @@ android { implementation 'androidx.webkit:webkit:1.0.0' } } + +// TODO(mklim): Remove this hack once androidx.lifecycle is included on stable. https://github.com/flutter/flutter/issues/42348 +afterEvaluate { + def containsEmbeddingDependencies = false + for (def configuration : configurations.all) { + for (def dependency : configuration.dependencies) { + if (dependency.group == 'io.flutter' && + dependency.name.startsWith('flutter_embedding') && + dependency.isTransitive()) + { + containsEmbeddingDependencies = true + break + } + } + } + if (!containsEmbeddingDependencies) { + android { + dependencies { + def lifecycle_version = "1.1.1" + compileOnly "android.arch.lifecycle:common-java8:$lifecycle_version" + compileOnly "android.arch.lifecycle:runtime:$lifecycle_version" + } + } + } +} \ No newline at end of file diff --git a/packages/webview_flutter/android/gradle.properties b/packages/webview_flutter/android/gradle.properties deleted file mode 100644 index 8bd86f680510..000000000000 --- a/packages/webview_flutter/android/gradle.properties +++ /dev/null @@ -1 +0,0 @@ -org.gradle.jvmargs=-Xmx1536M diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java index 908f877fb922..86b4fd412a29 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterCookieManager.java @@ -15,17 +15,11 @@ import io.flutter.plugin.common.MethodChannel.Result; class FlutterCookieManager implements MethodCallHandler { + private final MethodChannel methodChannel; - private FlutterCookieManager() { - // Do not instantiate. - // This class should only be used in context of a BinaryMessenger. - // Use FlutterCookieManager#registerWith instead. - } - - static void registerWith(BinaryMessenger messenger) { - MethodChannel methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager"); - FlutterCookieManager cookieManager = new FlutterCookieManager(); - methodChannel.setMethodCallHandler(cookieManager); + FlutterCookieManager(BinaryMessenger messenger) { + methodChannel = new MethodChannel(messenger, "plugins.flutter.io/cookie_manager"); + methodChannel.setMethodCallHandler(this); } @Override @@ -39,6 +33,10 @@ public void onMethodCall(MethodCall methodCall, Result result) { } } + void dispose() { + methodChannel.setMethodCallHandler(null); + } + private static void clearCookies(final Result result) { CookieManager cookieManager = CookieManager.getInstance(); final boolean hasCookies = cookieManager.hasCookies(); diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java index a7f2db308e15..83a7ed6340f8 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebView.java @@ -36,7 +36,7 @@ public class FlutterWebView implements PlatformView, MethodCallHandler { BinaryMessenger messenger, int id, Map params, - final View containerView) { + View containerView) { DisplayListenerProxy displayListenerProxy = new DisplayListenerProxy(); DisplayManager displayManager = @@ -95,6 +95,26 @@ public void onInputConnectionLocked() { webView.lockInputConnection(); } + // @Override + // This is overriding a method that hasn't rolled into stable Flutter yet. Including the + // annotation would cause compile time failures in versions of Flutter too old to include the new + // method. However leaving it raw like this means that the method will be ignored in old versions + // of Flutter but used as an override anyway wherever it's actually defined. + // TODO(mklim): Add the @Override annotation once stable passes v1.10.9. + public void onFlutterViewAttached(View flutterView) { + webView.setContainerView(flutterView); + } + + // @Override + // This is overriding a method that hasn't rolled into stable Flutter yet. Including the + // annotation would cause compile time failures in versions of Flutter too old to include the new + // method. However leaving it raw like this means that the method will be ignored in old versions + // of Flutter but used as an override anyway wherever it's actually defined. + // TODO(mklim): Add the @Override annotation once stable passes v1.10.9. + public void onFlutterViewDetached() { + webView.setContainerView(null); + } + @Override public void onMethodCall(MethodCall methodCall, Result result) { switch (methodCall.method) { diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java index bdd6abb66282..37ec1c992e26 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/FlutterWebViewClient.java @@ -11,7 +11,6 @@ import android.webkit.WebResourceRequest; import android.webkit.WebView; import android.webkit.WebViewClient; -import androidx.annotation.NonNull; import androidx.webkit.WebViewClientCompat; import io.flutter.plugin.common.MethodChannel; import java.util.HashMap; @@ -124,8 +123,7 @@ public void onUnhandledKeyEvent(WebView view, KeyEvent event) { private WebViewClientCompat internalCreateWebViewClientCompat() { return new WebViewClientCompat() { @Override - public boolean shouldOverrideUrlLoading( - @NonNull WebView view, @NonNull WebResourceRequest request) { + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { return FlutterWebViewClient.this.shouldOverrideUrlLoading(view, request); } diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java index 9275c380fb56..477eefc3565a 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/InputAwareWebView.java @@ -7,6 +7,7 @@ import static android.content.Context.INPUT_METHOD_SERVICE; import android.content.Context; +import android.util.Log; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.webkit.WebView; @@ -22,16 +23,29 @@ *

See also {@link ThreadedInputConnectionProxyAdapterView}. */ final class InputAwareWebView extends WebView { - private final View containerView; - + private static final String TAG = "InputAwareWebView"; private View threadedInputConnectionProxyView; private ThreadedInputConnectionProxyAdapterView proxyAdapterView; + private View containerView; InputAwareWebView(Context context, View containerView) { super(context); this.containerView = containerView; } + void setContainerView(View containerView) { + this.containerView = containerView; + + if (proxyAdapterView == null) { + return; + } + + Log.w(TAG, "The containerView has changed while the proxyAdapterView exists."); + if (containerView != null) { + setInputConnectionTarget(proxyAdapterView); + } + } + /** * Set our proxy adapter view to use its cached input connection instead of creating new ones. * @@ -81,6 +95,12 @@ public boolean checkInputConnectionProxy(final View view) { // This isn't a new ThreadedInputConnectionProxyView. Ignore it. return super.checkInputConnectionProxy(view); } + if (containerView == null) { + Log.e( + TAG, + "Can't create a proxy view because there's no container view. Text input may not work."); + return super.checkInputConnectionProxy(view); + } // We've never seen this before, so we make the assumption that this is WebView's // ThreadedInputConnectionProxyView. We are making the assumption that the only view that could @@ -120,6 +140,10 @@ private void resetInputConnection() { // No need to reset the InputConnection to the default thread if we've never changed it. return; } + if (containerView == null) { + Log.e(TAG, "Can't reset the input connection to the container view because there is none."); + return; + } setInputConnectionTarget(/*targetView=*/ containerView); } @@ -132,6 +156,13 @@ private void resetInputConnection() { * InputConnections should be created on. */ private void setInputConnectionTarget(final View targetView) { + if (containerView == null) { + Log.e( + TAG, + "Can't set the input connection target because there is no containerView to use as a handler."); + return; + } + targetView.requestFocus(); containerView.post( new Runnable() { diff --git a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java index 17177541222c..3acbe97c5cec 100644 --- a/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java +++ b/packages/webview_flutter/android/src/main/java/io/flutter/plugins/webviewflutter/WebViewFlutterPlugin.java @@ -4,17 +4,71 @@ package io.flutter.plugins.webviewflutter; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.PluginRegistry.Registrar; -/** WebViewFlutterPlugin */ -public class WebViewFlutterPlugin { - /** Plugin registration. */ +/** + * Java platform implementation of the webview_flutter plugin. + * + *

Register this in an add to app scenario to gracefully handle activity and context changes. + * + *

Call {@link #registerWith(Registrar)} to use the stable {@code io.flutter.plugin.common} + * package instead. + */ +public class WebViewFlutterPlugin implements FlutterPlugin { + + private FlutterCookieManager flutterCookieManager; + + /** + * Add an instance of this to {@link io.flutter.embedding.engine.plugins.PluginRegistry} to + * register it. + * + *

THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE + * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least + * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. Use the V1 embedding with {@link + * #registerWith(Registrar)} to use this plugin with older Flutter versions. + * + *

Registration should eventually be handled automatically by v2 of the + * GeneratedPluginRegistrant. https://github.com/flutter/flutter/issues/42694 + */ + public WebViewFlutterPlugin() {} + + /** + * Registers a plugin implementation that uses the stable {@code io.flutter.plugin.common} + * package. + * + *

Calling this automatically initializes the plugin. However plugins initialized this way + * won't react to changes in activity or context, unlike {@link CameraPlugin}. + */ public static void registerWith(Registrar registrar) { registrar .platformViewRegistry() .registerViewFactory( "plugins.flutter.io/webview", new WebViewFactory(registrar.messenger(), registrar.view())); - FlutterCookieManager.registerWith(registrar.messenger()); + new FlutterCookieManager(registrar.messenger()); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + BinaryMessenger messenger = binding.getFlutterEngine().getDartExecutor(); + binding + .getFlutterEngine() + .getPlatformViewsController() + .getRegistry() + .registerViewFactory( + "plugins.flutter.io/webview", new WebViewFactory(messenger, /*containerView=*/ null)); + flutterCookieManager = new FlutterCookieManager(messenger); + } + + @Override + public void onDetachedFromEngine(FlutterPluginBinding binding) { + if (flutterCookieManager == null) { + return; + } + + flutterCookieManager.dispose(); + flutterCookieManager = null; } } diff --git a/packages/webview_flutter/example/android/app/build.gradle b/packages/webview_flutter/example/android/app/build.gradle index 79a69ac3e4d7..706d501c4060 100644 --- a/packages/webview_flutter/example/android/app/build.gradle +++ b/packages/webview_flutter/example/android/app/build.gradle @@ -56,6 +56,7 @@ flutter { dependencies { testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.1.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' + androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/packages/webview_flutter/example/android/app/gradle.properties b/packages/webview_flutter/example/android/app/gradle.properties deleted file mode 100644 index 5465fec0ecad..000000000000 --- a/packages/webview_flutter/example/android/app/gradle.properties +++ /dev/null @@ -1,2 +0,0 @@ -android.enableJetifier=true -android.useAndroidX=true \ No newline at end of file diff --git a/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1ActivityTest.java b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1ActivityTest.java new file mode 100644 index 000000000000..fe10c6155e5a --- /dev/null +++ b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1ActivityTest.java @@ -0,0 +1,13 @@ +package io.flutter.plugins.webviewflutterexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class EmbeddingV1ActivityTest { + @Rule + public ActivityTestRule rule = + new ActivityTestRule<>(EmbeddingV1Activity.class); +} diff --git a/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java new file mode 100644 index 000000000000..a0bd4fe1a7f5 --- /dev/null +++ b/packages/webview_flutter/example/android/app/src/androidTestDebug/java/io/flutter/plugins/webviewflutterexample/MainActivityTest.java @@ -0,0 +1,11 @@ +package io.flutter.plugins.webviewflutterexample; + +import androidx.test.rule.ActivityTestRule; +import dev.flutter.plugins.e2e.FlutterRunner; +import org.junit.Rule; +import org.junit.runner.RunWith; + +@RunWith(FlutterRunner.class) +public class MainActivityTest { + @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); +} diff --git a/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml b/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml index 8fcbcd3908ba..fd570acc8959 100644 --- a/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml +++ b/packages/webview_flutter/example/android/app/src/main/AndroidManifest.xml @@ -1,39 +1,48 @@ + package="io.flutter.plugins.webviewflutterexample"> - - + + + + + + + + + + + + + - - - - - - - - - - - + + diff --git a/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1Activity.java b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1Activity.java new file mode 100644 index 000000000000..9b868934cc10 --- /dev/null +++ b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/EmbeddingV1Activity.java @@ -0,0 +1,17 @@ +// Copyright 2019 The Chromium 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.plugins.webviewflutterexample; + +import android.os.Bundle; +import io.flutter.app.FlutterActivity; +import io.flutter.plugins.GeneratedPluginRegistrant; + +public class EmbeddingV1Activity extends FlutterActivity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + GeneratedPluginRegistrant.registerWith(this); + } +} diff --git a/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java index f935d0030483..2f3b7edd3d9f 100644 --- a/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java +++ b/packages/webview_flutter/example/android/app/src/main/java/io/flutter/plugins/webviewflutterexample/MainActivity.java @@ -4,14 +4,22 @@ package io.flutter.plugins.webviewflutterexample; -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; +import io.flutter.embedding.android.FlutterActivity; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.plugins.webviewflutter.WebViewFlutterPlugin; +/** + * THIS PLUGIN CODE PATH DEPENDS ON A NEWER VERSION OF FLUTTER THAN THE ONE DEFINED IN THE + * PUBSPEC.YAML. Text input will fail on some Android devices unless this is used with at least + * flutter/flutter@1d4d63ace1f801a022ea9ec737bf8c15395588b9. + * + *

Use the V1 embedding as seen in {@link EmbeddingV1Activity} to use this plugin on older + * Flutter versions. + */ public class MainActivity extends FlutterActivity { + // TODO(mklim): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694 @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); + public void configureFlutterEngine(FlutterEngine flutterEngine) { + flutterEngine.getPlugins().add(new WebViewFlutterPlugin()); } } diff --git a/packages/webview_flutter/example/android/gradle.properties b/packages/webview_flutter/example/android/gradle.properties index ad8917e962e5..a6738207fd15 100644 --- a/packages/webview_flutter/example/android/gradle.properties +++ b/packages/webview_flutter/example/android/gradle.properties @@ -1,2 +1,4 @@ org.gradle.jvmargs=-Xmx1536M -android.useAndroidX=true \ No newline at end of file +android.useAndroidX=true +android.enableJetifier=true +android.enableR8=true diff --git a/packages/webview_flutter/example/pubspec.yaml b/packages/webview_flutter/example/pubspec.yaml index 8657cfde0f8b..ae1d71c1cade 100644 --- a/packages/webview_flutter/example/pubspec.yaml +++ b/packages/webview_flutter/example/pubspec.yaml @@ -1,7 +1,7 @@ name: webview_flutter_example description: Demonstrates how to use the webview_flutter plugin. -version: 1.0.1 +version: 1.0.4 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" @@ -17,6 +17,7 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter + e2e: "^0.2.0" flutter: uses-material-design: true diff --git a/packages/webview_flutter/example/test_driver/webview.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart similarity index 92% rename from packages/webview_flutter/example/test_driver/webview.dart rename to packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart index e24afd73f557..a5d4d7de66fe 100644 --- a/packages/webview_flutter/example/test_driver/webview.dart +++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e.dart @@ -9,19 +9,17 @@ import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:flutter_driver/driver_extension.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:webview_flutter/webview_flutter.dart'; +import 'package:e2e/e2e.dart'; void main() { - final Completer allTestsCompleter = Completer(); - enableFlutterDriverExtension(handler: (_) => allTestsCompleter.future); - tearDownAll(() => allTestsCompleter.complete(null)); + E2EWidgetsFlutterBinding.ensureInitialized(); - test('initalUrl', () async { + testWidgets('initalUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -38,10 +36,10 @@ void main() { expect(currentUrl, 'https://flutter.dev/'); }); - test('loadUrl', () async { + testWidgets('loadUrl', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -61,11 +59,11 @@ void main() { // enable this once https://github.com/flutter/flutter/issues/31510 // is resolved. - test('loadUrl with headers', () async { + testWidgets('loadUrl with headers', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final StreamController pageLoads = StreamController(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -96,12 +94,12 @@ void main() { expect(content.contains('flutter_test_header'), isTrue); }); - test('JavaScriptChannel', () async { + testWidgets('JavaScriptChannel', (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final Completer pageLoaded = Completer(); final List messagesReceived = []; - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -137,7 +135,7 @@ void main() { expect(messagesReceived, equals(['hello'])); }); - test('resize webview', () async { + testWidgets('resize webview', (WidgetTester tester) async { final String resizeTest = ''' Resize test @@ -184,7 +182,7 @@ void main() { javascriptMode: JavascriptMode.unrestricted, ); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Column( @@ -204,7 +202,7 @@ void main() { expect(resizeCompleter.isCompleted, false); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: Column( @@ -222,11 +220,11 @@ void main() { await resizeCompleter.future; }); - test('set custom userAgent', () async { + testWidgets('set custom userAgent', (WidgetTester tester) async { final Completer controllerCompleter1 = Completer(); final GlobalKey _globalKey = GlobalKey(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -244,7 +242,7 @@ void main() { final String customUserAgent1 = await _getUserAgent(controller1); expect(customUserAgent1, 'Custom_User_Agent1'); // rebuild the WebView with a different user agent. - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -260,12 +258,13 @@ void main() { expect(customUserAgent2, 'Custom_User_Agent2'); }); - test('use default platform userAgent after webView is rebuilt', () async { + testWidgets('use default platform userAgent after webView is rebuilt', + (WidgetTester tester) async { final Completer controllerCompleter = Completer(); final GlobalKey _globalKey = GlobalKey(); // Build the webView with no user agent to get the default platform user agent. - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -281,7 +280,7 @@ void main() { final WebViewController controller = await controllerCompleter.future; final String defaultPlatformUserAgent = await _getUserAgent(controller); // rebuild the WebView with a custom user agent. - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -295,7 +294,7 @@ void main() { final String customUserAgent = await _getUserAgent(controller); expect(customUserAgent, 'Custom_User_Agent'); // rebuilds the WebView with no user agent. - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -341,12 +340,12 @@ void main() { audioTestBase64 = base64Encode(const Utf8Encoder().convert(audioTest)); }); - test('Auto media playback', () async { + testWidgets('Auto media playback', (WidgetTester tester) async { Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -373,7 +372,7 @@ void main() { pageLoaded = Completer(); // We change the key to re-create a new webview as we change the initialMediaPlaybackPolicy - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -399,13 +398,14 @@ void main() { expect(isPaused, _webviewBool(true)); }); - test('Changes to initialMediaPlaybackPolocy are ignored', () async { + testWidgets('Changes to initialMediaPlaybackPolocy are ignored', + (WidgetTester tester) async { final Completer controllerCompleter = Completer(); Completer pageLoaded = Completer(); final GlobalKey key = GlobalKey(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -430,7 +430,7 @@ void main() { pageLoaded = Completer(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -458,7 +458,7 @@ void main() { }); }); - test('getTitle', () async { + testWidgets('getTitle', (WidgetTester tester) async { final String getTitleTest = ''' Some title @@ -473,7 +473,7 @@ void main() { final Completer controllerCompleter = Completer(); - await pumpWidget( + await tester.pumpWidget( Directionality( textDirection: TextDirection.ltr, child: WebView( @@ -496,11 +496,6 @@ void main() { }); } -Future pumpWidget(Widget widget) { - runApp(widget); - return WidgetsBinding.instance.endOfFrame; -} - // JavaScript booleans evaluate to different string values on Android and iOS. // This utility method returns the string boolean value of the current platform. String _webviewBool(bool value) { diff --git a/packages/webview_flutter/example/test_driver/webview_test.dart b/packages/webview_flutter/example/test_driver/webview_flutter_e2e_test.dart similarity index 72% rename from packages/webview_flutter/example/test_driver/webview_test.dart rename to packages/webview_flutter/example/test_driver/webview_flutter_e2e_test.dart index b0d3305cd652..2e5c27fd402e 100644 --- a/packages/webview_flutter/example/test_driver/webview_test.dart +++ b/packages/webview_flutter/example/test_driver/webview_flutter_e2e_test.dart @@ -3,11 +3,14 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:io'; import 'package:flutter_driver/flutter_driver.dart'; Future main() async { final FlutterDriver driver = await FlutterDriver.connect(); - await driver.requestData(null, timeout: const Duration(minutes: 1)); + final String result = + await driver.requestData(null, timeout: const Duration(minutes: 1)); driver.close(); + exit(result == 'pass' ? 0 : 1); } diff --git a/packages/webview_flutter/ios/Classes/FlutterWebView.m b/packages/webview_flutter/ios/Classes/FlutterWebView.m index 36f4e8cb5cfe..60fa24052038 100644 --- a/packages/webview_flutter/ios/Classes/FlutterWebView.m +++ b/packages/webview_flutter/ios/Classes/FlutterWebView.m @@ -48,7 +48,7 @@ - (instancetype)initWithFrame:(CGRect)frame viewIdentifier:(int64_t)viewId arguments:(id _Nullable)args binaryMessenger:(NSObject*)messenger { - if ([super init]) { + if (self = [super init]) { _viewId = viewId; NSString* channelName = [NSString stringWithFormat:@"plugins.flutter.io/webview_%lld", viewId]; diff --git a/packages/webview_flutter/ios/webview_flutter.podspec b/packages/webview_flutter/ios/webview_flutter.podspec index 1436eb1569d8..b10d7414b20a 100644 --- a/packages/webview_flutter/ios/webview_flutter.podspec +++ b/packages/webview_flutter/ios/webview_flutter.podspec @@ -15,7 +15,7 @@ A WebView Plugin for Flutter. s.source_files = 'Classes/**/*' s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - - s.ios.deployment_target = '8.0' -end + s.platform = :ios, '8.0' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } +end diff --git a/packages/webview_flutter/pubspec.yaml b/packages/webview_flutter/pubspec.yaml index e5f39f14da87..4d4df2676f03 100644 --- a/packages/webview_flutter/pubspec.yaml +++ b/packages/webview_flutter/pubspec.yaml @@ -1,12 +1,12 @@ name: webview_flutter description: A Flutter plugin that provides a WebView widget on Android and iOS. -version: 0.3.14+1 +version: 0.3.15+3 author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/webview_flutter environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" - flutter: ">=1.5.0 <2.0.0" + flutter: ">=1.9.1+hotfix.5 <2.0.0" dependencies: flutter: diff --git a/script/build_all_plugins_app.sh b/script/build_all_plugins_app.sh index 6b70ee00e9b1..5ba4b6e5ce02 100755 --- a/script/build_all_plugins_app.sh +++ b/script/build_all_plugins_app.sh @@ -10,7 +10,7 @@ readonly REPO_DIR="$(dirname "$SCRIPT_DIR")" source "$SCRIPT_DIR/common.sh" check_changed_packages > /dev/null -(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude instrumentation_adapter) +(cd "$REPO_DIR" && pub global run flutter_plugin_tools all-plugins-app --exclude instrumentation_adapter,url_launcher_platform_interface) function error() { echo "$@" 1>&2 diff --git a/script/check_publish.sh b/script/check_publish.sh index 72739410f17c..8c35642316ef 100755 --- a/script/check_publish.sh +++ b/script/check_publish.sh @@ -17,7 +17,7 @@ function check_publish() { echo "Checking that $package_name can be published." if [[ $(cd "$dir" && cat pubspec.yaml | grep -E "^publish_to: none") ]]; then echo "Package $package_name is marked as unpublishable. Skipping." - elif (cd "$dir" && pub publish --dry-run > /dev/null); then + elif (cd "$dir" && flutter pub publish -- --dry-run > /dev/null); then echo "Package $package_name is able to be published." else error "Unable to publish $package_name" diff --git a/script/incremental_build.sh b/script/incremental_build.sh index 0e0fb051e70c..17518cc4cf13 100755 --- a/script/incremental_build.sh +++ b/script/incremental_build.sh @@ -22,7 +22,10 @@ else if [[ "$CHANGED_PACKAGES" == "" ]]; then echo "No changes detected in packages." + echo "Running for all packages" + (cd "$REPO_DIR" && pub global run flutter_plugin_tools "${ACTIONS[@]}" $PLUGIN_SHARDING) else + echo running "${ACTIONS[@]}" (cd "$REPO_DIR" && pub global run flutter_plugin_tools "${ACTIONS[@]}" --plugins="$CHANGED_PACKAGES" $PLUGIN_SHARDING) echo "Running version check for changed packages" (cd "$REPO_DIR" && pub global run flutter_plugin_tools version-check --base_sha="$(get_branch_base_sha)") diff --git a/script/lint_darwin_plugins.sh b/script/lint_darwin_plugins.sh index b99bc8ce2751..94041c728191 100755 --- a/script/lint_darwin_plugins.sh +++ b/script/lint_darwin_plugins.sh @@ -13,23 +13,50 @@ function lint_package() { local package_dir="${REPO_DIR}/packages/$package_name/" local failure_count=0 - for podspec in "$(find "${package_dir}" -name '*\.podspec')"; do - echo "Linting $package_name.podspec" + # These podspecs are temporary multi-platform adoption dummy files. + local skipped_podspecs=( + "url_launcher_web.podspec" + ) + + # TODO: These packages have analyzer warnings. Remove plugins from this list as issues are fixed. + local skip_analysis_packages=( + "camera.podspec" # https://github.com/flutter/flutter/issues/42673 + ) + find "${package_dir}" -type f -name "*\.podspec" | while read podspec; do + local podspecBasename=$(basename "${podspec}") + if [[ "${skipped_podspecs[*]}" =~ "${podspecBasename}" ]]; then + continue + fi + + # TODO: Remove --allow-warnings flag https://github.com/flutter/flutter/issues/41444 + local lint_args=( + lib + lint + "${podspec}" + --allow-warnings + --fail-fast + --silent + ) + if [[ ! "${skip_analysis_packages[*]}" =~ "${podspecBasename}" ]]; then + lint_args+=(--analyze) + echo "Linting and analyzing ${podspecBasename}" + else + echo "Linting ${podspecBasename}" + fi # Build as frameworks. # This will also run any tests set up as a test_spec. See https://blog.cocoapods.org/CocoaPods-1.3.0. - # TODO: Add --analyze flag https://github.com/flutter/flutter/issues/41443 - # TODO: Remove --allow-warnings flag https://github.com/flutter/flutter/issues/41444 - pod lib lint "${podspec}" --allow-warnings --fail-fast --silent + pod "${lint_args[@]}" if [[ "$?" -ne 0 ]]; then - error "Package ${package_name} has framework issues. Run \"pod lib lint $podspec\" to inspect." + error "Package ${package_name} has framework issues. Run \"pod lib lint ${podspec} --analyze\" to inspect." failure_count+=1 fi # Build as libraries. - pod lib lint "${podspec}" --allow-warnings --use-libraries --fail-fast --silent + lint_args+=(--use-libraries) + pod "${lint_args[@]}" if [[ "$?" -ne 0 ]]; then - error "Package ${package_name} has library issues. Run \"pod lib lint $podspec --use-libraries\" to inspect." + error "Package ${package_name} has library issues. Run \"pod lib lint ${podspec} --use-libraries --analyze\" to inspect." failure_count+=1 fi done @@ -45,19 +72,7 @@ function lint_packages() { # TODO: These packages have linter errors. Remove plugins from this list as linter issues are fixed. local skipped_packages=( - 'battery' 'google_maps_flutter' - 'google_sign_in' - 'local_auth' - 'package_info' - 'path_provider' - 'quick_actions' - 'sensors' - 'share' - 'shared_preferences' - 'url_launcher' - 'video_player' - 'webview_flutter' ) local failure_count=0