From 0070f7b2c216a430e99ffb8ecb2c90674c16caf0 Mon Sep 17 00:00:00 2001 From: "Sergio R. Lumley" Date: Mon, 29 Jun 2015 11:47:43 +0200 Subject: [PATCH] Issue 129 - Integrated CafeBazaar --- .../java/org/onepf/oms/OpenIabHelper.java | 38 +++++ .../org/onepf/oms/appstore/CafeBazaar.java | 145 ++++++++++++++++++ .../appstore/cafebazaarUtils/IabHelper.java | 39 +++++ 3 files changed, 222 insertions(+) create mode 100644 library/src/main/java/org/onepf/oms/appstore/CafeBazaar.java create mode 100644 library/src/main/java/org/onepf/oms/appstore/cafebazaarUtils/IabHelper.java diff --git a/library/src/main/java/org/onepf/oms/OpenIabHelper.java b/library/src/main/java/org/onepf/oms/OpenIabHelper.java index a57f634a..a7fa6891 100644 --- a/library/src/main/java/org/onepf/oms/OpenIabHelper.java +++ b/library/src/main/java/org/onepf/oms/OpenIabHelper.java @@ -36,6 +36,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.onepf.oms.appstore.AmazonAppstore; +import org.onepf.oms.appstore.CafeBazaar; import org.onepf.oms.appstore.FortumoStore; import org.onepf.oms.appstore.GooglePlay; import org.onepf.oms.appstore.NokiaStore; @@ -204,6 +205,11 @@ public class OpenIabHelper { */ public static final String NAME_APTOIDE = "cm.aptoide.pt"; + /** + * Internal library name for the CafeBazaar store. + */ + public static final String NAME_CAFEBAZAAR = "com.farsitel.bazaar"; + private final PackageManager packageManager; private final Context context; @@ -333,6 +339,38 @@ public Appstore get() { return new SkubitTestAppstore(context); } }); + + appStorePackageMap.put(CafeBazaar.ANDROID_INSTALLER, NAME_CAFEBAZAAR); + appStoreFactoryMap.put(NAME_CAFEBAZAAR, new AppstoreFactory() { + @NotNull + @Override + public Appstore get() { + final String cafebazaarKey = options.getVerifyMode() != VERIFY_SKIP + ? options.getStoreKeys().get(NAME_CAFEBAZAAR) + : null; + return new CafeBazaar(new ContextWrapper(context.getApplicationContext()) { + @Override + public Context getApplicationContext() { + return this; + } + + @Override + public boolean bindService(final Intent service, final ServiceConnection conn, final int flags) { + final List infos = getPackageManager().queryIntentServices(service, 0); + if (CollectionUtils.isEmpty(infos)) { + return super.bindService(service, conn, flags); + } + final ResolveInfo serviceInfo = infos.get(0); + final String packageName = serviceInfo.serviceInfo.packageName; + final String className = serviceInfo.serviceInfo.name; + final ComponentName component = new ComponentName(packageName, className); + final Intent explicitIntent = new Intent(service); + explicitIntent.setComponent(component); + return super.bindService(explicitIntent, conn, flags); + } + }, cafebazaarKey); + } + }); } diff --git a/library/src/main/java/org/onepf/oms/appstore/CafeBazaar.java b/library/src/main/java/org/onepf/oms/appstore/CafeBazaar.java new file mode 100644 index 00000000..c70e1057 --- /dev/null +++ b/library/src/main/java/org/onepf/oms/appstore/CafeBazaar.java @@ -0,0 +1,145 @@ +package org.onepf.oms.appstore; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.IBinder; +import android.os.RemoteException; + +import com.android.vending.billing.IInAppBillingService; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.onepf.oms.Appstore; +import org.onepf.oms.AppstoreInAppBillingService; +import org.onepf.oms.DefaultAppstore; +import org.onepf.oms.OpenIabHelper; +import org.onepf.oms.appstore.cafebazaarUtils.IabHelper; +import org.onepf.oms.util.CollectionUtils; +import org.onepf.oms.util.Logger; +import org.onepf.oms.util.Utils; + +import java.util.List; +import java.util.concurrent.CountDownLatch; + +/** + * CafeBazaar copies one to one the implementation of Google In App Billing v3. Therefore, this implementation of the store is based on the implementation for Google In App Billing done by Ruslan Sayfutdinov on 16.04.13 + * @author Sergio R. Lumley + * @since 25.06.15. + * @see Cafe Bazaar implementation details + */ +public class CafeBazaar extends DefaultAppstore { + + public static final String VENDING_ACTION = "ir.cafebazaar.pardakht.InAppBillingService.BIND"; + public static final String ANDROID_INSTALLER = "com.farsitel.bazaar"; + + private final Context context; + private IabHelper mBillingService; + private final String publicKey; + + // isDebugMode = true |-> always returns app installed via Cafe Bazaar store + private final boolean isDebugMode = Boolean.parseBoolean("false"); // Avoid warnings by parsing its string value + + @Nullable + private volatile Boolean billingAvailable = null; // undefined until isBillingAvailable() is called + + public CafeBazaar(Context context, String publicKey) { + this.context = context; + this.publicKey = publicKey; + } + + @Override + public boolean isPackageInstaller(final String packageName) { + return isDebugMode || Utils.isPackageInstaller(context, ANDROID_INSTALLER); + } + + @Override + public boolean isBillingAvailable(final String packageName) { + Logger.d("isBillingAvailable() packageName: ", packageName); + if (billingAvailable != null) { + return billingAvailable; // return previosly checked result + } + + if (Utils.uiThread()) { + throw new IllegalStateException("Must no be called from UI thread."); + } + + if (!packageExists(context, ANDROID_INSTALLER)) { + Logger.d("isBillingAvailable() Cafe Bazaar is not available."); + // don't set billingAvailable variable in case Cafe Bazaar gets installed later + return false; + } + + final Intent intent = new Intent(VENDING_ACTION); + intent.setPackage(ANDROID_INSTALLER); + final List infoList = context.getPackageManager().queryIntentServices(intent, 0); + if (CollectionUtils.isEmpty(infoList)) { + Logger.e("isBillingAvailable() billing service is not available, even though Cafe Bazaar application seems to be installed."); + return false; + } + + final CountDownLatch latch = new CountDownLatch(1); + final boolean[] result = new boolean[1]; + final ServiceConnection serviceConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + final IInAppBillingService mService = IInAppBillingService.Stub.asInterface(service); + try { + final int response = mService.isBillingSupported(3, packageName, IabHelper.ITEM_TYPE_INAPP); + result[0] = response == IabHelper.BILLING_RESPONSE_RESULT_OK; + } catch (RemoteException e) { + result[0] = false; + Logger.e("isBillingAvailable() RemoteException while setting up in-app billing", e); + } finally { + latch.countDown(); + context.unbindService(this); + } + Logger.d("isBillingAvailable() Cafe Bazaar result: ", result[0]); + } + + public void onServiceDisconnected(ComponentName name) {/*do nothing*/} + }; + if (context.bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)) { + try { + latch.await(); + } catch (InterruptedException e) { + Logger.e("isBillingAvailable() InterruptedException while setting up in-app billing", e); + } + } else { + result[0] = false; + Logger.e("isBillingAvailable() billing is not supported. Initialization error."); + } + return (billingAvailable = result[0]); + } + + @Override + public int getPackageVersion(final String packageName) { + return Appstore.PACKAGE_VERSION_UNDEFINED; + } + + @Override + public String getAppstoreName() { + return OpenIabHelper.NAME_CAFEBAZAAR; + } + + @Nullable + @Override + public AppstoreInAppBillingService getInAppBillingService() { + if (mBillingService == null) { + mBillingService = new IabHelper(context, publicKey, this); + } + return mBillingService; + } + + private boolean packageExists(@NotNull Context context, String packageName) { + try { + context.getPackageManager().getPackageInfo(packageName, 0); + return true; + } catch (PackageManager.NameNotFoundException ignored) { + Logger.d(packageName, " package was not found."); + return false; + } + } +} diff --git a/library/src/main/java/org/onepf/oms/appstore/cafebazaarUtils/IabHelper.java b/library/src/main/java/org/onepf/oms/appstore/cafebazaarUtils/IabHelper.java new file mode 100644 index 00000000..cad2cfaf --- /dev/null +++ b/library/src/main/java/org/onepf/oms/appstore/cafebazaarUtils/IabHelper.java @@ -0,0 +1,39 @@ +package org.onepf.oms.appstore.cafebazaarUtils; + +import android.content.Context; +import android.content.Intent; + +import org.jetbrains.annotations.NotNull; +import org.onepf.oms.Appstore; +import org.onepf.oms.appstore.CafeBazaar; + +/** + * CafeBazaar helper performs the exact same actions as GooglePlay In-App Billing v3. Only requires to change the intent service. + * @author Sergio R. Lumley + * @since 25.06.15. + * @see Cafe Bazaar IabHelper changes + */ +public class IabHelper extends org.onepf.oms.appstore.googleUtils.IabHelper { + /** + * Creates an instance. After creation, it will not yet be ready to use. You must perform + * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not + * block and is safe to call from a UI thread. + * + * @param ctx Your application or Activity context. Needed to bind to the in-app billing service. + * @param base64PublicKey Your application's public key, encoded in base64. + * This is used for verification of purchase signatures. You can find your app's base64-encoded + * public key in your application's page on Google Play Developer Console. Note that this + * is NOT your "developer public key". + * @param appstore TODO + */ + public IabHelper(@NotNull final Context ctx, final String base64PublicKey, final Appstore appstore) { + super(ctx, base64PublicKey, appstore); + } + + @Override + protected Intent getServiceIntent() { + final Intent intent = new Intent(CafeBazaar.VENDING_ACTION); + intent.setPackage(CafeBazaar.ANDROID_INSTALLER); + return intent; + } +}