diff --git a/.github/workflows/google_api_availability.yaml b/.github/workflows/google_api_availability.yaml index de48cc2..53f1cec 100644 --- a/.github/workflows/google_api_availability.yaml +++ b/.github/workflows/google_api_availability.yaml @@ -27,10 +27,10 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # Make sure the stable version of Flutter is available - - uses: subosito/flutter-action@v1 + - uses: subosito/flutter-action@v2 with: channel: 'stable' @@ -53,11 +53,6 @@ jobs: - name: Run Android build run: flutter build apk --release working-directory: ${{env.example-directory}} - - # Build iOS version of the example app - - name: Run iOS build - run: flutter build ios --release --no-codesign - working-directory: ${{env.example-directory}} # Run all unit-tests with code coverage - name: Run unit tests @@ -65,7 +60,7 @@ jobs: working-directory: ${{env.source-directory}} # Upload code coverage information - - uses: codecov/codecov-action@v1 + - uses: codecov/codecov-action@v2 with: file: ${{env.source-directory}}/coverage/lcov.info # optional name: Google API Availability (App Facing Package) # optional diff --git a/.github/workflows/google_api_availability_android.yaml b/.github/workflows/google_api_availability_android.yaml index 6011189..bfa6533 100644 --- a/.github/workflows/google_api_availability_android.yaml +++ b/.github/workflows/google_api_availability_android.yaml @@ -23,7 +23,7 @@ jobs: env: source-directory: ./google_api_availability_android - example-directory: ./google_api_availability_android/example + example-directory: ./google_api_availability/example # Steps represent a sequence of tasks that will be executed as part of the job steps: @@ -54,4 +54,3 @@ jobs: - name: Run Android build run: flutter build apk --release working-directory: ${{env.example-directory}} - diff --git a/google_api_availability/android/gradle/wrapper/gradle-wrapper.properties b/google_api_availability/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/google_api_availability/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/google_api_availability_android/CHANGELOG.md b/google_api_availability_android/CHANGELOG.md new file mode 100644 index 0000000..6830014 --- /dev/null +++ b/google_api_availability_android/CHANGELOG.md @@ -0,0 +1,3 @@ +## 1.0.0 + +* Extracts the Android platform implementation from the google_api_availability package. diff --git a/google_api_availability_android/LICENSE b/google_api_availability_android/LICENSE new file mode 100644 index 0000000..b0b0b52 --- /dev/null +++ b/google_api_availability_android/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 Baseflow + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/google_api_availability_android/README.md b/google_api_availability_android/README.md new file mode 100644 index 0000000..827525c --- /dev/null +++ b/google_api_availability_android/README.md @@ -0,0 +1,26 @@ +# google_api_availability_android + +[![style: flutter_lints](https://img.shields.io/badge/style-flutter_lints-40c4ff.svg)](https://pub.dev/packages/flutter_lints) + +The official Android implementation of the [`google_api_availability`][1] plugin by Baseflow. + +## Usage + +This plugin is not yet in use, but will become the endorsed Android implementation later, once `google_api_availability` is updated. +More detailed instructions on using the API can be found in the [README.md][3] of the [`google_api_availability`][1] package. + +## Issues + +Please file any issues, bugs or feature requests as an issue on our [GitHub](https://github.com/Baseflow/flutter-google-api-availability/issues) page. Commercial support is available, you can contact us at . + +## Want to contribute + +If you would like to contribute to the plugin (e.g. by improving the documentation, solving a bug or adding a cool new feature), please carefully review our [contribution guide](../CONTRIBUTING.md) and send us your [pull request](https://github.com/Baseflow/flutter-google-api-availability/pulls). + +## Author + +This Google API Availability plugin for Flutter is developed by [Baseflow](https://baseflow.com). + +[1]: ../google_api_availability +[2]: lib/google_api_availability_platform_interface.dart +[3]: ../README.md diff --git a/google_api_availability_android/analysis_options.yaml b/google_api_availability_android/analysis_options.yaml new file mode 100644 index 0000000..fbe6296 --- /dev/null +++ b/google_api_availability_android/analysis_options.yaml @@ -0,0 +1,10 @@ +include: package:flutter_lints/flutter.yaml + +analyzer: + exclude: + # Ignore generated files + - '**/*.g.dart' + - 'lib/src/generated/*.dart' +linter: + rules: + - public_member_api_docs diff --git a/google_api_availability_android/android/build.gradle b/google_api_availability_android/android/build.gradle new file mode 100644 index 0000000..9df6455 --- /dev/null +++ b/google_api_availability_android/android/build.gradle @@ -0,0 +1,44 @@ +group 'com.baseflow.googleapiavailability' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + compileSdkVersion 31 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + implementation 'com.google.android.gms:play-services-base:18.1.0' + implementation 'androidx.annotation:annotation:1.1.0' +} diff --git a/google_api_availability_android/android/gradle/wrapper/gradle-wrapper.properties b/google_api_availability_android/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..69a9715 --- /dev/null +++ b/google_api_availability_android/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/google_api_availability_android/android/settings.gradle b/google_api_availability_android/android/settings.gradle new file mode 100644 index 0000000..f5ff5d0 --- /dev/null +++ b/google_api_availability_android/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'google_api_availability' diff --git a/google_api_availability_android/android/src/main/AndroidManifest.xml b/google_api_availability_android/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..fd45006 --- /dev/null +++ b/google_api_availability_android/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityConstants.java b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityConstants.java new file mode 100644 index 0000000..8c4aa2f --- /dev/null +++ b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityConstants.java @@ -0,0 +1,59 @@ +package com.baseflow.googleapiavailability; + +import androidx.annotation.IntDef; + +import com.google.android.gms.common.ConnectionResult; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +final class GoogleApiAvailabilityConstants { + static final String LOG_TAG = "google_api_availability"; + + static final int REQUEST_GOOGLE_PLAY_SERVICES = 1000; + + //GOOGLE_PLAY_SERVICES_AVAILABILITY + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_SUCCESS = 0; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_MISSING = 1; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_UPDATING = 2; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_VERSION_UPDATE_REQUIRED = 3; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_DISABLED = 4; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_INVALID = 5; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_NOT_AVAILABLE_ON_PLATFORM = 6; + static final int GOOGLE_PLAY_SERVICES_AVAILABILITY_UNKNOWN = 7; + + + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + GOOGLE_PLAY_SERVICES_AVAILABILITY_SUCCESS, + GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_MISSING, + GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_UPDATING, + GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_VERSION_UPDATE_REQUIRED, + GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_DISABLED, + GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_INVALID, + GOOGLE_PLAY_SERVICES_AVAILABILITY_NOT_AVAILABLE_ON_PLATFORM, + GOOGLE_PLAY_SERVICES_AVAILABILITY_UNKNOWN, + }) + @interface GooglePlayServicesAvailability { + } + + @GooglePlayServicesAvailability + static int toPlayServiceAvailability(int connectionResult) { + switch (connectionResult) { + case ConnectionResult.SUCCESS: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_SUCCESS; + case ConnectionResult.SERVICE_MISSING: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_MISSING; + case ConnectionResult.SERVICE_UPDATING: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_UPDATING; + case ConnectionResult.SERVICE_VERSION_UPDATE_REQUIRED: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_VERSION_UPDATE_REQUIRED; + case ConnectionResult.SERVICE_DISABLED: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_DISABLED; + case ConnectionResult.SERVICE_INVALID: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_SERVICE_INVALID; + default: + return GOOGLE_PLAY_SERVICES_AVAILABILITY_UNKNOWN; + } + } +} diff --git a/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityManager.java b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityManager.java new file mode 100644 index 0000000..8bb67ec --- /dev/null +++ b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityManager.java @@ -0,0 +1,149 @@ +package com.baseflow.googleapiavailability; + +import android.app.Activity; +import android.app.Dialog; +import android.app.PendingIntent; +import android.content.Context; +import android.util.Log; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.tasks.Task; + +import java.util.List; + +public class GoogleApiAvailabilityManager { + + GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance(); + + @FunctionalInterface + interface SuccessCallback { + void onSuccess(int connectionResult); + } + + @FunctionalInterface + interface MakeGooglePlayServicesAvailableCallback { + void onSuccess(Void v); + } + + @FunctionalInterface + interface getErrorStringCallback { + void onSuccess(String errorString); + } + + @FunctionalInterface + interface isUserResolvableCallback { + void onSuccess(boolean isUserResolvable); + } + + @FunctionalInterface + interface showErrorNotificationCallback { + void onSuccess(Void v); + } + + @FunctionalInterface + interface showErrorDialogFragmentCallback { + void onSuccess(boolean showErrorDialogFragmentCallback); + } + + @FunctionalInterface + interface ErrorCallback { + void onError(String errorCode, String errorDescription); + } + + void checkPlayServicesAvailability(Boolean showDialog, Activity activity, Context applicationContext, SuccessCallback successCallback, ErrorCallback errorCallback) { + if (applicationContext == null) { + Log.e(GoogleApiAvailabilityConstants.LOG_TAG, "The `ApplicationContext` cannot be null."); + errorCallback.onError("GoogleApiAvailability.GoogleApiAvailabilityManager", "Android `ApplicationContext` cannot be null."); + return; + } + + final int connectionResult = googleApiAvailability + .isGooglePlayServicesAvailable(applicationContext); + + if (activity == null) { + if (showDialog != null && showDialog) { + // Only log warning when `showDialog` property was `true`. + Log.w(GoogleApiAvailabilityConstants.LOG_TAG, "Unable to show dialog as `Activity` is not available."); + } + showDialog = false; + } + + if (showDialog != null && showDialog) { + googleApiAvailability + .showErrorDialogFragment(activity, connectionResult, GoogleApiAvailabilityConstants.REQUEST_GOOGLE_PLAY_SERVICES); + } + + successCallback.onSuccess(GoogleApiAvailabilityConstants.toPlayServiceAvailability(connectionResult)); + } + + void makeGooglePlayServicesAvailable(Activity activity, MakeGooglePlayServicesAvailableCallback successCallback, ErrorCallback errorCallback) { + if (activity == null) { + Log.e(GoogleApiAvailabilityConstants.LOG_TAG, "Activity cannot be null."); + errorCallback.onError("GoogleApiAvailability.makeGooglePlayServicesAvailable", "Android Activity cannot be null."); + return; + } + + googleApiAvailability.makeGooglePlayServicesAvailable(activity) + .addOnFailureListener((Exception e) -> errorCallback.onError("GoogleApiAvailability.makeGooglePlayServicesAvailable", e.getMessage())) + .addOnSuccessListener((Void t) -> successCallback.onSuccess(null)); + } + + void getErrorString(Context applicationContext, getErrorStringCallback successCallback, ErrorCallback errorCallback) { + if (applicationContext == null) { + Log.e(GoogleApiAvailabilityConstants.LOG_TAG, "Context cannot be null."); + errorCallback.onError("GoogleApiAvailability.getErrorString", "Android context cannot be null."); + return; + } + + final String errorString = googleApiAvailability.getErrorString(googleApiAvailability.isGooglePlayServicesAvailable(applicationContext)); + + successCallback.onSuccess(errorString); + } + + void isUserResolvable(Context applicationContext, isUserResolvableCallback successCallback, ErrorCallback errorCallback) { + if (applicationContext == null) { + Log.e(GoogleApiAvailabilityConstants.LOG_TAG, "Context cannot be null."); + errorCallback.onError("GoogleApiAvailability.isUserResolvable", "Android context cannot be null."); + return; + } + + final int connectionResult = googleApiAvailability + .isGooglePlayServicesAvailable(applicationContext); + + successCallback.onSuccess(googleApiAvailability.isUserResolvableError(connectionResult)); + } + + void showErrorNotification(Context applicationContext, showErrorNotificationCallback successCallback, ErrorCallback errorCallback) { + if (applicationContext == null) { + Log.e(GoogleApiAvailabilityConstants.LOG_TAG, "Context cannot be null."); + errorCallback.onError("GoogleApiAvailability.showErrorNotification", "Android context cannot be null."); + return; + } + + final int connectionResult = googleApiAvailability + .isGooglePlayServicesAvailable(applicationContext); + + googleApiAvailability.showErrorNotification(applicationContext, connectionResult); + + successCallback.onSuccess(null); + } + + void showErrorDialogFragment(Context applicationContext, Activity activity, showErrorDialogFragmentCallback successCallback, ErrorCallback errorCallback) { + if (applicationContext == null) { + Log.e(GoogleApiAvailabilityConstants.LOG_TAG, "Context cannot be null."); + errorCallback.onError("GoogleApiAvailability.showErrorDialogFragment", "Android context cannot be null."); + return; + } + + final int errorCode = googleApiAvailability + .isGooglePlayServicesAvailable(applicationContext); + + if (errorCode != GoogleApiAvailabilityConstants.GOOGLE_PLAY_SERVICES_AVAILABILITY_SUCCESS) { + googleApiAvailability.showErrorDialogFragment(activity, errorCode, GoogleApiAvailabilityConstants.REQUEST_GOOGLE_PLAY_SERVICES); + successCallback.onSuccess(true); + } + + successCallback.onSuccess(false); + } +} diff --git a/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityPlugin.java b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityPlugin.java new file mode 100644 index 0000000..aef880b --- /dev/null +++ b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/GoogleApiAvailabilityPlugin.java @@ -0,0 +1,82 @@ +package com.baseflow.googleapiavailability; + +import android.content.Context; +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.BinaryMessenger; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugin.common.PluginRegistry.ViewDestroyListener; +import io.flutter.view.FlutterNativeView; + +/** + * GoogleApiAvailabilityPlugin + */ +public class GoogleApiAvailabilityPlugin implements FlutterPlugin, ActivityAware { + + private final GoogleApiAvailabilityManager googleApiAvailabilityManager; + private MethodChannel channel; + private MethodCallHandlerImpl methodCallHandler; + + public GoogleApiAvailabilityPlugin() { + this.googleApiAvailabilityManager = new GoogleApiAvailabilityManager(); + } + + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + methodCallHandler.setActivity(binding.getActivity()); + } + + @Override + public void onDetachedFromActivity() { + methodCallHandler.setActivity(null); + } + + @Override + public void onReattachedToActivityForConfigChanges(@NonNull ActivityPluginBinding binding) { + methodCallHandler.setActivity(binding.getActivity()); + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + methodCallHandler.setActivity(null); + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + registerPlugin(binding.getApplicationContext(), binding.getBinaryMessenger()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + unregisterPlugin(); + } + + public static void registerWith(Registrar registrar) { + final GoogleApiAvailabilityPlugin plugin = new GoogleApiAvailabilityPlugin(); + plugin.registerPlugin(registrar.context(), registrar.messenger()); + plugin.methodCallHandler.setActivity(registrar.activity()); + + registrar.addViewDestroyListener(new ViewDestroyListener() { + @Override + public boolean onViewDestroy(FlutterNativeView view) { + plugin.unregisterPlugin(); + return false; + } + }); + } + + private void registerPlugin(Context context, BinaryMessenger messenger) { + methodCallHandler = new MethodCallHandlerImpl(context, googleApiAvailabilityManager); + channel = new MethodChannel(messenger, "flutter.baseflow.com/google_api_availability_android/methods"); + channel.setMethodCallHandler(methodCallHandler); + } + + private void unregisterPlugin() { + channel.setMethodCallHandler(null); + channel = null; + } +} \ No newline at end of file diff --git a/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/MethodCallHandlerImpl.java b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/MethodCallHandlerImpl.java new file mode 100644 index 0000000..4ce9ecc --- /dev/null +++ b/google_api_availability_android/android/src/main/java/com/baseflow/googleapiavailability/MethodCallHandlerImpl.java @@ -0,0 +1,93 @@ +package com.baseflow.googleapiavailability; + +import android.app.Activity; +import android.content.Context; + +import androidx.annotation.IntDef; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; + +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.Result; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class MethodCallHandlerImpl implements MethodChannel.MethodCallHandler { + + private final GoogleApiAvailabilityManager googleApiAvailabilityManager; + + MethodCallHandlerImpl(Context applicationContext, GoogleApiAvailabilityManager googleApiAvailabilityManager) { + this.applicationContext = applicationContext; + + this.googleApiAvailabilityManager = googleApiAvailabilityManager; + } + + private final Context applicationContext; + + @Nullable + private Activity activity; + + void setActivity(@Nullable Activity activity) { + this.activity = activity; + } + + + @Override + public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) { + switch (call.method) { + case "checkPlayServicesAvailability": { + final Boolean showDialog = call.argument("showDialog"); + googleApiAvailabilityManager.checkPlayServicesAvailability(showDialog, activity, applicationContext, result::success, + (String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + break; + } + case "makeGooglePlayServicesAvailable": { + googleApiAvailabilityManager.makeGooglePlayServicesAvailable(activity,result::success, + (String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + break; + } + case "getErrorString": { + googleApiAvailabilityManager.getErrorString(applicationContext, result::success,(String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + break; + } + case "isUserResolvable": { + googleApiAvailabilityManager.isUserResolvable(applicationContext, result::success,(String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + break; + } + case "showErrorNotification": { + googleApiAvailabilityManager.showErrorNotification(applicationContext, result::success,(String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + break; + } + case "showErrorDialogFragment": { + googleApiAvailabilityManager.showErrorDialogFragment(applicationContext, activity, result::success,(String errorCode, String errorDescription) -> result.error( + errorCode, + errorDescription, + null)); + break; + } + default: + result.notImplemented(); + break; + } + } +} diff --git a/google_api_availability_android/lib/google_api_availability_android.dart b/google_api_availability_android/lib/google_api_availability_android.dart new file mode 100644 index 0000000..f1abc49 --- /dev/null +++ b/google_api_availability_android/lib/google_api_availability_android.dart @@ -0,0 +1,3 @@ +library google_api_availability_android; + +export 'src/google_api_availability_android.dart'; diff --git a/google_api_availability_android/lib/src/google_api_availability_android.dart b/google_api_availability_android/lib/src/google_api_availability_android.dart new file mode 100644 index 0000000..a5537ce --- /dev/null +++ b/google_api_availability_android/lib/src/google_api_availability_android.dart @@ -0,0 +1,96 @@ +import 'dart:async'; +import 'package:flutter/services.dart'; +import 'package:google_api_availability_platform_interface/google_api_availability_platform_interface.dart'; + +/// An Android implementation of [GoogleApiAvailabilityPlatform] that used method channels. +class GoogleApiAvailabilityAndroid extends GoogleApiAvailabilityPlatform { + static const MethodChannel _methodChannel = MethodChannel( + 'flutter.baseflow.com/google_api_availability_android/methods'); + + /// Registers this class as the default instance of [GoogleApiAvailabilityPlatform]. + static void registerWith() { + GoogleApiAvailabilityPlatform.instance = GoogleApiAvailabilityAndroid(); + } + + /// Returns the connection status of Google Play Service. + /// + /// Optionally, you can also show an error dialog if the connection status is + /// not [GooglePlayServicesAvailability.success]. + @override + Future checkGooglePlayServicesAvailability( + [bool showDialogIfNecessary = false]) async { + final availability = await _methodChannel.invokeMethod( + 'checkPlayServicesAvailability', + {'showDialog': showDialogIfNecessary}, + ); + + if (availability == null) { + return GooglePlayServicesAvailability.unknown; + } + + return GooglePlayServicesAvailability.values[availability]; + } + + /// Attempts to make Google Play services available on this device. + /// + /// Shows a dialog if the error is resolvable by user. + /// + /// If the [Future] completes without throwing an exception, Play Services + /// is available on this device. + @override + Future makeGooglePlayServicesAvailable() async { + await _methodChannel.invokeMethod('makeGooglePlayServicesAvailable'); + } + + /// Returns a human-readable string of the error code. + @override + Future getErrorString() async { + final errorString = await _methodChannel.invokeMethod('getErrorString'); + + if (errorString == null) { + return "ErrorString is null"; + } + + return errorString; + } + + /// Determines whether an error can be resolved via user action. + @override + Future isUserResolvable() async { + final isUserResolvable = + await _methodChannel.invokeMethod('isUserResolvable'); + + if (isUserResolvable == null) { + return false; + } + + return isUserResolvable; + } + + /// Displays a notification for an error code, if it is resolvable by the user. + /// + /// This method is similar to [showErrorDialogFragment], but is provided for + /// background tasks that cannot or should not display dialogs. + @override + Future showErrorNotification() async { + await _methodChannel.invokeMethod('showErrorNotification'); + } + + /// Displays an error dialog according to the error code if the connection + /// status is not [GooglePlayServicesAvailability.success]. + /// + /// Returns true if the connection status did not equal + /// [GooglePlayServicesAvailability.success] or any other + /// non-[ConnectionResult] value. Returns false otherwise. + @override + Future showErrorDialogFragment() async { + final showErrorDialogFragment = + await _methodChannel.invokeMethod('showErrorDialogFragment'); + + if (showErrorDialogFragment == null) { + return false; + } + + return showErrorDialogFragment; + } +} diff --git a/google_api_availability_android/pubspec.yaml b/google_api_availability_android/pubspec.yaml new file mode 100644 index 0000000..77dfc5b --- /dev/null +++ b/google_api_availability_android/pubspec.yaml @@ -0,0 +1,29 @@ +name: google_api_availability_android +description: An Android implementation for the google_api_availability plugin. +repository: https://github.com/baseflow/flutter-google-api-availability/tree/main/google_api_availability_android +# 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.0 + +flutter: + plugin: + implements: google_api_availability + platforms: + android: + package: com.baseflow.googleapiavailability + pluginClass: GoogleApiAvailabilityPlugin + +dependencies: + flutter: + sdk: flutter + google_api_availability_platform_interface: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.1 + mockito: ^5.3.2 + +environment: + sdk: ">=2.15.0 <3.0.0" + flutter: ">=2.8.0" diff --git a/google_api_availability_android/test/google_api_availability_android_test.dart b/google_api_availability_android/test/google_api_availability_android_test.dart new file mode 100644 index 0000000..f71faff --- /dev/null +++ b/google_api_availability_android/test/google_api_availability_android_test.dart @@ -0,0 +1,169 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_api_availability_android/src/google_api_availability_android.dart'; +import 'package:google_api_availability_platform_interface/google_api_availability_platform_interface.dart'; + +import 'method_channel_mock.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('checkGooglePlayServiceAvailability', () { + test('Should receive the corresponding GooglePlayServiceAvailability', + () async { + // Arrange + const availability = GooglePlayServicesAvailability.serviceDisabled; + + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'checkPlayServicesAvailability', + result: availability.value, + ); + + // Act + final googlePlayServiceAvailability = await GoogleApiAvailabilityAndroid() + .checkGooglePlayServicesAvailability(); + + // Assert + expect(googlePlayServiceAvailability, availability); + }); + + test( + 'Should receive GooglePlayServiceAvailability.unknown when availability is null', + () async { + // Arrange + const availability = null; + + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'checkPlayServicesAvailability', + result: availability, + ); + + // Act + final googlePlayServiceAvailability = await GoogleApiAvailabilityAndroid() + .checkGooglePlayServicesAvailability(); + + // Assert + expect(googlePlayServiceAvailability, + GooglePlayServicesAvailability.unknown); + }); + }); + + group('getErrorString', () { + test('Should receive "ErrorString is null" when availability is null', + () async { + // Arrange + const errorString = null; + + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'getErrorString', + result: errorString, + ); + + // Act + final errorStringResult = + await GoogleApiAvailabilityAndroid().getErrorString(); + + //Assert + expect(errorStringResult, "ErrorString is null"); + }); + + test( + 'Should receive ${GooglePlayServicesAvailability.success} when connection status is success', + () async { + // Arrange + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'getErrorString', + result: "SUCCESS", + ); + + // Act + final errorString = await GoogleApiAvailabilityAndroid().getErrorString(); + + // Assert + expect(errorString, "SUCCESS"); + }); + }); + + group('isUserResolvable', () { + test('Should receive false when isUserResolvable is null', () async { + // Arrange + const isUserResolvable = null; + + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'isUserResolvable', + result: isUserResolvable, + ); + + // Act + final isUserResolvableResult = + await GoogleApiAvailabilityAndroid().isUserResolvable(); + + // Assert + expect(isUserResolvableResult, false); + }); + + test('Should receive true when error is user resolvable', () async { + // Arrange + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'isUserResolvable', + result: true, + ); + + // Act + final isUserResolvable = + await GoogleApiAvailabilityAndroid().isUserResolvable(); + + // Assert + expect(isUserResolvable, true); + }); + }); + + group('showErrorDialogFragment', () { + test('Should receive false when showErrorDialogFragment is null', () async { + // Arrange + const showErrorDialogFragment = null; + + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'showErrorDialogFragment', + result: showErrorDialogFragment, + ); + + // Act + final showErrorDialogFragmentResult = + await GoogleApiAvailabilityAndroid().showErrorDialogFragment(); + + // Assert + expect(showErrorDialogFragmentResult, false); + }); + + test('Should receive true when error dialog fragment is shown', () async { + // Arrange + MethodChannelMock( + channelName: + 'flutter.baseflow.com/google_api_availability_android/methods', + method: 'showErrorDialogFragment', + result: true, + ); + + // Act + final showErrorDialogFragment = + await GoogleApiAvailabilityAndroid().showErrorDialogFragment(); + + // Assert + expect(showErrorDialogFragment, true); + }); + }); +} diff --git a/google_api_availability_android/test/method_channel_mock.dart b/google_api_availability_android/test/method_channel_mock.dart new file mode 100644 index 0000000..ad85720 --- /dev/null +++ b/google_api_availability_android/test/method_channel_mock.dart @@ -0,0 +1,34 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class MethodChannelMock { + final MethodChannel methodChannel; + final String method; + final dynamic result; + final Duration delay; + + MethodChannelMock({ + required String channelName, + required this.method, + this.result, + this.delay = Duration.zero, + }) : methodChannel = MethodChannel(channelName) { + TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger + .setMockMethodCallHandler(methodChannel, _handler); + } + + Future _handler(MethodCall methodCall) async { + if (methodCall.method != method) { + throw MissingPluginException('No implementation found for method ' + '$method on channel ${methodChannel.name}'); + } + + return Future.delayed(delay, () { + if (result is Exception) { + throw result; + } + + return Future.value(result); + }); + } +}