list = manager.queryIntentServices(intent, 0);
35 | return list != null && !list.isEmpty();
36 | }
37 |
38 | @Override
39 | public boolean bind(@NonNull Intent intent, @NonNull ServiceConnection conn, int flags) {
40 | if (isBound()) {
41 | throw new IllegalStateException();
42 | }
43 | if (context.bindService(intent, conn, flags)) {
44 | connection = conn;
45 | return true;
46 | }
47 | return false;
48 | }
49 |
50 | @Override public void unbind() {
51 | if (connection != null) {
52 | context.unbindService(connection);
53 | connection = null;
54 | }
55 | }
56 |
57 | @Override public boolean isBound() {
58 | return connection != null;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/SimplePremiumerListener.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Intent;
4 | import android.support.annotation.MainThread;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 |
8 | /**
9 | * Stub implementations of the {@link PremiumerListener}.
10 | * Extend this if you do not intend to override every method of {@link PremiumerListener}.
11 | */
12 | @MainThread public class SimplePremiumerListener implements PremiumerListener {
13 | /**
14 | * {@inheritDoc}
15 | */
16 | @Override public void onShowAds() { }
17 |
18 | /**
19 | * {@inheritDoc}
20 | */
21 | @Override public void onHideAds() { }
22 |
23 | /**
24 | * {@inheritDoc}
25 | */
26 | @Override public void onBillingAvailable() { }
27 |
28 | /**
29 | * {@inheritDoc}
30 | */
31 | @Override public void onBillingUnavailable() { }
32 |
33 | /**
34 | * {@inheritDoc}
35 | */
36 | @Override public void onSkuDetails(@Nullable SkuDetails details) { }
37 |
38 | /**
39 | * {@inheritDoc}
40 | */
41 | @Override public void onSkuConsumed() { }
42 |
43 | /**
44 | * {@inheritDoc}
45 | */
46 | @Override public void onFailedToConsumeSku() { }
47 |
48 | /**
49 | * {@inheritDoc}
50 | */
51 | @Override public void onPurchaseRequested(@Nullable String payload) { }
52 |
53 | /**
54 | * {@inheritDoc}
55 | */
56 | @Override public void onPurchaseDetails(@Nullable Purchase purchase) { }
57 |
58 | /**
59 | * {@inheritDoc}
60 | */
61 | @Override public void onPurchaseSuccessful(@NonNull Purchase purchase) { }
62 |
63 | /**
64 | * {@inheritDoc}
65 | */
66 | @Override public void onPurchaseBadResult(int resultCode, @Nullable Intent data) { }
67 |
68 | /**
69 | * {@inheritDoc}
70 | */
71 | @Override public void onPurchaseBadResponse(@Nullable Intent data) { }
72 |
73 | /**
74 | * {@inheritDoc}
75 | */
76 | @Override public void onPurchaseFailedVerification() { }
77 | }
78 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/BillingBadResponseTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.os.Bundle;
4 | import android.os.IBinder;
5 | import android.os.RemoteException;
6 | import com.android.vending.billing.IInAppBillingService;
7 | import org.junit.runner.RunWith;
8 | import org.robolectric.RobolectricTestRunner;
9 |
10 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
11 |
12 | @RunWith(RobolectricTestRunner.class) public class BillingBadResponseTest extends BadBillingTest {
13 | @Override IInAppBillingService service() {
14 | return new BadResponseBillingService();
15 | }
16 |
17 | private static final class BadResponseBillingService implements IInAppBillingService {
18 | static final int BILLING_RESPONSE_RESULT_FAILURE = BILLING_RESPONSE_RESULT_OK - 1;
19 |
20 | @Override public int isBillingSupported(int apiVersion, String packageName, String type)
21 | throws RemoteException {
22 | return BILLING_RESPONSE_RESULT_FAILURE;
23 | }
24 |
25 | @Override
26 | public Bundle getSkuDetails(int apiVersion, String packageName, String type, Bundle skusBundle)
27 | throws RemoteException {
28 | return badResponseBundle();
29 | }
30 |
31 | @Override
32 | public Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
33 | String developerPayload) throws RemoteException {
34 | return badResponseBundle();
35 | }
36 |
37 | @Override public Bundle getPurchases(int apiVersion, String packageName, String type,
38 | String continuationToken) throws RemoteException {
39 | return badResponseBundle();
40 | }
41 |
42 | @Override public int consumePurchase(int apiVersion, String packageName, String purchaseToken)
43 | throws RemoteException {
44 | return BILLING_RESPONSE_RESULT_FAILURE;
45 | }
46 |
47 | @Override public IBinder asBinder() {
48 | throw new AssertionError();
49 | }
50 |
51 | static Bundle badResponseBundle() {
52 | final Bundle bundle = new Bundle(1);
53 | bundle.putInt(Constant.RESPONSE_CODE, BILLING_RESPONSE_RESULT_FAILURE);
54 | return bundle;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/tslamic/premiumer/LogPremiumerListener.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.premiumer;
2 |
3 | import android.content.Intent;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.util.Log;
7 | import io.github.tslamic.prem.PremiumerListener;
8 | import io.github.tslamic.prem.Purchase;
9 | import io.github.tslamic.prem.SkuDetails;
10 |
11 | class LogPremiumerListener implements PremiumerListener {
12 | private static final String TAG = LogPremiumerListener.class.getSimpleName();
13 |
14 | @Override public void onShowAds() {
15 | log("onShowAds()");
16 | }
17 |
18 | @Override public void onHideAds() {
19 | log("onHideAds()");
20 | }
21 |
22 | @Override public void onBillingAvailable() {
23 | log("onBillingAvailable()");
24 | }
25 |
26 | @Override public void onBillingUnavailable() {
27 | log("onBillingUnavailable()");
28 | }
29 |
30 | @Override public void onSkuDetails(@Nullable SkuDetails details) {
31 | log("onSkuDetails(), details=%s", details);
32 | }
33 |
34 | @Override public void onSkuConsumed() {
35 | log("onSkuConsumed()");
36 | }
37 |
38 | @Override public void onFailedToConsumeSku() {
39 | log("onFailedToConsumeSku()");
40 | }
41 |
42 | @Override public void onPurchaseRequested(@Nullable String payload) {
43 | log("onPurchaseRequested(), payload=%s", payload);
44 | }
45 |
46 | @Override public void onPurchaseDetails(@Nullable Purchase purchase) {
47 | log("onPurchaseDetails(), purchase=%s", purchase);
48 | }
49 |
50 | @Override public void onPurchaseSuccessful(@NonNull Purchase purchase) {
51 | log("onPurchaseSuccessful(), purchase=%s", purchase);
52 | }
53 |
54 | @Override public void onPurchaseBadResult(int resultCode, @Nullable Intent data) {
55 | log("onPurchaseBadResult(), resultCode=%d, data=%s", resultCode, data);
56 | }
57 |
58 | @Override public void onPurchaseBadResponse(@Nullable Intent data) {
59 | log("onPurchaseBadResponse(), data=%s", data);
60 | }
61 |
62 | @Override public void onPurchaseFailedVerification() {
63 | log("onPurchaseFailedVerification()");
64 | }
65 |
66 | private void log(String format, Object... args) {
67 | Log.d(TAG, String.format(format, args));
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/SkuDetailsTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import org.json.JSONException;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.robolectric.RobolectricTestRunner;
7 |
8 | import static com.google.common.truth.Truth.assertThat;
9 | import static io.github.tslamic.prem.AssertUtil.assertEq;
10 | import static io.github.tslamic.prem.TestUtil.JSON_SKU;
11 | import static io.github.tslamic.prem.TestUtil.fromParcel;
12 |
13 | @RunWith(RobolectricTestRunner.class) public class SkuDetailsTest {
14 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
15 | public void withNull() throws Exception {
16 | new SkuDetails(null);
17 | }
18 |
19 | @Test(expected = JSONException.class) public void withEmpty() throws Exception {
20 | new SkuDetails("");
21 | }
22 |
23 | @Test(expected = JSONException.class) public void withEmptyJson() throws Exception {
24 | new SkuDetails("{}");
25 | }
26 |
27 | @Test(expected = JSONException.class) public void withIncompleteJson() throws Exception {
28 | new SkuDetails("{\"productId\":\"TestProductId\"\"}");
29 | }
30 |
31 | @Test public void withProperJson() throws Exception {
32 | final SkuDetails details = new SkuDetails(JSON_SKU);
33 | assertThat(details.getTitle()).isEqualTo("TestTitle");
34 | assertThat(details.getPrice()).isEqualTo("€7.99");
35 | assertThat(details.getType()).isEqualTo("inapp");
36 | assertThat(details.getDescription()).isEqualTo("TestDescription");
37 | assertThat(details.getPriceAmount()).isEqualTo(7990000);
38 | assertThat(details.getCurrencyCode()).isEqualTo("EUR");
39 | assertThat(details.getSku()).isEqualTo("TestProductId");
40 | assertThat(details.asJson()).isEqualTo(JSON_SKU);
41 | }
42 |
43 | @Test public void eq() throws Exception {
44 | final SkuDetails s = new SkuDetails(JSON_SKU);
45 | final SkuDetails t = new SkuDetails(JSON_SKU);
46 | assertEq(s, t);
47 | }
48 |
49 | @Test public void fromJson() throws Exception {
50 | final SkuDetails s = new SkuDetails(JSON_SKU);
51 | final SkuDetails t = new SkuDetails(s.asJson());
52 | assertEq(s, t);
53 | }
54 |
55 | @Test public void parcelable() throws Exception {
56 | final SkuDetails s = new SkuDetails(JSON_SKU);
57 | final SkuDetails t = fromParcel(s, SkuDetails.CREATOR);
58 | assertThat(s.asJson()).isEqualTo(t.asJson());
59 | assertEq(s, t);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/SuccessfulBillingService.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.app.PendingIntent;
4 | import android.os.Bundle;
5 | import android.os.IBinder;
6 | import android.os.RemoteException;
7 | import com.android.vending.billing.IInAppBillingService;
8 |
9 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
10 | import static io.github.tslamic.prem.Constant.RESPONSE_BUY_INTENT;
11 | import static io.github.tslamic.prem.Constant.RESPONSE_CODE;
12 | import static io.github.tslamic.prem.Constant.RESPONSE_DETAILS_LIST;
13 | import static io.github.tslamic.prem.Constant.RESPONSE_ITEM_LIST;
14 | import static io.github.tslamic.prem.TestUtil.JSON_SKU;
15 | import static io.github.tslamic.prem.TestUtil.SKU;
16 | import static io.github.tslamic.prem.Util.arrayList;
17 | import static org.mockito.Mockito.mock;
18 |
19 | class SuccessfulBillingService implements IInAppBillingService {
20 | @Override public int isBillingSupported(int apiVersion, String packageName, String type)
21 | throws RemoteException {
22 | return BILLING_RESPONSE_RESULT_OK;
23 | }
24 |
25 | @Override
26 | public Bundle getSkuDetails(int apiVersion, String packageName, String type, Bundle skusBundle)
27 | throws RemoteException {
28 | final Bundle bundle = responseOkBundle();
29 | bundle.putStringArrayList(RESPONSE_DETAILS_LIST, arrayList(JSON_SKU));
30 | return bundle;
31 | }
32 |
33 | @Override public Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
34 | String developerPayload) throws RemoteException {
35 | final Bundle bundle = responseOkBundle();
36 | bundle.putParcelable(RESPONSE_BUY_INTENT, mock(PendingIntent.class));
37 | return bundle;
38 | }
39 |
40 | @Override public Bundle getPurchases(int apiVersion, String packageName, String type,
41 | String continuationToken) throws RemoteException {
42 | final Bundle bundle = responseOkBundle();
43 | bundle.putStringArrayList(RESPONSE_ITEM_LIST, arrayList(SKU));
44 | return bundle;
45 | }
46 |
47 | @Override public int consumePurchase(int apiVersion, String packageName, String purchaseToken)
48 | throws RemoteException {
49 | return BILLING_RESPONSE_RESULT_OK;
50 | }
51 |
52 | @Override public IBinder asBinder() {
53 | throw new AssertionError();
54 | }
55 |
56 | private static Bundle responseOkBundle() {
57 | final Bundle bundle = new Bundle();
58 | bundle.putInt(RESPONSE_CODE, BILLING_RESPONSE_RESULT_OK);
59 | return bundle;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/PurchaseCache.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.SharedPreferences;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import android.support.annotation.WorkerThread;
9 | import org.json.JSONException;
10 |
11 | import static io.github.tslamic.prem.Util.checkNotNull;
12 | import static io.github.tslamic.prem.Util.isBlank;
13 |
14 | /**
15 | * Used to store and retrieve a {@link Purchase}.
16 | */
17 | public interface PurchaseCache {
18 | /**
19 | * Stores purchase information - in plain text - to {@link SharedPreferences}.
20 | */
21 | final class SharedPrefsCache implements PurchaseCache {
22 | private static final String PREFS_NAME = "__premiumer";
23 | private static final String PURCHASE_JSON = "__premiumer_purchase";
24 | private static final String PURCHASE_SIGNATURE = "__premiumer_signature";
25 |
26 | private final SharedPreferences prefs;
27 |
28 | SharedPrefsCache(@NonNull Context context) {
29 | checkNotNull(context, "context == null");
30 | prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
31 | }
32 |
33 | @SuppressLint("CommitPrefEdits") // Already on worker thread.
34 | @WorkerThread @Override public void cache(@NonNull Purchase purchase) {
35 | final SharedPreferences.Editor editor = prefs.edit();
36 | editor.putString(PURCHASE_JSON, purchase.asJson());
37 | editor.putString(PURCHASE_SIGNATURE, purchase.signature);
38 | editor.commit();
39 | }
40 |
41 | @WorkerThread @Nullable @Override public Purchase load() {
42 | final String json = prefs.getString(PURCHASE_JSON, null);
43 | if (!isBlank(json)) {
44 | final String signature = prefs.getString(PURCHASE_SIGNATURE, null);
45 | try {
46 | return new Purchase(json, signature);
47 | } catch (JSONException ignore) {
48 | }
49 | }
50 | return null;
51 | }
52 |
53 | @Override public void clear() {
54 | prefs.edit().clear().apply();
55 | }
56 | }
57 |
58 | /**
59 | * Caches a {@link Purchase}. This will be invoked on a worker thread.
60 | */
61 | @WorkerThread void cache(@NonNull Purchase purchase);
62 |
63 | /**
64 | * Loads a {@link Purchase}. Returns {@code null} if no purchase is found.
65 | * This will be invoked on a worker thread.
66 | */
67 | @WorkerThread @Nullable Purchase load();
68 |
69 | /**
70 | * Clears this cache.
71 | */
72 | void clear();
73 | }
74 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/Builder.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.support.annotation.NonNull;
4 | import java.util.concurrent.Executor;
5 |
6 | public interface Builder {
7 | /**
8 | * Set the {@link Executor}.
9 | *
10 | * @throws NullPointerException if executor is {@code null}.
11 | */
12 | @NonNull Builder executor(@NonNull Executor executor);
13 |
14 | /**
15 | * If {@code true}, {@link PremiumerListener#onShowAds()} or {@link
16 | * PremiumerListener#onHideAds()} will be automatically invoked after a successful bind,
17 | * unbind, purchase or consumption. {@code true} by default.
18 | */
19 | @NonNull Builder autoNotifyAds(boolean autoNotifyAds);
20 |
21 | /**
22 | * The integer code returned by the in-app billing after a {@link
23 | * Premiumer#purchase(Activity)} request.
24 | */
25 | @NonNull Builder requestCode(int requestCode);
26 |
27 | /**
28 | * Generator for a developer-specified {@link String} containing
29 | * supplemental information about a purchase. {@link PayloadGenerator.UuidPayloadGenerator} by
30 | * default.
31 | *
32 | * @throws NullPointerException if generator is {@code null}.
33 | */
34 | @NonNull Builder payloadGenerator(@NonNull PayloadGenerator generator);
35 |
36 | /**
37 | * Verifies a {@link Purchase}. By default, no verification is performed.
38 | */
39 | @NonNull Builder purchaseVerifier(@NonNull PurchaseVerifier verifier);
40 |
41 | /**
42 | * Stores and retrieves a {@link Purchase}.
43 | * If none provided, {@link PurchaseCache.SharedPrefsCache} is used.
44 | */
45 | @NonNull Builder purchaseCache(@NonNull PurchaseCache cache);
46 |
47 | /**
48 | * Specify application's public key, encoded in base64.
49 | * This is used for verification of purchase signatures. You can find your app's base64-encoded
50 | * public key in your application's page on Google Play Developer Console. Note that this
51 | * is NOT your "developer public key".
52 | */
53 | @NonNull Builder signatureBase64(@NonNull String signatureBase64);
54 |
55 | /**
56 | * Builds a new {@link Premiumer} instance.
57 | */
58 | @NonNull Premiumer build();
59 |
60 | interface SkuProvider {
61 | /**
62 | * Set the sku name, e.g. {@code android.test.purchased}.
63 | *
64 | * @throws NullPointerException if sku is {@code null}.
65 | */
66 | @NonNull ListenerProvider sku(@NonNull String sku);
67 | }
68 |
69 | interface ListenerProvider {
70 | /**
71 | * Set a {@link PremiumerListener} receiving {@link Premiumer} events.
72 | *
73 | * @throws NullPointerException if listener is {@code null}.
74 | */
75 | @NonNull Builder listener(@NonNull PremiumerListener listener);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/PremiumerHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Intent;
4 | import android.os.Handler;
5 | import android.os.Looper;
6 | import android.os.Message;
7 | import android.support.annotation.NonNull;
8 |
9 | import static io.github.tslamic.prem.Util.checkNotNull;
10 |
11 | class PremiumerHandler extends Handler {
12 | static final int ON_SHOW_ADS = 0;
13 | static final int ON_HIDE_ADS = 1;
14 | static final int ON_BILLING_AVAILABLE = 2;
15 | static final int ON_BILLING_UNAVAILABLE = 3;
16 | static final int ON_SKU_DETAILS = 4;
17 | static final int ON_SKU_CONSUMED = 5;
18 | static final int ON_FAILED_TO_CONSUME_SKU = 6;
19 | static final int ON_PURCHASE_REQUESTED = 7;
20 | static final int ON_PURCHASE_DETAILS = 8;
21 | static final int ON_PURCHASE_SUCCESSFUL = 9;
22 | static final int ON_PURCHASE_BAD_RESULT = 10;
23 | static final int ON_PURCHASE_BAD_RESPONSE = 11;
24 | static final int ON_PURCHASE_FAILED_VERIFICATION = 12;
25 |
26 | private final PremiumerListener listener;
27 |
28 | PremiumerHandler(@NonNull PremiumerListener listener) {
29 | super(Looper.getMainLooper());
30 | this.listener = checkNotNull(listener, "listener == null");
31 | }
32 |
33 | @Override public void handleMessage(Message msg) {
34 | switch (msg.what) {
35 | case ON_SHOW_ADS:
36 | listener.onShowAds();
37 | break;
38 | case ON_HIDE_ADS:
39 | listener.onHideAds();
40 | break;
41 | case ON_BILLING_AVAILABLE:
42 | listener.onBillingAvailable();
43 | break;
44 | case ON_BILLING_UNAVAILABLE:
45 | listener.onBillingUnavailable();
46 | break;
47 | case ON_SKU_DETAILS:
48 | listener.onSkuDetails((SkuDetails) msg.obj);
49 | break;
50 | case ON_SKU_CONSUMED:
51 | listener.onSkuConsumed();
52 | break;
53 | case ON_FAILED_TO_CONSUME_SKU:
54 | listener.onFailedToConsumeSku();
55 | break;
56 | case ON_PURCHASE_REQUESTED:
57 | listener.onPurchaseRequested((String) msg.obj);
58 | break;
59 | case ON_PURCHASE_DETAILS:
60 | listener.onPurchaseDetails((Purchase) msg.obj);
61 | break;
62 | case ON_PURCHASE_SUCCESSFUL:
63 | listener.onPurchaseSuccessful((Purchase) msg.obj);
64 | break;
65 | case ON_PURCHASE_BAD_RESULT:
66 | listener.onPurchaseBadResult(msg.arg1, (Intent) msg.obj);
67 | break;
68 | case ON_PURCHASE_BAD_RESPONSE:
69 | listener.onPurchaseBadResponse((Intent) msg.obj);
70 | break;
71 | case ON_PURCHASE_FAILED_VERIFICATION:
72 | listener.onPurchaseFailedVerification();
73 | break;
74 | default:
75 | throw new AssertionError();
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/PurchaseTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import org.json.JSONException;
4 | import org.junit.Test;
5 | import org.junit.runner.RunWith;
6 | import org.robolectric.RobolectricTestRunner;
7 |
8 | import static com.google.common.truth.Truth.assertThat;
9 | import static io.github.tslamic.prem.AssertUtil.assertEq;
10 | import static io.github.tslamic.prem.TestUtil.JSON_PURCHASE;
11 | import static io.github.tslamic.prem.TestUtil.fromParcel;
12 |
13 | @RunWith(RobolectricTestRunner.class) public class PurchaseTest {
14 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
15 | public void withNull() throws Exception {
16 | new Purchase(null, null);
17 | }
18 |
19 | @Test(expected = JSONException.class) public void withEmpty() throws Exception {
20 | new Purchase("", null);
21 | }
22 |
23 | @Test(expected = JSONException.class) public void withEmptyJson() throws Exception {
24 | new Purchase("{}", null);
25 | }
26 |
27 | @Test(expected = JSONException.class) public void withIncompleteJson() throws Exception {
28 | new Purchase("{\"productId\":\"TestProductId\"\"}", null);
29 | }
30 |
31 | @Test public void withProperJson() throws Exception {
32 | final String signature = null;
33 | final Purchase purchase = new Purchase(JSON_PURCHASE, signature);
34 |
35 | assertThat(purchase.isAutoRenewing()).isFalse();
36 | assertThat(purchase.getOrderId()).isEqualTo("TestOrder");
37 | assertThat(purchase.getPackageName()).isEqualTo("com.example.app");
38 | assertThat(purchase.getSku()).isEqualTo("TestProductId");
39 | assertThat(purchase.getPurchaseTime()).isEqualTo(1345678900000L);
40 | assertThat(purchase.getPurchaseState()).isEqualTo(0);
41 | assertThat(purchase.isPurchased()).isTrue();
42 | assertThat(purchase.isCancelled()).isFalse();
43 | assertThat(purchase.isRefunded()).isFalse();
44 | assertThat(purchase.getDeveloperPayload()).isEqualTo("TestDeveloperPayload");
45 | assertThat(purchase.getToken()).isEqualTo("TestPurchaseToken");
46 | assertThat(purchase.getSignature()).isEqualTo(signature);
47 | assertThat(purchase.asJson()).isEqualTo(JSON_PURCHASE);
48 | }
49 |
50 | @Test public void eq() throws Exception {
51 | final Purchase p = new Purchase(JSON_PURCHASE, "signature");
52 | final Purchase q = new Purchase(JSON_PURCHASE, "signature");
53 | assertEq(p, q);
54 | }
55 |
56 | @Test public void eqFromJson() throws Exception {
57 | final Purchase p = new Purchase(JSON_PURCHASE, "signature");
58 | final Purchase q = new Purchase(p.asJson(), "signature");
59 | assertEq(p, q);
60 | }
61 |
62 | @Test public void parcelable() throws Exception {
63 | final Purchase p = new Purchase(JSON_PURCHASE, null);
64 | final Purchase q = fromParcel(p, Purchase.CREATOR);
65 | assertThat(p.asJson()).isEqualTo(q.asJson());
66 | assertEq(p, q);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/PremiumerListener.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Intent;
4 | import android.support.annotation.MainThread;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 |
8 | /**
9 | * Callback interface responding to {@link Premiumer} events. All listener methods will be invoked
10 | * on the main thread.
11 | * If you need to override only a handful of methods, use {@link SimplePremiumerListener}.
12 | *
13 | * @see SimplePremiumerListener
14 | */
15 | @MainThread public interface PremiumerListener {
16 | /**
17 | * Invoked if ads should be visible.
18 | */
19 | void onShowAds();
20 |
21 | /**
22 | * Invoked if ads should be hidden.
23 | */
24 | void onHideAds();
25 |
26 | /**
27 | * Invoked if in-app Billing is available.
28 | */
29 | void onBillingAvailable();
30 |
31 | /**
32 | * Invoked if in-app Billing is unavailable.
33 | */
34 | void onBillingUnavailable();
35 |
36 | /**
37 | * Invoked when {@link SkuDetails} information is retrieved.
38 | *
39 | * @param details {@link SkuDetails} instance or {@code null}, if an error occurred.
40 | */
41 | void onSkuDetails(@Nullable SkuDetails details);
42 |
43 | /**
44 | * Invoked if sku has been successfully consumed.
45 | */
46 | void onSkuConsumed();
47 |
48 | /**
49 | * Invoked if sku has not been successfully consumed.
50 | */
51 | void onFailedToConsumeSku();
52 |
53 | /**
54 | * Invoked on a purchase request.
55 | *
56 | * @param payload a developer-specified {@link String} containing supplemental information about
57 | * a purchase.
58 | */
59 | void onPurchaseRequested(@Nullable String payload);
60 |
61 | /**
62 | * Invoked when purchase details are retrieved.
63 | *
64 | * @param purchase {@link Purchase} instance or {@code null}, if unavailable.
65 | */
66 | void onPurchaseDetails(@Nullable Purchase purchase);
67 |
68 | /**
69 | * Invoked on a successful purchase.
70 | *
71 | * @param purchase the purchase data.
72 | */
73 | void onPurchaseSuccessful(@NonNull Purchase purchase);
74 |
75 | /**
76 | * Invoked when the sku purchase is unsuccessful.
77 | * This happens if the Activity.onActivityResult resultCode is not equal to
78 | * Activity.RESULT_OK.
79 | *
80 | * @param resultCode the onActivityResult resultCode value.
81 | * @param data the onActivityResult data.
82 | */
83 | void onPurchaseBadResult(int resultCode, @Nullable Intent data);
84 |
85 | /**
86 | * Invoked when the sku purchase is unsuccessful.
87 | * This happens if either onActivityResult data is null, or the billing response is invalid.
88 | *
89 | * @param data the onActivityResult data, which can be {@code null}.
90 | */
91 | void onPurchaseBadResponse(@Nullable Intent data);
92 |
93 | /**
94 | * Invoked if a purchase has failed verification.
95 | */
96 | void onPurchaseFailedVerification();
97 | }
98 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/Premiumer.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.support.annotation.MainThread;
6 | import android.support.annotation.Nullable;
7 |
8 | public interface Premiumer {
9 | /**
10 | * Binds to the in-app billing service.
11 | * Invokes {@link PremiumerListener#onBillingAvailable()} if successful, {@link
12 | * PremiumerListener#onBillingUnavailable()} otherwise.
13 | */
14 | @MainThread void bind();
15 |
16 | /**
17 | * Unbinds from the in-app billing service and invokes
18 | * {@link PremiumerListener#onBillingUnavailable()}.
19 | */
20 | @MainThread void unbind();
21 |
22 | /**
23 | * Starts the in-app billing purchase flow.
24 | * You must override {@code onActivityResult(int, int, Intent)} in the {@link Activity}
25 | * you specified to receive the result of this op.
26 | *
27 | * @return {@code true} if the flow is initiated, {@code false} otherwise.
28 | */
29 | @MainThread boolean purchase(@Nullable Activity activity);
30 |
31 | /**
32 | * Handles the in-app purchase flow result. This should be invoked in {@code
33 | * onActivityResult(int, int, Intent)} of the same {@link Activity} that initiated
34 | * {@link #purchase(Activity)}:
35 | *
36 | *
37 | * {@literal @}Override
38 | * protected void onActivityResult(int requestCode, int resultCode, Intent data) {
39 | * if (!premiumer.handleActivityResult(requestCode, resultCode, data)) {
40 | * super.onActivityResult(requestCode, resultCode, data);
41 | * }
42 | * }
43 | *
44 | *
45 | * Will invoke:
46 | *
47 | * - {@link PremiumerListener#onPurchaseBadResult(int, Intent)}, if {@code resultCode !=
48 | * Activity.RESULT_OK}.
49 | * - {@link PremiumerListener#onPurchaseBadResponse(Intent)}, if {@code data} is {@code null},
50 | * billing response is not OK or in-app billing information is missing.
51 | * - {@link PremiumerListener#onPurchaseFailedVerification()} if provided {@link
52 | * PurchaseVerifier} determines a purchase is invalid.
53 | * - {@link PremiumerListener#onPurchaseSuccessful(Purchase)} if a purchase has been
54 | * successful.
55 | *
56 | *
57 | * @return {@code true} if handled, {@code false} otherwise.
58 | */
59 | @MainThread boolean handleActivityResult(int requestCode, int resultCode, @Nullable Intent data);
60 |
61 | /**
62 | * Retrieves sku details.
63 | * If enqueued, {@link PremiumerListener#onSkuDetails(SkuDetails)} will be invoked.
64 | *
65 | * @return {@code true} if request has been enqueued, {@code false} otherwise.
66 | */
67 | boolean skuDetails();
68 |
69 | /**
70 | * Retrieves the purchase details.
71 | * If enqueued, {@link PremiumerListener#onPurchaseDetails(Purchase)} will be invoked.
72 | *
73 | * @return {@code true} if request has been enqueued, {@code false} otherwise.
74 | */
75 | boolean purchaseDetails();
76 |
77 | /**
78 | * Consumes sku.
79 | * Invokes {@link PremiumerListener#onSkuConsumed()} if successfully consumed, {@link
80 | * PremiumerListener#onFailedToConsumeSku()} otherwise.
81 | *
82 | * @return {@code true} if request has been enqueued, {@code false} otherwise.
83 | */
84 | boolean consumeSku();
85 | }
86 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/BuilderTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Context;
4 | import java.util.concurrent.Executor;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.robolectric.RobolectricTestRunner;
8 | import org.robolectric.RuntimeEnvironment;
9 |
10 | import static com.google.common.truth.Truth.assertThat;
11 | import static io.github.tslamic.prem.TestUtil.SKU;
12 | import static org.mockito.Mockito.mock;
13 |
14 | @RunWith(RobolectricTestRunner.class) public class BuilderTest {
15 | private final Context context = RuntimeEnvironment.application;
16 |
17 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
18 | public void contextNull() {
19 | PremiumerBuilder.with(null);
20 | }
21 |
22 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
23 | public void skuNull() {
24 | PremiumerBuilder.with(context).sku(null).listener(null).build();
25 | }
26 |
27 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
28 | public void listenerNullInBuild() {
29 | PremiumerBuilder.with(context).sku(SKU).listener(null).build();
30 | }
31 |
32 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
33 | public void generatorNull() {
34 | partialBuilder().payloadGenerator(null);
35 | }
36 |
37 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
38 | public void verifierNull() {
39 | partialBuilder().purchaseVerifier(null);
40 | }
41 |
42 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
43 | public void cacheNull() {
44 | partialBuilder().purchaseCache(null);
45 | }
46 |
47 | @SuppressWarnings("ConstantConditions") @Test(expected = NullPointerException.class)
48 | public void signatureNull() {
49 | partialBuilder().signatureBase64(null);
50 | }
51 |
52 | @Test public void instance() {
53 | final String signature = "signature";
54 | final boolean ads = false;
55 | final int requestCode = 666;
56 |
57 | final PremiumerListener listener = new SimplePremiumerListener();
58 | final Builder b = PremiumerBuilder.with(context)
59 | .sku(SKU)
60 | .listener(listener)
61 | .autoNotifyAds(ads)
62 | .requestCode(requestCode)
63 | .signatureBase64(signature);
64 |
65 | final PremiumerBuilder builder = (PremiumerBuilder) b;
66 | assertThat(builder.payloadGenerator).isNull();
67 | assertThat(builder.purchaseCache).isNull();
68 | assertThat(builder.executor).isNull();
69 |
70 | b.build();
71 | assertThat(builder.payloadGenerator).isNotNull();
72 | assertThat(builder.purchaseCache).isNotNull();
73 | assertThat(builder.executor).isNotNull();
74 |
75 | assertThat(builder.context).isEqualTo(context);
76 | assertThat(builder.sku).isEqualTo(SKU);
77 | assertThat(builder.listener).isInstanceOf(SimplePremiumerListener.class);
78 | assertThat(builder.executor).isInstanceOf(Executor.class);
79 | assertThat(builder.autoNotifyAds).isEqualTo(ads);
80 | assertThat(builder.requestCode).isEqualTo(requestCode);
81 | assertThat(builder.payloadGenerator).isInstanceOf(PayloadGenerator.UuidPayloadGenerator.class);
82 | assertThat(builder.purchaseVerifier).isNull();
83 | assertThat(builder.purchaseCache).isInstanceOf(PurchaseCache.SharedPrefsCache.class);
84 | assertThat(builder.signatureBase64).isEqualTo(signature);
85 | }
86 |
87 | private Builder partialBuilder() {
88 | final PremiumerListener listener = mock(PremiumerListener.class);
89 | return PremiumerBuilder.with(context).sku(SKU).listener(listener);
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/PremiumerBuilder.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Context;
4 | import android.support.annotation.NonNull;
5 | import java.util.concurrent.Executor;
6 | import java.util.concurrent.Executors;
7 |
8 | import static io.github.tslamic.prem.Util.checkNotNull;
9 |
10 | /**
11 | * Builds a new {@link Premiumer} instance.
12 | */
13 | public final class PremiumerBuilder
14 | implements Builder, Builder.SkuProvider, Builder.ListenerProvider {
15 | final Context context;
16 | String sku;
17 | PremiumerListener listener;
18 | Executor executor;
19 | boolean autoNotifyAds = true;
20 | int requestCode = 17;
21 | PayloadGenerator payloadGenerator;
22 | PurchaseVerifier purchaseVerifier;
23 | PurchaseCache purchaseCache;
24 | String signatureBase64;
25 |
26 | /**
27 | * Creates a new builder starting point.
28 | */
29 | @NonNull public static SkuProvider with(@NonNull Context context) {
30 | return new PremiumerBuilder(context);
31 | }
32 |
33 | private PremiumerBuilder(@NonNull Context context) {
34 | this.context = checkNotNull(context, "context == null").getApplicationContext();
35 | }
36 |
37 | /**
38 | * {@inheritDoc}
39 | */
40 | @NonNull @Override public ListenerProvider sku(@NonNull String sku) {
41 | this.sku = checkNotNull(sku, "sku == null");
42 | return this;
43 | }
44 |
45 | /**
46 | * {@inheritDoc}
47 | */
48 | @NonNull public Builder listener(@NonNull PremiumerListener listener) {
49 | this.listener = checkNotNull(listener, "listener == null");
50 | return this;
51 | }
52 |
53 | /**
54 | * {@inheritDoc}
55 | */
56 | @NonNull public Builder executor(@NonNull Executor executor) {
57 | this.executor = checkNotNull(executor, "executor == null");
58 | return this;
59 | }
60 |
61 | /**
62 | * {@inheritDoc}
63 | */
64 | @NonNull public Builder autoNotifyAds(boolean notify) {
65 | this.autoNotifyAds = notify;
66 | return this;
67 | }
68 |
69 | /**
70 | * {@inheritDoc}
71 | */
72 | @NonNull public Builder requestCode(int requestCode) {
73 | this.requestCode = requestCode;
74 | return this;
75 | }
76 |
77 | /**
78 | * {@inheritDoc}
79 | */
80 | @NonNull public Builder payloadGenerator(@NonNull PayloadGenerator generator) {
81 | this.payloadGenerator = checkNotNull(generator, "generator == null");
82 | return this;
83 | }
84 |
85 | /**
86 | * {@inheritDoc}
87 | */
88 | @NonNull public Builder purchaseVerifier(@NonNull PurchaseVerifier verifier) {
89 | this.purchaseVerifier = checkNotNull(verifier, "verifier == null");
90 | return this;
91 | }
92 |
93 | /**
94 | * {@inheritDoc}
95 | */
96 | @NonNull public Builder purchaseCache(@NonNull PurchaseCache cache) {
97 | this.purchaseCache = checkNotNull(cache, "cache == null");
98 | return this;
99 | }
100 |
101 | /**
102 | * {@inheritDoc}
103 | */
104 | @NonNull public Builder signatureBase64(@NonNull String signatureBase64) {
105 | this.signatureBase64 = checkNotNull(signatureBase64, "signatureBase64 == null");
106 | return this;
107 | }
108 |
109 | /**
110 | * {@inheritDoc}
111 | */
112 | @NonNull public Premiumer build() {
113 | if (payloadGenerator == null) {
114 | payloadGenerator = new PayloadGenerator.UuidPayloadGenerator();
115 | }
116 | if (purchaseCache == null) {
117 | purchaseCache = new PurchaseCache.SharedPrefsCache(context);
118 | }
119 | if (executor == null) {
120 | executor = Executors.newSingleThreadExecutor();
121 | }
122 | return new SimplePremiumer(this);
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/SkuDetails.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.os.Parcel;
4 | import android.support.annotation.NonNull;
5 | import org.json.JSONException;
6 | import org.json.JSONObject;
7 |
8 | /**
9 | * Represents an in-app product's listing details.
10 | */
11 | public final class SkuDetails extends BillingItem {
12 | String sku;
13 | String type;
14 | String price;
15 | long priceAmount;
16 | String currencyCode;
17 | String title;
18 | String description;
19 |
20 | SkuDetails(@NonNull String json) throws JSONException {
21 | super(json);
22 | }
23 |
24 | @Override void init(@NonNull JSONObject object) throws JSONException {
25 | sku = object.getString("productId");
26 | type = object.getString("type");
27 | price = object.getString("price");
28 | priceAmount = object.getLong("price_amount_micros");
29 | currencyCode = object.getString("price_currency_code");
30 | title = object.getString("title");
31 | description = object.getString("description");
32 | }
33 |
34 | @NonNull public String getSku() {
35 | return sku;
36 | }
37 |
38 | @NonNull public String getType() {
39 | return type;
40 | }
41 |
42 | @NonNull public String getPrice() {
43 | return price;
44 | }
45 |
46 | public long getPriceAmount() {
47 | return priceAmount;
48 | }
49 |
50 | @NonNull public String getCurrencyCode() {
51 | return currencyCode;
52 | }
53 |
54 | @NonNull public String getTitle() {
55 | return title;
56 | }
57 |
58 | @NonNull public String getDescription() {
59 | return description;
60 | }
61 |
62 | @Override public boolean equals(Object o) {
63 | if (this == o) {
64 | return true;
65 | }
66 | if (o == null || getClass() != o.getClass()) {
67 | return false;
68 | }
69 | final SkuDetails details = (SkuDetails) o;
70 | return priceAmount == details.priceAmount
71 | && sku.equals(details.sku)
72 | && type.equals(details.type)
73 | && price.equals(details.price)
74 | && currencyCode.equals(details.currencyCode)
75 | && title.equals(details.title)
76 | && description.equals(details.description);
77 | }
78 |
79 | @Override public int hashCode() {
80 | int result = sku.hashCode();
81 | result = 31 * result + type.hashCode();
82 | result = 31 * result + price.hashCode();
83 | result = 31 * result + (int) (priceAmount ^ (priceAmount >>> 32));
84 | result = 31 * result + currencyCode.hashCode();
85 | result = 31 * result + title.hashCode();
86 | result = 31 * result + description.hashCode();
87 | return result;
88 | }
89 |
90 | // Parcelable stuff below.
91 |
92 | private SkuDetails(@NonNull Parcel parcel) {
93 | super(parcel);
94 | sku = parcel.readString();
95 | type = parcel.readString();
96 | price = parcel.readString();
97 | priceAmount = parcel.readLong();
98 | currencyCode = parcel.readString();
99 | title = parcel.readString();
100 | description = parcel.readString();
101 | }
102 |
103 | public static final Creator CREATOR = new Creator() {
104 | @Override public SkuDetails createFromParcel(Parcel source) {
105 | return new SkuDetails(source);
106 | }
107 |
108 | @Override public SkuDetails[] newArray(int size) {
109 | return new SkuDetails[size];
110 | }
111 | };
112 |
113 | @Override public void writeToParcel(Parcel dest, int flags) {
114 | super.writeToParcel(dest, flags);
115 | dest.writeString(sku);
116 | dest.writeString(type);
117 | dest.writeString(price);
118 | dest.writeLong(priceAmount);
119 | dest.writeString(currencyCode);
120 | dest.writeString(title);
121 | dest.writeString(description);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/PremiumerHandlerTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.os.Handler;
4 | import org.junit.Assert;
5 | import org.junit.Test;
6 | import org.junit.runner.RunWith;
7 | import org.robolectric.RobolectricTestRunner;
8 |
9 | import static io.github.tslamic.prem.AssertUtil.assertInvokedOnce;
10 | import static io.github.tslamic.prem.PremiumerHandler.ON_BILLING_AVAILABLE;
11 | import static io.github.tslamic.prem.PremiumerHandler.ON_BILLING_UNAVAILABLE;
12 | import static io.github.tslamic.prem.PremiumerHandler.ON_FAILED_TO_CONSUME_SKU;
13 | import static io.github.tslamic.prem.PremiumerHandler.ON_HIDE_ADS;
14 | import static io.github.tslamic.prem.PremiumerHandler.ON_PURCHASE_BAD_RESPONSE;
15 | import static io.github.tslamic.prem.PremiumerHandler.ON_PURCHASE_BAD_RESULT;
16 | import static io.github.tslamic.prem.PremiumerHandler.ON_PURCHASE_DETAILS;
17 | import static io.github.tslamic.prem.PremiumerHandler.ON_PURCHASE_FAILED_VERIFICATION;
18 | import static io.github.tslamic.prem.PremiumerHandler.ON_PURCHASE_REQUESTED;
19 | import static io.github.tslamic.prem.PremiumerHandler.ON_PURCHASE_SUCCESSFUL;
20 | import static io.github.tslamic.prem.PremiumerHandler.ON_SHOW_ADS;
21 | import static io.github.tslamic.prem.PremiumerHandler.ON_SKU_CONSUMED;
22 | import static io.github.tslamic.prem.PremiumerHandler.ON_SKU_DETAILS;
23 | import static org.mockito.Mockito.mock;
24 |
25 | @RunWith(RobolectricTestRunner.class) public class PremiumerHandlerTest {
26 | private static final int[] MESSAGES = {
27 | ON_SHOW_ADS, ON_HIDE_ADS, ON_BILLING_AVAILABLE, ON_BILLING_UNAVAILABLE, ON_SKU_DETAILS,
28 | ON_SKU_CONSUMED, ON_FAILED_TO_CONSUME_SKU, ON_PURCHASE_REQUESTED, ON_PURCHASE_DETAILS,
29 | ON_PURCHASE_SUCCESSFUL, ON_PURCHASE_BAD_RESULT, ON_PURCHASE_BAD_RESPONSE,
30 | ON_PURCHASE_FAILED_VERIFICATION,
31 | };
32 |
33 | @Test public void messages() {
34 | for (int message : MESSAGES) {
35 | final PremiumerListener listener = mock(PremiumerListener.class);
36 | final Handler handler = TestFactory.premiumerHandler(listener);
37 | handler.obtainMessage(message).sendToTarget();
38 | assertInvoked(listener, message);
39 | }
40 | }
41 |
42 | private static void assertInvoked(PremiumerListener listener, int message) {
43 | switch (message) {
44 | case ON_SHOW_ADS:
45 | assertInvokedOnce(listener).onShowAds();
46 | break;
47 | case ON_HIDE_ADS:
48 | assertInvokedOnce(listener).onHideAds();
49 | break;
50 | case ON_BILLING_AVAILABLE:
51 | assertInvokedOnce(listener).onBillingAvailable();
52 | break;
53 | case ON_BILLING_UNAVAILABLE:
54 | assertInvokedOnce(listener).onBillingUnavailable();
55 | break;
56 | case ON_SKU_DETAILS:
57 | assertInvokedOnce(listener).onSkuDetails(null);
58 | break;
59 | case ON_SKU_CONSUMED:
60 | assertInvokedOnce(listener).onSkuConsumed();
61 | break;
62 | case ON_FAILED_TO_CONSUME_SKU:
63 | assertInvokedOnce(listener).onFailedToConsumeSku();
64 | break;
65 | case ON_PURCHASE_REQUESTED:
66 | assertInvokedOnce(listener).onPurchaseRequested(null);
67 | break;
68 | case ON_PURCHASE_DETAILS:
69 | assertInvokedOnce(listener).onPurchaseDetails(null);
70 | break;
71 | case ON_PURCHASE_SUCCESSFUL:
72 | assertInvokedOnce(listener).onPurchaseSuccessful(null);
73 | break;
74 | case ON_PURCHASE_BAD_RESULT:
75 | assertInvokedOnce(listener).onPurchaseBadResult(0, null);
76 | break;
77 | case ON_PURCHASE_BAD_RESPONSE:
78 | assertInvokedOnce(listener).onPurchaseBadResponse(null);
79 | break;
80 | case ON_PURCHASE_FAILED_VERIFICATION:
81 | assertInvokedOnce(listener).onPurchaseFailedVerification();
82 | break;
83 | default:
84 | Assert.fail();
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/SimplePremiumerBindTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Intent;
4 | import android.content.ServiceConnection;
5 | import android.os.IBinder;
6 | import android.os.RemoteException;
7 | import android.support.annotation.NonNull;
8 | import com.android.vending.billing.IInAppBillingService;
9 | import io.github.tslamic.prem.stub.BillingServiceStub;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.robolectric.RobolectricTestRunner;
13 | import org.robolectric.RuntimeEnvironment;
14 |
15 | import static io.github.tslamic.prem.AssertUtil.assertInvokedNever;
16 | import static io.github.tslamic.prem.AssertUtil.assertInvokedOnce;
17 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
18 | import static org.mockito.Mockito.mock;
19 |
20 | @RunWith(RobolectricTestRunner.class) public class SimplePremiumerBindTest {
21 | @Test public void bindNoBillingCapabilitiesNoBind() {
22 | final Binder binder = new SimpleBinderMock(false, false);
23 | assertSimplePremiumer(binder, false);
24 | }
25 |
26 | @Test public void bindNoBillingCapabilitiesBind() {
27 | final Binder binder = new SimpleBinderMock(false, true);
28 | assertSimplePremiumer(binder, false);
29 | }
30 |
31 | @Test public void bindBillingCapabilitiesNoBind() {
32 | final Binder binder = new SimpleBinderMock(true, false);
33 | assertSimplePremiumer(binder, false);
34 | }
35 |
36 | @Test public void bindBillingCapabilitiesBindServiceBillingNotSupported() {
37 | final Binder binder = new SimpleBinderMock(true, true, false);
38 | assertSimplePremiumer(binder, false);
39 | }
40 |
41 | @Test public void bindBillingCapabilitiesBindServiceBillingSupported() {
42 | final Binder binder = new SimpleBinderMock(true, true, true);
43 | assertSimplePremiumer(binder, true);
44 | }
45 |
46 | private static void assertSimplePremiumer(@NonNull Binder binder, boolean isBound) {
47 | final PremiumerListener listener = mock(PremiumerListener.class);
48 | premiumer(listener, binder).bind();
49 | if (isBound) {
50 | assertInvokedOnce(listener).onBillingAvailable();
51 | assertInvokedNever(listener).onBillingUnavailable();
52 | } else {
53 | assertInvokedNever(listener).onBillingAvailable();
54 | assertInvokedOnce(listener).onBillingUnavailable();
55 | }
56 | }
57 |
58 | private static Premiumer premiumer(@NonNull PremiumerListener listener, @NonNull Binder binder) {
59 | final Builder builder = PremiumerBuilder.with(RuntimeEnvironment.application)
60 | .sku("dummy.sku")
61 | .listener(listener)
62 | .autoNotifyAds(false);
63 | return TestFactory.premiumer(builder, binder);
64 | }
65 |
66 | static class SimpleBinderMock extends SimpleBinder {
67 | final boolean hasBillingCapabilities;
68 | final boolean canBind;
69 | final boolean isBillingSupported;
70 |
71 | SimpleBinderMock(boolean hasBillingCapabilities, boolean canBind) {
72 | this(hasBillingCapabilities, canBind, false);
73 | }
74 |
75 | SimpleBinderMock(boolean hasBillingCapabilities, boolean canBind, boolean isBillingSupported) {
76 | super(RuntimeEnvironment.application);
77 | this.hasBillingCapabilities = hasBillingCapabilities;
78 | this.canBind = canBind;
79 | this.isBillingSupported = isBillingSupported;
80 | }
81 |
82 | @Override public boolean hasBillingCapabilities(@NonNull Intent intent) {
83 | return hasBillingCapabilities;
84 | }
85 |
86 | @Override
87 | public boolean bind(@NonNull Intent intent, @NonNull ServiceConnection conn, int flags) {
88 | return canBind && super.bind(intent, conn, flags);
89 | }
90 |
91 | @NonNull @Override public IInAppBillingService service(IBinder binder) {
92 | return new BillingServiceStub() {
93 | @Override public int isBillingSupported(int apiVersion, String packageName, String type)
94 | throws RemoteException {
95 | return isBillingSupported ? BILLING_RESPONSE_RESULT_OK : (BILLING_RESPONSE_RESULT_OK - 1);
96 | }
97 | };
98 | }
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/SimpleBilling.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.app.Activity;
4 | import android.app.PendingIntent;
5 | import android.content.IntentSender;
6 | import android.os.Bundle;
7 | import android.os.RemoteException;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import com.android.vending.billing.IInAppBillingService;
11 | import java.util.ArrayList;
12 | import org.json.JSONException;
13 |
14 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
15 | import static io.github.tslamic.prem.Constant.BILLING_TYPE;
16 | import static io.github.tslamic.prem.Constant.REQUEST_ITEM_ID_LIST;
17 | import static io.github.tslamic.prem.Constant.RESPONSE_BUY_INTENT;
18 | import static io.github.tslamic.prem.Constant.RESPONSE_CODE;
19 | import static io.github.tslamic.prem.Constant.RESPONSE_DETAILS_LIST;
20 | import static io.github.tslamic.prem.Constant.RESPONSE_ITEM_LIST;
21 | import static io.github.tslamic.prem.Util.arrayList;
22 | import static io.github.tslamic.prem.Util.checkNotNull;
23 |
24 | class SimpleBilling implements Billing {
25 | private final String packageName;
26 | private final IInAppBillingService service;
27 |
28 | SimpleBilling(@NonNull String packageName, @NonNull IInAppBillingService service) {
29 | this.packageName = checkNotNull(packageName, "packageName == null");
30 | this.service = checkNotNull(service, "service == null");
31 | }
32 |
33 | @Override public boolean isBillingSupported() {
34 | try {
35 | final int response = service.isBillingSupported(3, packageName, BILLING_TYPE);
36 | return response == BILLING_RESPONSE_RESULT_OK;
37 | } catch (RemoteException ignore) {
38 | }
39 | return false;
40 | }
41 |
42 | @Override
43 | public boolean purchase(@NonNull Activity activity, @NonNull String sku, int requestCode,
44 | @Nullable String payload) {
45 | try {
46 | final Bundle bundle = service.getBuyIntent(3, packageName, sku, BILLING_TYPE, payload);
47 | if (responseOk(bundle)) {
48 | final PendingIntent pendingIntent = bundle.getParcelable(RESPONSE_BUY_INTENT);
49 | if (pendingIntent != null) {
50 | final IntentSender sender = pendingIntent.getIntentSender();
51 | activity.startIntentSenderForResult(sender, requestCode, null, 0, 0, 0);
52 | return true;
53 | }
54 | }
55 | } catch (RemoteException | IntentSender.SendIntentException ignore) {
56 | }
57 | return false;
58 | }
59 |
60 | @Override public SkuDetails skuDetails(@NonNull String sku) {
61 | try {
62 | final Bundle skus = new Bundle(1);
63 | skus.putStringArrayList(REQUEST_ITEM_ID_LIST, arrayList(sku));
64 | final Bundle bundle = service.getSkuDetails(3, packageName, BILLING_TYPE, skus);
65 | if (responseOk(bundle)) {
66 | final ArrayList list = bundle.getStringArrayList(RESPONSE_DETAILS_LIST);
67 | if (list != null && !list.isEmpty()) {
68 | final String json = list.get(0);
69 | return new SkuDetails(json);
70 | }
71 | }
72 | } catch (RemoteException | JSONException ignore) {
73 | }
74 | return null;
75 | }
76 |
77 | @Override public boolean consumeSku(@NonNull String purchaseToken) {
78 | try {
79 | final int response = service.consumePurchase(3, packageName, purchaseToken);
80 | return response == BILLING_RESPONSE_RESULT_OK;
81 | } catch (RemoteException ignore) {
82 | }
83 | return false;
84 | }
85 |
86 | @Override public boolean ownsSku(@NonNull String sku) {
87 | try {
88 | final Bundle bundle = service.getPurchases(3, packageName, BILLING_TYPE, null);
89 | if (responseOk(bundle)) {
90 | final ArrayList list = bundle.getStringArrayList(RESPONSE_ITEM_LIST);
91 | return null != list && list.contains(sku);
92 | }
93 | } catch (RemoteException ignore) {
94 | }
95 | return false;
96 | }
97 |
98 | private static boolean responseOk(@Nullable Bundle bundle) {
99 | return bundle != null && bundle.getInt(RESPONSE_CODE) == BILLING_RESPONSE_RESULT_OK;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/tslamic/premiumer/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.premiumer;
2 |
3 | import android.app.AlertDialog;
4 | import android.app.Dialog;
5 | import android.content.Intent;
6 | import android.graphics.Color;
7 | import android.os.Bundle;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import android.support.v4.app.DialogFragment;
11 | import android.support.v7.app.AppCompatActivity;
12 | import android.text.TextUtils;
13 | import android.view.View;
14 | import android.widget.TextView;
15 | import io.github.tslamic.prem.Premiumer;
16 | import io.github.tslamic.prem.PremiumerBuilder;
17 | import io.github.tslamic.prem.Purchase;
18 | import io.github.tslamic.prem.SkuDetails;
19 |
20 | public class MainActivity extends AppCompatActivity {
21 | class AdsListener extends LogPremiumerListener {
22 | @Override public void onBillingAvailable() {
23 | super.onBillingAvailable();
24 | status.setTextColor(Color.GREEN);
25 | status.setText("Billing Available");
26 | }
27 |
28 | @Override public void onBillingUnavailable() {
29 | super.onBillingUnavailable();
30 | status.setTextColor(Color.RED);
31 | status.setText("Billing Unavailable");
32 | }
33 |
34 | @Override public void onShowAds() {
35 | super.onShowAds();
36 | ads.setVisibility(View.VISIBLE);
37 | }
38 |
39 | @Override public void onHideAds() {
40 | super.onHideAds();
41 | ads.setVisibility(View.GONE);
42 | }
43 |
44 | @Override public void onSkuDetails(@Nullable SkuDetails details) {
45 | super.onSkuDetails(details);
46 | show("Sku Details", details);
47 | }
48 |
49 | @Override public void onPurchaseDetails(@Nullable Purchase purchase) {
50 | super.onPurchaseDetails(purchase);
51 | show("Purchase Details", purchase);
52 | }
53 |
54 | private void show(@NonNull String title, @Nullable Object content) {
55 | final String c = content == null ? "Not Available" : content.toString();
56 | InfoDialogFragment.newInstance(title, c).show(getSupportFragmentManager(), "tag");
57 | }
58 | }
59 |
60 | private Premiumer premiumer;
61 | private TextView status;
62 | private View ads;
63 |
64 | @Override protected void onCreate(Bundle savedInstanceState) {
65 | super.onCreate(savedInstanceState);
66 | setContentView(R.layout.activity_main);
67 | status = (TextView) findViewById(R.id.status);
68 | ads = findViewById(R.id.ads);
69 | premiumer = PremiumerBuilder.with(this)
70 | .sku("android.test.purchased")
71 | .listener(new AdsListener())
72 | .build();
73 | }
74 |
75 | //@Override protected void onStart() {
76 | // super.onStart();
77 | // premiumer.bind();
78 | //}
79 | //
80 | //@Override protected void onStop() {
81 | // super.onStop();
82 | // premiumer.unbind();
83 | //}
84 |
85 | @Override protected void onResume() {
86 | super.onResume();
87 | premiumer.bind();
88 | }
89 |
90 | @Override protected void onPause() {
91 | super.onPause();
92 | premiumer.unbind();
93 | }
94 |
95 | public void onPurchase(View v) {
96 | premiumer.purchase(this);
97 | }
98 |
99 | public void onConsume(View v) {
100 | premiumer.consumeSku();
101 | }
102 |
103 | public void onSkuDetails(View v) {
104 | premiumer.skuDetails();
105 | }
106 |
107 | public void onPurchaseDetails(View v) {
108 | premiumer.purchaseDetails();
109 | }
110 |
111 | @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
112 | if (!premiumer.handleActivityResult(requestCode, resultCode, data)) {
113 | super.onActivityResult(requestCode, resultCode, data);
114 | }
115 | }
116 |
117 | public static class InfoDialogFragment extends DialogFragment {
118 | static InfoDialogFragment newInstance(String title, String content) {
119 | final Bundle args = new Bundle(2);
120 | args.putString("title", title);
121 | args.putString("content", TextUtils.isEmpty(content) ? "null" : content);
122 |
123 | final InfoDialogFragment f = new InfoDialogFragment();
124 | f.setArguments(args);
125 | return f;
126 | }
127 |
128 | @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) {
129 | final Bundle args = getArguments();
130 | final String title = args.getString("title");
131 | final String content = args.getString("content");
132 | return new AlertDialog.Builder(getActivity()).setTitle(title).setMessage(content).create();
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/SimpleBinderTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.content.ServiceConnection;
6 | import android.content.pm.PackageManager;
7 | import android.content.pm.ResolveInfo;
8 | import android.support.annotation.NonNull;
9 | import android.support.annotation.Nullable;
10 | import io.github.tslamic.prem.stub.ContextStub;
11 | import io.github.tslamic.prem.stub.PackageManagerStub;
12 | import java.util.List;
13 | import org.junit.Test;
14 | import org.junit.runner.RunWith;
15 | import org.robolectric.RobolectricTestRunner;
16 |
17 | import static com.google.common.truth.Truth.assertThat;
18 | import static io.github.tslamic.prem.TestFactory.binder;
19 | import static io.github.tslamic.prem.Util.arrayList;
20 | import static org.mockito.Mockito.mock;
21 |
22 | @RunWith(RobolectricTestRunner.class) public class SimpleBinderTest {
23 | private void assertBillingCapabilities(@NonNull Context context, boolean expected) {
24 | final Binder binder = binder(context);
25 | final Intent intent = new Intent();
26 | assertThat(binder.hasBillingCapabilities(intent)).isEqualTo(expected);
27 | }
28 |
29 | @Test public void hasBillingCapabilitiesNull() {
30 | final Context context = new SimpleBinderContext(null);
31 | assertBillingCapabilities(context, false);
32 | }
33 |
34 | @Test public void hasBillingCapabilitiesEmpty() {
35 | final Context context = new SimpleBinderContext();
36 | assertBillingCapabilities(context, false);
37 | }
38 |
39 | @Test public void hasBillingCapabilitiesOk() {
40 | final List list = arrayList(mock(ResolveInfo.class));
41 | final Context context = new SimpleBinderContext(list);
42 | assertBillingCapabilities(context, true);
43 | }
44 |
45 | @Test public void bind() {
46 | final SimpleBinderContext context = new SimpleBinderContext();
47 | final Intent intent = new Intent();
48 | final ServiceConnection connection = mock(ServiceConnection.class);
49 | final Binder binder = binder(context);
50 |
51 | binder.bind(intent, connection, 0);
52 |
53 | assertThat(context.service).isEqualTo(intent);
54 | assertThat(context.connection).isEqualTo(connection);
55 | assertThat(context.flags).isEqualTo(0);
56 | assertThat(binder.isBound()).isTrue();
57 | }
58 |
59 | @Test public void bindFailure() {
60 | final Context context = new ContextStub() {
61 | @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) {
62 | return false;
63 | }
64 | };
65 | final Intent intent = new Intent();
66 | final ServiceConnection connection = mock(ServiceConnection.class);
67 | final Binder binder = binder(context);
68 | binder.bind(intent, connection, 0);
69 | assertThat(binder.isBound()).isFalse();
70 | }
71 |
72 | @Test public void unbind() {
73 | final SimpleBinderContext context = new SimpleBinderContext();
74 | final Intent intent = new Intent();
75 | final ServiceConnection connection = mock(ServiceConnection.class);
76 | final Binder binder = binder(context);
77 |
78 | binder.bind(intent, connection, 0);
79 | binder.unbind();
80 |
81 | assertThat(context.service).isNull();
82 | assertThat(context.connection).isNull();
83 | assertThat(context.flags).isEqualTo(0);
84 | assertThat(binder.isBound()).isFalse();
85 | }
86 |
87 | static class SimpleBinderContext extends ContextStub {
88 | final PackageManager manager;
89 | Intent service;
90 | ServiceConnection connection;
91 | int flags;
92 |
93 | SimpleBinderContext() {
94 | this(java.util.Collections.emptyList());
95 | }
96 |
97 | SimpleBinderContext(List services) {
98 | this.manager = new SimpleBinderPackageManager(services);
99 | }
100 |
101 | @Override public boolean bindService(Intent service, ServiceConnection conn, int flags) {
102 | this.service = service;
103 | this.connection = conn;
104 | this.flags = flags;
105 | return true;
106 | }
107 |
108 | @Override public void unbindService(ServiceConnection conn) {
109 | if (conn.equals(connection)) {
110 | service = null;
111 | connection = null;
112 | flags = 0;
113 | }
114 | }
115 |
116 | @Override public PackageManager getPackageManager() {
117 | return manager;
118 | }
119 | }
120 |
121 | static class SimpleBinderPackageManager extends PackageManagerStub {
122 | private final List services;
123 |
124 | SimpleBinderPackageManager(@Nullable List services) {
125 | this.services = services;
126 | }
127 |
128 | @Override public List queryIntentServices(Intent intent, int flags) {
129 | return services;
130 | }
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/Purchase.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.os.Parcel;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import org.json.JSONException;
7 | import org.json.JSONObject;
8 |
9 | import static io.github.tslamic.prem.Util.safeEquals;
10 |
11 | /**
12 | * Represents an in-app billing purchase.
13 | */
14 | public class Purchase extends BillingItem {
15 | private static final int STATE_PURCHASED = 0;
16 | private static final int STATE_CANCELLED = 1;
17 | private static final int STATE_REFUNDED = 2;
18 |
19 | boolean autoRenewing; // Even though this will never be used, it's here for completeness.
20 | String orderId;
21 | String packageName;
22 | String sku;
23 | long purchaseTime;
24 | int purchaseState;
25 | String developerPayload;
26 | String purchaseToken;
27 | String signature;
28 |
29 | Purchase(@NonNull String json, @Nullable String signature) throws JSONException {
30 | super(json);
31 | this.signature = signature;
32 | }
33 |
34 | @Override void init(@NonNull JSONObject object) throws JSONException {
35 | autoRenewing = object.optBoolean("autoRenewing", false);
36 | orderId = object.getString("orderId");
37 | packageName = object.getString("packageName");
38 | sku = object.getString("productId");
39 | purchaseTime = object.getLong("purchaseTime");
40 | purchaseState = object.getInt("purchaseState");
41 | developerPayload = object.getString("developerPayload");
42 | purchaseToken = object.getString("purchaseToken");
43 | }
44 |
45 | public boolean isAutoRenewing() {
46 | return autoRenewing;
47 | }
48 |
49 | @NonNull public String getOrderId() {
50 | return orderId;
51 | }
52 |
53 | @NonNull public String getPackageName() {
54 | return packageName;
55 | }
56 |
57 | @NonNull public String getSku() {
58 | return sku;
59 | }
60 |
61 | public long getPurchaseTime() {
62 | return purchaseTime;
63 | }
64 |
65 | public int getPurchaseState() {
66 | return purchaseState;
67 | }
68 |
69 | public boolean isPurchased() {
70 | return purchaseState == STATE_PURCHASED;
71 | }
72 |
73 | public boolean isCancelled() {
74 | return purchaseState == STATE_CANCELLED;
75 | }
76 |
77 | public boolean isRefunded() {
78 | return purchaseState == STATE_REFUNDED;
79 | }
80 |
81 | @NonNull public String getDeveloperPayload() {
82 | return developerPayload;
83 | }
84 |
85 | @NonNull public String getToken() {
86 | return purchaseToken;
87 | }
88 |
89 | @Nullable public String getSignature() {
90 | return signature;
91 | }
92 |
93 | @Override public boolean equals(Object o) {
94 | if (this == o) {
95 | return true;
96 | }
97 | if (o == null || getClass() != o.getClass()) {
98 | return false;
99 | }
100 | final Purchase purchase = (Purchase) o;
101 | return autoRenewing == purchase.autoRenewing
102 | && purchaseTime == purchase.purchaseTime
103 | && purchaseState == purchase.purchaseState
104 | && orderId.equals(purchase.orderId)
105 | && packageName.equals(purchase.packageName)
106 | && sku.equals(purchase.sku)
107 | && developerPayload.equals(purchase.developerPayload)
108 | && purchaseToken.equals(purchase.purchaseToken)
109 | && safeEquals(signature, purchase.signature);
110 | }
111 |
112 | @Override public int hashCode() {
113 | int result = (autoRenewing ? 1 : 0);
114 | result = 31 * result + orderId.hashCode();
115 | result = 31 * result + packageName.hashCode();
116 | result = 31 * result + sku.hashCode();
117 | result = 31 * result + (int) (purchaseTime ^ (purchaseTime >>> 32));
118 | result = 31 * result + purchaseState;
119 | result = 31 * result + developerPayload.hashCode();
120 | result = 31 * result + purchaseToken.hashCode();
121 | result = 31 * result + (signature == null ? 0 : signature.hashCode());
122 | return result;
123 | }
124 |
125 | // Parcelable stuff below.
126 |
127 | private Purchase(@NonNull Parcel parcel) {
128 | super(parcel);
129 | autoRenewing = parcel.readInt() == 1;
130 | orderId = parcel.readString();
131 | packageName = parcel.readString();
132 | sku = parcel.readString();
133 | purchaseTime = parcel.readLong();
134 | purchaseState = parcel.readInt();
135 | developerPayload = parcel.readString();
136 | purchaseToken = parcel.readString();
137 | signature = parcel.readString();
138 | }
139 |
140 | public static final Creator CREATOR = new Creator() {
141 | @Override public Purchase createFromParcel(Parcel source) {
142 | return new Purchase(source);
143 | }
144 |
145 | @Override public Purchase[] newArray(int size) {
146 | return new Purchase[size];
147 | }
148 | };
149 |
150 | @Override public void writeToParcel(Parcel dest, int flags) {
151 | super.writeToParcel(dest, flags);
152 | dest.writeInt(autoRenewing ? 1 : 0);
153 | dest.writeString(orderId);
154 | dest.writeString(packageName);
155 | dest.writeString(sku);
156 | dest.writeLong(purchaseTime);
157 | dest.writeInt(purchaseState);
158 | dest.writeString(developerPayload);
159 | dest.writeString(purchaseToken);
160 | dest.writeString(signature);
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/config/checkstyle/checkstyle.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/BillingTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.os.RemoteException;
6 | import android.support.annotation.NonNull;
7 | import android.support.annotation.Nullable;
8 | import com.android.vending.billing.IInAppBillingService;
9 | import io.github.tslamic.prem.stub.BillingServiceStub;
10 | import java.util.ArrayList;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.robolectric.RobolectricTestRunner;
14 |
15 | import static com.google.common.truth.Truth.assertThat;
16 | import static io.github.tslamic.prem.AssertUtil.assertInvokedOnce;
17 | import static io.github.tslamic.prem.AssertUtil.assertNoInteraction;
18 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
19 | import static io.github.tslamic.prem.Constant.RESPONSE_BUY_INTENT;
20 | import static io.github.tslamic.prem.Constant.RESPONSE_CODE;
21 | import static io.github.tslamic.prem.Constant.RESPONSE_DETAILS_LIST;
22 | import static io.github.tslamic.prem.Constant.RESPONSE_ITEM_LIST;
23 | import static io.github.tslamic.prem.TestFactory.billing;
24 | import static io.github.tslamic.prem.TestUtil.JSON_SKU;
25 | import static io.github.tslamic.prem.TestUtil.SKU;
26 | import static io.github.tslamic.prem.Util.arrayList;
27 | import static org.mockito.Mockito.mock;
28 |
29 | @RunWith(RobolectricTestRunner.class) public class BillingTest {
30 | @Test public void billingSupportedOk() {
31 | final IInAppBillingService service = new SuccessfulBillingService();
32 | final Billing billing = billing(service);
33 | final boolean result = billing.isBillingSupported();
34 | assertThat(result).isTrue();
35 | }
36 |
37 | @Test public void purchaseNullPendingIntent() {
38 | final IInAppBillingService service = new BillingServiceStub() {
39 | @Override
40 | public Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
41 | String developerPayload) throws RemoteException {
42 | final Bundle bundle = new Bundle(2);
43 | bundle.putInt(RESPONSE_CODE, BILLING_RESPONSE_RESULT_OK);
44 | bundle.putParcelable(RESPONSE_BUY_INTENT, null);
45 | return bundle;
46 | }
47 | };
48 | final Billing billing = billing(service);
49 | final Activity activity = mock(Activity.class);
50 | final boolean response = billing.purchase(activity, SKU, 0, null);
51 | assertThat(response).isFalse();
52 | assertNoInteraction(activity);
53 | }
54 |
55 | @Test public void purchaseOk() throws Exception {
56 | final IInAppBillingService service = new SuccessfulBillingService();
57 | final Activity activity = mock(Activity.class);
58 | final Billing billing = billing(service);
59 | final boolean response = billing.purchase(activity, SKU, 0, null);
60 | assertInvokedOnce(activity).startIntentSenderForResult(null, 0, null, 0, 0, 0);
61 | assertThat(response).isTrue();
62 | }
63 |
64 | @Test public void skuDetailsNull() {
65 | final IInAppBillingService service = withJsonSkus(null);
66 | final Billing billing = billing(service);
67 | final SkuDetails details = billing.skuDetails(SKU);
68 | assertThat(details).isNull();
69 | }
70 |
71 | @Test public void skuDetailsEmpty() {
72 | final IInAppBillingService service = withJsonSkus();
73 | final Billing billing = billing(service);
74 | final SkuDetails details = billing.skuDetails(SKU);
75 | assertThat(details).isNull();
76 | }
77 |
78 | @Test public void skuDetailsOk() throws Exception {
79 | final String json = JSON_SKU;
80 | final IInAppBillingService service = withJsonSkus(json);
81 | final Billing billing = billing(service);
82 |
83 | final SkuDetails details = billing.skuDetails(SKU);
84 | assertThat(details).isNotNull();
85 |
86 | final SkuDetails actual = new SkuDetails(json);
87 | assertThat(details).isEqualTo(actual);
88 | }
89 |
90 | @Test public void consumeSkuOk() {
91 | final IInAppBillingService service = new SuccessfulBillingService();
92 | final Billing billing = billing(service);
93 | final boolean result = billing.consumeSku(SKU);
94 | assertThat(result).isTrue();
95 | }
96 |
97 | @Test public void ownsSkuNullResponseList() {
98 | final IInAppBillingService service = withOwnedSkus(null);
99 | final Billing billing = billing(service);
100 | final boolean result = billing.ownsSku(SKU);
101 | assertThat(result).isFalse();
102 | }
103 |
104 | @Test public void ownsSkuEmptyResponseList() {
105 | final IInAppBillingService service = withOwnedSkus();
106 | final Billing billing = billing(service);
107 | final boolean result = billing.ownsSku(SKU);
108 | assertThat(result).isFalse();
109 | }
110 |
111 | @Test public void ownsSkuOk() {
112 | final IInAppBillingService service = withOwnedSkus(SKU);
113 | final Billing billing = billing(service);
114 | final boolean result = billing.ownsSku(SKU);
115 | assertThat(result).isTrue();
116 | }
117 |
118 | private static IInAppBillingService withJsonSkus(String... jsonSkus) {
119 | final Bundle bundle = populateLSuccessfulList(RESPONSE_DETAILS_LIST, jsonSkus);
120 | return new BillingServiceStub() {
121 | @Override public Bundle getSkuDetails(int apiVersion, String packageName, String type,
122 | Bundle skusBundle) throws RemoteException {
123 | return bundle;
124 | }
125 | };
126 | }
127 |
128 | private static IInAppBillingService withOwnedSkus(String... ownedSkus) {
129 | final Bundle bundle = populateLSuccessfulList(RESPONSE_ITEM_LIST, ownedSkus);
130 | return new BillingServiceStub() {
131 | @Override public Bundle getPurchases(int apiVersion, String packageName, String type,
132 | String continuationToken) throws RemoteException {
133 | return bundle;
134 | }
135 | };
136 | }
137 |
138 | private static Bundle populateLSuccessfulList(@NonNull String key, @Nullable String... values) {
139 | final ArrayList list;
140 | if (values == null) {
141 | list = null;
142 | } else {
143 | list = arrayList(values);
144 | }
145 | final Bundle bundle = new Bundle(2);
146 | bundle.putInt(RESPONSE_CODE, BILLING_RESPONSE_RESULT_OK);
147 | bundle.putStringArrayList(key, list);
148 | return bundle;
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | Showing ads in your app? Wanna offer a single in-app purchase to remove them? Premiumer does just that!
4 |
5 | [](https://travis-ci.org/tslamic/premiumer)
6 | [](https://codecov.io/gh/tslamic/premiumer)
7 |
8 | # How?
9 | Using Premiumer is incredibly easy. First, add the following dependency:
10 |
11 | ```groovy
12 | compile 'com.github.tslamic:premiumer2:1.0'
13 | ```
14 |
15 | Then, create an instance:
16 |
17 | ```java
18 | Premiumer premiumer = PremiumerBuilder.with(context)
19 | .sku(billingSku)
20 | .listener(premiumerListnener)
21 | .build();
22 | ```
23 |
24 | Next, bind to the underlying in-app billing service. This should usually follow the `Activity` or `Fragment` lifecycle. Also, ensure `premiumer` can handle purchase results by overriding `onActivityResult`. For example:
25 |
26 | ```java
27 | @Override protected void onStart() {
28 | super.onStart();
29 | premiumer.bind();
30 | }
31 |
32 | @Override protected void onStop() {
33 | super.onStop();
34 | premiumer.unbind();
35 | }
36 |
37 | @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
38 | if (!premiumer.handleActivityResult(requestCode, resultCode, data)) {
39 | super.onActivityResult(requestCode, resultCode, data);
40 | }
41 | }
42 | ```
43 |
44 | If the in-app billing service is availabe and `premiumer` was successfully bound, `onBillingAvailable()` will be invoked on the listener you provided in the builder, `onBillingUnavailable()` otherwise.
45 |
46 | You're now all set! :tada:
47 |
48 | To perform a purchase, invoke `premiumer.purchase(Activity)`. This should be the same `Activity` overriding `onActivityResult`. Just before a purchase is shipped over to in-app billing service for processing, you'll receive a `onPurchaseRequested(String)` callback.
49 |
50 | The `String` argument is a developer-specified payload, uniquely identifying a purchase. By default, Premiumer will use a randomly generated UUID, but you can easily provide your own `PayloadGenerator` when building a Premiumer instance:
51 |
52 | ```java
53 | PremiumerBuilder.with(context)
54 | .sku(billingSku)
55 | .listener(premiumerListnener)
56 | .payloadGenerator(customGenerator); // custom generator
57 | .build();
58 | ```
59 |
60 | Once a purchase is processed, the in-app billing service will return a result to the `Activity` triggering the purchase. If you've properly overriden `onActivityResult`, Premiumer will notify you what happened:
61 |
62 | | Callback | Meaning |
63 | | ------------: |:-------------|
64 | | `onPurchaseBadResult (int, Intent)` | purchase result was not OK, e.g. the user cancelled the purchase flow |
65 | | `onPurchaseBadResponse(Intent)` | in-app billing response was incomplete or missing information |
66 | | `onPurchaseFailedVerification() ` | purchase verification failed |
67 | | `onPurchaseSuccessful(Purchase)` | purchase succeeded |
68 |
69 | By default, no purchase verification is done. You can change that, however, by providing a `PurchaseVerifier` instance when building Premiumer:
70 |
71 | ```java
72 | PremiumerBuilder.with(context)
73 | .sku(billingSku)
74 | .listener(premiumerListnener)
75 | .purchaseVerifier(verifier); // custom verifier
76 | .build();
77 | ```
78 |
79 | When a purchase is successful, Premiumer will, by default, store the purchase information in plain text to `SharedPreferences`. You can provide a different caching mechanism by specifying `PurchaseCache` when building Premiumer:
80 |
81 | ```java
82 | PremiumerBuilder.with(context)
83 | .sku(billingSku)
84 | .listener(premiumerListnener)
85 | .purchaseCache(cache); // custom cache
86 | .build();
87 | ```
88 |
89 | You can always obtain the information about the item you're about to purchase by invoking `premiumer.skuDetails()`. This will return `onSkuDetails(SkuDetails)` callback.
90 |
91 | Similarly, once a purchase has been made, you can retrieve it by invoking `premiumer.purchaseDetails()`. This will invoke `onPurchaseDetails(Purchase` callback.
92 |
93 | If, for any reason, you wish to consume a purchase, invoke `premiumer.consumeSku()`. If successful, `onSkuConsumed()` will be invoked, `onFailedToConsumeSku()` otherwise.
94 |
95 | # Callbacks
96 |
97 | As you might have guessed, all `Premiumer` interaction results in a `PremiumerListener` callback. Here's the complete list:
98 |
99 | | Method name | Meaning |
100 | | ------------: |:-------------|
101 | | `onShowAds()` | Invoked if ads should be visible. |
102 | | `onHideAds()` | Invoked if ads should be hidden. |
103 | | `onBillingAvailable()` | Invoked if in-app Billing is available. |
104 | | `onBillingUnavailable()` | Invoked if in-app Billing is unavailable. |
105 | | `onSkuDetails(SkuDetails)` | Invoked when `SkuDetails` information is retireved. |
106 | | `onSkuConsumed()` | Invoked if sku has been successfully consumed. |
107 | | `onFailedToConsumeSku()` | Invoked if sku has not been successfully consumed. |
108 | | `onPurchaseRequested(String)` | Invoked on a purchase request. |
109 | | `onPurchaseDetails(Purchase)` | Invoked when purchase details are retrieved. |
110 | | `onPurchaseSuccessful(Purchase)` | Invoked on a successful purchase. |
111 | | `onPurchaseBadResult(int, Intent)` | Invoked when the sku purchase is unsuccessful. |
112 | | `onPurchaseBadResponse(Intent)` | Invoked when the sku purchase returns bad data. |
113 | | `onPurchaseFailedVerification()`| Invoked if a purchase has failed verification. |
114 |
115 | If you feel that's too much, you can cherry pick the methods by extending `SimplePremiumerListener`.
116 |
117 | Contribution
118 | ---
119 |
120 | Improvement suggestions, bug reports, pull requests, etc. are very welcome and greatly appreciated!
121 |
122 | License
123 | ---
124 |
125 | Copyright 2017 Tadej Slamic
126 |
127 | Licensed under the Apache License, Version 2.0 (the "License");
128 | you may not use this file except in compliance with the License.
129 | You may obtain a copy of the License at
130 |
131 | http://www.apache.org/licenses/LICENSE-2.0
132 |
133 | Unless required by applicable law or agreed to in writing, software
134 | distributed under the License is distributed on an "AS IS" BASIS,
135 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136 | See the License for the specific language governing permissions and
137 | limitations under the License.
138 |
--------------------------------------------------------------------------------
/prem/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.android.vending.billing;
18 |
19 | import android.os.Bundle;
20 |
21 | /**
22 | * InAppBillingService is the service that provides in-app billing version 3 and beyond.
23 | * This service provides the following features:
24 | * 1. Provides a new API to get details of in-app items published for the app including
25 | * price, type, title and description.
26 | * 2. The purchase flow is synchronous and purchase information is available immediately
27 | * after it completes.
28 | * 3. Purchase information of in-app purchases is maintained within the Google Play system
29 | * till the purchase is consumed.
30 | * 4. An API to consume a purchase of an inapp item. All purchases of one-time
31 | * in-app items are consumable and thereafter can be purchased again.
32 | * 5. An API to get current purchases of the user immediately. This will not contain any
33 | * consumed purchases.
34 | *
35 | * All calls will give a response code with the following possible values
36 | * RESULT_OK = 0 - success
37 | * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog
38 | * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested
39 | * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase
40 | * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API
41 | * RESULT_ERROR = 6 - Fatal error during the API action
42 | * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned
43 | * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned
44 | */
45 | interface IInAppBillingService {
46 | /**
47 | * Checks support for the requested billing API version, package and in-app type.
48 | * Minimum API version supported by this interface is 3.
49 | * @param apiVersion the billing version which the app is using
50 | * @param packageName the package name of the calling app
51 | * @param type type of the in-app item being purchased "inapp" for one-time purchases
52 | * and "subs" for subscription.
53 | * @return RESULT_OK(0) on success, corresponding result code on failures
54 | */
55 | int isBillingSupported(int apiVersion, String packageName, String type);
56 |
57 | /**
58 | * Provides details of a list of SKUs
59 | * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle
60 | * with a list JSON strings containing the productId, price, title and description.
61 | * This API can be called with a maximum of 20 SKUs.
62 | * @param apiVersion billing API version that the Third-party is using
63 | * @param packageName the package name of the calling app
64 | * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST"
65 | * @return Bundle containing the following key-value pairs
66 | * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
67 | * failure as listed above.
68 | * "DETAILS_LIST" with a StringArrayList containing purchase information
69 | * in JSON format similar to:
70 | * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00",
71 | * "title : "Example Title", "description" : "This is an example description" }'
72 | */
73 | Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle);
74 |
75 | /**
76 | * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU,
77 | * the type, a unique purchase token and an optional developer payload.
78 | * @param apiVersion billing API version that the app is using
79 | * @param packageName package name of the calling app
80 | * @param sku the SKU of the in-app item as published in the developer console
81 | * @param type the type of the in-app item ("inapp" for one-time purchases
82 | * and "subs" for subscription).
83 | * @param developerPayload optional argument to be sent back with the purchase information
84 | * @return Bundle containing the following key-value pairs
85 | * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
86 | * failure as listed above.
87 | * "BUY_INTENT" - PendingIntent to start the purchase flow
88 | *
89 | * The Pending intent should be launched with startIntentSenderForResult. When purchase flow
90 | * has completed, the onActivityResult() will give a resultCode of OK or CANCELED.
91 | * If the purchase is successful, the result data will contain the following key-value pairs
92 | * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
93 | * failure as listed above.
94 | * "INAPP_PURCHASE_DATA" - String in JSON format similar to
95 | * '{"orderId":"12999763169054705758.1371079406387615",
96 | * "packageName":"com.example.app",
97 | * "productId":"exampleSku",
98 | * "purchaseTime":1345678900000,
99 | * "purchaseToken" : "122333444455555",
100 | * "developerPayload":"example developer payload" }'
101 | * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that
102 | * was signed with the private key of the developer
103 | * TODO: change this to app-specific keys.
104 | */
105 | Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type,
106 | String developerPayload);
107 |
108 | /**
109 | * Returns the current SKUs owned by the user of the type and package name specified along with
110 | * purchase information and a signature of the data to be validated.
111 | * This will return all SKUs that have been purchased in V3 and managed items purchased using
112 | * V1 and V2 that have not been consumed.
113 | * @param apiVersion billing API version that the app is using
114 | * @param packageName package name of the calling app
115 | * @param type the type of the in-app items being requested
116 | * ("inapp" for one-time purchases and "subs" for subscription).
117 | * @param continuationToken to be set as null for the first call, if the number of owned
118 | * skus are too many, a continuationToken is returned in the response bundle.
119 | * This method can be called again with the continuation token to get the next set of
120 | * owned skus.
121 | * @return Bundle containing the following key-value pairs
122 | * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on
123 | * failure as listed above.
124 | * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs
125 | * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information
126 | * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures
127 | * of the purchase information
128 | * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the
129 | * next set of in-app purchases. Only set if the
130 | * user has more owned skus than the current list.
131 | */
132 | Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken);
133 |
134 | /**
135 | * Consume the last purchase of the given SKU. This will result in this item being removed
136 | * from all subsequent responses to getPurchases() and allow re-purchase of this item.
137 | * @param apiVersion billing API version that the app is using
138 | * @param packageName package name of the calling app
139 | * @param purchaseToken token in the purchase information JSON that identifies the purchase
140 | * to be consumed
141 | * @return 0 if consumption succeeded. Appropriate error values for failures.
142 | */
143 | int consumePurchase(int apiVersion, String packageName, String purchaseToken);
144 | }
--------------------------------------------------------------------------------
/prem/src/main/java/io/github/tslamic/prem/SimplePremiumer.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.app.Activity;
4 | import android.content.ComponentName;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.ServiceConnection;
8 | import android.os.IBinder;
9 | import android.support.annotation.NonNull;
10 | import android.support.annotation.Nullable;
11 | import android.support.annotation.VisibleForTesting;
12 | import android.support.annotation.WorkerThread;
13 | import com.android.vending.billing.IInAppBillingService;
14 | import java.util.concurrent.Executor;
15 | import org.json.JSONException;
16 |
17 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
18 | import static io.github.tslamic.prem.Constant.RESPONSE_CODE;
19 | import static io.github.tslamic.prem.Constant.RESPONSE_PURCHASE_DATA;
20 | import static io.github.tslamic.prem.Constant.RESPONSE_SIGNATURE;
21 | import static io.github.tslamic.prem.Util.checkNotNull;
22 | import static io.github.tslamic.prem.Util.isBlank;
23 |
24 | class SimplePremiumer implements Premiumer {
25 | private static final Intent BILLING_INTENT =
26 | new Intent("com.android.vending.billing.InAppBillingService.BIND").setPackage(
27 | "com.android.vending");
28 |
29 | private final ServiceConnection connection = new Connection();
30 | private final Binder binder;
31 | private Billing billing;
32 |
33 | // Builder values.
34 | private final Context context;
35 | private final String sku;
36 | private final PremiumerListener listener;
37 | private final Executor executor;
38 | private final int requestCode;
39 | private final PayloadGenerator payloadGenerator;
40 | private final PurchaseVerifier verifier;
41 | private final PurchaseCache cache;
42 | private final String signatureBase64;
43 | private final PremiumerHandler handler;
44 |
45 | SimplePremiumer(@NonNull PremiumerBuilder builder) {
46 | this(builder, new SimpleBinder(builder.context));
47 | }
48 |
49 | SimplePremiumer(@NonNull PremiumerBuilder builder, @NonNull Binder binder) {
50 | this.binder = checkNotNull(binder, "binder == null");
51 | this.context = builder.context;
52 | this.sku = builder.sku;
53 | this.executor = builder.executor;
54 | this.requestCode = builder.requestCode;
55 | this.payloadGenerator = builder.payloadGenerator;
56 | this.verifier = builder.purchaseVerifier;
57 | this.cache = builder.purchaseCache;
58 | this.signatureBase64 = builder.signatureBase64;
59 |
60 | // Wrap the listener in a proxy, if show/hide ads should be invoked automatically.
61 | this.listener = builder.autoNotifyAds ? new NotifyAdsProxy(builder.listener) : builder.listener;
62 | this.handler = new PremiumerHandler(this.listener);
63 | }
64 |
65 | @Override public void bind() {
66 | final boolean bound =
67 | binder.hasBillingCapabilities(BILLING_INTENT) && binder.bind(BILLING_INTENT, connection,
68 | Context.BIND_AUTO_CREATE); // listener.onBillingAvailable() will be invoked later.
69 | if (!bound) {
70 | listener.onBillingUnavailable();
71 | }
72 | }
73 |
74 | @Override public void unbind() {
75 | binder.unbind();
76 | listener.onBillingUnavailable();
77 | }
78 |
79 | @Override public boolean purchase(@Nullable Activity activity) {
80 | if (billing == null || activity == null) {
81 | return false;
82 | }
83 | final String payload = payloadGenerator.generate();
84 | if (billing.purchase(activity, sku, requestCode, payload)) {
85 | listener.onPurchaseRequested(payload);
86 | return true;
87 | }
88 | return false;
89 | }
90 |
91 | @Override
92 | public boolean handleActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
93 | // Ignore handling if not related to Premiumer.
94 | if (this.requestCode != requestCode) {
95 | return false;
96 | }
97 |
98 | // If the result is not RESULT_OK, notify and return.
99 | if (resultCode != Activity.RESULT_OK) {
100 | listener.onPurchaseBadResult(resultCode, data);
101 | return true;
102 | }
103 |
104 | // If the intent data is missing, notify and return.
105 | if (data == null) {
106 | listener.onPurchaseBadResponse(null);
107 | return true;
108 | }
109 |
110 | // Assume intents with no response code are OK (known issue).
111 | // If the response is not OK, notify and return.
112 | final int response = data.getIntExtra(RESPONSE_CODE, BILLING_RESPONSE_RESULT_OK);
113 | if (response != BILLING_RESPONSE_RESULT_OK) {
114 | listener.onPurchaseBadResponse(data);
115 | return true;
116 | }
117 |
118 | // Ensure the important stuff is present, otherwise this is a bad response.
119 | final String purchaseData = data.getStringExtra(RESPONSE_PURCHASE_DATA);
120 | if (isBlank(purchaseData)) {
121 | listener.onPurchaseBadResponse(data);
122 | return true;
123 | }
124 |
125 | // Success!
126 | final String signature = data.getStringExtra(RESPONSE_SIGNATURE);
127 | executor.execute(new Runnable() {
128 | @Override public void run() {
129 | onSuccessfulPurchase(purchaseData, signature);
130 | }
131 | });
132 | return true;
133 | }
134 |
135 | @Override public boolean skuDetails() {
136 | return exec(new Runnable() {
137 | @Override public void run() {
138 | final SkuDetails details = billing.skuDetails(sku);
139 | handler.obtainMessage(PremiumerHandler.ON_SKU_DETAILS, details).sendToTarget();
140 | }
141 | });
142 | }
143 |
144 | @Override public boolean purchaseDetails() {
145 | return exec(new Runnable() {
146 | @Override public void run() {
147 | final Purchase purchase = cache.load();
148 | handler.obtainMessage(PremiumerHandler.ON_PURCHASE_DETAILS, purchase).sendToTarget();
149 | }
150 | });
151 | }
152 |
153 | @Override public boolean consumeSku() {
154 | return exec(new Runnable() {
155 | @Override public void run() {
156 | boolean consumed = false;
157 | final Purchase purchase = cache.load();
158 | if (purchase != null && purchase.purchaseToken != null) {
159 | consumed = billing.consumeSku(purchase.purchaseToken);
160 | }
161 | final int message;
162 | if (consumed) {
163 | cache.clear();
164 | message = PremiumerHandler.ON_SKU_CONSUMED;
165 | } else {
166 | message = PremiumerHandler.ON_FAILED_TO_CONSUME_SKU;
167 | }
168 | handler.obtainMessage(message, purchase).sendToTarget();
169 | }
170 | });
171 | }
172 |
173 | @WorkerThread void onSuccessfulPurchase(@NonNull String purchaseData, @NonNull String signature) {
174 | final boolean verified =
175 | verifier == null || verifier.verify(signatureBase64, purchaseData, signature);
176 | if (verified) {
177 | final Purchase purchase = fromJson(purchaseData, signature);
178 | cache.cache(purchase);
179 | handler.obtainMessage(PremiumerHandler.ON_PURCHASE_SUCCESSFUL, purchase).sendToTarget();
180 | } else {
181 | handler.obtainMessage(PremiumerHandler.ON_PURCHASE_FAILED_VERIFICATION).sendToTarget();
182 | }
183 | }
184 |
185 | @VisibleForTesting boolean checkAds() {
186 | return exec(new Runnable() {
187 | @Override public void run() {
188 | final boolean owns = billing.ownsSku(sku);
189 | final int id = owns ? PremiumerHandler.ON_HIDE_ADS : PremiumerHandler.ON_SHOW_ADS;
190 | handler.obtainMessage(id).sendToTarget();
191 | }
192 | });
193 | }
194 |
195 | private boolean exec(@NonNull Runnable runnable) {
196 | if (billing == null) {
197 | return false;
198 | }
199 | executor.execute(runnable);
200 | return true;
201 | }
202 |
203 | private static Purchase fromJson(@NonNull String json, @Nullable String signature) {
204 | try {
205 | return new Purchase(json, signature);
206 | } catch (JSONException e) {
207 | // This only happens if in-app billing returns a corrupted JSON,
208 | // in which case it's impossible to recover properly.
209 | throw new RuntimeException(e);
210 | }
211 | }
212 |
213 | private final class NotifyAdsProxy extends PremiumerListenerProxy {
214 | NotifyAdsProxy(@NonNull PremiumerListener listener) {
215 | super(listener);
216 | }
217 |
218 | @Override public void onBillingAvailable() {
219 | super.onBillingAvailable();
220 | checkAds();
221 | }
222 |
223 | @Override public void onBillingUnavailable() {
224 | super.onBillingUnavailable();
225 | onShowAds();
226 | }
227 |
228 | @Override public void onPurchaseSuccessful(@NonNull Purchase purchase) {
229 | super.onPurchaseSuccessful(purchase);
230 | onHideAds();
231 | }
232 |
233 | @Override public void onSkuConsumed() {
234 | super.onSkuConsumed();
235 | onShowAds();
236 | }
237 | }
238 |
239 | private final class Connection implements ServiceConnection {
240 | @Override public void onServiceConnected(ComponentName name, IBinder iBinder) {
241 | final IInAppBillingService s = binder.service(iBinder);
242 | final Billing b = binder.billing(context.getPackageName(), s);
243 | if (b.isBillingSupported()) {
244 | billing = b;
245 | listener.onBillingAvailable();
246 | } else {
247 | binder.unbind();
248 | listener.onBillingUnavailable();
249 | }
250 | }
251 |
252 | @Override public void onServiceDisconnected(ComponentName name) {
253 | billing = null;
254 | binder.unbind();
255 | listener.onBillingUnavailable();
256 | }
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/PremiumerTest.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.content.ServiceConnection;
6 | import android.os.IBinder;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 | import com.android.vending.billing.IInAppBillingService;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.robolectric.RobolectricTestRunner;
14 | import org.robolectric.RuntimeEnvironment;
15 |
16 | import static android.app.Activity.RESULT_CANCELED;
17 | import static android.app.Activity.RESULT_OK;
18 | import static com.google.common.truth.Truth.assertThat;
19 | import static io.github.tslamic.prem.AssertUtil.assertInvokedOnce;
20 | import static io.github.tslamic.prem.Constant.BILLING_RESPONSE_RESULT_OK;
21 | import static io.github.tslamic.prem.Constant.RESPONSE_CODE;
22 | import static io.github.tslamic.prem.Constant.RESPONSE_PURCHASE_DATA;
23 | import static io.github.tslamic.prem.Constant.RESPONSE_SIGNATURE;
24 | import static io.github.tslamic.prem.TestUtil.EAGER_EXECUTOR;
25 | import static io.github.tslamic.prem.TestUtil.JSON_PURCHASE;
26 | import static io.github.tslamic.prem.TestUtil.JSON_SKU;
27 | import static io.github.tslamic.prem.TestUtil.SKU;
28 | import static org.mockito.Mockito.mock;
29 | import static org.mockito.Mockito.when;
30 |
31 | @RunWith(RobolectricTestRunner.class) public class PremiumerTest {
32 | private static final String SIGNATURE = "signature";
33 | private static final int REQUEST_CODE = 123;
34 |
35 | private PurchaseVerifier verifier;
36 | private PurchaseCache cache;
37 | private PayloadGenerator generator;
38 | private PremiumerListener listener;
39 | private Binder binder;
40 | private Premiumer premiumer;
41 |
42 | @Before public void setUp() {
43 | verifier = mock(PurchaseVerifier.class);
44 | cache = mock(PurchaseCache.class);
45 | generator = mock(PayloadGenerator.class);
46 | listener = mock(PremiumerListener.class);
47 |
48 | final Builder builder = PremiumerBuilder.with(RuntimeEnvironment.application)
49 | .sku(SKU)
50 | .listener(listener)
51 | .executor(EAGER_EXECUTOR)
52 | .payloadGenerator(generator)
53 | .purchaseVerifier(verifier)
54 | .purchaseCache(cache)
55 | .autoNotifyAds(false)
56 | .requestCode(REQUEST_CODE)
57 | .signatureBase64(SIGNATURE);
58 |
59 | binder = new BinderMock();
60 | premiumer = TestFactory.premiumer(builder, binder);
61 | }
62 |
63 | @Test public void bindUnbind() {
64 | premiumer.bind();
65 | assertBound(true);
66 |
67 | premiumer.unbind();
68 | assertBound(false);
69 | }
70 |
71 | @Test public void purchaseNotBound() {
72 | assertThat(binder.isBound()).isFalse();
73 |
74 | final Activity activity = mock(Activity.class);
75 | final boolean purchased = premiumer.purchase(activity);
76 | assertThat(purchased).isFalse();
77 | }
78 |
79 | @Test public void purchaseActivityNull() {
80 | premiumer.bind();
81 | assertBound(true);
82 |
83 | final boolean purchased = premiumer.purchase(null);
84 | assertThat(purchased).isFalse();
85 | }
86 |
87 | @Test public void purchaseOk() {
88 | premiumer.bind();
89 | assertBound(true);
90 |
91 | final String payload = "payload";
92 | when(generator.generate()).thenReturn(payload);
93 |
94 | final Activity activity = mock(Activity.class);
95 | final boolean purchased = premiumer.purchase(activity);
96 | assertThat(purchased).isTrue();
97 | assertInvokedOnce(generator).generate();
98 | assertInvokedOnce(listener).onPurchaseRequested(payload);
99 | }
100 |
101 | @Test public void handleActivityResultBadRequestCode() {
102 | final int badRequestCode = BILLING_RESPONSE_RESULT_OK - 1;
103 | final boolean handled = premiumer.handleActivityResult(badRequestCode, RESULT_OK, new Intent());
104 | assertThat(handled).isFalse();
105 | }
106 |
107 | @Test public void handleActivityResultBadResult() {
108 | final int result = RESULT_CANCELED;
109 | final Intent intent = new Intent();
110 | final boolean handled = premiumer.handleActivityResult(REQUEST_CODE, result, intent);
111 | assertThat(handled).isTrue();
112 | assertInvokedOnce(listener).onPurchaseBadResult(result, intent);
113 | }
114 |
115 | @Test public void handleActivityResultBadResponseNull() {
116 | final boolean handled = premiumer.handleActivityResult(REQUEST_CODE, RESULT_OK, null);
117 | assertThat(handled).isTrue();
118 | assertInvokedOnce(listener).onPurchaseBadResponse(null);
119 | }
120 |
121 | @Test public void handleActivityResultBadResponse() {
122 | final int badResponseCode = BILLING_RESPONSE_RESULT_OK - 1;
123 | final Intent intent = newBillingIntent(badResponseCode, null, null);
124 | final boolean handled = premiumer.handleActivityResult(REQUEST_CODE, RESULT_OK, intent);
125 | assertThat(handled).isTrue();
126 | assertInvokedOnce(listener).onPurchaseBadResponse(intent);
127 | }
128 |
129 | private void handleActivityResultBadData(@Nullable String data) {
130 | final Intent intent = newBillingIntent(BILLING_RESPONSE_RESULT_OK, data, null);
131 | final boolean handled = premiumer.handleActivityResult(REQUEST_CODE, RESULT_OK, intent);
132 | assertThat(handled).isTrue();
133 | assertInvokedOnce(listener).onPurchaseBadResponse(intent);
134 | }
135 |
136 | @Test public void handleActivityResultBadDataNull() {
137 | handleActivityResultBadData(null);
138 | }
139 |
140 | @Test public void handleActivityResultBadDataEmpty() {
141 | handleActivityResultBadData("");
142 | }
143 |
144 | private void handleActivityVerification(boolean verified) throws Exception {
145 | final String responseData = JSON_PURCHASE;
146 | final String responseSignature = "responseSignature";
147 |
148 | when(verifier.verify(SIGNATURE, responseData, responseSignature)).thenReturn(verified);
149 |
150 | final Intent intent =
151 | newBillingIntent(BILLING_RESPONSE_RESULT_OK, responseData, responseSignature);
152 | final boolean handled = premiumer.handleActivityResult(REQUEST_CODE, RESULT_OK, intent);
153 | assertThat(handled).isTrue();
154 | assertInvokedOnce(verifier).verify(SIGNATURE, responseData, responseSignature);
155 |
156 | if (verified) {
157 | final Purchase purchase = new Purchase(responseData, responseSignature);
158 | assertInvokedOnce(cache).cache(purchase);
159 | assertInvokedOnce(listener).onPurchaseSuccessful(purchase);
160 | } else {
161 | assertInvokedOnce(listener).onPurchaseFailedVerification();
162 | }
163 | }
164 |
165 | @Test public void handleActivityVerificationFailed() throws Exception {
166 | handleActivityVerification(false);
167 | }
168 |
169 | @Test public void handleActivityResultVerificationOk() throws Exception {
170 | handleActivityVerification(true);
171 | }
172 |
173 | @Test public void skuDetails() throws Exception {
174 | premiumer.bind();
175 | assertBound(true);
176 |
177 | final boolean enqueued = premiumer.skuDetails();
178 | assertThat(enqueued).isTrue();
179 |
180 | final SkuDetails details = new SkuDetails(JSON_SKU);
181 | assertInvokedOnce(listener).onSkuDetails(details);
182 | }
183 |
184 | @Test public void purchaseDetails() throws Exception {
185 | final Purchase purchase = new Purchase(JSON_PURCHASE, null);
186 | when(cache.load()).thenReturn(purchase);
187 |
188 | premiumer.bind();
189 | assertBound(true);
190 |
191 | final boolean enqueued = premiumer.purchaseDetails();
192 | assertThat(enqueued).isTrue();
193 | assertInvokedOnce(listener).onPurchaseDetails(purchase);
194 | }
195 |
196 | private void consumeSku(@Nullable Purchase cached) {
197 | premiumer.bind();
198 | assertBound(true);
199 |
200 | when(cache.load()).thenReturn(cached);
201 |
202 | final boolean consumed = premiumer.consumeSku();
203 | assertThat(consumed).isTrue();
204 | assertInvokedOnce(cache).load();
205 |
206 | if (cached == null) {
207 | assertInvokedOnce(listener).onFailedToConsumeSku();
208 | } else {
209 | assertInvokedOnce(listener).onSkuConsumed();
210 | }
211 | }
212 |
213 | @Test public void consumeSkuFailed() throws Exception {
214 | consumeSku(null);
215 | }
216 |
217 | @Test public void consumeSkuOk() throws Exception {
218 | final Purchase purchase = new Purchase(JSON_PURCHASE, null);
219 | consumeSku(purchase);
220 | }
221 |
222 | @Test public void checkShowAds() {
223 | premiumer.bind();
224 | assertBound(true);
225 |
226 | assertThat(premiumer).isInstanceOf(SimplePremiumer.class);
227 | final SimplePremiumer simplePremiumer = (SimplePremiumer) premiumer;
228 |
229 | final boolean check = simplePremiumer.checkAds();
230 | assertThat(check).isTrue();
231 | assertInvokedOnce(listener).onHideAds();
232 | }
233 |
234 | private void assertBound(boolean bound) {
235 | assertThat(binder.isBound()).isEqualTo(bound);
236 | if (bound) {
237 | assertInvokedOnce(listener).onBillingAvailable();
238 | } else {
239 | assertInvokedOnce(listener).onBillingUnavailable();
240 | }
241 | }
242 |
243 | private static Intent newBillingIntent(int responseCode, @Nullable String purchaseData,
244 | @Nullable String signature) {
245 | final Intent intent = new Intent();
246 | intent.putExtra(RESPONSE_CODE, responseCode);
247 | intent.putExtra(RESPONSE_PURCHASE_DATA, purchaseData);
248 | intent.putExtra(RESPONSE_SIGNATURE, signature);
249 | return intent;
250 | }
251 |
252 | static final class BinderMock implements Binder {
253 | boolean isBound;
254 |
255 | @NonNull @Override public IInAppBillingService service(IBinder binder) {
256 | return new SuccessfulBillingService();
257 | }
258 |
259 | @NonNull @Override
260 | public Billing billing(@NonNull String packageName, @NonNull IInAppBillingService service) {
261 | return new SimpleBilling(packageName, service);
262 | }
263 |
264 | @Override public boolean hasBillingCapabilities(@NonNull Intent intent) {
265 | return true;
266 | }
267 |
268 | @Override
269 | public boolean bind(@NonNull Intent intent, @NonNull ServiceConnection conn, int flags) {
270 | isBound = true;
271 | conn.onServiceConnected(null, null);
272 | return true;
273 | }
274 |
275 | @Override public void unbind() {
276 | isBound = false;
277 | }
278 |
279 | @Override public boolean isBound() {
280 | return isBound;
281 | }
282 | }
283 | }
284 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/stub/PackageManagerStub.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem.stub;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Intent;
5 | import android.content.IntentFilter;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.pm.ApplicationInfo;
8 | import android.content.pm.FeatureInfo;
9 | import android.content.pm.InstrumentationInfo;
10 | import android.content.pm.PackageInfo;
11 | import android.content.pm.PackageInstaller;
12 | import android.content.pm.PackageManager;
13 | import android.content.pm.PermissionGroupInfo;
14 | import android.content.pm.PermissionInfo;
15 | import android.content.pm.ProviderInfo;
16 | import android.content.pm.ResolveInfo;
17 | import android.content.pm.ServiceInfo;
18 | import android.content.res.Resources;
19 | import android.content.res.XmlResourceParser;
20 | import android.graphics.Rect;
21 | import android.graphics.drawable.Drawable;
22 | import android.os.UserHandle;
23 | import android.support.annotation.NonNull;
24 | import android.support.annotation.Nullable;
25 | import java.util.List;
26 |
27 | public class PackageManagerStub extends PackageManager {
28 | @Override public PackageInfo getPackageInfo(String packageName, int flags)
29 | throws NameNotFoundException {
30 | return null;
31 | }
32 |
33 | @Override public String[] currentToCanonicalPackageNames(String[] names) {
34 | return new String[0];
35 | }
36 |
37 | @Override public String[] canonicalToCurrentPackageNames(String[] names) {
38 | return new String[0];
39 | }
40 |
41 | @Override public Intent getLaunchIntentForPackage(String packageName) {
42 | return null;
43 | }
44 |
45 | @Override public Intent getLeanbackLaunchIntentForPackage(String packageName) {
46 | return null;
47 | }
48 |
49 | @Override public int[] getPackageGids(String packageName) throws NameNotFoundException {
50 | return new int[0];
51 | }
52 |
53 | @Override public PermissionInfo getPermissionInfo(String name, int flags)
54 | throws NameNotFoundException {
55 | return null;
56 | }
57 |
58 | @Override public List queryPermissionsByGroup(String group, int flags)
59 | throws NameNotFoundException {
60 | return null;
61 | }
62 |
63 | @Override public PermissionGroupInfo getPermissionGroupInfo(String name, int flags)
64 | throws NameNotFoundException {
65 | return null;
66 | }
67 |
68 | @Override public List getAllPermissionGroups(int flags) {
69 | return null;
70 | }
71 |
72 | @Override public ApplicationInfo getApplicationInfo(String packageName, int flags)
73 | throws NameNotFoundException {
74 | return null;
75 | }
76 |
77 | @Override public ActivityInfo getActivityInfo(ComponentName component, int flags)
78 | throws NameNotFoundException {
79 | return null;
80 | }
81 |
82 | @Override public ActivityInfo getReceiverInfo(ComponentName component, int flags)
83 | throws NameNotFoundException {
84 | return null;
85 | }
86 |
87 | @Override public ServiceInfo getServiceInfo(ComponentName component, int flags)
88 | throws NameNotFoundException {
89 | return null;
90 | }
91 |
92 | @Override public ProviderInfo getProviderInfo(ComponentName component, int flags)
93 | throws NameNotFoundException {
94 | return null;
95 | }
96 |
97 | @Override public List getInstalledPackages(int flags) {
98 | return null;
99 | }
100 |
101 | @Override
102 | public List getPackagesHoldingPermissions(String[] permissions, int flags) {
103 | return null;
104 | }
105 |
106 | @Override public int checkPermission(String permName, String pkgName) {
107 | return 0;
108 | }
109 |
110 | @Override public boolean addPermission(PermissionInfo info) {
111 | return false;
112 | }
113 |
114 | @Override public boolean addPermissionAsync(PermissionInfo info) {
115 | return false;
116 | }
117 |
118 | @Override public void removePermission(String name) {
119 |
120 | }
121 |
122 | @Override public int checkSignatures(String pkg1, String pkg2) {
123 | return 0;
124 | }
125 |
126 | @Override public int checkSignatures(int uid1, int uid2) {
127 | return 0;
128 | }
129 |
130 | @Nullable @Override public String[] getPackagesForUid(int uid) {
131 | return new String[0];
132 | }
133 |
134 | @Nullable @Override public String getNameForUid(int uid) {
135 | return null;
136 | }
137 |
138 | @Override public List getInstalledApplications(int flags) {
139 | return null;
140 | }
141 |
142 | @Override public String[] getSystemSharedLibraryNames() {
143 | return new String[0];
144 | }
145 |
146 | @Override public FeatureInfo[] getSystemAvailableFeatures() {
147 | return new FeatureInfo[0];
148 | }
149 |
150 | @Override public boolean hasSystemFeature(String name) {
151 | return false;
152 | }
153 |
154 | @Override public ResolveInfo resolveActivity(Intent intent, int flags) {
155 | return null;
156 | }
157 |
158 | @Override public List queryIntentActivities(Intent intent, int flags) {
159 | return null;
160 | }
161 |
162 | @Override
163 | public List queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
164 | Intent intent, int flags) {
165 | return null;
166 | }
167 |
168 | @Override public List queryBroadcastReceivers(Intent intent, int flags) {
169 | return null;
170 | }
171 |
172 | @Override public ResolveInfo resolveService(Intent intent, int flags) {
173 | return null;
174 | }
175 |
176 | @Override public List queryIntentServices(Intent intent, int flags) {
177 | return null;
178 | }
179 |
180 | @Override public List queryIntentContentProviders(Intent intent, int flags) {
181 | return null;
182 | }
183 |
184 | @Override public ProviderInfo resolveContentProvider(String name, int flags) {
185 | return null;
186 | }
187 |
188 | @Override
189 | public List queryContentProviders(String processName, int uid, int flags) {
190 | return null;
191 | }
192 |
193 | @Override public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags)
194 | throws NameNotFoundException {
195 | return null;
196 | }
197 |
198 | @Override public List queryInstrumentation(String targetPackage, int flags) {
199 | return null;
200 | }
201 |
202 | @Override public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) {
203 | return null;
204 | }
205 |
206 | @Override public Drawable getActivityIcon(ComponentName activityName)
207 | throws NameNotFoundException {
208 | return null;
209 | }
210 |
211 | @Override public Drawable getActivityIcon(Intent intent) throws NameNotFoundException {
212 | return null;
213 | }
214 |
215 | @Override public Drawable getActivityBanner(ComponentName activityName)
216 | throws NameNotFoundException {
217 | return null;
218 | }
219 |
220 | @Override public Drawable getActivityBanner(Intent intent) throws NameNotFoundException {
221 | return null;
222 | }
223 |
224 | @Override public Drawable getDefaultActivityIcon() {
225 | return null;
226 | }
227 |
228 | @Override public Drawable getApplicationIcon(ApplicationInfo info) {
229 | return null;
230 | }
231 |
232 | @Override public Drawable getApplicationIcon(String packageName) throws NameNotFoundException {
233 | return null;
234 | }
235 |
236 | @Override public Drawable getApplicationBanner(ApplicationInfo info) {
237 | return null;
238 | }
239 |
240 | @Override public Drawable getApplicationBanner(String packageName) throws NameNotFoundException {
241 | return null;
242 | }
243 |
244 | @Override public Drawable getActivityLogo(ComponentName activityName)
245 | throws NameNotFoundException {
246 | return null;
247 | }
248 |
249 | @Override public Drawable getActivityLogo(Intent intent) throws NameNotFoundException {
250 | return null;
251 | }
252 |
253 | @Override public Drawable getApplicationLogo(ApplicationInfo info) {
254 | return null;
255 | }
256 |
257 | @Override public Drawable getApplicationLogo(String packageName) throws NameNotFoundException {
258 | return null;
259 | }
260 |
261 | @Override public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
262 | return null;
263 | }
264 |
265 | @Override public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
266 | Rect badgeLocation, int badgeDensity) {
267 | return null;
268 | }
269 |
270 | @Override public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
271 | return null;
272 | }
273 |
274 | @Override public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) {
275 | return null;
276 | }
277 |
278 | @Override
279 | public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) {
280 | return null;
281 | }
282 |
283 | @Override public CharSequence getApplicationLabel(ApplicationInfo info) {
284 | return null;
285 | }
286 |
287 | @Override public Resources getResourcesForActivity(ComponentName activityName)
288 | throws NameNotFoundException {
289 | return null;
290 | }
291 |
292 | @Override public Resources getResourcesForApplication(ApplicationInfo app)
293 | throws NameNotFoundException {
294 | return null;
295 | }
296 |
297 | @Override public Resources getResourcesForApplication(String appPackageName)
298 | throws NameNotFoundException {
299 | return null;
300 | }
301 |
302 | @Override public void verifyPendingInstall(int id, int verificationCode) {
303 |
304 | }
305 |
306 | @Override public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
307 | long millisecondsToDelay) {
308 |
309 | }
310 |
311 | @Override public void setInstallerPackageName(String targetPackage, String installerPackageName) {
312 |
313 | }
314 |
315 | @Override public String getInstallerPackageName(String packageName) {
316 | return null;
317 | }
318 |
319 | @Override public void addPackageToPreferred(String packageName) {
320 |
321 | }
322 |
323 | @Override public void removePackageFromPreferred(String packageName) {
324 |
325 | }
326 |
327 | @Override public List getPreferredPackages(int flags) {
328 | return null;
329 | }
330 |
331 | @Override public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set,
332 | ComponentName activity) {
333 |
334 | }
335 |
336 | @Override public void clearPackagePreferredActivities(String packageName) {
337 |
338 | }
339 |
340 | @Override public int getPreferredActivities(@NonNull List outFilters,
341 | @NonNull List outActivities, String packageName) {
342 | return 0;
343 | }
344 |
345 | @Override
346 | public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
347 |
348 | }
349 |
350 | @Override public int getComponentEnabledSetting(ComponentName componentName) {
351 | return 0;
352 | }
353 |
354 | @Override public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
355 |
356 | }
357 |
358 | @Override public int getApplicationEnabledSetting(String packageName) {
359 | return 0;
360 | }
361 |
362 | @Override public boolean isSafeMode() {
363 | return false;
364 | }
365 |
366 | @NonNull @Override public PackageInstaller getPackageInstaller() {
367 | return null;
368 | }
369 |
370 | @Override public int[] getPackageGids(String packageName, int flags)
371 | throws NameNotFoundException {
372 | return new int[0];
373 | }
374 |
375 | @Override public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
376 | return 0;
377 | }
378 |
379 | @Override public boolean isPermissionRevokedByPolicy(String permName, String pkgName) {
380 | return false;
381 | }
382 |
383 | @Override public boolean hasSystemFeature(String name, int version) {
384 | return false;
385 | }
386 | }
387 |
--------------------------------------------------------------------------------
/prem/src/test/java/io/github/tslamic/prem/stub/ContextStub.java:
--------------------------------------------------------------------------------
1 | package io.github.tslamic.prem.stub;
2 |
3 | import android.content.BroadcastReceiver;
4 | import android.content.ComponentName;
5 | import android.content.ContentResolver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.IntentFilter;
9 | import android.content.IntentSender;
10 | import android.content.ServiceConnection;
11 | import android.content.SharedPreferences;
12 | import android.content.pm.ApplicationInfo;
13 | import android.content.pm.PackageManager;
14 | import android.content.res.AssetManager;
15 | import android.content.res.Configuration;
16 | import android.content.res.Resources;
17 | import android.database.DatabaseErrorHandler;
18 | import android.database.sqlite.SQLiteDatabase;
19 | import android.graphics.Bitmap;
20 | import android.graphics.drawable.Drawable;
21 | import android.net.Uri;
22 | import android.os.Bundle;
23 | import android.os.Handler;
24 | import android.os.Looper;
25 | import android.os.UserHandle;
26 | import android.support.annotation.NonNull;
27 | import android.support.annotation.Nullable;
28 | import android.view.Display;
29 | import java.io.File;
30 | import java.io.FileInputStream;
31 | import java.io.FileNotFoundException;
32 | import java.io.FileOutputStream;
33 | import java.io.IOException;
34 | import java.io.InputStream;
35 |
36 | public class ContextStub extends Context {
37 | @Override public AssetManager getAssets() {
38 | return null;
39 | }
40 |
41 | @Override public Resources getResources() {
42 | return null;
43 | }
44 |
45 | @Override public PackageManager getPackageManager() {
46 | return null;
47 | }
48 |
49 | @Override public ContentResolver getContentResolver() {
50 | return null;
51 | }
52 |
53 | @Override public Looper getMainLooper() {
54 | return null;
55 | }
56 |
57 | @Override public Context getApplicationContext() {
58 | return null;
59 | }
60 |
61 | @Override public void setTheme(int resid) {
62 |
63 | }
64 |
65 | @Override public Resources.Theme getTheme() {
66 | return null;
67 | }
68 |
69 | @Override public ClassLoader getClassLoader() {
70 | return null;
71 | }
72 |
73 | @Override public String getPackageName() {
74 | return null;
75 | }
76 |
77 | @Override public ApplicationInfo getApplicationInfo() {
78 | return null;
79 | }
80 |
81 | @Override public String getPackageResourcePath() {
82 | return null;
83 | }
84 |
85 | @Override public String getPackageCodePath() {
86 | return null;
87 | }
88 |
89 | @Override public SharedPreferences getSharedPreferences(String name, int mode) {
90 | return null;
91 | }
92 |
93 | @Override public FileInputStream openFileInput(String name) throws FileNotFoundException {
94 | return null;
95 | }
96 |
97 | @Override public FileOutputStream openFileOutput(String name, int mode)
98 | throws FileNotFoundException {
99 | return null;
100 | }
101 |
102 | @Override public boolean deleteFile(String name) {
103 | return false;
104 | }
105 |
106 | @Override public File getFileStreamPath(String name) {
107 | return null;
108 | }
109 |
110 | @Override public File getFilesDir() {
111 | return null;
112 | }
113 |
114 | @Override public File getNoBackupFilesDir() {
115 | return null;
116 | }
117 |
118 | @Nullable @Override public File getExternalFilesDir(String type) {
119 | return null;
120 | }
121 |
122 | @Override public File[] getExternalFilesDirs(String type) {
123 | return new File[0];
124 | }
125 |
126 | @Override public File getObbDir() {
127 | return null;
128 | }
129 |
130 | @Override public File[] getObbDirs() {
131 | return new File[0];
132 | }
133 |
134 | @Override public File getCacheDir() {
135 | return null;
136 | }
137 |
138 | @Override public File getCodeCacheDir() {
139 | return null;
140 | }
141 |
142 | @Nullable @Override public File getExternalCacheDir() {
143 | return null;
144 | }
145 |
146 | @Override public File[] getExternalCacheDirs() {
147 | return new File[0];
148 | }
149 |
150 | @Override public File[] getExternalMediaDirs() {
151 | return new File[0];
152 | }
153 |
154 | @Override public String[] fileList() {
155 | return new String[0];
156 | }
157 |
158 | @Override public File getDir(String name, int mode) {
159 | return null;
160 | }
161 |
162 | @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode,
163 | SQLiteDatabase.CursorFactory factory) {
164 | return null;
165 | }
166 |
167 | @Override public SQLiteDatabase openOrCreateDatabase(String name, int mode,
168 | SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {
169 | return null;
170 | }
171 |
172 | @Override public boolean deleteDatabase(String name) {
173 | return false;
174 | }
175 |
176 | @Override public File getDatabasePath(String name) {
177 | return null;
178 | }
179 |
180 | @Override public String[] databaseList() {
181 | return new String[0];
182 | }
183 |
184 | @Override public Drawable getWallpaper() {
185 | return null;
186 | }
187 |
188 | @Override public Drawable peekWallpaper() {
189 | return null;
190 | }
191 |
192 | @Override public int getWallpaperDesiredMinimumWidth() {
193 | return 0;
194 | }
195 |
196 | @Override public int getWallpaperDesiredMinimumHeight() {
197 | return 0;
198 | }
199 |
200 | @Override public void setWallpaper(Bitmap bitmap) throws IOException {
201 |
202 | }
203 |
204 | @Override public void setWallpaper(InputStream data) throws IOException {
205 |
206 | }
207 |
208 | @Override public void clearWallpaper() throws IOException {
209 |
210 | }
211 |
212 | @Override public void startActivity(Intent intent) {
213 |
214 | }
215 |
216 | @Override public void startActivity(Intent intent, Bundle options) {
217 |
218 | }
219 |
220 | @Override public void startActivities(Intent[] intents) {
221 |
222 | }
223 |
224 | @Override public void startActivities(Intent[] intents, Bundle options) {
225 |
226 | }
227 |
228 | @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask,
229 | int flagsValues, int extraFlags) throws IntentSender.SendIntentException {
230 |
231 | }
232 |
233 | @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask,
234 | int flagsValues, int extraFlags, Bundle options) throws IntentSender.SendIntentException {
235 |
236 | }
237 |
238 | @Override public void sendBroadcast(Intent intent) {
239 |
240 | }
241 |
242 | @Override public void sendBroadcast(Intent intent, String receiverPermission) {
243 |
244 | }
245 |
246 | @Override public void sendOrderedBroadcast(Intent intent, String receiverPermission) {
247 |
248 | }
249 |
250 | @Override public void sendOrderedBroadcast(@NonNull Intent intent, String receiverPermission,
251 | BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
252 | Bundle initialExtras) {
253 |
254 | }
255 |
256 | @Override public void sendBroadcastAsUser(Intent intent, UserHandle user) {
257 |
258 | }
259 |
260 | @Override
261 | public void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) {
262 |
263 | }
264 |
265 | @Override
266 | public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission,
267 | BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
268 | Bundle initialExtras) {
269 |
270 | }
271 |
272 | @Override public void sendStickyBroadcast(Intent intent) {
273 |
274 | }
275 |
276 | @Override public void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver,
277 | Handler scheduler, int initialCode, String initialData, Bundle initialExtras) {
278 |
279 | }
280 |
281 | @Override public void removeStickyBroadcast(Intent intent) {
282 |
283 | }
284 |
285 | @Override public void sendStickyBroadcastAsUser(Intent intent, UserHandle user) {
286 |
287 | }
288 |
289 | @Override public void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user,
290 | BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData,
291 | Bundle initialExtras) {
292 |
293 | }
294 |
295 | @Override public void removeStickyBroadcastAsUser(Intent intent, UserHandle user) {
296 |
297 | }
298 |
299 | @Nullable @Override
300 | public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
301 | return null;
302 | }
303 |
304 | @Nullable @Override
305 | public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
306 | String broadcastPermission, Handler scheduler) {
307 | return null;
308 | }
309 |
310 | @Override public void unregisterReceiver(BroadcastReceiver receiver) {
311 |
312 | }
313 |
314 | @Nullable @Override public ComponentName startService(Intent service) {
315 | return null;
316 | }
317 |
318 | @Override public boolean stopService(Intent service) {
319 | return false;
320 | }
321 |
322 | @Override public boolean bindService(Intent service, @NonNull ServiceConnection conn, int flags) {
323 | return false;
324 | }
325 |
326 | @Override public void unbindService(@NonNull ServiceConnection conn) {
327 |
328 | }
329 |
330 | @Override
331 | public boolean startInstrumentation(@NonNull ComponentName className, String profileFile,
332 | Bundle arguments) {
333 | return false;
334 | }
335 |
336 | @Override public Object getSystemService(@NonNull String name) {
337 | return null;
338 | }
339 |
340 | @Override public int checkPermission(@NonNull String permission, int pid, int uid) {
341 | return 0;
342 | }
343 |
344 | @Override public int checkCallingPermission(@NonNull String permission) {
345 | return 0;
346 | }
347 |
348 | @Override public int checkCallingOrSelfPermission(@NonNull String permission) {
349 | return 0;
350 | }
351 |
352 | @Override
353 | public void enforcePermission(@NonNull String permission, int pid, int uid, String message) {
354 |
355 | }
356 |
357 | @Override public void enforceCallingPermission(@NonNull String permission, String message) {
358 |
359 | }
360 |
361 | @Override public void enforceCallingOrSelfPermission(@NonNull String permission, String message) {
362 |
363 | }
364 |
365 | @Override public void grantUriPermission(String toPackage, Uri uri, int modeFlags) {
366 |
367 | }
368 |
369 | @Override public void revokeUriPermission(Uri uri, int modeFlags) {
370 |
371 | }
372 |
373 | @Override public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) {
374 | return 0;
375 | }
376 |
377 | @Override public int checkCallingUriPermission(Uri uri, int modeFlags) {
378 | return 0;
379 | }
380 |
381 | @Override public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) {
382 | return 0;
383 | }
384 |
385 | @Override
386 | public int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid,
387 | int uid, int modeFlags) {
388 | return 0;
389 | }
390 |
391 | @Override
392 | public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) {
393 |
394 | }
395 |
396 | @Override public void enforceCallingUriPermission(Uri uri, int modeFlags, String message) {
397 |
398 | }
399 |
400 | @Override public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) {
401 |
402 | }
403 |
404 | @Override
405 | public void enforceUriPermission(Uri uri, String readPermission, String writePermission, int pid,
406 | int uid, int modeFlags, String message) {
407 |
408 | }
409 |
410 | @Override public Context createPackageContext(String packageName, int flags)
411 | throws PackageManager.NameNotFoundException {
412 | return null;
413 | }
414 |
415 | @Override
416 | public Context createConfigurationContext(@NonNull Configuration overrideConfiguration) {
417 | return null;
418 | }
419 |
420 | @Override public Context createDisplayContext(@NonNull Display display) {
421 | return null;
422 | }
423 |
424 | @Override public boolean moveSharedPreferencesFrom(Context sourceContext, String name) {
425 | return false;
426 | }
427 |
428 | @Override public boolean deleteSharedPreferences(String name) {
429 | return false;
430 | }
431 |
432 | @Override public File getDataDir() {
433 | return null;
434 | }
435 |
436 | @Override public boolean moveDatabaseFrom(Context sourceContext, String name) {
437 | return false;
438 | }
439 |
440 | @Override public String getSystemServiceName(Class> serviceClass) {
441 | return null;
442 | }
443 |
444 | @Override public int checkSelfPermission(String permission) {
445 | return 0;
446 | }
447 |
448 | @Override public Context createDeviceProtectedStorageContext() {
449 | return null;
450 | }
451 |
452 | @Override public boolean isDeviceProtectedStorage() {
453 | return false;
454 | }
455 | }
456 |
--------------------------------------------------------------------------------