diff --git a/OsmAnd/.gitignore b/OsmAnd/.gitignore index e3071e5fbf..e1887fffff 100644 --- a/OsmAnd/.gitignore +++ b/OsmAnd/.gitignore @@ -17,6 +17,7 @@ libs/huawei-*.jar huaweidrmlib/ HwDRM_SDK_* drm_strings.xml +agconnect-services.json # copy_widget_icons.sh res/drawable-large/map_* diff --git a/OsmAnd/AndroidManifest-freehuawei.xml b/OsmAnd/AndroidManifest-freehuawei.xml index e96bb7fe47..c234628537 100644 --- a/OsmAnd/AndroidManifest-freehuawei.xml +++ b/OsmAnd/AndroidManifest-freehuawei.xml @@ -2,24 +2,24 @@ - - - - + + + + + - + tools:replace="android:authorities" + android:authorities="net.osmand.huawei.fileprovider"/> - \ No newline at end of file diff --git a/OsmAnd/AndroidManifest-huawei.xml b/OsmAnd/AndroidManifest-huawei.xml deleted file mode 100644 index bc847980cd..0000000000 --- a/OsmAnd/AndroidManifest-huawei.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 91b0b8022f..0aef4aa139 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -40,10 +40,17 @@ android { keyAlias "osmand" keyPassword System.getenv("OSMAND_APK_PASSWORD") } + + publishingHuawei { + storeFile file("/var/lib/jenkins/osmand_hw_key") + storePassword System.getenv("OSMAND_HW_APK_PASSWORD") + keyAlias "OsmAndHms" + keyPassword System.getenv("OSMAND_HW_APK_PASSWORD") + } } defaultConfig { - minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 15 + minSdkVersion System.getenv("MIN_SDK_VERSION") ? System.getenv("MIN_SDK_VERSION").toInteger() : 17 targetSdkVersion 28 versionCode 390 versionCode System.getenv("APK_NUMBER_VERSION") ? System.getenv("APK_NUMBER_VERSION").toInteger() : versionCode @@ -107,19 +114,23 @@ android { debug { manifest.srcFile "AndroidManifest-debug.xml" } + full { + java.srcDirs = ["src-google"] + } free { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-free.xml" } freedev { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-freedev.xml" } freecustom { + java.srcDirs = ["src-google"] manifest.srcFile "AndroidManifest-freecustom.xml" } - huawei { - manifest.srcFile "AndroidManifest-huawei.xml" - } freehuawei { + java.srcDirs = ["src-huawei"] manifest.srcFile "AndroidManifest-freehuawei.xml" } @@ -191,10 +202,6 @@ android { resConfig "en" //resConfigs "xxhdpi", "nodpi" } - huawei { - dimension "version" - applicationId "net.osmand.plus.huawei" - } freehuawei { dimension "version" applicationId "net.osmand.huawei" @@ -219,7 +226,10 @@ android { signingConfig signingConfigs.development } release { - signingConfig signingConfigs.publishing + productFlavors.all { flavor -> + flavor.signingConfig signingConfigs.publishing + } + productFlavors.freehuawei.signingConfig signingConfigs.publishingHuawei } } @@ -276,46 +286,14 @@ task downloadWorldMiniBasemap { } } -task downloadHuaweiDrmZip { +task setupHuaweiConfig { doLast { - ant.get(src: 'https://obs.cn-north-2.myhwclouds.com/hms-ds-wf/sdk/HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'HwDRM_SDK_2.5.2.300_ADT.zip', skipexisting: 'true') - ant.unzip(src: 'HwDRM_SDK_2.5.2.300_ADT.zip', dest: 'huaweidrmlib/') + if (System.getenv("HUAWEI_SDK_JSON")) { + new File("agconnect-services.json").text = System.getenv("HUAWEI_SDK_JSON") + } } } -task copyHuaweiDrmLibs(type: Copy) { - dependsOn downloadHuaweiDrmZip - from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/libs" - into "libs" -} - -task copyHuaweiDrmValues(type: Copy) { - dependsOn downloadHuaweiDrmZip - from "huaweidrmlib/HwDRM_SDK_2.5.2.300_ADT/res" - into "res" -} - -task downloadPrebuiltHuaweiDrm { - dependsOn copyHuaweiDrmLibs, copyHuaweiDrmValues -} - -task cleanHuaweiDrmLibs(type: Delete) { - delete "huaweidrmlib" - delete fileTree("libs").matching { - include "**/huawei-*.jar" - } -} - -task cleanHuaweiDrmValues(type: Delete) { - delete fileTree("res").matching { - include "**/drm_strings.xml" - } -} - -task cleanPrebuiltHuaweiDrm { - dependsOn cleanHuaweiDrmLibs, cleanHuaweiDrmValues -} - task collectVoiceAssets(type: Sync) { from "../../resources/voice" into "assets/voice" @@ -397,8 +375,6 @@ task copyLargePOIIcons(type: Sync) { } } - - task copyWidgetIconsXhdpi(type: Sync) { from "res/drawable-xxhdpi/" into "res/drawable-large-xhdpi/" @@ -445,13 +421,9 @@ task collectExternalResources { copyPoiCategories, downloadWorldMiniBasemap - Gradle gradle = getGradle() - String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() - // Use Drm SDK only for huawei build + String tskReqStr = gradle.startParameter.taskNames.toString() if (tskReqStr.contains("huawei")) { - dependsOn downloadPrebuiltHuaweiDrm - } else { - dependsOn cleanPrebuiltHuaweiDrm + dependsOn setupHuaweiConfig } } @@ -503,10 +475,16 @@ task cleanupDuplicatesInCore() { file("libs/x86_64/libc++_shared.so").renameTo(file("libc++/x86_64/libc++_shared.so")) } } + afterEvaluate { android.applicationVariants.all { variant -> variant.javaCompiler.dependsOn(collectExternalResources, buildOsmAndCore, cleanupDuplicatesInCore) } + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString().toLowerCase() + if (tskReqStr.contains("huawei")) { + apply plugin: 'com.huawei.agconnect' + } } task appStart(type: Exec) { @@ -516,7 +494,6 @@ task appStart(type: Exec) { // commandLine 'cmd', '/c', 'adb', 'shell', 'am', 'start', '-n', 'net.osmand.plus/net.osmand.plus.activities.MapActivity' } - dependencies { implementation project(path: ':OsmAnd-java', configuration: 'android') implementation project(':OsmAnd-api') @@ -565,6 +542,6 @@ dependencies { } implementation 'com.jaredrummler:colorpicker:1.1.0' - huaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar') - freehuaweiImplementation files('libs/huawei-android-drm_v2.5.2.300.jar') + //freehuaweiImplementation 'com.huawei.agconnect:agconnect-core:1.4.1.300' + freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' } diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java new file mode 100644 index 0000000000..c48294aeb2 --- /dev/null +++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -0,0 +1,546 @@ +package net.osmand.plus.inapp; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.billingclient.api.BillingClient; +import com.android.billingclient.api.BillingResult; +import com.android.billingclient.api.Purchase; +import com.android.billingclient.api.SkuDetails; +import com.android.billingclient.api.SkuDetailsResponseListener; + +import net.osmand.AndroidUtils; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.inapp.util.BillingManager; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import java.lang.ref.WeakReference; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { + + // The helper object + private BillingManager billingManager; + private List skuDetailsList; + + /* 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 InAppPurchaseHelperImpl(OsmandApplication ctx) { + super(ctx); + } + + private BillingManager getBillingManager() { + return billingManager; + } + + protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable) { + billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() { + + @Override + public void onBillingClientSetupFinished() { + logDebug("Setup finished."); + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (!billingManager.isServiceConnected()) { + // Oh noes, there was a problem. + //complain("Problem setting up in-app billing: " + result); + notifyError(taskType, billingManager.getBillingClientResponseMessage()); + stop(true); + return; + } + + processingTask = !runnable.run(InAppPurchaseHelperImpl.this); + } + + @Override + public void onConsumeFinished(String token, BillingResult billingResult) { + } + + @Override + public void onPurchasesUpdated(final List purchases) { + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { + List skuInApps = new ArrayList<>(); + for (InAppPurchases.InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + skuInApps.add(purchase.getSku()); + } + for (Purchase p : purchases) { + skuInApps.add(p.getSku()); + } + billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuSubscriptions = new ArrayList<>(); + for (InAppPurchases.InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { + skuSubscriptions.add(subscription.getSku()); + } + for (Purchase p : purchases) { + skuSubscriptions.add(p.getSku()); + } + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + billingManager.querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuDetailsList = new ArrayList<>(skuDetailsListInApps); + skuDetailsList.addAll(skuDetailsListSubscriptions); + InAppPurchaseHelperImpl.this.skuDetailsList = skuDetailsList; + + mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); + } + }); + } + }); + } + for (Purchase purchase : purchases) { + if (!purchase.isAcknowledged()) { + onPurchaseFinished(purchase); + } + } + } + + @Override + public void onPurchaseCanceled() { + stop(true); + } + }); + } + + public void purchaseFullVersion(final Activity activity) { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); + exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku()); + if (skuDetails == null) { + throw new IllegalArgumentException("Cannot find sku details"); + } + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.initiatePurchaseFlow(activity, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + complain("Cannot launch full version purchase!"); + logError("purchaseFullVersion Error", e); + stop(true); + } + return true; + } + }); + } + + public void purchaseDepthContours(final Activity activity) { + notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); + exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku()); + if (skuDetails == null) { + throw new IllegalArgumentException("Cannot find sku details"); + } + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.initiatePurchaseFlow(activity, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + complain("Cannot launch depth contours purchase!"); + logError("purchaseDepthContours Error", e); + stop(true); + } + return true; + } + }); + } + + @Nullable + private SkuDetails getSkuDetails(@NonNull String sku) { + List skuDetailsList = this.skuDetailsList; + if (skuDetailsList != null) { + for (SkuDetails details : skuDetailsList) { + if (details.getSku().equals(sku)) { + return details; + } + } + } + return null; + } + + private boolean hasDetails(@NonNull String sku) { + return getSkuDetails(sku) != null; + } + + @Nullable + private Purchase getPurchase(@NonNull String sku) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + List purchases = billingManager.getPurchases(); + if (purchases != null) { + for (Purchase p : purchases) { + if (p.getSku().equals(sku)) { + return p; + } + } + } + } + return null; + } + + // Listener that's called when we finish querying the items and subscriptions we own + private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { + + @NonNull + private List getAllOwnedSubscriptionSkus() { + List result = new ArrayList<>(); + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + for (Purchase p : billingManager.getPurchases()) { + if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { + result.add(p.getSku()); + } + } + } + return result; + } + + @Override + public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { + + logDebug("Query sku details finished."); + + // Have we been disposed of in the meantime? If so, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inventory: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + logDebug("Query sku details was successful."); + + /* + * Check for items we own. Notice that for each purchase, we check + * the developer payload to see if it's correct! See + * verifyDeveloperPayload(). + */ + + List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); + for (InAppPurchases.InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { + if (hasDetails(s.getSku())) { + Purchase purchase = getPurchase(s.getSku()); + SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); + if (liveUpdatesDetails != null) { + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + allOwnedSubscriptionSkus.remove(s.getSku()); + } + } + for (String sku : allOwnedSubscriptionSkus) { + Purchase purchase = getPurchase(sku); + SkuDetails liveUpdatesDetails = getSkuDetails(sku); + if (liveUpdatesDetails != null) { + InAppPurchases.InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); + if (s == null) { + s = new InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); + } + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + } + + InAppPurchases.InAppPurchase fullVersion = getFullVersion(); + if (hasDetails(fullVersion.getSku())) { + Purchase purchase = getPurchase(fullVersion.getSku()); + SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); + if (fullPriceDetails != null) { + fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); + } + } + + InAppPurchases.InAppPurchase depthContours = getDepthContours(); + if (hasDetails(depthContours.getSku())) { + Purchase purchase = getPurchase(depthContours.getSku()); + SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); + if (depthContoursDetails != null) { + fetchInAppPurchase(depthContours, depthContoursDetails, purchase); + } + } + + InAppPurchases.InAppPurchase contourLines = getContourLines(); + if (hasDetails(contourLines.getSku())) { + Purchase purchase = getPurchase(contourLines.getSku()); + SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); + if (contourLinesDetails != null) { + fetchInAppPurchase(contourLines, contourLinesDetails, purchase); + } + } + + Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); + boolean fullVersionPurchased = fullVersionPurchase != null; + if (fullVersionPurchased) { + ctx.getSettings().FULL_VERSION_PURCHASED.set(true); + } + + Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); + boolean depthContoursPurchased = depthContoursPurchase != null; + if (depthContoursPurchased) { + ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); + } + + // Do we have the live updates? + boolean subscribedToLiveUpdates = false; + List liveUpdatesPurchases = new ArrayList<>(); + for (InAppPurchases.InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { + Purchase purchase = getPurchase(p.getSku()); + if (purchase != null) { + liveUpdatesPurchases.add(purchase); + if (!subscribedToLiveUpdates) { + subscribedToLiveUpdates = true; + } + } + } + OsmandSettings.OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; + if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { + if (subscriptionCancelledTime.get() == 0) { + subscriptionCancelledTime.set(System.currentTimeMillis()); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); + } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); + if (!isDepthContoursPurchased(ctx)) { + ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); + } + } + } else if (subscribedToLiveUpdates) { + subscriptionCancelledTime.set(0L); + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); + } + + lastValidationCheckTime = System.currentTimeMillis(); + logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + + " live updates purchased."); + + OsmandSettings settings = ctx.getSettings(); + settings.INAPPS_READ.set(true); + + List tokensToSend = new ArrayList<>(); + if (liveUpdatesPurchases.size() > 0) { + List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); + for (Purchase purchase : liveUpdatesPurchases) { + if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) + && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { + String payload = purchase.getDeveloperPayload(); + if (!Algorithms.isEmpty(payload)) { + String[] arr = payload.split(" "); + if (arr.length > 0) { + settings.BILLING_USER_ID.set(arr[0]); + } + if (arr.length > 1) { + token = arr[1]; + settings.BILLING_USER_TOKEN.set(token); + } + } + } + if (!tokensSent.contains(purchase.getSku())) { + tokensToSend.add(purchase); + } + } + } + List purchaseInfoList = new ArrayList<>(); + for (Purchase purchase : tokensToSend) { + purchaseInfoList.add(getPurchaseInfo(purchase)); + } + onSkuDetailsResponseDone(purchaseInfoList); + } + }; + + private PurchaseInfo getPurchaseInfo(Purchase purchase) { + return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); + } + + private void fetchInAppPurchase(@NonNull InAppPurchases.InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { + if (purchase != null) { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.PURCHASED); + inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); + } else { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.NOT_PURCHASED); + } + inAppPurchase.setPrice(skuDetails.getPrice()); + inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); + if (skuDetails.getPriceAmountMicros() > 0) { + inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); + } + String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); + if (!Algorithms.isEmpty(subscriptionPeriod)) { + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + try { + ((InAppPurchases.InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); + } catch (ParseException e) { + LOG.error(e); + } + } + } + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + String introductoryPrice = skuDetails.getIntroductoryPrice(); + String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); + String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); + long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); + if (!Algorithms.isEmpty(introductoryPrice)) { + InAppPurchases.InAppSubscription s = (InAppPurchases.InAppSubscription) inAppPurchase; + try { + s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, + introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); + } catch (ParseException e) { + LOG.error(e); + } + } + } + } + + protected InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + Activity a = activity.get(); + SkuDetails skuDetails = getSkuDetails(sku); + if (AndroidUtils.isActivityNotDestroyed(a) && skuDetails != null) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.setPayload(payload); + billingManager.initiatePurchaseFlow(a, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } else { + stop(true); + } + } catch (Exception e) { + logError("launchPurchaseFlow Error", e); + stop(true); + } + return true; + } + }; + } + + protected InAppRunnable getRequestInventoryCommand() { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + logDebug("Setup successful. Querying inventory."); + try { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.queryPurchases(); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + logError("queryInventoryAsync Error", e); + notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); + stop(true); + } + return true; + } + }; + } + + // Call when a purchase is finished + private void onPurchaseFinished(Purchase purchase) { + logDebug("Purchase finished: " + purchase); + + // if we were disposed of in the meantime, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + onPurchaseDone(getPurchaseInfo(purchase)); + } + + @Override + protected boolean isBillingManagerExists() { + return getBillingManager() != null; + } + + @Override + protected void destroyBillingManager() { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.destroy(); + this.billingManager = null; + } + } +} diff --git a/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java new file mode 100644 index 0000000000..a7e8589f33 --- /dev/null +++ b/OsmAnd/src-huawei/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java @@ -0,0 +1,475 @@ +package net.osmand.plus.inapp; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.AndroidUtils; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.inapp.util.BillingManager; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.util.Algorithms; + +import java.lang.ref.WeakReference; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { + + // The helper object + private BillingManager billingManager; + private List skuDetailsList; + + + public InAppPurchaseHelperImpl(OsmandApplication ctx) { + super(ctx); + } + + protected void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable) { + billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingManager.BillingUpdatesListener() { + + @Override + public void onBillingClientSetupFinished() { + logDebug("Setup finished."); + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (!billingManager.isServiceConnected()) { + // Oh noes, there was a problem. + //complain("Problem setting up in-app billing: " + result); + notifyError(taskType, billingManager.getBillingClientResponseMessage()); + stop(true); + return; + } + + processingTask = !runnable.run(InAppPurchaseHelperImpl.this); + } + + @Override + public void onConsumeFinished(String token, BillingResult billingResult) { + } + + @Override + public void onPurchasesUpdated(final List purchases) { + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { + List skuInApps = new ArrayList<>(); + for (InAppPurchases.InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { + skuInApps.add(purchase.getSku()); + } + for (Purchase p : purchases) { + skuInApps.add(p.getSku()); + } + billingManager.querySkuDetailsAsync(BillingClient.SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuSubscriptions = new ArrayList<>(); + for (InAppPurchases.InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { + skuSubscriptions.add(subscription.getSku()); + } + for (Purchase p : purchases) { + skuSubscriptions.add(p.getSku()); + } + + BillingManager billingManager = getBillingManager(); + // Have we been disposed of in the meantime? If so, quit. + if (billingManager == null) { + stop(true); + return; + } + + billingManager.querySkuDetailsAsync(BillingClient.SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { + @Override + public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + List skuDetailsList = new ArrayList<>(skuDetailsListInApps); + skuDetailsList.addAll(skuDetailsListSubscriptions); + InAppPurchaseHelperImpl.this.skuDetailsList = skuDetailsList; + + mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); + } + }); + } + }); + } + for (Purchase purchase : purchases) { + if (!purchase.isAcknowledged()) { + onPurchaseFinished(purchase); + } + } + } + + @Override + public void onPurchaseCanceled() { + stop(true); + } + }); + } + + @Override + public void purchaseFullVersion(Activity activity) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Override + public void purchaseDepthContours(Activity activity) throws UnsupportedOperationException { + throw new UnsupportedOperationException(); + } + + @Nullable + private SkuDetails getSkuDetails(@NonNull String sku) { + List skuDetailsList = this.skuDetailsList; + if (skuDetailsList != null) { + for (SkuDetails details : skuDetailsList) { + if (details.getSku().equals(sku)) { + return details; + } + } + } + return null; + } + + private boolean hasDetails(@NonNull String sku) { + return getSkuDetails(sku) != null; + } + + @Nullable + private Purchase getPurchase(@NonNull String sku) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + List purchases = billingManager.getPurchases(); + if (purchases != null) { + for (Purchase p : purchases) { + if (p.getSku().equals(sku)) { + return p; + } + } + } + } + return null; + } + + // Listener that's called when we finish querying the items and subscriptions we own + private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { + + @NonNull + private List getAllOwnedSubscriptionSkus() { + List result = new ArrayList<>(); + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + for (Purchase p : billingManager.getPurchases()) { + if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { + result.add(p.getSku()); + } + } + } + return result; + } + + @Override + public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { + + logDebug("Query sku details finished."); + + // Have we been disposed of in the meantime? If so, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + // Is it a failure? + if (billingResult.getResponseCode() != BillingClient.BillingResponseCode.OK) { + logError("Failed to query inventory: " + billingResult.getResponseCode()); + notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); + stop(true); + return; + } + + logDebug("Query sku details was successful."); + + /* + * Check for items we own. Notice that for each purchase, we check + * the developer payload to see if it's correct! See + * verifyDeveloperPayload(). + */ + + List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); + for (InAppPurchases.InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { + if (hasDetails(s.getSku())) { + Purchase purchase = getPurchase(s.getSku()); + SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); + if (liveUpdatesDetails != null) { + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + allOwnedSubscriptionSkus.remove(s.getSku()); + } + } + for (String sku : allOwnedSubscriptionSkus) { + Purchase purchase = getPurchase(sku); + SkuDetails liveUpdatesDetails = getSkuDetails(sku); + if (liveUpdatesDetails != null) { + InAppPurchases.InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); + if (s == null) { + s = new InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); + } + fetchInAppPurchase(s, liveUpdatesDetails, purchase); + } + } + + InAppPurchases.InAppPurchase fullVersion = getFullVersion(); + if (hasDetails(fullVersion.getSku())) { + Purchase purchase = getPurchase(fullVersion.getSku()); + SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); + if (fullPriceDetails != null) { + fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); + } + } + + InAppPurchases.InAppPurchase depthContours = getDepthContours(); + if (hasDetails(depthContours.getSku())) { + Purchase purchase = getPurchase(depthContours.getSku()); + SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); + if (depthContoursDetails != null) { + fetchInAppPurchase(depthContours, depthContoursDetails, purchase); + } + } + + InAppPurchases.InAppPurchase contourLines = getContourLines(); + if (hasDetails(contourLines.getSku())) { + Purchase purchase = getPurchase(contourLines.getSku()); + SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); + if (contourLinesDetails != null) { + fetchInAppPurchase(contourLines, contourLinesDetails, purchase); + } + } + + Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); + boolean fullVersionPurchased = fullVersionPurchase != null; + if (fullVersionPurchased) { + ctx.getSettings().FULL_VERSION_PURCHASED.set(true); + } + + Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); + boolean depthContoursPurchased = depthContoursPurchase != null; + if (depthContoursPurchased) { + ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); + } + + // Do we have the live updates? + boolean subscribedToLiveUpdates = false; + List liveUpdatesPurchases = new ArrayList<>(); + for (InAppPurchases.InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { + Purchase purchase = getPurchase(p.getSku()); + if (purchase != null) { + liveUpdatesPurchases.add(purchase); + if (!subscribedToLiveUpdates) { + subscribedToLiveUpdates = true; + } + } + } + OsmandSettings.OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; + if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { + if (subscriptionCancelledTime.get() == 0) { + subscriptionCancelledTime.set(System.currentTimeMillis()); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); + ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); + } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); + if (!isDepthContoursPurchased(ctx)) { + ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); + } + } + } else if (subscribedToLiveUpdates) { + subscriptionCancelledTime.set(0L); + ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); + } + + lastValidationCheckTime = System.currentTimeMillis(); + logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + + " live updates purchased."); + + OsmandSettings settings = ctx.getSettings(); + settings.INAPPS_READ.set(true); + + List tokensToSend = new ArrayList<>(); + if (liveUpdatesPurchases.size() > 0) { + List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); + for (Purchase purchase : liveUpdatesPurchases) { + if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) + && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { + String payload = purchase.getDeveloperPayload(); + if (!Algorithms.isEmpty(payload)) { + String[] arr = payload.split(" "); + if (arr.length > 0) { + settings.BILLING_USER_ID.set(arr[0]); + } + if (arr.length > 1) { + token = arr[1]; + settings.BILLING_USER_TOKEN.set(token); + } + } + } + if (!tokensSent.contains(purchase.getSku())) { + tokensToSend.add(purchase); + } + } + } + List purchaseInfoList = new ArrayList<>(); + for (Purchase purchase : tokensToSend) { + purchaseInfoList.add(getPurchaseInfo(purchase)); + } + onSkuDetailsResponseDone(purchaseInfoList); + } + }; + + private PurchaseInfo getPurchaseInfo(Purchase purchase) { + return new PurchaseInfo(purchase.getSku(), purchase.getOrderId(), purchase.getPurchaseToken()); + } + + private void fetchInAppPurchase(@NonNull InAppPurchases.InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { + if (purchase != null) { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.PURCHASED); + inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); + } else { + inAppPurchase.setPurchaseState(InAppPurchases.InAppPurchase.PurchaseState.NOT_PURCHASED); + } + inAppPurchase.setPrice(skuDetails.getPrice()); + inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); + if (skuDetails.getPriceAmountMicros() > 0) { + inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); + } + String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); + if (!Algorithms.isEmpty(subscriptionPeriod)) { + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + try { + ((InAppPurchases.InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); + } catch (ParseException e) { + LOG.error(e); + } + } + } + if (inAppPurchase instanceof InAppPurchases.InAppSubscription) { + String introductoryPrice = skuDetails.getIntroductoryPrice(); + String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); + String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); + long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); + if (!Algorithms.isEmpty(introductoryPrice)) { + InAppPurchases.InAppSubscription s = (InAppPurchases.InAppSubscription) inAppPurchase; + try { + s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, + introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); + } catch (ParseException e) { + LOG.error(e); + } + } + } + } + + protected InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, final String sku, final String payload) { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + try { + Activity a = activity.get(); + SkuDetails skuDetails = getSkuDetails(sku); + if (AndroidUtils.isActivityNotDestroyed(a) && skuDetails != null) { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.setPayload(payload); + billingManager.initiatePurchaseFlow(a, skuDetails); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } else { + stop(true); + } + } catch (Exception e) { + logError("launchPurchaseFlow Error", e); + stop(true); + } + return true; + } + }; + } + + protected InAppRunnable getRequestInventoryCommand() { + return new InAppRunnable() { + @Override + public boolean run(InAppPurchaseHelper helper) { + logDebug("Setup successful. Querying inventory."); + try { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.queryPurchases(); + } else { + throw new IllegalStateException("BillingManager disposed"); + } + return false; + } catch (Exception e) { + logError("queryInventoryAsync Error", e); + notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); + stop(true); + } + return true; + } + }; + } + + // Call when a purchase is finished + private void onPurchaseFinished(Purchase purchase) { + logDebug("Purchase finished: " + purchase); + + // if we were disposed of in the meantime, quit. + if (getBillingManager() == null) { + stop(true); + return; + } + + onPurchaseDone(getPurchaseInfo(purchase)); + } + + @Override + protected boolean isBillingManagerExists() { + return getBillingManager() != null; + } + + @Override + protected void destroyBillingManager() { + BillingManager billingManager = getBillingManager(); + if (billingManager != null) { + billingManager.destroy(); + this.billingManager = null; + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/AppInitializer.java b/OsmAnd/src/net/osmand/plus/AppInitializer.java index 2837bff6f8..0ae24b7c5f 100644 --- a/OsmAnd/src/net/osmand/plus/AppInitializer.java +++ b/OsmAnd/src/net/osmand/plus/AppInitializer.java @@ -38,7 +38,7 @@ import net.osmand.plus.download.ui.AbstractLoadLocalIndexTask; import net.osmand.plus.helpers.AvoidSpecificRoads; import net.osmand.plus.helpers.LockHelper; import net.osmand.plus.helpers.WaypointHelper; -import net.osmand.plus.inapp.InAppPurchaseHelper; +import net.osmand.plus.inapp.InAppPurchaseHelperImpl; import net.osmand.plus.liveupdates.LiveUpdatesHelper; import net.osmand.plus.mapmarkers.MapMarkersDbHelper; import net.osmand.plus.monitoring.LiveMonitoringHelper; @@ -428,7 +428,7 @@ public class AppInitializer implements IProgress { } getLazyRoutingConfig(); app.applyTheme(app); - app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelper(app), InAppPurchaseHelper.class); + app.inAppPurchaseHelper = startupInit(new InAppPurchaseHelperImpl(app), InAppPurchaseHelperImpl.class); app.poiTypes = startupInit(MapPoiTypes.getDefaultNoInit(), MapPoiTypes.class); app.transportRoutingHelper = startupInit(new TransportRoutingHelper(app), TransportRoutingHelper.class); app.routingHelper = startupInit(new RoutingHelper(app), RoutingHelper.class); diff --git a/OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java b/OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java deleted file mode 100644 index 7cc2f2798e..0000000000 --- a/OsmAnd/src/net/osmand/plus/HuaweiDrmHelper.java +++ /dev/null @@ -1,66 +0,0 @@ -package net.osmand.plus; - -import android.app.Activity; -import android.util.Log; - -import java.lang.ref.WeakReference; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class HuaweiDrmHelper { - private static final String TAG = HuaweiDrmHelper.class.getSimpleName(); - - //Copyright protection id - private static final String DRM_ID = "101117397"; - //Copyright protection public key - private static final String DRM_PUBLIC_KEY = "9d6f861e7d46be167809a6a62302749a6753b3c1bd02c9729efb3973e268091d"; - - public static void check(Activity activity) { - boolean succeed = false; - try { - final WeakReference activityRef = new WeakReference<>(activity); - Class drmCheckCallbackClass = Class.forName("com.huawei.android.sdk.drm.DrmCheckCallback"); - Object callback = java.lang.reflect.Proxy.newProxyInstance( - drmCheckCallbackClass.getClassLoader(), - new java.lang.Class[]{drmCheckCallbackClass}, - new java.lang.reflect.InvocationHandler() { - - @Override - public Object invoke(Object proxy, java.lang.reflect.Method method, Object[] args) { - Activity a = activityRef.get(); - if (a != null && !a.isFinishing()) { - String method_name = method.getName(); - if (method_name.equals("onCheckSuccess")) { - // skip now - } else if (method_name.equals("onCheckFailed")) { - closeApplication(a); - } - } - return null; - } - }); - - Class drmClass = Class.forName("com.huawei.android.sdk.drm.Drm"); - Class[] partypes = new Class[]{Activity.class, String.class, String.class, String.class, drmCheckCallbackClass}; - Method check = drmClass.getMethod("check", partypes); - check.invoke(null, activity, activity.getPackageName(), DRM_ID, DRM_PUBLIC_KEY, callback); - succeed = true; - - } catch (ClassNotFoundException e) { - Log.e(TAG, "check: ", e); - } catch (NoSuchMethodException e) { - Log.e(TAG, "check: ", e); - } catch (IllegalAccessException e) { - Log.e(TAG, "check: ", e); - } catch (InvocationTargetException e) { - Log.e(TAG, "check: ", e); - } - if (!succeed) { - closeApplication(activity); - } - } - - private static void closeApplication(Activity activity) { - ((OsmandApplication) activity.getApplication()).closeApplicationAnywayImpl(activity, true); - } -} diff --git a/OsmAnd/src/net/osmand/plus/Version.java b/OsmAnd/src/net/osmand/plus/Version.java index 14ed68b100..86d26ec954 100644 --- a/OsmAnd/src/net/osmand/plus/Version.java +++ b/OsmAnd/src/net/osmand/plus/Version.java @@ -121,8 +121,8 @@ public class Version { public static boolean isFreeVersion(OsmandApplication ctx){ return ctx.getPackageName().equals(FREE_VERSION_NAME) || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME) || - ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME) - ; + ctx.getPackageName().equals(FREE_CUSTOM_VERSION_NAME) || + isHuawei(ctx); } public static boolean isPaidVersion(OsmandApplication ctx) { diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index e23e596dcf..db9f055406 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -67,7 +67,6 @@ import net.osmand.plus.AppInitializer; import net.osmand.plus.AppInitializer.AppInitializeListener; import net.osmand.plus.AppInitializer.InitEvents; import net.osmand.plus.GpxSelectionHelper.GpxDisplayItem; -import net.osmand.plus.HuaweiDrmHelper; import net.osmand.plus.MapMarkersHelper.MapMarker; import net.osmand.plus.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.OnDismissDialogFragmentListener; @@ -276,9 +275,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven super.onCreate(savedInstanceState); - if (Version.isHuawei(getMyApplication())) { - HuaweiDrmHelper.check(this); - } // Full screen is not used here // getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.main); diff --git a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java index 61ae82399f..855f648332 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/DiscountHelper.java @@ -81,7 +81,7 @@ public class DiscountHelper { public static void checkAndDisplay(final MapActivity mapActivity) { OsmandApplication app = mapActivity.getMyApplication(); OsmandSettings settings = app.getSettings(); - if (settings.DO_NOT_SHOW_STARTUP_MESSAGES.get() || !settings.INAPPS_READ.get() || Version.isHuawei(app)) { + if (settings.DO_NOT_SHOW_STARTUP_MESSAGES.get() || !settings.INAPPS_READ.get()) { return; } if (mBannerVisible) { diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java index ac7e2c77fb..c037c13d0a 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java @@ -9,33 +9,21 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.billingclient.api.BillingClient.BillingResponseCode; -import com.android.billingclient.api.BillingClient.SkuType; -import com.android.billingclient.api.BillingResult; -import com.android.billingclient.api.Purchase; -import com.android.billingclient.api.SkuDetails; -import com.android.billingclient.api.SkuDetailsResponseListener; - import net.osmand.AndroidNetworkUtils; import net.osmand.AndroidNetworkUtils.OnRequestResultListener; import net.osmand.AndroidNetworkUtils.OnRequestsResultListener; import net.osmand.AndroidNetworkUtils.RequestResponse; import net.osmand.PlatformUtil; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.settings.backend.OsmandSettings; -import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference; import net.osmand.plus.R; import net.osmand.plus.Version; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState; -import net.osmand.plus.inapp.InAppPurchases.InAppPurchaseLiveUpdatesOldSubscription; import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; -import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionIntroductoryInfo; import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList; -import net.osmand.plus.inapp.util.BillingManager; -import net.osmand.plus.inapp.util.BillingManager.BillingUpdatesListener; import net.osmand.plus.liveupdates.CountrySelectionFragment; import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem; +import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.Algorithms; import org.json.JSONArray; @@ -43,7 +31,6 @@ import org.json.JSONException; import org.json.JSONObject; import java.lang.ref.WeakReference; -import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -53,52 +40,28 @@ import java.util.List; import java.util.Map; import java.util.Set; -public class InAppPurchaseHelper { +public abstract class InAppPurchaseHelper { // Debug tag, for logging - private static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class); + protected static final org.apache.commons.logging.Log LOG = PlatformUtil.getLog(InAppPurchaseHelper.class); private static final String TAG = InAppPurchaseHelper.class.getSimpleName(); private boolean mDebugLog = false; public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days - private InAppPurchases purchases; - private long lastValidationCheckTime; - private boolean inventoryRequested; + protected InAppPurchases purchases; + protected long lastValidationCheckTime; + protected boolean inventoryRequested; private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily - // (arbitrary) request code for the purchase flow - private static final int RC_REQUEST = 10001; - // The helper object - private BillingManager billingManager; - private List skuDetailsList; + protected boolean isDeveloperVersion; + protected String token = ""; + protected InAppPurchaseTaskType activeTask; + protected boolean processingTask = false; + protected boolean inventoryRequestPending = false; - private boolean isDeveloperVersion; - private String token = ""; - private InAppPurchaseTaskType activeTask; - private boolean processingTask = false; - private boolean inventoryRequestPending = false; - - private OsmandApplication ctx; - private InAppPurchaseListener uiActivity = null; - - /* 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"; + protected OsmandApplication ctx; + protected InAppPurchaseListener uiActivity = null; public interface InAppPurchaseListener { void onError(InAppPurchaseTaskType taskType, String error); @@ -124,6 +87,30 @@ public class InAppPurchaseHelper { boolean run(InAppPurchaseHelper helper); } + public static class PurchaseInfo { + private String sku; + private String orderId; + private String purchaseToken; + + public PurchaseInfo(String sku, String orderId, String purchaseToken) { + this.sku = sku; + this.orderId = orderId; + this.purchaseToken = purchaseToken; + } + + public String getSku() { + return sku; + } + + public String getOrderId() { + return orderId; + } + + public String getPurchaseToken() { + return purchaseToken; + } + } + public String getToken() { return token; } @@ -194,11 +181,7 @@ public class InAppPurchaseHelper { return false; } - private BillingManager getBillingManager() { - return billingManager; - } - - private void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) { + protected void exec(final @NonNull InAppPurchaseTaskType taskType, final @NonNull InAppRunnable runnable) { if (isDeveloperVersion || !Version.isGooglePlayEnabled(ctx)) { notifyDismissProgress(taskType); stop(true); @@ -222,117 +205,15 @@ public class InAppPurchaseHelper { try { processingTask = true; activeTask = taskType; - billingManager = new BillingManager(ctx, BASE64_ENCODED_PUBLIC_KEY, new BillingUpdatesListener() { - - @Override - public void onBillingClientSetupFinished() { - logDebug("Setup finished."); - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } - - if (!billingManager.isServiceConnected()) { - // Oh noes, there was a problem. - //complain("Problem setting up in-app billing: " + result); - notifyError(taskType, billingManager.getBillingClientResponseMessage()); - stop(true); - return; - } - - processingTask = !runnable.run(InAppPurchaseHelper.this); - } - - @Override - public void onConsumeFinished(String token, BillingResult billingResult) { - } - - @Override - public void onPurchasesUpdated(final List purchases) { - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } - - if (activeTask == InAppPurchaseTaskType.REQUEST_INVENTORY) { - List skuInApps = new ArrayList<>(); - for (InAppPurchase purchase : getInAppPurchases().getAllInAppPurchases(false)) { - skuInApps.add(purchase.getSku()); - } - for (Purchase p : purchases) { - skuInApps.add(p.getSku()); - } - billingManager.querySkuDetailsAsync(SkuType.INAPP, skuInApps, new SkuDetailsResponseListener() { - @Override - public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListInApps) { - // Is it a failure? - if (billingResult.getResponseCode() != BillingResponseCode.OK) { - logError("Failed to query inapps sku details: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - List skuSubscriptions = new ArrayList<>(); - for (InAppSubscription subscription : getInAppPurchases().getAllInAppSubscriptions()) { - skuSubscriptions.add(subscription.getSku()); - } - for (Purchase p : purchases) { - skuSubscriptions.add(p.getSku()); - } - - BillingManager billingManager = getBillingManager(); - // Have we been disposed of in the meantime? If so, quit. - if (billingManager == null) { - stop(true); - return; - } - - billingManager.querySkuDetailsAsync(SkuType.SUBS, skuSubscriptions, new SkuDetailsResponseListener() { - @Override - public void onSkuDetailsResponse(BillingResult billingResult, final List skuDetailsListSubscriptions) { - // Is it a failure? - if (billingResult.getResponseCode() != BillingResponseCode.OK) { - logError("Failed to query subscriptipons sku details: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - List skuDetailsList = new ArrayList<>(skuDetailsListInApps); - skuDetailsList.addAll(skuDetailsListSubscriptions); - InAppPurchaseHelper.this.skuDetailsList = skuDetailsList; - - mSkuDetailsResponseListener.onSkuDetailsResponse(billingResult, skuDetailsList); - } - }); - } - }); - } - for (Purchase purchase : purchases) { - if (!purchase.isAcknowledged()) { - onPurchaseFinished(purchase); - } - } - } - - @Override - public void onPurchaseCanceled() { - stop(true); - } - }); + execImpl(taskType, runnable); } catch (Exception e) { logError("exec Error", e); stop(true); } } + protected abstract void execImpl(@NonNull final InAppPurchaseTaskType taskType, @NonNull final InAppRunnable runnable); + public boolean needRequestInventory() { return !inventoryRequested && ((isSubscribedToLiveUpdates(ctx) && Algorithms.isEmpty(ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get())) || System.currentTimeMillis() - lastValidationCheckTime > PURCHASE_VALIDATION_PERIOD_MSEC); @@ -343,32 +224,7 @@ public class InAppPurchaseHelper { new RequestInventoryTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public void purchaseFullVersion(final Activity activity) { - notifyShowProgress(InAppPurchaseTaskType.PURCHASE_FULL_VERSION); - exec(InAppPurchaseTaskType.PURCHASE_FULL_VERSION, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - try { - SkuDetails skuDetails = getSkuDetails(getFullVersion().getSku()); - if (skuDetails == null) { - throw new IllegalArgumentException("Cannot find sku details"); - } - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.initiatePurchaseFlow(activity, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } catch (Exception e) { - complain("Cannot launch full version purchase!"); - logError("purchaseFullVersion Error", e); - stop(true); - } - return true; - } - }); - } + public abstract void purchaseFullVersion(final Activity activity) throws UnsupportedOperationException; public void purchaseLiveUpdates(Activity activity, String sku, String email, String userName, String countryDownloadName, boolean hideUserName) { @@ -377,288 +233,7 @@ public class InAppPurchaseHelper { .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void) null); } - public void purchaseDepthContours(final Activity activity) { - notifyShowProgress(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS); - exec(InAppPurchaseTaskType.PURCHASE_DEPTH_CONTOURS, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - try { - SkuDetails skuDetails = getSkuDetails(getDepthContours().getSku()); - if (skuDetails == null) { - throw new IllegalArgumentException("Cannot find sku details"); - } - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.initiatePurchaseFlow(activity, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } catch (Exception e) { - complain("Cannot launch depth contours purchase!"); - logError("purchaseDepthContours Error", e); - stop(true); - } - return true; - } - }); - } - - @Nullable - private SkuDetails getSkuDetails(@NonNull String sku) { - List skuDetailsList = this.skuDetailsList; - if (skuDetailsList != null) { - for (SkuDetails details : skuDetailsList) { - if (details.getSku().equals(sku)) { - return details; - } - } - } - return null; - } - - private boolean hasDetails(@NonNull String sku) { - return getSkuDetails(sku) != null; - } - - @Nullable - private Purchase getPurchase(@NonNull String sku) { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - List purchases = billingManager.getPurchases(); - if (purchases != null) { - for (Purchase p : purchases) { - if (p.getSku().equals(sku)) { - return p; - } - } - } - } - return null; - } - - // Listener that's called when we finish querying the items and subscriptions we own - private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() { - - @NonNull - private List getAllOwnedSubscriptionSkus() { - List result = new ArrayList<>(); - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - for (Purchase p : billingManager.getPurchases()) { - if (getInAppPurchases().getInAppSubscriptionBySku(p.getSku()) != null) { - result.add(p.getSku()); - } - } - } - return result; - } - - @Override - public void onSkuDetailsResponse(BillingResult billingResult, List skuDetailsList) { - - logDebug("Query sku details finished."); - - // Have we been disposed of in the meantime? If so, quit. - if (getBillingManager() == null) { - stop(true); - return; - } - - // Is it a failure? - if (billingResult.getResponseCode() != BillingResponseCode.OK) { - logError("Failed to query inventory: " + billingResult.getResponseCode()); - notifyError(InAppPurchaseTaskType.REQUEST_INVENTORY, billingResult.getDebugMessage()); - stop(true); - return; - } - - logDebug("Query sku details was successful."); - - /* - * Check for items we own. Notice that for each purchase, we check - * the developer payload to see if it's correct! See - * verifyDeveloperPayload(). - */ - - List allOwnedSubscriptionSkus = getAllOwnedSubscriptionSkus(); - for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) { - if (hasDetails(s.getSku())) { - Purchase purchase = getPurchase(s.getSku()); - SkuDetails liveUpdatesDetails = getSkuDetails(s.getSku()); - if (liveUpdatesDetails != null) { - fetchInAppPurchase(s, liveUpdatesDetails, purchase); - } - allOwnedSubscriptionSkus.remove(s.getSku()); - } - } - for (String sku : allOwnedSubscriptionSkus) { - Purchase purchase = getPurchase(sku); - SkuDetails liveUpdatesDetails = getSkuDetails(sku); - if (liveUpdatesDetails != null) { - InAppSubscription s = getLiveUpdates().upgradeSubscription(sku); - if (s == null) { - s = new InAppPurchaseLiveUpdatesOldSubscription(liveUpdatesDetails); - } - fetchInAppPurchase(s, liveUpdatesDetails, purchase); - } - } - - InAppPurchase fullVersion = getFullVersion(); - if (hasDetails(fullVersion.getSku())) { - Purchase purchase = getPurchase(fullVersion.getSku()); - SkuDetails fullPriceDetails = getSkuDetails(fullVersion.getSku()); - if (fullPriceDetails != null) { - fetchInAppPurchase(fullVersion, fullPriceDetails, purchase); - } - } - - InAppPurchase depthContours = getDepthContours(); - if (hasDetails(depthContours.getSku())) { - Purchase purchase = getPurchase(depthContours.getSku()); - SkuDetails depthContoursDetails = getSkuDetails(depthContours.getSku()); - if (depthContoursDetails != null) { - fetchInAppPurchase(depthContours, depthContoursDetails, purchase); - } - } - - InAppPurchase contourLines = getContourLines(); - if (hasDetails(contourLines.getSku())) { - Purchase purchase = getPurchase(contourLines.getSku()); - SkuDetails contourLinesDetails = getSkuDetails(contourLines.getSku()); - if (contourLinesDetails != null) { - fetchInAppPurchase(contourLines, contourLinesDetails, purchase); - } - } - - Purchase fullVersionPurchase = getPurchase(fullVersion.getSku()); - boolean fullVersionPurchased = fullVersionPurchase != null; - if (fullVersionPurchased) { - ctx.getSettings().FULL_VERSION_PURCHASED.set(true); - } - - Purchase depthContoursPurchase = getPurchase(depthContours.getSku()); - boolean depthContoursPurchased = depthContoursPurchase != null; - if (depthContoursPurchased) { - ctx.getSettings().DEPTH_CONTOURS_PURCHASED.set(true); - } - - // Do we have the live updates? - boolean subscribedToLiveUpdates = false; - List liveUpdatesPurchases = new ArrayList<>(); - for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { - Purchase purchase = getPurchase(p.getSku()); - if (purchase != null) { - liveUpdatesPurchases.add(purchase); - if (!subscribedToLiveUpdates) { - subscribedToLiveUpdates = true; - } - } - } - OsmandPreference subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME; - if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { - if (subscriptionCancelledTime.get() == 0) { - subscriptionCancelledTime.set(System.currentTimeMillis()); - ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); - ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false); - } else if (System.currentTimeMillis() - subscriptionCancelledTime.get() > SUBSCRIPTION_HOLDING_TIME_MSEC) { - ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false); - if (!isDepthContoursPurchased(ctx)) { - ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(false); - } - } - } else if (subscribedToLiveUpdates) { - subscriptionCancelledTime.set(0L); - ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); - } - - lastValidationCheckTime = System.currentTimeMillis(); - logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") - + " live updates purchased."); - - OsmandSettings settings = ctx.getSettings(); - settings.INAPPS_READ.set(true); - - List tokensToSend = new ArrayList<>(); - if (liveUpdatesPurchases.size() > 0) { - List tokensSent = Arrays.asList(settings.BILLING_PURCHASE_TOKENS_SENT.get().split(";")); - for (Purchase purchase : liveUpdatesPurchases) { - if ((Algorithms.isEmpty(settings.BILLING_USER_ID.get()) || Algorithms.isEmpty(settings.BILLING_USER_TOKEN.get())) - && !Algorithms.isEmpty(purchase.getDeveloperPayload())) { - String payload = purchase.getDeveloperPayload(); - if (!Algorithms.isEmpty(payload)) { - String[] arr = payload.split(" "); - if (arr.length > 0) { - settings.BILLING_USER_ID.set(arr[0]); - } - if (arr.length > 1) { - token = arr[1]; - settings.BILLING_USER_TOKEN.set(token); - } - } - } - if (!tokensSent.contains(purchase.getSku())) { - tokensToSend.add(purchase); - } - } - } - - final OnRequestResultListener listener = new OnRequestResultListener() { - @Override - public void onResult(String result) { - notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); - notifyGetItems(); - stop(true); - logDebug("Initial inapp query finished"); - } - }; - - if (tokensToSend.size() > 0) { - sendTokens(tokensToSend, listener); - } else { - listener.onResult("OK"); - } - } - }; - - private void fetchInAppPurchase(@NonNull InAppPurchase inAppPurchase, @NonNull SkuDetails skuDetails, @Nullable Purchase purchase) { - if (purchase != null) { - inAppPurchase.setPurchaseState(PurchaseState.PURCHASED); - inAppPurchase.setPurchaseTime(purchase.getPurchaseTime()); - } else { - inAppPurchase.setPurchaseState(PurchaseState.NOT_PURCHASED); - } - inAppPurchase.setPrice(skuDetails.getPrice()); - inAppPurchase.setPriceCurrencyCode(skuDetails.getPriceCurrencyCode()); - if (skuDetails.getPriceAmountMicros() > 0) { - inAppPurchase.setPriceValue(skuDetails.getPriceAmountMicros() / 1000000d); - } - String subscriptionPeriod = skuDetails.getSubscriptionPeriod(); - if (!Algorithms.isEmpty(subscriptionPeriod)) { - if (inAppPurchase instanceof InAppSubscription) { - try { - ((InAppSubscription) inAppPurchase).setSubscriptionPeriodString(subscriptionPeriod); - } catch (ParseException e) { - LOG.error(e); - } - } - } - if (inAppPurchase instanceof InAppSubscription) { - String introductoryPrice = skuDetails.getIntroductoryPrice(); - String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); - String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); - long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); - if (!Algorithms.isEmpty(introductoryPrice)) { - InAppSubscription s = (InAppSubscription) inAppPurchase; - try { - s.setIntroductoryInfo(new InAppSubscriptionIntroductoryInfo(s, introductoryPrice, - introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); - } catch (ParseException e) { - LOG.error(e); - } - } - } - } + public abstract void purchaseDepthContours(final Activity activity) throws UnsupportedOperationException; @SuppressLint("StaticFieldLeak") private class LiveUpdatesPurchaseTask extends AsyncTask { @@ -746,31 +321,7 @@ public class InAppPurchaseHelper { if (!Algorithms.isEmpty(userId) && !Algorithms.isEmpty(token)) { logDebug("Launching purchase flow for live updates subscription for userId=" + userId); final String payload = userId + " " + token; - exec(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - try { - Activity a = activity.get(); - SkuDetails skuDetails = getSkuDetails(sku); - if (a != null && skuDetails != null) { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.setPayload(payload); - billingManager.initiatePurchaseFlow(a, skuDetails); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } else { - stop(true); - } - } catch (Exception e) { - logError("launchPurchaseFlow Error", e); - stop(true); - } - return true; - } - }); + exec(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, getPurchaseLiveUpdatesCommand(activity, sku, payload)); } else { notifyError(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES, "Empty userId"); stop(true); @@ -778,6 +329,9 @@ public class InAppPurchaseHelper { } } + protected abstract InAppRunnable getPurchaseLiveUpdatesCommand(final WeakReference activity, + final String sku, final String payload) throws UnsupportedOperationException; + @SuppressLint("StaticFieldLeak") private class RequestInventoryTask extends AsyncTask { @@ -808,38 +362,41 @@ public class InAppPurchaseHelper { try { JSONObject obj = new JSONObject(response); JSONArray names = obj.names(); - for (int i = 0; i < names.length(); i++) { - String skuType = names.getString(i); - JSONObject subObj = obj.getJSONObject(skuType); - String sku = subObj.getString("sku"); - if (!Algorithms.isEmpty(sku)) { - getLiveUpdates().upgradeSubscription(sku); + if (names != null) { + for (int i = 0; i < names.length(); i++) { + String skuType = names.getString(i); + JSONObject subObj = obj.getJSONObject(skuType); + String sku = subObj.getString("sku"); + if (!Algorithms.isEmpty(sku)) { + getLiveUpdates().upgradeSubscription(sku); + } } } } catch (JSONException e) { logError("Json parsing error", e); } } - exec(InAppPurchaseTaskType.REQUEST_INVENTORY, new InAppRunnable() { - @Override - public boolean run(InAppPurchaseHelper helper) { - logDebug("Setup successful. Querying inventory."); - try { - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { - billingManager.queryPurchases(); - } else { - throw new IllegalStateException("BillingManager disposed"); - } - return false; - } catch (Exception e) { - logError("queryInventoryAsync Error", e); - notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); - stop(true); - } - return true; - } - }); + exec(InAppPurchaseTaskType.REQUEST_INVENTORY, getRequestInventoryCommand()); + } + } + + protected abstract InAppRunnable getRequestInventoryCommand() throws UnsupportedOperationException; + + protected void onSkuDetailsResponseDone(List purchaseInfoList) { + final AndroidNetworkUtils.OnRequestResultListener listener = new AndroidNetworkUtils.OnRequestResultListener() { + @Override + public void onResult(String result) { + notifyDismissProgress(InAppPurchaseTaskType.REQUEST_INVENTORY); + notifyGetItems(); + stop(true); + logDebug("Initial inapp query finished"); + } + }; + + if (purchaseInfoList.size() > 0) { + sendTokens(purchaseInfoList, listener); + } else { + listener.onResult("OK"); } } @@ -852,25 +409,16 @@ public class InAppPurchaseHelper { parameters.put("aid", ctx.getUserAndroidId()); } - // Call when a purchase is finished - private void onPurchaseFinished(Purchase purchase) { - logDebug("Purchase finished: " + purchase); - - // if we were disposed of in the meantime, quit. - if (getBillingManager() == null) { - stop(true); - return; - } - + protected void onPurchaseDone(PurchaseInfo info) { logDebug("Purchase successful."); - InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(purchase.getSku()); + InAppPurchase liveUpdatesPurchase = getLiveUpdates().getSubscriptionBySku(info.getSku()); if (liveUpdatesPurchase != null) { // bought live updates logDebug("Live updates subscription purchased."); final String sku = liveUpdatesPurchase.getSku(); liveUpdatesPurchase.setPurchaseState(PurchaseState.PURCHASED); - sendTokens(Collections.singletonList(purchase), new OnRequestResultListener() { + sendTokens(Collections.singletonList(info), new OnRequestResultListener() { @Override public void onResult(String result) { boolean active = ctx.getSettings().LIVE_UPDATES_PURCHASED.get(); @@ -887,7 +435,7 @@ public class InAppPurchaseHelper { } }); - } else if (purchase.getSku().equals(getFullVersion().getSku())) { + } else if (info.getSku().equals(getFullVersion().getSku())) { // bought full version getFullVersion().setPurchaseState(PurchaseState.PURCHASED); logDebug("Full version purchased."); @@ -898,7 +446,7 @@ public class InAppPurchaseHelper { notifyItemPurchased(getFullVersion().getSku(), false); stop(true); - } else if (purchase.getSku().equals(getDepthContours().getSku())) { + } else if (info.getSku().equals(getDepthContours().getSku())) { // bought sea depth contours getDepthContours().setPurchaseState(PurchaseState.PURCHASED); logDebug("Sea depth contours purchased."); @@ -921,17 +469,19 @@ public class InAppPurchaseHelper { stop(false); } - private void stop(boolean taskDone) { + protected abstract boolean isBillingManagerExists(); + + protected abstract void destroyBillingManager(); + + protected void stop(boolean taskDone) { logDebug("Destroying helper."); - BillingManager billingManager = getBillingManager(); - if (billingManager != null) { + if (isBillingManagerExists()) { if (taskDone) { processingTask = false; } if (!processingTask) { activeTask = null; - billingManager.destroy(); - this.billingManager = null; + destroyBillingManager(); } } else { processingTask = false; @@ -943,7 +493,7 @@ public class InAppPurchaseHelper { } } - private void sendTokens(@NonNull final List purchases, final OnRequestResultListener listener) { + protected void sendTokens(@NonNull final List purchaseInfoList, final OnRequestResultListener listener) { final String userId = ctx.getSettings().BILLING_USER_ID.get(); final String token = ctx.getSettings().BILLING_USER_TOKEN.get(); final String email = ctx.getSettings().BILLING_USER_EMAIL.get(); @@ -951,12 +501,12 @@ public class InAppPurchaseHelper { String url = "https://osmand.net/subscription/purchased"; String userOperation = "Sending purchase info..."; final List requests = new ArrayList<>(); - for (Purchase purchase : purchases) { + for (PurchaseInfo info : purchaseInfoList) { Map parameters = new HashMap<>(); parameters.put("userid", userId); - parameters.put("sku", purchase.getSku()); - parameters.put("orderId", purchase.getOrderId()); - parameters.put("purchaseToken", purchase.getPurchaseToken()); + parameters.put("sku", info.getSku()); + parameters.put("orderId", info.getOrderId()); + parameters.put("purchaseToken", info.getPurchaseToken()); parameters.put("email", email); parameters.put("token", token); addUserInfo(parameters); @@ -967,9 +517,9 @@ public class InAppPurchaseHelper { public void onResult(@NonNull List results) { for (RequestResponse rr : results) { String sku = rr.getRequest().getParameters().get("sku"); - Purchase purchase = getPurchase(sku); - if (purchase != null) { - updateSentTokens(purchase); + PurchaseInfo info = getPurchaseInfo(sku); + if (info != null) { + updateSentTokens(info); String result = rr.getResponse(); if (result != null) { try { @@ -979,13 +529,13 @@ public class InAppPurchaseHelper { } else { complain("SendToken Error: " + obj.getString("error") - + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + purchase.toString() + ")"); + + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + info.toString() + ")"); } } catch (JSONException e) { logError("SendToken", e); complain("SendToken Error: " + (e.getMessage() != null ? e.getMessage() : "JSONException") - + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + purchase.toString() + ")"); + + " (userId=" + userId + " token=" + token + " response=" + result + " google=" + info.toString() + ")"); } } } @@ -995,10 +545,10 @@ public class InAppPurchaseHelper { } } - private void updateSentTokens(@NonNull Purchase purchase) { + private void updateSentTokens(@NonNull PurchaseInfo info) { String tokensSentStr = ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.get(); Set tokensSent = new HashSet<>(Arrays.asList(tokensSentStr.split(";"))); - tokensSent.add(purchase.getSku()); + tokensSent.add(info.getSku()); ctx.getSettings().BILLING_PURCHASE_TOKENS_SENT.set(TextUtils.join(";", tokensSent)); } @@ -1032,10 +582,10 @@ public class InAppPurchaseHelper { } @Nullable - private Purchase getPurchase(String sku) { - for (Purchase purchase : purchases) { - if (purchase.getSku().equals(sku)) { - return purchase; + private PurchaseInfo getPurchaseInfo(String sku) { + for (PurchaseInfo info : purchaseInfoList) { + if (info.getSku().equals(sku)) { + return info; } } return null; @@ -1049,31 +599,31 @@ public class InAppPurchaseHelper { } } - private void notifyError(InAppPurchaseTaskType taskType, String message) { + protected void notifyError(InAppPurchaseTaskType taskType, String message) { if (uiActivity != null) { uiActivity.onError(taskType, message); } } - private void notifyGetItems() { + protected void notifyGetItems() { if (uiActivity != null) { uiActivity.onGetItems(); } } - private void notifyItemPurchased(String sku, boolean active) { + protected void notifyItemPurchased(String sku, boolean active) { if (uiActivity != null) { uiActivity.onItemPurchased(sku, active); } } - private void notifyShowProgress(InAppPurchaseTaskType taskType) { + protected void notifyShowProgress(InAppPurchaseTaskType taskType) { if (uiActivity != null) { uiActivity.showProgress(taskType); } } - private void notifyDismissProgress(InAppPurchaseTaskType taskType) { + protected void notifyDismissProgress(InAppPurchaseTaskType taskType) { if (uiActivity != null) { uiActivity.dismissProgress(taskType); } @@ -1090,26 +640,26 @@ public class InAppPurchaseHelper { } } - private void complain(String message) { + protected void complain(String message) { logError("**** InAppPurchaseHelper Error: " + message); showToast(message); } - private void showToast(final String message) { + protected void showToast(final String message) { ctx.showToastMessage(message); } - private void logDebug(String msg) { + protected void logDebug(String msg) { if (mDebugLog) { Log.d(TAG, msg); } } - private void logError(String msg) { + protected void logError(String msg) { Log.e(TAG, msg); } - private void logError(String msg, Throwable e) { + protected void logError(String msg, Throwable e) { Log.e(TAG, "Error: " + msg, e); } diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java index b42b57f045..8c42448ec7 100644 --- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java +++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java @@ -55,6 +55,12 @@ public class InAppPurchases { new InAppPurchaseLiveUpdatesAnnualFree() }; + private static final InAppSubscription[] LIVE_UPDATES_HW_FREE = new InAppSubscription[]{ + new InAppPurchaseLiveUpdatesMonthlyHWFree(), + new InAppPurchaseLiveUpdates3MonthsHWFree(), + new InAppPurchaseLiveUpdatesAnnualHWFree() + }; + private InAppPurchase fullVersion; private InAppPurchase depthContours; private InAppPurchase contourLines; @@ -65,7 +71,9 @@ public class InAppPurchases { InAppPurchases(OsmandApplication ctx) { fullVersion = FULL_VERSION; - if (Version.isFreeVersion(ctx)) { + if (Version.isHuawei(ctx)) { + liveUpdates = new LiveUpdatesInAppPurchasesHWFree(); + } else if (Version.isFreeVersion(ctx)) { liveUpdates = new LiveUpdatesInAppPurchasesFree(); } else { liveUpdates = new LiveUpdatesInAppPurchasesFull(); @@ -267,6 +275,13 @@ public class InAppPurchases { } } + public static class LiveUpdatesInAppPurchasesHWFree extends InAppSubscriptionList { + + public LiveUpdatesInAppPurchasesHWFree() { + super(LIVE_UPDATES_HW_FREE); + } + } + public static class LiveUpdatesInAppPurchasesFull extends InAppSubscriptionList { public LiveUpdatesInAppPurchasesFull() { @@ -943,6 +958,25 @@ public class InAppPurchases { } } + public static class InAppPurchaseLiveUpdatesMonthlyHWFree extends InAppPurchaseLiveUpdatesMonthly { + + private static final String SKU_LIVE_UPDATES_MONTHLY_HW_FREE = "net.osmand.test.monthly"; + + InAppPurchaseLiveUpdatesMonthlyHWFree() { + super(SKU_LIVE_UPDATES_MONTHLY_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdatesMonthlyHWFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesMonthlyHWFree(sku) : null; + } + } + public static abstract class InAppPurchaseLiveUpdates3Months extends InAppSubscription { InAppPurchaseLiveUpdates3Months(String skuNoVersion, int version) { @@ -1024,6 +1058,25 @@ public class InAppPurchases { } } + public static class InAppPurchaseLiveUpdates3MonthsHWFree extends InAppPurchaseLiveUpdates3Months { + + private static final String SKU_LIVE_UPDATES_3_MONTHS_HW_FREE = "net.osmand.test.3months"; + + InAppPurchaseLiveUpdates3MonthsHWFree() { + super(SKU_LIVE_UPDATES_3_MONTHS_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdates3MonthsHWFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdates3MonthsHWFree(sku) : null; + } + } + public static abstract class InAppPurchaseLiveUpdatesAnnual extends InAppSubscription { InAppPurchaseLiveUpdatesAnnual(String skuNoVersion, int version) { @@ -1105,6 +1158,25 @@ public class InAppPurchases { } } + public static class InAppPurchaseLiveUpdatesAnnualHWFree extends InAppPurchaseLiveUpdatesAnnual { + + private static final String SKU_LIVE_UPDATES_ANNUAL_HW_FREE = "net.osmand.test.annual"; + + InAppPurchaseLiveUpdatesAnnualHWFree() { + super(SKU_LIVE_UPDATES_ANNUAL_HW_FREE, 1); + } + + private InAppPurchaseLiveUpdatesAnnualHWFree(@NonNull String sku) { + super(sku); + } + + @Nullable + @Override + protected InAppSubscription newInstance(@NonNull String sku) { + return sku.startsWith(getSkuNoVersion()) ? new InAppPurchaseLiveUpdatesAnnualHWFree(sku) : null; + } + } + public static class InAppPurchaseLiveUpdatesOldMonthly extends InAppPurchaseLiveUpdatesMonthly { InAppPurchaseLiveUpdatesOldMonthly(String sku) { diff --git a/OsmAndHms.jks b/OsmAndHms.jks new file mode 100644 index 0000000000..b1d7e59362 Binary files /dev/null and b/OsmAndHms.jks differ diff --git a/agconnect-services.json b/agconnect-services.json new file mode 100644 index 0000000000..c72a2b0122 --- /dev/null +++ b/agconnect-services.json @@ -0,0 +1 @@ +{ "client":{ "cp_id":"890031000000000038", "product_id":"9105385871708313983", "client_id":"250816257723466880", "client_secret":"FC1C714C19B635505454739C0E3EB87D2B8F09669F31748BA81DB77B5E686F8F", "app_id":"101486545", "package_name":"net.osmand.huawei", "api_key":"CV5WXNxMPZSL29Fs885sxIwCyl5xCy+KTRUGP0mzC1K2poUrcCR/b+1IwGZtQJwiH18AoSOC1N2sxQ/dVtsqN38yrMpa" }, "configuration_version":"1.0" } \ No newline at end of file