diff --git a/packages/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/CHANGELOG.md index e7eb3a4faebc..fbbb870dcbe0 100644 --- a/packages/in_app_purchase/CHANGELOG.md +++ b/packages/in_app_purchase/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.2.1+2 + +* Android: Require a non-null Activity to use the `launchBillingFlow` method. + ## 0.2.1+1 * Remove skipped driver test. 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..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 @@ -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 @@ -69,13 +69,12 @@ static final class MethodNames { public static void registerWith(Registrar registrar) { 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, 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(); + this.registrar = registrar; this.channel = channel; } @@ -114,16 +113,17 @@ 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.context = null; - this.activity = null; + this.applicationContext = registrar.context(); + this.registrar = registrar; } private void startConnection(final int handle, final Result result) { if (billingClient == null) { - billingClient = buildBillingClient(context, channel); + billingClient = buildBillingClient(applicationContext, channel); } billingClient.startConnection( @@ -201,6 +201,17 @@ private void launchBillingFlow(String sku, @Nullable String accountId, Result re 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); 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..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 @@ -28,6 +28,8 @@ import static org.mockito.Mockito.verify; 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; @@ -41,9 +43,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,11 +64,16 @@ public class InAppPurchasePluginTest { @Mock BillingClient mockBillingClient; @Mock MethodChannel mockMethodChannel; @Spy Result result; + @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); + when(registrar.context()).thenReturn(context); + plugin = new InAppPurchasePlugin(registrar, mockBillingClient, mockMethodChannel); } @Test @@ -219,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)); @@ -245,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"; diff --git a/packages/in_app_purchase/pubspec.yaml b/packages/in_app_purchase/pubspec.yaml index 1bd07cda704b..d3a8cf0fa230 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+1 +version: 0.2.1+2 dependencies: @@ -33,4 +33,3 @@ flutter: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" flutter: ">=1.5.0 <2.0.0" -