diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index 5606640dd7..ee43428dba 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -43,6 +43,7 @@ import net.osmand.plus.dialogs.RateUsBottomSheetDialog; import net.osmand.plus.download.DownloadIndexesThread; import net.osmand.plus.helpers.AvoidSpecificRoads; import net.osmand.plus.helpers.WaypointHelper; +import net.osmand.plus.inapp.InAppHelper; import net.osmand.plus.mapcontextmenu.other.RoutePreferencesMenu; import net.osmand.plus.monitoring.LiveMonitoringHelper; import net.osmand.plus.poi.PoiFiltersHelper; @@ -157,6 +158,7 @@ public class OsmandApplication extends MultiDexApplication { // targetPointsHelper.clearPointToNavigate(false); // } + InAppHelper.initialize(this); initRemoteConfig(); startApplication(); System.out.println("Time to start application " + (System.currentTimeMillis() - timeToStart) + " ms. Should be less < 800 ms"); diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index bf2b326cea..3c82a549e7 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -84,6 +84,7 @@ import net.osmand.plus.helpers.DiscountHelper; import net.osmand.plus.helpers.ExternalApiHelper; import net.osmand.plus.helpers.GpxImportHelper; import net.osmand.plus.helpers.WakeLockHelper; +import net.osmand.plus.inapp.InAppHelper; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.mapcontextmenu.MapContextMenuFragment; import net.osmand.plus.mapcontextmenu.other.DestinationReachedMenu; @@ -180,6 +181,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven private boolean permissionGranted; private boolean mIsDestroyed = false; + private InAppHelper inAppHelper; @Override public void onCreate(Bundle savedInstanceState) { @@ -929,6 +931,9 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven if (atlasMapRendererView != null) { atlasMapRendererView.handleOnDestroy(); } + if (inAppHelper != null) { + inAppHelper.stop(); + } mIsDestroyed = true; } @@ -1212,6 +1217,9 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { + if (inAppHelper != null && inAppHelper.onActivityResultHandled(requestCode, resultCode, data)) { + return; + } for (ActivityResultListener listener : activityResultListeners) { if (listener.processResult(requestCode, resultCode, data)) { removeActivityResultListener(listener); @@ -1538,4 +1546,39 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven NEW_IF_EXPIRED, CURRENT, } + + public InAppHelper execInAppTask(@NonNull InAppHelper.InAppRunnable runnable) { + if (inAppHelper != null) { + inAppHelper.stop(); + } + inAppHelper = new InAppHelper(getMyApplication(), false); + inAppHelper.addListener(new InAppHelper.InAppListener() { + @Override + public void onError(String error) { + inAppHelper = null; + } + + @Override + public void onGetItems() { + inAppHelper = null; + } + + @Override + public void onItemPurchased(String sku) { + inAppHelper = null; + } + + @Override + public void showProgress() { + + } + + @Override + public void dismissProgress() { + + } + }); + inAppHelper.exec(runnable); + return inAppHelper; + } } diff --git a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java index 9b910dbee9..2f2d1e6be4 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java @@ -11,6 +11,8 @@ import net.osmand.plus.OsmandSettings; import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.inapp.InAppHelper; +import net.osmand.plus.liveupdates.OsmLiveActivity; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarController; import net.osmand.plus.views.mapwidgets.MapInfoWidgetsFactory.TopToolbarControllerType; import net.osmand.util.Algorithms; @@ -124,10 +126,8 @@ public class DiscountHelper { settings.DISCOUNT_SHOW_DATETIME_MS.set(System.currentTimeMillis()); showDiscountBanner(mapActivity, message, description, icon, url); } - } } - } catch (Exception e) { logError("JSON parsing error: ", e); } @@ -154,7 +154,8 @@ public class DiscountHelper { return result; } - private static void showDiscountBanner(final MapActivity mapActivity, final String title, final String description, final String icon, final String url) { + private static void showDiscountBanner(final MapActivity mapActivity, final String title, + final String description, final String icon, final String url) { final DiscountBarController toolbarController = new DiscountBarController(); toolbarController.setTitle(title); toolbarController.setDescription(description); @@ -166,6 +167,7 @@ public class DiscountHelper { public void onClick(View v) { mapActivity.getMyApplication().logEvent(mapActivity, "motd_click"); mBannerVisible = false; + mapActivity.hideTopToolbar(toolbarController); openUrl(mapActivity, url); } }); @@ -174,6 +176,7 @@ public class DiscountHelper { public void onClick(View v) { mapActivity.getMyApplication().logEvent(mapActivity, "motd_click"); mBannerVisible = false; + mapActivity.hideTopToolbar(toolbarController); openUrl(mapActivity, url); } }); @@ -196,10 +199,26 @@ public class DiscountHelper { mapActivity.showTopToolbar(toolbarController); } - private static void openUrl(MapActivity mapActivity, String url) { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(url)); - mapActivity.startActivity(intent); + private static void openUrl(final MapActivity mapActivity, String url) { + if (url.startsWith("osmand-in-app:")) { + if (url.contains(InAppHelper.SKU_FULL_VERSION_PRICE)) { + mapActivity.execInAppTask(new InAppHelper.InAppRunnable() { + @Override + public void run(InAppHelper helper) { + helper.purchaseFullVersion(mapActivity); + } + }); + } else if (url.contains(InAppHelper.SKU_LIVE_UPDATES)){ + Intent intent = new Intent(mapActivity, OsmLiveActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + intent.putExtra(OsmLiveActivity.OPEN_SUBSCRIPTION_INTENT_PARAM, true); + mapActivity.startActivity(intent); + } + } else { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(url)); + mapActivity.startActivity(intent); + } } private static class DiscountBarController extends TopToolbarController { diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppHelper.java index 0bb469b235..1b73ba8d34 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppHelper.java @@ -3,6 +3,7 @@ package net.osmand.plus.inapp; import android.app.Activity; import android.content.Intent; import android.os.AsyncTask; +import android.support.annotation.NonNull; import android.util.Log; import net.osmand.AndroidNetworkUtils; @@ -41,10 +42,10 @@ public class InAppHelper { private static long lastValidationCheckTime; private static String mFullVersionPrice; - private static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price"; + public static final String SKU_FULL_VERSION_PRICE = "osmand_full_version_price"; private static final String SKU_LIVE_UPDATES_FULL = "osm_live_subscription_2"; private static final String SKU_LIVE_UPDATES_FREE = "osm_free_live_subscription_2"; - private static String SKU_LIVE_UPDATES; + public static String SKU_LIVE_UPDATES; private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily // (arbitrary) request code for the purchase flow @@ -60,6 +61,24 @@ public class InAppHelper { private OsmandApplication ctx; private List listeners = new ArrayList<>(); + /* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY + * (that you got from the Google Play developer console). This is not your + * developer public key, it's the *app-specific* public key. + * + * Instead of just storing the entire literal string here embedded in the + * program, construct the key at runtime from pieces or + * use bit manipulation (for example, XOR with some other string) to hide + * the actual key. The key itself is not secret information, but we don't + * want to make it easy for an attacker to replace the public key with one + * of their own and then fake messages from the server. + */ + private static final String BASE64_ENCODED_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgk8cEx" + + "UO4mfEwWFLkQnX1Tkzehr4SnXLXcm2Osxs5FTJPEgyTckTh0POKVMrxeGLn0KoTY2NTgp1U/inp" + + "wccWisPhVPEmw9bAVvWsOkzlyg1kv03fJdnAXRBSqDDPV6X8Z3MtkPVqZkupBsxyIllEILKHK06" + + "OCw49JLTsMR3oTRifGzma79I71X0spw0fM+cIRlkS2tsXN8GPbdkJwHofZKPOXS51pgC1zU8uWX" + + "I+ftJO46a1XkNh1dO2anUiQ8P/H4yOTqnMsXF7biyYuiwjXPOcy0OMhEHi54Dq6Mr3u5ZALOAkc" + + "YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB"; + public interface InAppListener { void onError(String error); @@ -72,6 +91,10 @@ public class InAppHelper { void dismissProgress(); } + public interface InAppRunnable { + void run(InAppHelper helper); + } + public String getToken() { return token; } @@ -100,9 +123,7 @@ public class InAppHelper { return !Algorithms.isEmpty(mLiveUpdatesPrice) && !Algorithms.isEmpty(mFullVersionPrice); } - public InAppHelper(OsmandApplication ctx, boolean forceRequestInventory) { - this.ctx = ctx; - this.forceRequestInventory = forceRequestInventory; + public static void initialize(OsmandApplication ctx) { if (SKU_LIVE_UPDATES == null) { if (Version.isFreeVersion(ctx)) { SKU_LIVE_UPDATES = SKU_LIVE_UPDATES_FREE; @@ -110,6 +131,12 @@ public class InAppHelper { SKU_LIVE_UPDATES = SKU_LIVE_UPDATES_FULL; } } + } + + public InAppHelper(OsmandApplication ctx, boolean forceRequestInventory) { + this.ctx = ctx; + this.forceRequestInventory = forceRequestInventory; + isDeveloperVersion = Version.isDeveloperVersion(ctx); if (isDeveloperVersion) { mSubscribedToLiveUpdates = true; @@ -119,29 +146,47 @@ public class InAppHelper { } } - public void start(final boolean stopAfterResult) { - this.stopAfterResult = stopAfterResult; - /* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY - * (that you got from the Google Play developer console). This is not your - * developer public key, it's the *app-specific* public key. - * - * Instead of just storing the entire literal string here embedded in the - * program, construct the key at runtime from pieces or - * use bit manipulation (for example, XOR with some other string) to hide - * the actual key. The key itself is not secret information, but we don't - * want to make it easy for an attacker to replace the public key with one - * of their own and then fake messages from the server. - */ - String base64EncodedPublicKey = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgk8cEx" + - "UO4mfEwWFLkQnX1Tkzehr4SnXLXcm2Osxs5FTJPEgyTckTh0POKVMrxeGLn0KoTY2NTgp1U/inp" + - "wccWisPhVPEmw9bAVvWsOkzlyg1kv03fJdnAXRBSqDDPV6X8Z3MtkPVqZkupBsxyIllEILKHK06" + - "OCw49JLTsMR3oTRifGzma79I71X0spw0fM+cIRlkS2tsXN8GPbdkJwHofZKPOXS51pgC1zU8uWX" + - "I+ftJO46a1XkNh1dO2anUiQ8P/H4yOTqnMsXF7biyYuiwjXPOcy0OMhEHi54Dq6Mr3u5ZALOAkc" + - "YTjh1H/ZgqIHy5ZluahINuDE76qdLYMXrDMQIDAQAB"; + public void exec(final @NonNull InAppRunnable runnable) { + this.stopAfterResult = true; // Create the helper, passing it our context and the public key to verify signatures with logDebug("Creating InAppHelper."); - mHelper = new IabHelper(ctx, base64EncodedPublicKey); + mHelper = new IabHelper(ctx, BASE64_ENCODED_PUBLIC_KEY); + + // enable debug logging (for a production application, you should set this to false). + mHelper.enableDebugLogging(false); + + // Start setup. This is asynchronous and the specified listener + // will be called once setup completes. + logDebug("Starting setup."); + mHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { + public void onIabSetupFinished(IabResult result) { + logDebug("Setup finished."); + + if (!result.isSuccess()) { + // Oh noes, there was a problem. + complain("Problem setting up in-app billing: " + result); + notifyError(result.getMessage()); + if (stopAfterResult) { + stop(); + } + return; + } + + // Have we been disposed of in the meantime? If so, quit. + if (mHelper == null) return; + + runnable.run(InAppHelper.this); + } + }); + } + + public void start(final boolean stopAfterResult) { + this.stopAfterResult = stopAfterResult; + + // Create the helper, passing it our context and the public key to verify signatures with + logDebug("Creating InAppHelper."); + mHelper = new IabHelper(ctx, BASE64_ENCODED_PUBLIC_KEY); // enable debug logging (for a production application, you should set this to false). mHelper.enableDebugLogging(false); @@ -601,15 +646,15 @@ public class InAppHelper { ctx.showToastMessage(message); } - void logDebug(String msg) { + private void logDebug(String msg) { if (mDebugLog) Log.d(TAG, msg); } - void logError(String msg) { + private void logError(String msg) { Log.e(TAG, msg); } - void logError(String msg, Throwable e) { + private void logError(String msg, Throwable e) { Log.e(TAG, "Error: " + msg, e); }