diff --git a/OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml b/OsmAnd/res/layout/osmlive_gone_dialog_fragment.xml
index a936b85350..1a2a28f1c6 100644
--- a/OsmAnd/res/layout/osmlive_gone_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 086a7470d5..f072d06cb2 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
Start/finish icons
Name: A – Z
Name: Z – A
diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
index e23e596dcf..c5d93b8c29 100644
--- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
+++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java
@@ -84,7 +84,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.DashboardOnMap;
import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment;
import net.osmand.plus.dialogs.ImportGpxBottomSheetDialogFragment;
@@ -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);
}
@@ -2267,6 +2265,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/OsmLiveGoneDialog.java b/OsmAnd/src/net/osmand/plus/chooseplan/OsmLiveGoneDialog.java
new file mode 100644
index 0000000000..d402ae93b6
--- /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.OsmandSettings;
+import net.osmand.plus.settings.backend.OsmandSettings.OsmandPreference;
+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);
+ }
+ }
+}
diff --git a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
index b42b57f045..e9552bd9eb 100644
--- a/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
+++ b/OsmAnd/src/net/osmand/plus/inapp/InAppPurchases.java
@@ -27,6 +27,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;
@@ -258,6 +260,28 @@ public 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 static class LiveUpdatesInAppPurchasesFree extends InAppSubscriptionList {
@@ -636,9 +660,45 @@ public 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;
+ }
+ }
+
InAppSubscription(@NonNull String skuNoVersion, int version) {
super(skuNoVersion + "_v" + version);
this.skuNoVersion = skuNoVersion;
@@ -674,6 +734,28 @@ public 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 efa7147e6c..a0295bd895 100644
--- a/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
+++ b/OsmAnd/src/net/osmand/plus/settings/backend/OsmandSettings.java
@@ -2003,9 +2003,8 @@ public class OsmandSettings {
public final OsmandPreference BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference("billing_purchase_token_sent", false).makeGlobal();
public final OsmandPreference BILLING_PURCHASE_TOKENS_SENT = new StringPreference("billing_purchase_tokens_sent", "").makeGlobal();
public final OsmandPreference LIVE_UPDATES_PURCHASED = new BooleanPreference("billing_live_updates_purchased", false).makeGlobal();
- public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_TIME = new LongPreference("live_updates_purchase_cancelled_time", 0).makeGlobal();
- public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN = new BooleanPreference("live_updates_purchase_cancelled_first_dlg_shown", false).makeGlobal();
- public final OsmandPreference LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference("live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal();
+ public final OsmandPreference LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME = new LongPreference("live_updates_expired_first_dlg_shown_time", 0).makeGlobal();
+ public final OsmandPreference LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME = new LongPreference("live_updates_expired_second_dlg_shown_time", 0).makeGlobal();
public final OsmandPreference FULL_VERSION_PURCHASED = new BooleanPreference("billing_full_version_purchased", false).makeGlobal();
public final OsmandPreference DEPTH_CONTOURS_PURCHASED = new BooleanPreference("billing_sea_depth_purchased", false).makeGlobal();
public final OsmandPreference EMAIL_SUBSCRIBED = new BooleanPreference("email_subscribed", false).makeGlobal();