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