diff --git a/OsmAnd/res/layout/osmlive_cancelled_dialog_fragment.xml b/OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml
similarity index 81%
rename from OsmAnd/res/layout/osmlive_cancelled_dialog_fragment.xml
rename to OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml
index a936b85350..1a2a28f1c6 100644
--- a/OsmAnd/res/layout/osmlive_cancelled_dialog_fragment.xml
+++ b/OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml
@@ -84,26 +84,11 @@
-
-
+ android:layout_marginTop="@dimen/title_padding">
diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml
index 91eec18222..c0d8bb8721 100644
--- a/OsmAnd/res/values/strings.xml
+++ b/OsmAnd/res/values/strings.xml
@@ -11,6 +11,11 @@
Thx - Hardy
-->
+ OsmAnd Live subscription is on hold
+ OsmAnd Live subscription has been paused
+ OsmAnd Live subscription has been expired
+ There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method.
+ Manage subscription
You must add at least two points.
Travel
Emergency
diff --git a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java
index 5bd9f2cb23..d5ff6fcedf 100644
--- a/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java
+++ b/OsmAnd/src-google/net/osmand/plus/inapp/InAppPurchaseHelperImpl.java
@@ -21,8 +21,10 @@ import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
import net.osmand.plus.inapp.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.util.BillingManager;
+import net.osmand.plus.settings.backend.CommonPreference;
import net.osmand.plus.settings.backend.OsmandPreference;
import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.srtmplugin.SRTMPlugin;
@@ -33,6 +35,7 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map.Entry;
public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
@@ -139,6 +142,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
for (Purchase p : purchases) {
skuSubscriptions.add(p.getSku());
}
+ skuSubscriptions.addAll(subscriptionStateMap.keySet());
BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit.
@@ -291,7 +295,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
}
// Listener that's called when we finish querying the items and subscriptions we own
- private SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
+ private final SkuDetailsResponseListener mSkuDetailsResponseListener = new SkuDetailsResponseListener() {
@NonNull
private List getAllOwnedSubscriptionSkus() {
@@ -304,6 +308,15 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
}
}
}
+ for (Entry entry : subscriptionStateMap.entrySet()) {
+ SubscriptionState state = entry.getValue();
+ if (state == SubscriptionState.PAUSED || state == SubscriptionState.ON_HOLD) {
+ String sku = entry.getKey();
+ if (!result.contains(sku)) {
+ result.add(sku);
+ }
+ }
+ }
return result;
}
@@ -399,35 +412,28 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
// 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);
+ for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
+ Purchase purchase = getPurchase(s.getSku());
+ if (purchase != null || s.getState().isActive()) {
+ 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);
- }
+ 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.");
+ logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + " live updates purchased.");
OsmandSettings settings = ctx.getSettings();
settings.INAPPS_READ.set(true);
@@ -490,12 +496,24 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
}
}
if (inAppPurchase instanceof InAppSubscription) {
+ InAppSubscription s = (InAppSubscription) inAppPurchase;
+
+ SubscriptionState state = subscriptionStateMap.get(inAppPurchase.getSku());
+ s.setState(state == null ? SubscriptionState.UNDEFINED : state);
+ CommonPreference statePref = ctx.getSettings().registerStringPreference(
+ s.getSku() + "_state", SubscriptionState.UNDEFINED.getStateStr()).makeGlobal();
+ s.setPrevState(SubscriptionState.getByStateStr(statePref.get()));
+ statePref.set(s.getState().getStateStr());
+ if (s.getState().isGone() && s.hasStateChanged()) {
+ ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L);
+ ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L);
+ }
+
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 InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));
diff --git a/OsmAnd/src/net/osmand/plus/Version.java b/OsmAnd/src/net/osmand/plus/Version.java
index 48dd9b1feb..8e23518613 100644
--- a/OsmAnd/src/net/osmand/plus/Version.java
+++ b/OsmAnd/src/net/osmand/plus/Version.java
@@ -134,11 +134,11 @@ public class Version {
}
public static boolean isDeveloperVersion(OsmandApplication ctx){
- return getAppName(ctx).contains("~") || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME);
+ return false;//getAppName(ctx).contains("~") || ctx.getPackageName().equals(FREE_DEV_VERSION_NAME);
}
public static boolean isDeveloperBuild(OsmandApplication ctx){
- return getAppName(ctx).contains("~");
+ return false;//getAppName(ctx).contains("~");
}
public static String getVersionForTracker(OsmandApplication ctx) {
diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
index 8cb6e832c5..2d33be5f82 100644
--- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
+++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
@@ -83,7 +83,7 @@ import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.FailSafeFuntions;
import net.osmand.plus.base.MapViewTrackingUtilities;
-import net.osmand.plus.chooseplan.OsmLiveCancelledDialog;
+import net.osmand.plus.chooseplan.OsmLiveGoneDialog;
import net.osmand.plus.dashboard.DashBaseFragment;
import net.osmand.plus.dashboard.DashboardOnMap;
import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment;
@@ -845,8 +845,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, new FirstUsageWelcomeFragment(),
FirstUsageWelcomeFragment.TAG).commitAllowingStateLoss();
- } else if (!isFirstScreenShowing() && OsmLiveCancelledDialog.shouldShowDialog(app)) {
- OsmLiveCancelledDialog.showInstance(getSupportFragmentManager());
} else if (SendAnalyticsBottomSheetDialogFragment.shouldShowDialog(app)) {
SendAnalyticsBottomSheetDialogFragment.showInstance(app, getSupportFragmentManager(), null);
}
@@ -2290,6 +2288,9 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
@Override
public void onInAppPurchaseGetItems() {
DiscountHelper.checkAndDisplay(this);
+ if (!isFirstScreenShowing() && OsmLiveGoneDialog.shouldShowDialog(app)) {
+ OsmLiveGoneDialog.showInstance(app, getSupportFragmentManager());
+ }
}
public enum ShowQuickSearchMode {
diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveCancelledDialog.java b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveCancelledDialog.java
deleted file mode 100644
index a62ffcd296..0000000000
--- a/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveCancelledDialog.java
+++ /dev/null
@@ -1,245 +0,0 @@
-package net.osmand.plus.chooseplan;
-
-import android.app.Activity;
-import android.app.Dialog;
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.os.Build;
-import android.os.Bundle;
-import android.view.ContextThemeWrapper;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.Window;
-import android.widget.ProgressBar;
-
-import androidx.annotation.ColorRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.content.ContextCompat;
-import androidx.fragment.app.FragmentActivity;
-import androidx.fragment.app.FragmentManager;
-
-import net.osmand.PlatformUtil;
-import net.osmand.plus.OsmandApplication;
-import net.osmand.plus.Version;
-import net.osmand.plus.settings.backend.OsmandSettings;
-import net.osmand.plus.settings.backend.OsmandPreference;
-import net.osmand.plus.R;
-import net.osmand.plus.activities.MapActivity;
-import net.osmand.plus.base.BaseOsmAndDialogFragment;
-import net.osmand.plus.chooseplan.ChoosePlanDialogFragment.OsmAndFeature;
-import net.osmand.plus.inapp.InAppPurchaseHelper;
-import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseListener;
-import net.osmand.plus.inapp.InAppPurchaseHelper.InAppPurchaseTaskType;
-import net.osmand.plus.widgets.TextViewEx;
-
-import org.apache.commons.logging.Log;
-
-import static net.osmand.plus.inapp.InAppPurchaseHelper.SUBSCRIPTION_HOLDING_TIME_MSEC;
-
-public class OsmLiveCancelledDialog extends BaseOsmAndDialogFragment implements InAppPurchaseListener {
- public static final String TAG = OsmLiveCancelledDialog.class.getSimpleName();
- private static final Log LOG = PlatformUtil.getLog(OsmLiveCancelledDialog.class);
-
- private OsmandApplication app;
- private InAppPurchaseHelper purchaseHelper;
-
- private boolean nightMode;
- private View osmLiveButton;
-
- private final OsmAndFeature[] osmLiveFeatures = {
- OsmAndFeature.DAILY_MAP_UPDATES,
- OsmAndFeature.UNLIMITED_DOWNLOADS,
- OsmAndFeature.WIKIPEDIA_OFFLINE,
- OsmAndFeature.WIKIVOYAGE_OFFLINE,
- OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS,
- OsmAndFeature.SEA_DEPTH_MAPS,
- OsmAndFeature.UNLOCK_ALL_FEATURES,
- };
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- app = getMyApplication();
- purchaseHelper = app.getInAppPurchaseHelper();
- nightMode = isNightMode(getMapActivity() != null);
- }
-
- @NonNull
- @Override
- public Dialog onCreateDialog(Bundle savedInstanceState) {
- Activity ctx = requireActivity();
- int themeId = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
- Dialog dialog = new Dialog(ctx, themeId);
- Window window = dialog.getWindow();
- if (window != null) {
- window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
- if (!getSettings().DO_NOT_USE_ANIMATIONS.get()) {
- window.getAttributes().windowAnimations = R.style.Animations_Alpha;
- }
- if (Build.VERSION.SDK_INT >= 21) {
- window.setStatusBarColor(ContextCompat.getColor(ctx, getStatusBarColor()));
- }
- }
- return dialog;
- }
-
- @Nullable
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- Context ctx = getContext();
- if (ctx == null) {
- return null;
- }
- int themeRes = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
- View view = LayoutInflater.from(new ContextThemeWrapper(getContext(), themeRes))
- .inflate(R.layout.osmlive_cancelled_dialog_fragment, container, false);
-
- view.findViewById(R.id.button_close).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- }
- });
-
- TextViewEx infoDescr = (TextViewEx) view.findViewById(R.id.info_description);
- StringBuilder descr = new StringBuilder();
- descr.append(getString(R.string.purchase_cancelled_dialog_descr));
- for (OsmAndFeature feature : osmLiveFeatures) {
- descr.append("\n").append("— ").append(feature.toHumanString(ctx));
- }
- infoDescr.setText(descr);
- TextViewEx inappDescr = (TextViewEx) view.findViewById(R.id.inapp_descr);
- inappDescr.setText(Version.isHuawei(app) ? R.string.osm_live_payment_desc_hw : R.string.osm_live_payment_desc);
-
- osmLiveButton = view.findViewById(R.id.card_button);
-
- return view;
- }
-
- @Nullable
- public MapActivity getMapActivity() {
- Activity activity = getActivity();
- if (activity instanceof MapActivity) {
- return (MapActivity) activity;
- }
- return null;
- }
-
- @Override
- public void onResume() {
- super.onResume();
-
- MapActivity mapActivity = getMapActivity();
- if (mapActivity != null) {
- mapActivity.disableDrawer();
- }
-
- boolean requestingInventory = purchaseHelper != null && purchaseHelper.getActiveTask() == InAppPurchaseTaskType.REQUEST_INVENTORY;
- setupOsmLiveButton(requestingInventory);
-
- OsmandPreference firstTimeShown = app.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN;
- OsmandPreference secondTimeShown = app.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN;
- if (!firstTimeShown.get()) {
- firstTimeShown.set(true);
- } else if (!secondTimeShown.get()) {
- secondTimeShown.set(true);
- }
- }
-
- @Override
- public void onPause() {
- super.onPause();
-
- MapActivity mapActivity = getMapActivity();
- if (mapActivity != null) {
- mapActivity.enableDrawer();
- }
- }
-
- @ColorRes
- protected int getStatusBarColor() {
- return nightMode ? R.color.status_bar_wikivoyage_dark : R.color.status_bar_wikivoyage_light;
- }
-
- @Override
- public void onError(InAppPurchaseTaskType taskType, String error) {
- if (taskType == InAppPurchaseTaskType.REQUEST_INVENTORY) {
- setupOsmLiveButton(false);
- }
- }
-
- @Override
- public void onGetItems() {
- }
-
- @Override
- public void onItemPurchased(String sku, boolean active) {
- }
-
- @Override
- public void showProgress(InAppPurchaseTaskType taskType) {
- if (taskType == InAppPurchaseTaskType.REQUEST_INVENTORY) {
- setupOsmLiveButton(true);
- }
- }
-
- @Override
- public void dismissProgress(InAppPurchaseTaskType taskType) {
- if (taskType == InAppPurchaseTaskType.REQUEST_INVENTORY) {
- setupOsmLiveButton(false);
- }
- }
-
- private void setupOsmLiveButton(boolean progress) {
- if (osmLiveButton != null) {
- ProgressBar progressBar = (ProgressBar) osmLiveButton.findViewById(R.id.card_button_progress);
- TextViewEx buttonTitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_title);
- TextViewEx buttonSubtitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_subtitle);
- buttonTitle.setText(getString(R.string.osm_live_plan_pricing));
- buttonSubtitle.setVisibility(View.GONE);
- if (progress) {
- buttonTitle.setVisibility(View.GONE);
- progressBar.setVisibility(View.VISIBLE);
- osmLiveButton.setOnClickListener(null);
- } else {
- buttonTitle.setVisibility(View.VISIBLE);
- progressBar.setVisibility(View.GONE);
- osmLiveButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- dismiss();
- FragmentActivity activity = getActivity();
- if (activity != null) {
- ChoosePlanDialogFragment.showOsmLiveInstance(activity.getSupportFragmentManager());
- }
- }
- });
- }
- }
- }
-
- public static boolean shouldShowDialog(OsmandApplication app) {
- OsmandSettings settings = app.getSettings();
- long cancelledTime = settings.LIVE_UPDATES_PURCHASE_CANCELLED_TIME.get();
- boolean firstTimeShown = settings.LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.get();
- boolean secondTimeShown = settings.LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.get();
- return cancelledTime > 0
- && (!firstTimeShown
- || (System.currentTimeMillis() - cancelledTime > SUBSCRIPTION_HOLDING_TIME_MSEC
- && !secondTimeShown));
- }
-
- public static void showInstance(@NonNull FragmentManager fm) {
- try {
- if (fm.findFragmentByTag(OsmLiveCancelledDialog.TAG) == null) {
- OsmLiveCancelledDialog fragment = new OsmLiveCancelledDialog();
- fragment.show(fm, OsmLiveCancelledDialog.TAG);
- }
- } catch (RuntimeException e) {
- LOG.error("showInstance", e);
- }
- }
-}
diff --git a/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java
new file mode 100644
index 0000000000..fdbce9a076
--- /dev/null
+++ b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java
@@ -0,0 +1,334 @@
+package net.osmand.plus.chooseplan;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import androidx.annotation.ColorRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager;
+
+import net.osmand.PlatformUtil;
+import net.osmand.plus.OsmandApplication;
+import net.osmand.plus.R;
+import net.osmand.plus.activities.MapActivity;
+import net.osmand.plus.base.BaseOsmAndDialogFragment;
+import net.osmand.plus.chooseplan.ChoosePlanDialogFragment.OsmAndFeature;
+import net.osmand.plus.inapp.InAppPurchaseHelper;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscription;
+import net.osmand.plus.settings.backend.OsmandPreference;
+import net.osmand.plus.settings.backend.OsmandSettings;
+import net.osmand.plus.widgets.TextViewEx;
+import net.osmand.util.Algorithms;
+
+import org.apache.commons.logging.Log;
+
+public abstract class OsmLiveGoneDialog extends BaseOsmAndDialogFragment {
+ public static final String TAG = OsmLiveGoneDialog.class.getName();
+ private static final Log LOG = PlatformUtil.getLog(OsmLiveGoneDialog.class);
+
+ private static final long TIME_BETWEEN_DIALOGS_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days
+
+ private OsmandApplication app;
+ private boolean nightMode;
+ private View osmLiveButton;
+
+ private final OsmAndFeature[] osmLiveFeatures = {
+ OsmAndFeature.DAILY_MAP_UPDATES,
+ OsmAndFeature.UNLIMITED_DOWNLOADS,
+ OsmAndFeature.WIKIPEDIA_OFFLINE,
+ OsmAndFeature.WIKIVOYAGE_OFFLINE,
+ OsmAndFeature.CONTOUR_LINES_HILLSHADE_MAPS,
+ OsmAndFeature.SEA_DEPTH_MAPS,
+ OsmAndFeature.UNLOCK_ALL_FEATURES,
+ };
+
+ public static class OsmLiveOnHoldDialog extends OsmLiveGoneDialog {
+ public static final String TAG = OsmLiveOnHoldDialog.class.getSimpleName();
+
+ @Override
+ protected OsmLiveButtonType getOsmLiveButtonType() {
+ return OsmLiveButtonType.MANAGE_SUBSCRIPTION;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.subscription_on_hold_title);
+ }
+
+ @Override
+ protected String getSubscriptionDescr() {
+ return getString(R.string.subscription_payment_issue_title);
+ }
+ }
+
+ public static class OsmLivePausedDialog extends OsmLiveGoneDialog {
+ public static final String TAG = OsmLivePausedDialog.class.getSimpleName();
+
+ @Override
+ protected OsmLiveButtonType getOsmLiveButtonType() {
+ return OsmLiveButtonType.MANAGE_SUBSCRIPTION;
+ }
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.subscription_paused_title);
+ }
+ }
+
+ public static class OsmLiveExpiredDialog extends OsmLiveGoneDialog {
+ public static final String TAG = OsmLiveExpiredDialog.class.getSimpleName();
+
+ @Override
+ protected String getTitle() {
+ return getString(R.string.subscription_expired_title);
+ }
+ }
+
+ protected enum OsmLiveButtonType {
+ PURCHASE_SUBSCRIPTION,
+ MANAGE_SUBSCRIPTION
+ }
+
+ protected OsmLiveButtonType getOsmLiveButtonType() {
+ return OsmLiveButtonType.PURCHASE_SUBSCRIPTION;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ app = getMyApplication();
+ nightMode = isNightMode(getMapActivity() != null);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Activity ctx = requireActivity();
+ int themeId = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
+ Dialog dialog = new Dialog(ctx, themeId);
+ Window window = dialog.getWindow();
+ if (window != null) {
+ window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+ if (!getSettings().DO_NOT_USE_ANIMATIONS.get()) {
+ window.getAttributes().windowAnimations = R.style.Animations_Alpha;
+ }
+ if (Build.VERSION.SDK_INT >= 21) {
+ window.setStatusBarColor(ContextCompat.getColor(ctx, getStatusBarColor()));
+ }
+ }
+ return dialog;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ Context ctx = getContext();
+ if (ctx == null) {
+ return null;
+ }
+ int themeRes = nightMode ? R.style.OsmandDarkTheme_DarkActionbar : R.style.OsmandLightTheme_DarkActionbar_LightStatusBar;
+ View view = LayoutInflater.from(new ContextThemeWrapper(getContext(), themeRes))
+ .inflate(R.layout.osmlive_gone_dialog_fragment, container, false);
+
+ view.findViewById(R.id.button_close).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ }
+ });
+
+ TextViewEx title = (TextViewEx) view.findViewById(R.id.title);
+ title.setText(getTitle());
+ TextViewEx infoDescr = (TextViewEx) view.findViewById(R.id.info_description);
+ StringBuilder descr = new StringBuilder();
+ String subscriptionDescr = getSubscriptionDescr();
+ if (!Algorithms.isEmpty(subscriptionDescr)) {
+ descr.append(subscriptionDescr).append("\n\n");
+ }
+ descr.append(getString(R.string.purchase_cancelled_dialog_descr));
+ for (OsmAndFeature feature : osmLiveFeatures) {
+ descr.append("\n").append("— ").append(feature.toHumanString(ctx));
+ }
+ infoDescr.setText(descr);
+
+ osmLiveButton = view.findViewById(R.id.card_button);
+
+ return view;
+ }
+
+ protected abstract String getTitle();
+
+ protected String getSubscriptionDescr() {
+ return null;
+ }
+
+ @Nullable
+ public MapActivity getMapActivity() {
+ Activity activity = getActivity();
+ if (activity instanceof MapActivity) {
+ return (MapActivity) activity;
+ }
+ return null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ MapActivity mapActivity = getMapActivity();
+ if (mapActivity != null) {
+ mapActivity.disableDrawer();
+ }
+
+ setupOsmLiveButton();
+
+ OsmandPreference firstTimeShownTime = app.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME;
+ OsmandPreference secondTimeShownTime = app.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME;
+ if (firstTimeShownTime.get() == 0) {
+ firstTimeShownTime.set(System.currentTimeMillis());
+ } else if (secondTimeShownTime.get() == 0) {
+ secondTimeShownTime.set(System.currentTimeMillis());
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ MapActivity mapActivity = getMapActivity();
+ if (mapActivity != null) {
+ mapActivity.enableDrawer();
+ }
+ }
+
+ @ColorRes
+ protected int getStatusBarColor() {
+ return nightMode ? R.color.status_bar_wikivoyage_dark : R.color.status_bar_wikivoyage_light;
+ }
+
+ private void setupOsmLiveButton() {
+ if (osmLiveButton != null) {
+ TextViewEx buttonTitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_title);
+ TextViewEx buttonSubtitle = (TextViewEx) osmLiveButton.findViewById(R.id.card_button_subtitle);
+ switch (getOsmLiveButtonType()) {
+ case PURCHASE_SUBSCRIPTION:
+ buttonTitle.setText(getString(R.string.osm_live_plan_pricing));
+ osmLiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ FragmentActivity activity = getActivity();
+ if (activity != null) {
+ ChoosePlanDialogFragment.showOsmLiveInstance(activity.getSupportFragmentManager());
+ }
+ }
+ });
+ break;
+ case MANAGE_SUBSCRIPTION:
+ buttonTitle.setText(getString(R.string.manage_subscription));
+ osmLiveButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dismiss();
+ FragmentActivity activity = getActivity();
+ if (activity != null) {
+ InAppSubscription expiredSubscription = getExpiredSubscription((OsmandApplication) activity.getApplication());
+ if (expiredSubscription != null) {
+ manageSubscription(expiredSubscription.getSku());
+ }
+ }
+ }
+ });
+ break;
+ }
+ buttonSubtitle.setVisibility(View.GONE);
+ buttonTitle.setVisibility(View.VISIBLE);
+ osmLiveButton.findViewById(R.id.card_button_progress).setVisibility(View.GONE);
+ }
+ }
+
+ private void manageSubscription(@Nullable String sku) {
+ Context ctx = getContext();
+ if (ctx != null) {
+ String url = "https://play.google.com/store/account/subscriptions?package=" + ctx.getPackageName();
+ if (!Algorithms.isEmpty(sku)) {
+ url += "&sku=" + sku;
+ }
+ Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+ startActivity(intent);
+ }
+ }
+
+ @Nullable
+ private static InAppSubscription getExpiredSubscription(@NonNull OsmandApplication app) {
+ if (!app.getSettings().LIVE_UPDATES_PURCHASED.get()) {
+ InAppPurchaseHelper purchaseHelper = app.getInAppPurchaseHelper();
+ return purchaseHelper.getLiveUpdates().getTopExpiredSubscription();
+ }
+ return null;
+ }
+
+ public static boolean shouldShowDialog(@NonNull OsmandApplication app) {
+ InAppSubscription expiredSubscription = getExpiredSubscription(app);
+ if (expiredSubscription == null) {
+ return false;
+ }
+ OsmandSettings settings = app.getSettings();
+ long firstTimeShownTime = settings.LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.get();
+ long secondTimeShownTime = settings.LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.get();
+ return firstTimeShownTime == 0
+ || (System.currentTimeMillis() - firstTimeShownTime > TIME_BETWEEN_DIALOGS_MSEC && secondTimeShownTime == 0);
+ }
+
+ public static void showInstance(@NonNull OsmandApplication app, @NonNull FragmentManager fm) {
+ try {
+ InAppSubscription expiredSubscription = getExpiredSubscription(app);
+ if (expiredSubscription == null) {
+ return;
+ }
+ String tag = null;
+ DialogFragment fragment = null;
+ switch (expiredSubscription.getState()) {
+ case ON_HOLD:
+ tag = OsmLiveOnHoldDialog.TAG;
+ if (fm.findFragmentByTag(tag) == null) {
+ fragment = new OsmLiveOnHoldDialog();
+ }
+ break;
+ case PAUSED:
+ tag = OsmLivePausedDialog.TAG;
+ if (fm.findFragmentByTag(tag) == null) {
+ fragment = new OsmLivePausedDialog();
+ }
+ break;
+ case EXPIRED:
+ tag = OsmLiveExpiredDialog.TAG;
+ if (fm.findFragmentByTag(tag) == null) {
+ fragment = new OsmLiveExpiredDialog();
+ }
+ break;
+ }
+ if (fragment != null) {
+ fragment.show(fm, tag);
+ }
+ } catch (RuntimeException e) {
+ LOG.error("showInstance", e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java
index 543c3a819d..c7464c6b1f 100644
--- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java
+++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchaseHelper.java
@@ -17,13 +17,12 @@ 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.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.InAppSubscription;
+import net.osmand.plus.inapp.InAppPurchases.InAppSubscription.SubscriptionState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.liveupdates.CountrySelectionFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
@@ -50,11 +49,10 @@ public abstract class InAppPurchaseHelper {
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
-
protected InAppPurchases purchases;
protected long lastValidationCheckTime;
protected boolean inventoryRequested;
+ protected Map subscriptionStateMap = new HashMap<>();
private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily
@@ -375,21 +373,33 @@ public abstract class InAppPurchaseHelper {
final String sku, final String payload) throws UnsupportedOperationException;
@SuppressLint("StaticFieldLeak")
- private class RequestInventoryTask extends AsyncTask {
+ private class RequestInventoryTask extends AsyncTask {
RequestInventoryTask() {
}
@Override
- protected String doInBackground(Void... params) {
+ protected String[] doInBackground(Void... params) {
try {
Map parameters = new HashMap<>();
parameters.put("androidPackage", ctx.getPackageName());
addUserInfo(parameters);
- return AndroidNetworkUtils.sendRequest(ctx,
+ String activeSubscriptionsIds = AndroidNetworkUtils.sendRequest(ctx,
"https://osmand.net/api/subscriptions/active",
parameters, "Requesting active subscriptions...", false, false);
+ String subscriptionsState = null;
+ String userId = ctx.getSettings().BILLING_USER_ID.get();
+ String userToken = ctx.getSettings().BILLING_USER_TOKEN.get();
+ if (!Algorithms.isEmpty(userId) && !Algorithms.isEmpty(userToken)) {
+ parameters.put("userId", userId);
+ parameters.put("userToken", userToken);
+ subscriptionsState = AndroidNetworkUtils.sendRequest(ctx,
+ "https://osmand.net/api/subscriptions/get",
+ parameters, "Requesting subscriptions state...", false, false);
+ }
+
+ return new String[] { activeSubscriptionsIds, subscriptionsState };
} catch (Exception e) {
logError("sendRequest Error", e);
}
@@ -397,12 +407,14 @@ public abstract class InAppPurchaseHelper {
}
@Override
- protected void onPostExecute(String response) {
- logDebug("Response=" + response);
- if (response != null) {
+ protected void onPostExecute(String[] response) {
+ logDebug("Response=" + Arrays.toString(response));
+ String activeSubscriptionsIdsJson = response[0];
+ String subscriptionsStateJson = response[1];
+ if (activeSubscriptionsIdsJson != null) {
inventoryRequested = true;
try {
- JSONObject obj = new JSONObject(response);
+ JSONObject obj = new JSONObject(activeSubscriptionsIdsJson);
JSONArray names = obj.names();
if (names != null) {
for (int i = 0; i < names.length(); i++) {
@@ -418,6 +430,24 @@ public abstract class InAppPurchaseHelper {
logError("Json parsing error", e);
}
}
+ if (subscriptionsStateJson != null) {
+ inventoryRequested = true;
+ Map subscriptionStateMap = new HashMap<>();
+ try {
+ JSONArray subArrJson = new JSONArray(subscriptionsStateJson);
+ for (int i = 0; i < subArrJson.length(); i++) {
+ JSONObject subObj = subArrJson.getJSONObject(i);
+ String sku = subObj.getString("sku");
+ String state = subObj.getString("state");
+ if (!Algorithms.isEmpty(sku) && !Algorithms.isEmpty(state)) {
+ subscriptionStateMap.put(sku, SubscriptionState.getByStateStr(state));
+ }
+ }
+ } catch (JSONException e) {
+ logError("Json parsing error", e);
+ }
+ InAppPurchaseHelper.this.subscriptionStateMap = subscriptionStateMap;
+ }
exec(InAppPurchaseTaskType.REQUEST_INVENTORY, getRequestInventoryCommand());
}
}
@@ -467,9 +497,8 @@ public abstract class InAppPurchaseHelper {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
- ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L);
- ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false);
- ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
+ ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L);
+ ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
notifyItemPurchased(sku, active);
diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
index 5004e97165..7cc9a8fbee 100644
--- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
+++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
@@ -24,6 +24,8 @@ import java.text.NumberFormat;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Currency;
import java.util.List;
import java.util.Locale;
@@ -190,6 +192,28 @@ public abstract class InAppPurchases {
}
return null;
}
+
+ @Nullable
+ public InAppSubscription getTopExpiredSubscription() {
+ List expiredSubscriptions = new ArrayList<>();
+ for (InAppSubscription s : getAllSubscriptions()) {
+ if (s.getState().isGone()) {
+ expiredSubscriptions.add(s);
+ }
+ }
+ Collections.sort(expiredSubscriptions, new Comparator() {
+ @Override
+ public int compare(InAppSubscription s1, InAppSubscription s2) {
+ int orderS1 = s1.getState().ordinal();
+ int orderS2 = s2.getState().ordinal();
+ if (orderS1 != orderS2) {
+ return (orderS1 < orderS2) ? -1 : ((orderS1 == orderS2) ? 0 : 1);
+ }
+ return Double.compare(s1.getMonthlyPriceValue(), s2.getMonthlyPriceValue());
+ }
+ });
+ return expiredSubscriptions.isEmpty() ? null : expiredSubscriptions.get(0);
+ }
}
public abstract static class InAppPurchase {
@@ -554,9 +578,49 @@ public abstract class InAppPurchases {
private String subscriptionPeriodString;
private Period subscriptionPeriod;
private boolean upgrade = false;
+ private SubscriptionState state = SubscriptionState.UNDEFINED;
+ private SubscriptionState prevState = SubscriptionState.UNDEFINED;
private InAppSubscriptionIntroductoryInfo introductoryInfo;
+ public enum SubscriptionState {
+ UNDEFINED("undefined"),
+ ACTIVE("active"),
+ CANCELLED("cancelled"),
+ IN_GRACE_PERIOD("in_grace_period"),
+ ON_HOLD("on_hold"),
+ PAUSED("paused"),
+ EXPIRED("expired");
+
+ private final String stateStr;
+
+ SubscriptionState(@NonNull String stateStr) {
+ this.stateStr = stateStr;
+ }
+
+ public String getStateStr() {
+ return stateStr;
+ }
+
+ @NonNull
+ public static SubscriptionState getByStateStr(@NonNull String stateStr) {
+ for (SubscriptionState state : SubscriptionState.values()) {
+ if (state.stateStr.equals(stateStr)) {
+ return state;
+ }
+ }
+ return UNDEFINED;
+ }
+
+ public boolean isGone() {
+ return this == ON_HOLD || this == PAUSED || this == EXPIRED;
+ }
+
+ public boolean isActive() {
+ return this == ACTIVE || this == CANCELLED || this == IN_GRACE_PERIOD;
+ }
+ }
+
InAppSubscription(@NonNull String skuNoVersion, int version) {
super(skuNoVersion + "_v" + version);
this.skuNoVersion = skuNoVersion;
@@ -592,6 +656,28 @@ public abstract class InAppPurchases {
return upgrade;
}
+ @NonNull
+ public SubscriptionState getState() {
+ return state;
+ }
+
+ public void setState(@NonNull SubscriptionState state) {
+ this.state = state;
+ }
+
+ @NonNull
+ public SubscriptionState getPrevState() {
+ return prevState;
+ }
+
+ public void setPrevState(@NonNull SubscriptionState prevState) {
+ this.prevState = prevState;
+ }
+
+ public boolean hasStateChanged() {
+ return state != prevState;
+ }
+
public boolean isAnyPurchased() {
if (isPurchased()) {
return true;
diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
index bcc7a4c170..d24dd23ee6 100644
--- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
+++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
@@ -1104,9 +1104,8 @@ public class OsmandSettings {
public final OsmandPreference BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference(this, "billing_purchase_token_sent", false).makeGlobal();
public final OsmandPreference BILLING_PURCHASE_TOKENS_SENT = new StringPreference(this, "billing_purchase_tokens_sent", "").makeGlobal();
public final OsmandPreference LIVE_UPDATES_PURCHASED = new BooleanPreference(this, "billing_live_updates_purchased", false).makeGlobal();
- public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_TIME = new LongPreference(this, "live_updates_purchase_cancelled_time", 0).makeGlobal();
- public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_first_dlg_shown", false).makeGlobal();
- public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal();
+ public final OsmandPreference LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_first_dlg_shown_time", 0).makeGlobal();
+ public final OsmandPreference LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_second_dlg_shown_time", 0).makeGlobal();
public final OsmandPreference FULL_VERSION_PURCHASED = new BooleanPreference(this, "billing_full_version_purchased", false).makeGlobal();
public final OsmandPreference DEPTH_CONTOURS_PURCHASED = new BooleanPreference(this, "billing_sea_depth_purchased", false).makeGlobal();
public final OsmandPreference CONTOUR_LINES_PURCHASED = new BooleanPreference(this, "billing_srtm_purchased", false).makeGlobal();