[in_app_purchase] Android example using in_app_purchase_android package (#3861)

Adds the example app for the in_app_purchase_android platform implementation.

Adds the Android implementation for issue flutter/flutter#81695
NOTE: this PR builds on top of the "[in_app_purchase] Federated Android implementation" pull request. If have split in into a separate PR so it would be easier to review.
diff --git a/packages/in_app_purchase/in_app_purchase/example/README.md b/packages/in_app_purchase/in_app_purchase/example/README.md
index dc59c6e..65b5dad 100644
--- a/packages/in_app_purchase/in_app_purchase/example/README.md
+++ b/packages/in_app_purchase/in_app_purchase/example/README.md
@@ -32,7 +32,7 @@
    - `subscription_silver`: A lower level subscription.
    - `subscription_gold`: A higher level subscription.
 
-   Make sure that all of the products are set to `ACTIVE`.
+   Make sure that all the products are set to `ACTIVE`.
 
 4. Update `APP_ID` in `example/android/app/build.gradle` to match your package
    ID in the PDC.
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/README.md b/packages/in_app_purchase/in_app_purchase_android/example/README.md
new file mode 100644
index 0000000..255e838
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/README.md
@@ -0,0 +1,58 @@
+# In App Purchase Example
+
+Demonstrates how to use the In App Purchase Android (IAP) Plugin.
+
+## Getting Started
+
+### Preparation
+
+There's a significant amount of setup required for testing in-app purchases
+successfully, including registering new app IDs and store entries to use for
+testing in the Play Developer Console. Google Play requires developers to 
+configure an app with in-app items for purchase to call their in-app-purchase 
+APIs. The Google Play Store has extensive documentation on how to do this, and 
+we've also included a high level guide below.
+
+* [Google Play Billing Overview](https://developer.android.com/google/play/billing/billing_overview)
+
+### Android
+
+1. Create a new app in the [Play Developer
+   Console](https://play.google.com/apps/publish/) (PDC).
+
+2. Sign up for a merchant's account in the PDC.
+
+3. Create IAPs in the PDC available for purchase in the app. The example assumes
+   the following SKU IDs exist:
+
+   - `consumable`: A managed product.
+   - `upgrade`: A managed product.
+   - `subscription_silver`: A lower level subscription.
+   - `subscription_gold`: A higher level subscription.
+
+   Make sure that all of the products are set to `ACTIVE`.
+
+4. Update `APP_ID` in `example/android/app/build.gradle` to match your package
+   ID in the PDC.
+
+5. Create an `example/android/keystore.properties` file with all your signing
+   information. `keystore.example.properties` exists as an example to follow.
+   It's impossible to use any of the `BillingClient` APIs from an unsigned APK.
+   See
+   [here](https://developer.android.com/studio/publish/app-signing#secure-shared-keystore)
+   and [here](https://developer.android.com/studio/publish/app-signing#sign-apk)
+   for more information.
+
+6. Build a signed apk. `flutter build apk` will work for this, the gradle files
+   in this project have been configured to sign even debug builds.
+
+7. Upload the signed APK from step 6 to the PDC, and publish that to the alpha
+   test channel. Add your test account as an approved tester. The
+   `BillingClient` APIs won't work unless the app has been fully published to
+   the alpha channel and is being used by an authorized test account. See
+   [here](https://support.google.com/googleplay/android-developer/answer/3131213)
+   for more info.
+
+8. Sign in to the test device with the test account from step #7. Then use
+   `flutter run` to install the app to the device and test like normal.
+   
\ No newline at end of file
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle
new file mode 100644
index 0000000..373d4a8
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/build.gradle
@@ -0,0 +1,115 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+// Load the build signing secrets from a local `keystore.properties` file.
+// TODO(YOU): Create release keys and a `keystore.properties` file. See
+// `example/README.md` for more info and `keystore.example.properties` for an
+// example.
+def keystorePropertiesFile = rootProject.file("keystore.properties")
+def keystoreProperties = new Properties()
+def configured = true
+try {
+    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
+} catch (IOException e) {
+    configured = false
+    logger.error('Release signing information not found.')
+}
+
+project.ext {
+    // TODO(YOU): Create release keys and a `keystore.properties` file. See
+    // `example/README.md` for more info and `keystore.example.properties` for an
+    // example.
+    APP_ID = configured ? keystoreProperties['appId'] : "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE"
+    KEYSTORE_STORE_FILE = configured ? rootProject.file(keystoreProperties['storeFile']) : null
+    KEYSTORE_STORE_PASSWORD = keystoreProperties['storePassword']
+    KEYSTORE_KEY_ALIAS = keystoreProperties['keyAlias']
+    KEYSTORE_KEY_PASSWORD = keystoreProperties['keyPassword']
+    VERSION_CODE = configured ? keystoreProperties['versionCode'].toInteger() : 1
+    VERSION_NAME = configured ? keystoreProperties['versionName'] : "0.0.1"
+}
+
+if (project.APP_ID == "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE") {
+    configured = false
+    logger.error('Unique package name not set, defaulting to "io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE".')
+}
+
+// Log a final error message if we're unable to create a release key signed
+// build for an app configured in the Play Developer Console. Apks built in this
+// condition won't be able to call any of the BillingClient APIs.
+if (!configured) {
+    logger.error('The app could not be configured for release signing. In app purchases will not be testable. See `example/README.md` for more info and instructions.')
+}
+
+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.")
+}
+
+apply plugin: 'com.android.application'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    signingConfigs {
+        release {
+            storeFile project.KEYSTORE_STORE_FILE
+            storePassword project.KEYSTORE_STORE_PASSWORD
+            keyAlias project.KEYSTORE_KEY_ALIAS
+            keyPassword project.KEYSTORE_KEY_PASSWORD
+        }
+    }
+
+    compileSdkVersion 29
+
+    lintOptions {
+        disable 'InvalidPackage'
+    }
+
+    defaultConfig {
+        applicationId project.APP_ID
+        minSdkVersion 16
+        targetSdkVersion 29
+        versionCode project.VERSION_CODE
+        versionName project.VERSION_NAME
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    buildTypes {
+        // Google Play Billing APIs only work with apps signed for production.
+        debug {
+            if (configured) {
+                signingConfig signingConfigs.release
+            } else {
+                signingConfig signingConfigs.debug
+            }
+        }
+        release {
+            if (configured) {
+                signingConfig signingConfigs.release
+            } else {
+                signingConfig signingConfigs.debug
+            }
+        }
+    }
+
+    testOptions {
+        unitTests.returnDefaultValues = true
+    }
+}
+
+flutter {
+    source '../..'
+}
+
+dependencies {
+    implementation 'com.android.billingclient:billing:3.0.2'
+    testImplementation 'junit:junit:4.12'
+    testImplementation 'org.mockito:mockito-core:3.6.0'
+    testImplementation 'org.json:json:20180813'
+    androidTestImplementation 'androidx.test:runner:1.1.1'
+    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/in_app_purchase/in_app_purchase_android/example/android/app/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9a4163a
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..a17382b
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.flutter.plugins.inapppurchaseexample">
+
+    <!-- The INTERNET permission is required for development. Specifically,
+         flutter needs it to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
+         calls FlutterMain.startInitialization(this); in its onCreate method.
+         In most cases you can leave this as-is, but you if you want to provide
+         additional functionality it is fine to subclass or reimplement
+         FlutterApplication and put your custom class here. -->
+    <application
+        android:name="io.flutter.app.FlutterApplication"
+        android:label="in_app_purchase_example"
+        android:icon="@mipmap/ic_launcher">
+        <activity
+            android:name=".EmbeddingV1Activity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
+            android:hardwareAccelerated="true"
+            android:exported="true"
+            android:windowSoftInputMode="adjustResize">
+            <!-- This keeps the window background of the activity showing
+                 until Flutter renders its first frame. It can be removed if
+                 there is no splash screen (such as the default splash screen
+                 defined in @style/LaunchTheme). -->
+            <meta-data
+                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
+                android:value="true" />
+        </activity>
+        <activity
+            android:name="io.flutter.embedding.android.FlutterActivity"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <meta-data android:name="flutterEmbedding" android:value="2"/>
+    </application>
+</manifest>
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/EmbeddingV1Activity.java b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/EmbeddingV1Activity.java
new file mode 100644
index 0000000..c74ad94
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/EmbeddingV1Activity.java
@@ -0,0 +1,24 @@
+// Copyright 2013 The Flutter 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.inapppurchaseexample;
+
+import android.os.Bundle;
+import dev.flutter.plugins.integration_test.IntegrationTestPlugin;
+import io.flutter.plugins.inapppurchase.InAppPurchasePlugin;
+import io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin;
+
+@SuppressWarnings("deprecation")
+public class EmbeddingV1Activity extends io.flutter.app.FlutterActivity {
+  @Override
+  protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+    IntegrationTestPlugin.registerWith(
+        registrarFor("dev.flutter.plugins.integration_test.IntegrationTestPlugin"));
+    SharedPreferencesPlugin.registerWith(
+        registrarFor("io.flutter.plugins.sharedpreferences.SharedPreferencesPlugin"));
+    InAppPurchasePlugin.registerWith(
+        registrarFor("io.flutter.plugins.inapppurchase.InAppPurchasePlugin"));
+  }
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/EmbeddingV1ActivityTest.java b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/EmbeddingV1ActivityTest.java
new file mode 100644
index 0000000..55d97a6
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/EmbeddingV1ActivityTest.java
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter 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.inapppurchaseexample;
+
+import androidx.test.rule.ActivityTestRule;
+import dev.flutter.plugins.integration_test.FlutterTestRunner;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(FlutterTestRunner.class)
+@SuppressWarnings("deprecation")
+public class EmbeddingV1ActivityTest {
+  @Rule
+  public ActivityTestRule<EmbeddingV1Activity> rule =
+      new ActivityTestRule<>(EmbeddingV1Activity.class);
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/FlutterActivityTest.java b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/FlutterActivityTest.java
new file mode 100644
index 0000000..a605995
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/java/io/flutter/plugins/inapppurchaseexample/FlutterActivityTest.java
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter 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.inapppurchaseexample;
+
+import androidx.test.rule.ActivityTestRule;
+import dev.flutter.plugins.integration_test.FlutterTestRunner;
+import io.flutter.embedding.android.FlutterActivity;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(FlutterTestRunner.class)
+public class FlutterActivityTest {
+  @Rule
+  public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/drawable/launch_background.xml b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/values/styles.xml b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..00fa441
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+</resources>
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
new file mode 100644
index 0000000..1f0955d
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
@@ -0,0 +1 @@
+mock-maker-inline
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle
new file mode 100644
index 0000000..541636c
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+    repositories {
+        google()
+        jcenter()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.3.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/in_app_purchase/in_app_purchase_android/example/android/gradle.properties b/packages/in_app_purchase/in_app_purchase_android/example/android/gradle.properties
new file mode 100644
index 0000000..38c8d45
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/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/in_app_purchase/in_app_purchase_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/in_app_purchase/in_app_purchase_android/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..2819f02
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/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-4.10.2-all.zip
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties b/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties
new file mode 100644
index 0000000..ccbbb36
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/android/keystore.example.properties
@@ -0,0 +1,7 @@
+storePassword=???
+keyPassword=???
+keyAlias=???
+storeFile=???
+appId=io.flutter.plugins.inapppurchaseexample.DEFAULT_DO_NOT_USE
+versionCode=1
+versionName=0.0.1
\ No newline at end of file
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle b/packages/in_app_purchase/in_app_purchase_android/example/android/settings.gradle
new file mode 100644
index 0000000..5a2f14f
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/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/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart b/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart
new file mode 100644
index 0000000..b6fdf1d
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/integration_test/in_app_purchase_test.dart
@@ -0,0 +1,22 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.9
+import 'package:flutter_test/flutter_test.dart';
+import 'package:in_app_purchase_android/in_app_purchase_android.dart';
+import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
+import 'package:integration_test/integration_test.dart';
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  testWidgets('Can create InAppPurchaseAndroid instance',
+      (WidgetTester tester) async {
+    InAppPurchaseAndroidPlatformAddition.enablePendingPurchases();
+    InAppPurchaseAndroidPlatform.registerPlatform();
+    final InAppPurchasePlatform androidPlatform =
+        InAppPurchasePlatform.instance;
+    expect(androidPlatform, isNotNull);
+  });
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/consumable_store.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/consumable_store.dart
new file mode 100644
index 0000000..4d10a50
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/consumable_store.dart
@@ -0,0 +1,51 @@
+// Copyright 2013 The Flutter 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:shared_preferences/shared_preferences.dart';
+
+/// A store of consumable items.
+///
+/// This is a development prototype tha stores consumables in the shared
+/// preferences. Do not use this in real world apps.
+class ConsumableStore {
+  static const String _kPrefKey = 'consumables';
+  static Future<void> _writes = Future.value();
+
+  /// Adds a consumable with ID `id` to the store.
+  ///
+  /// The consumable is only added after the returned Future is complete.
+  static Future<void> save(String id) {
+    _writes = _writes.then((void _) => _doSave(id));
+    return _writes;
+  }
+
+  /// Consumes a consumable with ID `id` from the store.
+  ///
+  /// The consumable was only consumed after the returned Future is complete.
+  static Future<void> consume(String id) {
+    _writes = _writes.then((void _) => _doConsume(id));
+    return _writes;
+  }
+
+  /// Returns the list of consumables from the store.
+  static Future<List<String>> load() async {
+    return (await SharedPreferences.getInstance()).getStringList(_kPrefKey) ??
+        [];
+  }
+
+  static Future<void> _doSave(String id) async {
+    List<String> cached = await load();
+    SharedPreferences prefs = await SharedPreferences.getInstance();
+    cached.add(id);
+    await prefs.setStringList(_kPrefKey, cached);
+  }
+
+  static Future<void> _doConsume(String id) async {
+    List<String> cached = await load();
+    SharedPreferences prefs = await SharedPreferences.getInstance();
+    cached.remove(id);
+    await prefs.setStringList(_kPrefKey, cached);
+  }
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart
new file mode 100644
index 0000000..c5726c4
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/lib/main.dart
@@ -0,0 +1,436 @@
+// Copyright 2013 The Flutter 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 'dart:io';
+
+import 'package:flutter/material.dart';
+import 'package:in_app_purchase_android/billing_client_wrappers.dart';
+import 'package:in_app_purchase_android/in_app_purchase_android.dart';
+import 'package:in_app_purchase_platform_interface/in_app_purchase_platform_interface.dart';
+
+import 'consumable_store.dart';
+
+void main() {
+  WidgetsFlutterBinding.ensureInitialized();
+
+  // For play billing library 2.0 on Android, it is mandatory to call
+  // [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases)
+  // as part of initializing the app.
+  InAppPurchaseAndroidPlatformAddition.enablePendingPurchases();
+
+  // When using the Android plugin directly it is mandatory to register
+  // the plugin as default instance as part of initializing the app.
+  InAppPurchaseAndroidPlatform.registerPlatform();
+
+  runApp(_MyApp());
+}
+
+const bool _kAutoConsume = true;
+
+const String _kConsumableId = 'consumable';
+const String _kUpgradeId = 'upgrade';
+const String _kSilverSubscriptionId = 'subscription_silver';
+const String _kGoldSubscriptionId = 'subscription_gold';
+const List<String> _kProductIds = <String>[
+  _kConsumableId,
+  _kUpgradeId,
+  _kSilverSubscriptionId,
+  _kGoldSubscriptionId,
+];
+
+class _MyApp extends StatefulWidget {
+  @override
+  _MyAppState createState() => _MyAppState();
+}
+
+class _MyAppState extends State<_MyApp> {
+  final InAppPurchasePlatform _inAppPurchasePlatform =
+      InAppPurchasePlatform.instance;
+  late StreamSubscription<List<PurchaseDetails>> _subscription;
+  List<String> _notFoundIds = [];
+  List<ProductDetails> _products = [];
+  List<PurchaseDetails> _purchases = [];
+  List<String> _consumables = [];
+  bool _isAvailable = false;
+  bool _purchasePending = false;
+  bool _loading = true;
+  String? _queryProductError;
+
+  @override
+  void initState() {
+    final Stream<List<PurchaseDetails>> purchaseUpdated =
+        _inAppPurchasePlatform.purchaseStream;
+    _subscription = purchaseUpdated.listen((purchaseDetailsList) {
+      _listenToPurchaseUpdated(purchaseDetailsList);
+    }, onDone: () {
+      _subscription.cancel();
+    }, onError: (error) {
+      // handle error here.
+    });
+    initStoreInfo();
+    super.initState();
+  }
+
+  Future<void> initStoreInfo() async {
+    final bool isAvailable = await _inAppPurchasePlatform.isAvailable();
+    if (!isAvailable) {
+      setState(() {
+        _isAvailable = isAvailable;
+        _products = [];
+        _purchases = [];
+        _notFoundIds = [];
+        _consumables = [];
+        _purchasePending = false;
+        _loading = false;
+      });
+      return;
+    }
+
+    ProductDetailsResponse productDetailResponse =
+        await _inAppPurchasePlatform.queryProductDetails(_kProductIds.toSet());
+    if (productDetailResponse.error != null) {
+      setState(() {
+        _queryProductError = productDetailResponse.error!.message;
+        _isAvailable = isAvailable;
+        _products = productDetailResponse.productDetails;
+        _purchases = [];
+        _notFoundIds = productDetailResponse.notFoundIDs;
+        _consumables = [];
+        _purchasePending = false;
+        _loading = false;
+      });
+      return;
+    }
+
+    if (productDetailResponse.productDetails.isEmpty) {
+      setState(() {
+        _queryProductError = null;
+        _isAvailable = isAvailable;
+        _products = productDetailResponse.productDetails;
+        _purchases = [];
+        _notFoundIds = productDetailResponse.notFoundIDs;
+        _consumables = [];
+        _purchasePending = false;
+        _loading = false;
+      });
+      return;
+    }
+
+    await _inAppPurchasePlatform.restorePurchases();
+
+    List<String> consumables = await ConsumableStore.load();
+    setState(() {
+      _isAvailable = isAvailable;
+      _products = productDetailResponse.productDetails;
+      _notFoundIds = productDetailResponse.notFoundIDs;
+      _consumables = consumables;
+      _purchasePending = false;
+      _loading = false;
+    });
+  }
+
+  @override
+  void dispose() {
+    _subscription.cancel();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    List<Widget> stack = [];
+    if (_queryProductError == null) {
+      stack.add(
+        ListView(
+          children: [
+            _buildConnectionCheckTile(),
+            _buildProductList(),
+            _buildConsumableBox(),
+          ],
+        ),
+      );
+    } else {
+      stack.add(Center(
+        child: Text(_queryProductError!),
+      ));
+    }
+    if (_purchasePending) {
+      stack.add(
+        Stack(
+          children: [
+            Opacity(
+              opacity: 0.3,
+              child: const ModalBarrier(dismissible: false, color: Colors.grey),
+            ),
+            Center(
+              child: CircularProgressIndicator(),
+            ),
+          ],
+        ),
+      );
+    }
+
+    return MaterialApp(
+      home: Scaffold(
+        appBar: AppBar(
+          title: const Text('IAP Example'),
+        ),
+        body: Stack(
+          children: stack,
+        ),
+      ),
+    );
+  }
+
+  Card _buildConnectionCheckTile() {
+    if (_loading) {
+      return Card(child: ListTile(title: const Text('Trying to connect...')));
+    }
+    final Widget storeHeader = ListTile(
+      leading: Icon(_isAvailable ? Icons.check : Icons.block,
+          color: _isAvailable ? Colors.green : ThemeData.light().errorColor),
+      title: Text(
+          'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'),
+    );
+    final List<Widget> children = <Widget>[storeHeader];
+
+    if (!_isAvailable) {
+      children.addAll([
+        Divider(),
+        ListTile(
+          title: Text('Not connected',
+              style: TextStyle(color: ThemeData.light().errorColor)),
+          subtitle: const Text(
+              'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'),
+        ),
+      ]);
+    }
+    return Card(child: Column(children: children));
+  }
+
+  Card _buildProductList() {
+    if (_loading) {
+      return Card(
+          child: (ListTile(
+              leading: CircularProgressIndicator(),
+              title: Text('Fetching products...'))));
+    }
+    if (!_isAvailable) {
+      return Card();
+    }
+    final ListTile productHeader = ListTile(title: Text('Products for Sale'));
+    List<ListTile> productList = <ListTile>[];
+    if (_notFoundIds.isNotEmpty) {
+      productList.add(ListTile(
+          title: Text('[${_notFoundIds.join(", ")}] not found',
+              style: TextStyle(color: ThemeData.light().errorColor)),
+          subtitle: Text(
+              'This app needs special configuration to run. Please see example/README.md for instructions.')));
+    }
+
+    // This loading previous purchases code is just a demo. Please do not use this as it is.
+    // In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it.
+    // We recommend that you use your own server to verify the purchase data.
+    Map<String, PurchaseDetails> purchases =
+        Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
+      if (purchase.pendingCompletePurchase) {
+        _inAppPurchasePlatform.completePurchase(purchase);
+      }
+      return MapEntry<String, PurchaseDetails>(purchase.productID, purchase);
+    }));
+    productList.addAll(_products.map(
+      (ProductDetails productDetails) {
+        PurchaseDetails? previousPurchase = purchases[productDetails.id];
+        return ListTile(
+            title: Text(
+              productDetails.title,
+            ),
+            subtitle: Text(
+              productDetails.description,
+            ),
+            trailing: previousPurchase != null
+                ? Icon(Icons.check)
+                : TextButton(
+                    child: Text(productDetails.price),
+                    style: TextButton.styleFrom(
+                      backgroundColor: Colors.green[800],
+                      primary: Colors.white,
+                    ),
+                    onPressed: () {
+                      // NOTE: If you are making a subscription purchase/upgrade/downgrade, we recommend you to
+                      // verify the latest status of you your subscription by using server side receipt validation
+                      // and update the UI accordingly. The subscription purchase status shown
+                      // inside the app may not be accurate.
+                      final oldSubscription = _getOldSubscription(
+                          productDetails as GooglePlayProductDetails,
+                          purchases);
+                      GooglePlayPurchaseParam purchaseParam =
+                          GooglePlayPurchaseParam(
+                              productDetails: productDetails,
+                              applicationUserName: null,
+                              changeSubscriptionParam: oldSubscription != null
+                                  ? ChangeSubscriptionParam(
+                                      oldPurchaseDetails: oldSubscription,
+                                      prorationMode: ProrationMode
+                                          .immediateWithTimeProration)
+                                  : null);
+                      if (productDetails.id == _kConsumableId) {
+                        _inAppPurchasePlatform.buyConsumable(
+                            purchaseParam: purchaseParam,
+                            autoConsume: _kAutoConsume || Platform.isIOS);
+                      } else {
+                        _inAppPurchasePlatform.buyNonConsumable(
+                            purchaseParam: purchaseParam);
+                      }
+                    },
+                  ));
+      },
+    ));
+
+    return Card(
+        child:
+            Column(children: <Widget>[productHeader, Divider()] + productList));
+  }
+
+  Card _buildConsumableBox() {
+    if (_loading) {
+      return Card(
+          child: (ListTile(
+              leading: CircularProgressIndicator(),
+              title: Text('Fetching consumables...'))));
+    }
+    if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) {
+      return Card();
+    }
+    final ListTile consumableHeader =
+        ListTile(title: Text('Purchased consumables'));
+    final List<Widget> tokens = _consumables.map((String id) {
+      return GridTile(
+        child: IconButton(
+          icon: Icon(
+            Icons.stars,
+            size: 42.0,
+            color: Colors.orange,
+          ),
+          splashColor: Colors.yellowAccent,
+          onPressed: () => consume(id),
+        ),
+      );
+    }).toList();
+    return Card(
+        child: Column(children: <Widget>[
+      consumableHeader,
+      Divider(),
+      GridView.count(
+        crossAxisCount: 5,
+        children: tokens,
+        shrinkWrap: true,
+        padding: EdgeInsets.all(16.0),
+      )
+    ]));
+  }
+
+  Future<void> consume(String id) async {
+    await ConsumableStore.consume(id);
+    final List<String> consumables = await ConsumableStore.load();
+    setState(() {
+      _consumables = consumables;
+    });
+  }
+
+  void showPendingUI() {
+    setState(() {
+      _purchasePending = true;
+    });
+  }
+
+  void deliverProduct(PurchaseDetails purchaseDetails) async {
+    // IMPORTANT!! Always verify purchase details before delivering the product.
+    if (purchaseDetails.productID == _kConsumableId) {
+      await ConsumableStore.save(purchaseDetails.purchaseID!);
+      List<String> consumables = await ConsumableStore.load();
+      setState(() {
+        _purchasePending = false;
+        _consumables = consumables;
+      });
+    } else {
+      setState(() {
+        _purchases.add(purchaseDetails);
+        _purchasePending = false;
+      });
+    }
+  }
+
+  void handleError(IAPError error) {
+    setState(() {
+      _purchasePending = false;
+    });
+  }
+
+  Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) {
+    // IMPORTANT!! Always verify a purchase before delivering the product.
+    // For the purpose of an example, we directly return true.
+    return Future<bool>.value(true);
+  }
+
+  void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
+    // handle invalid purchase here if  _verifyPurchase` failed.
+  }
+
+  void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
+    purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
+      if (purchaseDetails.status == PurchaseStatus.pending) {
+        showPendingUI();
+      } else {
+        if (purchaseDetails.status == PurchaseStatus.error) {
+          handleError(purchaseDetails.error!);
+        } else if (purchaseDetails.status == PurchaseStatus.purchased ||
+            purchaseDetails.status == PurchaseStatus.restored) {
+          bool valid = await _verifyPurchase(purchaseDetails);
+          if (valid) {
+            deliverProduct(purchaseDetails);
+          } else {
+            _handleInvalidPurchase(purchaseDetails);
+            return;
+          }
+        }
+
+        if (!_kAutoConsume && purchaseDetails.productID == _kConsumableId) {
+          final InAppPurchaseAndroidPlatformAddition addition =
+              InAppPurchasePlatformAddition.instance
+                  as InAppPurchaseAndroidPlatformAddition;
+
+          await addition.consumePurchase(purchaseDetails);
+        }
+
+        if (purchaseDetails.pendingCompletePurchase) {
+          await _inAppPurchasePlatform.completePurchase(purchaseDetails);
+        }
+      }
+    });
+  }
+
+  GooglePlayPurchaseDetails? _getOldSubscription(
+      GooglePlayProductDetails productDetails,
+      Map<String, PurchaseDetails> purchases) {
+    // This is just to demonstrate a subscription upgrade or downgrade.
+    // This method assumes that you have only 2 subscriptions under a group, 'subscription_silver' & 'subscription_gold'.
+    // The 'subscription_silver' subscription can be upgraded to 'subscription_gold' and
+    // the 'subscription_gold' subscription can be downgraded to 'subscription_silver'.
+    // Please remember to replace the logic of finding the old subscription Id as per your app.
+    // The old subscription is only required on Android since Apple handles this internally
+    // by using the subscription group feature in iTunesConnect.
+    GooglePlayPurchaseDetails? oldSubscription;
+    if (productDetails.id == _kSilverSubscriptionId &&
+        purchases[_kGoldSubscriptionId] != null) {
+      oldSubscription =
+          purchases[_kGoldSubscriptionId] as GooglePlayPurchaseDetails;
+    } else if (productDetails.id == _kGoldSubscriptionId &&
+        purchases[_kSilverSubscriptionId] != null) {
+      oldSubscription =
+          purchases[_kSilverSubscriptionId] as GooglePlayPurchaseDetails;
+    }
+    return oldSubscription;
+  }
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml
new file mode 100644
index 0000000..29da00e
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/pubspec.yaml
@@ -0,0 +1,31 @@
+name: in_app_purchase_android_example
+description: Demonstrates how to use the in_app_purchase_android plugin.
+publish_to: none
+
+dependencies:
+  flutter:
+    sdk: flutter
+  shared_preferences: ^2.0.0
+  in_app_purchase_android:
+    # When depending on this package from a real application you should use:
+    #   in_app_purchase_android: ^x.y.z
+    # See https://dart.dev/tools/pub/dependencies#version-constraints
+    # The example app is bundled with the plugin so we use a path dependency on
+    # the parent directory to use the current plugin's version.
+    path: ../
+
+  in_app_purchase_platform_interface: ^1.0.0
+
+dev_dependencies:
+  flutter_driver:
+    sdk: flutter
+  integration_test:
+    sdk: flutter
+  pedantic: ^1.10.0
+
+flutter:
+  uses-material-design: true
+
+environment:
+  sdk: ">=2.12.0 <3.0.0"
+  flutter: ">=1.9.1+hotfix.2"
diff --git a/packages/in_app_purchase/in_app_purchase_android/example/test_driver/test/integration_test.dart b/packages/in_app_purchase/in_app_purchase_android/example/test_driver/test/integration_test.dart
new file mode 100644
index 0000000..4c4c006
--- /dev/null
+++ b/packages/in_app_purchase/in_app_purchase_android/example/test_driver/test/integration_test.dart
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// @dart = 2.9
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+import 'package:flutter_driver/flutter_driver.dart';
+
+Future<void> main() async {
+  final FlutterDriver driver = await FlutterDriver.connect();
+  final String data =
+      await driver.requestData(null, timeout: const Duration(minutes: 1));
+  await driver.close();
+  final Map<String, dynamic> result = jsonDecode(data);
+  exit(result['result'] == 'true' ? 0 : 1);
+}
diff --git a/packages/in_app_purchase/in_app_purchase_android/lib/in_app_purchase_android.dart b/packages/in_app_purchase/in_app_purchase_android/lib/in_app_purchase_android.dart
index 9d74a56..71e4e7a 100644
--- a/packages/in_app_purchase/in_app_purchase_android/lib/in_app_purchase_android.dart
+++ b/packages/in_app_purchase/in_app_purchase_android/lib/in_app_purchase_android.dart
@@ -3,4 +3,5 @@
 // found in the LICENSE file.
 
 export 'src/in_app_purchase_android_platform.dart';
+export 'src/in_app_purchase_android_platform_addition.dart';
 export 'src/types/types.dart';
diff --git a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
index b492c7c..19c5723 100644
--- a/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
+++ b/packages/in_app_purchase/in_app_purchase_android/pubspec.yaml
@@ -29,4 +29,4 @@
 
 environment:
   sdk: ">=2.12.0 <3.0.0"
-  flutter: ">=1.20.0"
\ No newline at end of file
+  flutter: ">=1.20.0"