From 52cd0a0bbaf3f069f1565388adb7b4a141d0f3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Sun, 25 Aug 2019 22:59:10 -0300 Subject: [PATCH 1/7] Do not register plugin if Activity is null. --- packages/in_app_purchase/CHANGELOG.md | 4 +++ .../inapppurchase/InAppPurchasePlugin.java | 32 ++++++++++++------- packages/in_app_purchase/pubspec.yaml | 2 +- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 891818199c1a..0b9f6214c62e 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+1 + +* Android: Do not register if Activity is null. + ## 0.2.1 * iOS: Add currencyCode to priceLocale on productDetails. 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 16ab5916c908..e7f3f3d62c71 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 @@ -11,6 +11,7 @@ import android.app.Activity; import android.content.Context; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; @@ -67,10 +68,13 @@ static final class MethodNames { /** Plugin registration. */ public static void registerWith(Registrar registrar) { + final Activity activity = registrar.activity(); + if (activity == null) { + return; + } final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase"); - channel.setMethodCallHandler( - new InAppPurchasePlugin(registrar.context(), registrar.activity(), channel)); + channel.setMethodCallHandler(new InAppPurchasePlugin(registrar.context(), activity, channel)); } public InAppPurchasePlugin(Context context, Activity activity, MethodChannel channel) { @@ -80,33 +84,37 @@ public InAppPurchasePlugin(Context context, Activity activity, MethodChannel cha } @Override - public void onMethodCall(MethodCall call, Result result) { + public void onMethodCall(MethodCall call, @NonNull Result result) { + final String skuType = call.argument("skuType"); switch (call.method) { case MethodNames.IS_READY: isReady(result); break; case MethodNames.START_CONNECTION: - startConnection((int) call.argument("handle"), result); + final Integer handle = call.argument("handle"); + startConnection(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); + final List skusList = call.argument("skusList"); + querySkuDetailsAsync(skuType, skusList, result); break; case MethodNames.LAUNCH_BILLING_FLOW: - launchBillingFlow( - (String) call.argument("sku"), (String) call.argument("accountId"), result); + final String sku = call.argument("sku"); + final String accountId = call.argument("accountId"); + launchBillingFlow(sku, accountId, result); break; case MethodNames.QUERY_PURCHASES: - queryPurchases((String) call.argument("skuType"), result); + queryPurchases(skuType, result); break; case MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: - queryPurchaseHistoryAsync((String) call.argument("skuType"), result); + queryPurchaseHistoryAsync(skuType, result); break; case MethodNames.CONSUME_PURCHASE_ASYNC: - consumeAsync((String) call.argument("purchaseToken"), result); + final String purchaseToken = call.argument("purchaseToken"); + consumeAsync(purchaseToken, result); break; default: result.notImplemented(); @@ -121,7 +129,7 @@ public void onMethodCall(MethodCall call, Result result) { this.activity = null; } - private void startConnection(final int handle, final Result result) { + private void startConnection(final Integer handle, final Result result) { if (billingClient == null) { billingClient = buildBillingClient(context, channel); } diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index a398d945fa03..1bd07cda704b 100644 --- a/packages/in_app_purchase/pubspec.yaml +++ b/packages/in_app_purchase/pubspec.yaml @@ -2,7 +2,7 @@ name: in_app_purchase description: A Flutter plugin for in-app purchases. Exposes APIs for making in-app purchases through the App Store and Google Play. author: Flutter Team homepage: https://github.com/flutter/plugins/tree/master/packages/in_app_purchase -version: 0.2.1 +version: 0.2.1+1 dependencies: From da055d5f700d4f7a333fee921554a077dd66ca2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 16:11:42 -0300 Subject: [PATCH 2/7] Revert unrelated changes. --- .../inapppurchase/InAppPurchasePlugin.java | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) 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 e7f3f3d62c71..19dd3070d692 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 @@ -11,7 +11,6 @@ import android.app.Activity; import android.content.Context; import android.util.Log; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.billingclient.api.BillingClient; @@ -84,37 +83,33 @@ public InAppPurchasePlugin(Context context, Activity activity, MethodChannel cha } @Override - public void onMethodCall(MethodCall call, @NonNull Result result) { - final String skuType = call.argument("skuType"); + public void onMethodCall(MethodCall call, Result result) { switch (call.method) { case MethodNames.IS_READY: isReady(result); break; case MethodNames.START_CONNECTION: - final Integer handle = call.argument("handle"); - startConnection(handle, result); + startConnection((int) call.argument("handle"), result); break; case MethodNames.END_CONNECTION: endConnection(result); break; case MethodNames.QUERY_SKU_DETAILS: - final List skusList = call.argument("skusList"); - querySkuDetailsAsync(skuType, skusList, result); + querySkuDetailsAsync( + (String) call.argument("skuType"), (List) call.argument("skusList"), result); break; case MethodNames.LAUNCH_BILLING_FLOW: - final String sku = call.argument("sku"); - final String accountId = call.argument("accountId"); - launchBillingFlow(sku, accountId, result); + launchBillingFlow( + (String) call.argument("sku"), (String) call.argument("accountId"), result); break; case MethodNames.QUERY_PURCHASES: - queryPurchases(skuType, result); + queryPurchases((String) call.argument("skuType"), result); break; case MethodNames.QUERY_PURCHASE_HISTORY_ASYNC: - queryPurchaseHistoryAsync(skuType, result); + queryPurchaseHistoryAsync((String) call.argument("skuType"), result); break; case MethodNames.CONSUME_PURCHASE_ASYNC: - final String purchaseToken = call.argument("purchaseToken"); - consumeAsync(purchaseToken, result); + consumeAsync((String) call.argument("purchaseToken"), result); break; default: result.notImplemented(); @@ -129,7 +124,7 @@ public void onMethodCall(MethodCall call, @NonNull Result result) { this.activity = null; } - private void startConnection(final Integer handle, final Result result) { + private void startConnection(final int handle, final Result result) { if (billingClient == null) { billingClient = buildBillingClient(context, channel); } From bcf733a045e5a601623508dd33c167ade44fd6b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 16:12:07 -0300 Subject: [PATCH 3/7] Adds UnitTest for registration. --- .../InAppPurchasePluginTest.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java index 4792505b4476..34ce31943542 100644 --- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java +++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Activity; import androidx.annotation.Nullable; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClient.BillingResponse; @@ -41,9 +42,11 @@ import com.android.billingclient.api.SkuDetails; import com.android.billingclient.api.SkuDetailsParams; import com.android.billingclient.api.SkuDetailsResponseListener; +import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugins.inapppurchase.InAppPurchasePlugin.PluginPurchaseListener; import java.util.HashMap; import java.util.List; @@ -60,6 +63,9 @@ public class InAppPurchasePluginTest { @Mock BillingClient mockBillingClient; @Mock MethodChannel mockMethodChannel; @Spy Result result; + @Mock PluginRegistry.Registrar registrar; + @Mock Activity activity; + @Mock BinaryMessenger messenger; @Before public void setUp() { @@ -67,6 +73,20 @@ public void setUp() { plugin = new InAppPurchasePlugin(mockBillingClient, mockMethodChannel); } + @Test + public void doNotRegisterWithoutActivityAvailable() { + InAppPurchasePlugin.registerWith(registrar); + verify(registrar, never()).messenger(); + } + + @Test + public void registerWithoutActivityAvailable() { + when(registrar.messenger()).thenReturn(messenger); + when(registrar.activity()).thenReturn(activity); + InAppPurchasePlugin.registerWith(registrar); + verify(registrar).messenger(); + } + @Test public void invalidMethod() { MethodCall call = new MethodCall("invalid", null); From cdaea1525fdf099e4f60efab8a1ea9df85a83655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 22:16:27 -0300 Subject: [PATCH 4/7] Allow background registration and adds check for Activity nullability in a more specific method. --- .../inapppurchase/InAppPurchasePlugin.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) 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 19dd3070d692..d3d8932983f2 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 @@ -37,8 +37,8 @@ public class InAppPurchasePlugin implements MethodCallHandler { private static final String TAG = "InAppPurchasePlugin"; private @Nullable BillingClient billingClient; - private final Activity activity; - private final Context context; + private final Registrar registrar; + private final Context applicationContext; private final MethodChannel channel; @VisibleForTesting @@ -67,18 +67,14 @@ static final class MethodNames { /** Plugin registration. */ public static void registerWith(Registrar registrar) { - final Activity activity = registrar.activity(); - if (activity == null) { - return; - } final MethodChannel channel = new MethodChannel(registrar.messenger(), "plugins.flutter.io/in_app_purchase"); - channel.setMethodCallHandler(new InAppPurchasePlugin(registrar.context(), activity, channel)); + channel.setMethodCallHandler(new InAppPurchasePlugin(registrar, channel)); } - public InAppPurchasePlugin(Context context, Activity activity, MethodChannel channel) { - this.context = context; - this.activity = activity; + public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) { + this.applicationContext = registrar.context().getApplicationContext(); + this.registrar = registrar; this.channel = channel; } @@ -120,13 +116,13 @@ public void onMethodCall(MethodCall call, Result result) { /*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) { this.billingClient = billingClient; this.channel = channel; - this.context = null; - this.activity = null; + this.applicationContext = null; + this.registrar = null; } private void startConnection(final int handle, final Result result) { if (billingClient == null) { - billingClient = buildBillingClient(context, channel); + billingClient = buildBillingClient(applicationContext, channel); } billingClient.startConnection( @@ -204,6 +200,15 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re null); return; } + final Activity activity = registrar.activity(); + + if (activity == null) { + result.error( + "UNAVAILABLE", + "Details for sku " + sku + " are not available. This method requires to be run with the app in foreground.", + null); + return; + } BillingFlowParams.Builder paramsBuilder = BillingFlowParams.newBuilder().setSkuDetails(skuDetails); From 65a6a1a5f730a967fe83ec57290b9ec8859f18f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cotta?= Date: Mon, 26 Aug 2019 22:30:52 -0300 Subject: [PATCH 5/7] update tests and changelog. --- packages/in_app_purchase/CHANGELOG.md | 2 +- .../inapppurchase/InAppPurchasePlugin.java | 17 ++++---- .../InAppPurchasePluginTest.java | 39 ++++++++++++------- 3 files changed, 35 insertions(+), 23 deletions(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index 0b9f6214c62e..a6b00383e6c6 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.2.1+1 -* Android: Do not register if Activity is null. +* Android: Require Activity only to use launchBillingFlow method. ## 0.2.1 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 d3d8932983f2..cfccb6c82105 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 @@ -73,7 +73,7 @@ public static void registerWith(Registrar registrar) { } public InAppPurchasePlugin(Registrar registrar, MethodChannel channel) { - this.applicationContext = registrar.context().getApplicationContext(); + this.applicationContext = registrar.context(); this.registrar = registrar; this.channel = channel; } @@ -113,11 +113,12 @@ public void onMethodCall(MethodCall call, Result result) { } @VisibleForTesting - /*package*/ InAppPurchasePlugin(@Nullable BillingClient billingClient, MethodChannel channel) { + /*package*/ InAppPurchasePlugin( + Registrar registrar, @Nullable BillingClient billingClient, MethodChannel channel) { this.billingClient = billingClient; this.channel = channel; - this.applicationContext = null; - this.registrar = null; + this.applicationContext = registrar.context(); + this.registrar = registrar; } private void startConnection(final int handle, final Result result) { @@ -204,9 +205,11 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re if (activity == null) { result.error( - "UNAVAILABLE", - "Details for sku " + sku + " are not available. This method requires to be run with the app in foreground.", - null); + "ACTIVITY_UNAVAILABLE", + "Details for sku " + + sku + + " are not available. This method requires to be run with the app in foreground.", + null); return; } diff --git a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java index 34ce31943542..41a60788691f 100644 --- a/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java +++ b/packages/in_app_purchase/example/android/app/src/test/java/io/flutter/plugins/inapppurchase/InAppPurchasePluginTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.app.Activity; +import android.content.Context; import androidx.annotation.Nullable; import com.android.billingclient.api.BillingClient; import com.android.billingclient.api.BillingClient.BillingResponse; @@ -66,25 +67,13 @@ public class InAppPurchasePluginTest { @Mock PluginRegistry.Registrar registrar; @Mock Activity activity; @Mock BinaryMessenger messenger; + @Mock Context context; @Before public void setUp() { MockitoAnnotations.initMocks(this); - plugin = new InAppPurchasePlugin(mockBillingClient, mockMethodChannel); - } - - @Test - public void doNotRegisterWithoutActivityAvailable() { - InAppPurchasePlugin.registerWith(registrar); - verify(registrar, never()).messenger(); - } - - @Test - public void registerWithoutActivityAvailable() { - when(registrar.messenger()).thenReturn(messenger); - when(registrar.activity()).thenReturn(activity); - InAppPurchasePlugin.registerWith(registrar); - verify(registrar).messenger(); + when(registrar.context()).thenReturn(context); + plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel); } @Test @@ -239,6 +228,7 @@ public void querySkuDetailsAsync_clientDisconnected() { @Test public void launchBillingFlow_ok_nullAccountId() { + when(registrar.activity()).thenReturn(activity); // Fetch the sku details first and then prepare the launch billing flow call String skuId = "foo"; queryForSkus(singletonList(skuId)); @@ -265,8 +255,27 @@ public void launchBillingFlow_ok_nullAccountId() { verify(result, times(1)).success(responseCode); } + @Test + public void launchBillingFlow_ok_null_Activity() { + // Fetch the sku details first and then prepare the launch billing flow call + String skuId = "foo"; + String accountId = "account"; + queryForSkus(singletonList(skuId)); + HashMap arguments = new HashMap<>(); + arguments.put("sku", skuId); + arguments.put("accountId", accountId); + MethodCall launchCall = new MethodCall(LAUNCH_BILLING_FLOW, arguments); + + plugin.onMethodCall(launchCall, result); + + // Verify we pass the response code to result + verify(result).error(contains("ACTIVITY_UNAVAILABLE"), contains("foreground"), any()); + verify(result, never()).success(any()); + } + @Test public void launchBillingFlow_ok_AccountId() { + when(registrar.activity()).thenReturn(activity); // Fetch the sku details first and query the method call String skuId = "foo"; String accountId = "account"; From 346dd510a96c40c9c815b43a612e5952d64bef8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cesar=20Bueno=20Cotta?= Date: Tue, 27 Aug 2019 15:39:48 -0300 Subject: [PATCH 6/7] Update packages/in_app_purchase/android/src/main/java/io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java Co-Authored-By: Michael Klimushyn --- .../io/flutter/plugins/inapppurchase/InAppPurchasePlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 cfccb6c82105..e914d2a455de 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 @@ -208,7 +208,7 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re "ACTIVITY_UNAVAILABLE", "Details for sku " + sku - + " are not available. This method requires to be run with the app in foreground.", + + " are not available. This method must be run with the app in foreground.", null); return; } From ee54c28e52cf4dc8878d8e069b86726df6333739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20Cesar=20Bueno=20Cotta?= Date: Tue, 27 Aug 2019 15:40:02 -0300 Subject: [PATCH 7/7] Update packages/in_app_purchase/CHANGELOG.md Co-Authored-By: Michael Klimushyn --- packages/in_app_purchase/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index a6b00383e6c6..a1ab62b3ae0a 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,6 +1,6 @@ ## 0.2.1+1 -* Android: Require Activity only to use launchBillingFlow method. +* Android: Require a non-null Activity to use the `launchBillingFlow` method. ## 0.2.1