diff --git a/packages/device_info/CHANGELOG.md b/packages/device_info/CHANGELOG.md index 62d4960dc73b..1fff8c7020df 100644 --- a/packages/device_info/CHANGELOG.md +++ b/packages/device_info/CHANGELOG.md @@ -1,3 +1,11 @@ +## 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..7dfea2df5dac 100644 --- a/packages/device_info/android/build.gradle +++ b/packages/device_info/android/build.gradle @@ -45,3 +45,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" + api "android.arch.lifecycle:runtime:$lifecycle_version" + api "android.arch.lifecycle:common:$lifecycle_version" + api "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/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..580df1452ff1 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 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"