Handle subscription states (on hold / paused)

This commit is contained in:
max-klaus 2020-11-06 13:17:40 +03:00
parent 2256e98ee1
commit 5f45b8751f
10 changed files with 514 additions and 302 deletions

View file

@ -84,26 +84,11 @@
</ScrollView> </ScrollView>
<net.osmand.plus.widgets.TextViewEx
android:id="@+id/inapp_descr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/list_content_padding_large"
android:layout_marginRight="@dimen/list_content_padding_large"
android:layout_marginTop="@dimen/title_padding"
android:gravity="center"
android:text="@string/osm_live_payment_desc"
android:textColor="?attr/card_description_text_color"
android:textSize="@dimen/default_desc_text_size"
osmand:typeface="@string/font_roboto_regular"
android:layout_marginStart="@dimen/list_content_padding_large"
android:layout_marginEnd="@dimen/list_content_padding_large" />
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/card_padding" android:layout_marginBottom="@dimen/card_padding"
android:layout_marginTop="@dimen/card_padding"> android:layout_marginTop="@dimen/title_padding">
<include layout="@layout/purchase_dialog_card_shadow_button"/> <include layout="@layout/purchase_dialog_card_shadow_button"/>

View file

@ -11,6 +11,11 @@
Thx - Hardy Thx - Hardy
--> -->
<string name="subscription_on_hold_title">OsmAnd Live subscription is on hold</string>
<string name="subscription_paused_title">OsmAnd Live subscription has been paused</string>
<string name="subscription_expired_title">OsmAnd Live subscription has been expired</string>
<string name="subscription_payment_issue_title">There is a problem with your subscription. Click the button to go to the Google Play subscription settings to fix your payment method.</string>
<string name="manage_subscription">Manage subscription</string>
<string name="message_you_need_add_two_points_to_show_graphs">You must add at least two points.</string> <string name="message_you_need_add_two_points_to_show_graphs">You must add at least two points.</string>
<string name="icon_group_travel">Travel</string> <string name="icon_group_travel">Travel</string>
<string name="icon_group_emergency">Emergency</string> <string name="icon_group_emergency">Emergency</string>

View file

@ -21,8 +21,10 @@ import net.osmand.plus.OsmandPlugin;
import net.osmand.plus.R; import net.osmand.plus.R;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; 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.InAppPurchasesImpl.InAppPurchaseLiveUpdatesOldSubscription;
import net.osmand.plus.inapp.util.BillingManager; 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.OsmandPreference;
import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.OsmandSettings;
import net.osmand.plus.srtmplugin.SRTMPlugin; import net.osmand.plus.srtmplugin.SRTMPlugin;
@ -33,6 +35,7 @@ import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map.Entry;
public class InAppPurchaseHelperImpl extends InAppPurchaseHelper { public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
@ -139,6 +142,7 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
for (Purchase p : purchases) { for (Purchase p : purchases) {
skuSubscriptions.add(p.getSku()); skuSubscriptions.add(p.getSku());
} }
skuSubscriptions.addAll(subscriptionStateMap.keySet());
BillingManager billingManager = getBillingManager(); BillingManager billingManager = getBillingManager();
// Have we been disposed of in the meantime? If so, quit. // 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 // 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 @NonNull
private List<String> getAllOwnedSubscriptionSkus() { private List<String> getAllOwnedSubscriptionSkus() {
@ -304,6 +308,15 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
} }
} }
} }
for (Entry<String, SubscriptionState> 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; return result;
} }
@ -399,35 +412,28 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
// Do we have the live updates? // Do we have the live updates?
boolean subscribedToLiveUpdates = false; boolean subscribedToLiveUpdates = false;
List<Purchase> liveUpdatesPurchases = new ArrayList<>(); List<Purchase> liveUpdatesPurchases = new ArrayList<>();
for (InAppPurchase p : getLiveUpdates().getAllSubscriptions()) { for (InAppSubscription s : getLiveUpdates().getAllSubscriptions()) {
Purchase purchase = getPurchase(p.getSku()); Purchase purchase = getPurchase(s.getSku());
if (purchase != null) { if (purchase != null || s.getState().isActive()) {
liveUpdatesPurchases.add(purchase); if (purchase != null) {
liveUpdatesPurchases.add(purchase);
}
if (!subscribedToLiveUpdates) { if (!subscribedToLiveUpdates) {
subscribedToLiveUpdates = true; subscribedToLiveUpdates = true;
} }
} }
} }
OsmandPreference<Long> subscriptionCancelledTime = ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME;
if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) { if (!subscribedToLiveUpdates && ctx.getSettings().LIVE_UPDATES_PURCHASED.get()) {
if (subscriptionCancelledTime.get() == 0) { ctx.getSettings().LIVE_UPDATES_PURCHASED.set(false);
subscriptionCancelledTime.set(System.currentTimeMillis()); if (!isDepthContoursPurchased(ctx)) {
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); ctx.getSettings().getCustomRenderBooleanProperty("depthContours").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) { } else if (subscribedToLiveUpdates) {
subscriptionCancelledTime.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
} }
lastValidationCheckTime = System.currentTimeMillis(); lastValidationCheckTime = System.currentTimeMillis();
logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") logDebug("User " + (subscribedToLiveUpdates ? "HAS" : "DOES NOT HAVE") + " live updates purchased.");
+ " live updates purchased.");
OsmandSettings settings = ctx.getSettings(); OsmandSettings settings = ctx.getSettings();
settings.INAPPS_READ.set(true); settings.INAPPS_READ.set(true);
@ -490,12 +496,24 @@ public class InAppPurchaseHelperImpl extends InAppPurchaseHelper {
} }
} }
if (inAppPurchase instanceof InAppSubscription) { if (inAppPurchase instanceof InAppSubscription) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
SubscriptionState state = subscriptionStateMap.get(inAppPurchase.getSku());
s.setState(state == null ? SubscriptionState.UNDEFINED : state);
CommonPreference<String> 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 introductoryPrice = skuDetails.getIntroductoryPrice();
String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod(); String introductoryPricePeriod = skuDetails.getIntroductoryPricePeriod();
String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles(); String introductoryPriceCycles = skuDetails.getIntroductoryPriceCycles();
long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros(); long introductoryPriceAmountMicros = skuDetails.getIntroductoryPriceAmountMicros();
if (!Algorithms.isEmpty(introductoryPrice)) { if (!Algorithms.isEmpty(introductoryPrice)) {
InAppSubscription s = (InAppSubscription) inAppPurchase;
try { try {
s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice, s.setIntroductoryInfo(new InAppPurchases.InAppSubscriptionIntroductoryInfo(s, introductoryPrice,
introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles)); introductoryPriceAmountMicros, introductoryPricePeriod, introductoryPriceCycles));

View file

@ -134,11 +134,11 @@ public class Version {
} }
public static boolean isDeveloperVersion(OsmandApplication ctx){ 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){ public static boolean isDeveloperBuild(OsmandApplication ctx){
return getAppName(ctx).contains("~"); return false;//getAppName(ctx).contains("~");
} }
public static String getVersionForTracker(OsmandApplication ctx) { public static String getVersionForTracker(OsmandApplication ctx) {

View file

@ -83,7 +83,7 @@ import net.osmand.plus.base.BaseOsmAndFragment;
import net.osmand.plus.base.ContextMenuFragment; import net.osmand.plus.base.ContextMenuFragment;
import net.osmand.plus.base.FailSafeFuntions; import net.osmand.plus.base.FailSafeFuntions;
import net.osmand.plus.base.MapViewTrackingUtilities; 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.DashBaseFragment;
import net.osmand.plus.dashboard.DashboardOnMap; import net.osmand.plus.dashboard.DashboardOnMap;
import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment; import net.osmand.plus.dialogs.CrashBottomSheetDialogFragment;
@ -845,8 +845,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
getSupportFragmentManager().beginTransaction() getSupportFragmentManager().beginTransaction()
.add(R.id.fragmentContainer, new FirstUsageWelcomeFragment(), .add(R.id.fragmentContainer, new FirstUsageWelcomeFragment(),
FirstUsageWelcomeFragment.TAG).commitAllowingStateLoss(); FirstUsageWelcomeFragment.TAG).commitAllowingStateLoss();
} else if (!isFirstScreenShowing() && OsmLiveCancelledDialog.shouldShowDialog(app)) {
OsmLiveCancelledDialog.showInstance(getSupportFragmentManager());
} else if (SendAnalyticsBottomSheetDialogFragment.shouldShowDialog(app)) { } else if (SendAnalyticsBottomSheetDialogFragment.shouldShowDialog(app)) {
SendAnalyticsBottomSheetDialogFragment.showInstance(app, getSupportFragmentManager(), null); SendAnalyticsBottomSheetDialogFragment.showInstance(app, getSupportFragmentManager(), null);
} }
@ -2290,6 +2288,9 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven
@Override @Override
public void onInAppPurchaseGetItems() { public void onInAppPurchaseGetItems() {
DiscountHelper.checkAndDisplay(this); DiscountHelper.checkAndDisplay(this);
if (!isFirstScreenShowing() && OsmLiveGoneDialog.shouldShowDialog(app)) {
OsmLiveGoneDialog.showInstance(app, getSupportFragmentManager());
}
} }
public enum ShowQuickSearchMode { public enum ShowQuickSearchMode {

View file

@ -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<Boolean> firstTimeShown = app.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN;
OsmandPreference<Boolean> 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);
}
}
}

View file

@ -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<Long> firstTimeShownTime = app.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME;
OsmandPreference<Long> 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);
}
}
}

View file

@ -17,13 +17,12 @@ import net.osmand.AndroidNetworkUtils.OnRequestsResultListener;
import net.osmand.AndroidNetworkUtils.RequestResponse; import net.osmand.AndroidNetworkUtils.RequestResponse;
import net.osmand.PlatformUtil; import net.osmand.PlatformUtil;
import net.osmand.plus.OsmandApplication; 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.R;
import net.osmand.plus.Version; import net.osmand.plus.Version;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase;
import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState; import net.osmand.plus.inapp.InAppPurchases.InAppPurchase.PurchaseState;
import net.osmand.plus.inapp.InAppPurchases.InAppSubscription; 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.inapp.InAppPurchases.InAppSubscriptionList;
import net.osmand.plus.liveupdates.CountrySelectionFragment; import net.osmand.plus.liveupdates.CountrySelectionFragment;
import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem; import net.osmand.plus.liveupdates.CountrySelectionFragment.CountryItem;
@ -50,11 +49,10 @@ public abstract class InAppPurchaseHelper {
private static final String TAG = InAppPurchaseHelper.class.getSimpleName(); private static final String TAG = InAppPurchaseHelper.class.getSimpleName();
private boolean mDebugLog = false; private boolean mDebugLog = false;
public static final long SUBSCRIPTION_HOLDING_TIME_MSEC = 1000 * 60 * 60 * 24 * 3; // 3 days
protected InAppPurchases purchases; protected InAppPurchases purchases;
protected long lastValidationCheckTime; protected long lastValidationCheckTime;
protected boolean inventoryRequested; protected boolean inventoryRequested;
protected Map<String, SubscriptionState> subscriptionStateMap = new HashMap<>();
private static final long PURCHASE_VALIDATION_PERIOD_MSEC = 1000 * 60 * 60 * 24; // daily 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; final String sku, final String payload) throws UnsupportedOperationException;
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
private class RequestInventoryTask extends AsyncTask<Void, Void, String> { private class RequestInventoryTask extends AsyncTask<Void, Void, String[]> {
RequestInventoryTask() { RequestInventoryTask() {
} }
@Override @Override
protected String doInBackground(Void... params) { protected String[] doInBackground(Void... params) {
try { try {
Map<String, String> parameters = new HashMap<>(); Map<String, String> parameters = new HashMap<>();
parameters.put("androidPackage", ctx.getPackageName()); parameters.put("androidPackage", ctx.getPackageName());
addUserInfo(parameters); addUserInfo(parameters);
return AndroidNetworkUtils.sendRequest(ctx, String activeSubscriptionsIds = AndroidNetworkUtils.sendRequest(ctx,
"https://osmand.net/api/subscriptions/active", "https://osmand.net/api/subscriptions/active",
parameters, "Requesting active subscriptions...", false, false); 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) { } catch (Exception e) {
logError("sendRequest Error", e); logError("sendRequest Error", e);
} }
@ -397,12 +407,14 @@ public abstract class InAppPurchaseHelper {
} }
@Override @Override
protected void onPostExecute(String response) { protected void onPostExecute(String[] response) {
logDebug("Response=" + response); logDebug("Response=" + Arrays.toString(response));
if (response != null) { String activeSubscriptionsIdsJson = response[0];
String subscriptionsStateJson = response[1];
if (activeSubscriptionsIdsJson != null) {
inventoryRequested = true; inventoryRequested = true;
try { try {
JSONObject obj = new JSONObject(response); JSONObject obj = new JSONObject(activeSubscriptionsIdsJson);
JSONArray names = obj.names(); JSONArray names = obj.names();
if (names != null) { if (names != null) {
for (int i = 0; i < names.length(); i++) { for (int i = 0; i < names.length(); i++) {
@ -418,6 +430,24 @@ public abstract class InAppPurchaseHelper {
logError("Json parsing error", e); logError("Json parsing error", e);
} }
} }
if (subscriptionsStateJson != null) {
inventoryRequested = true;
Map<String, SubscriptionState> 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()); exec(InAppPurchaseTaskType.REQUEST_INVENTORY, getRequestInventoryCommand());
} }
} }
@ -467,9 +497,8 @@ public abstract class InAppPurchaseHelper {
ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true); ctx.getSettings().LIVE_UPDATES_PURCHASED.set(true);
ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true); ctx.getSettings().getCustomRenderBooleanProperty("depthContours").set(true);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_TIME.set(0L); ctx.getSettings().LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN.set(false); ctx.getSettings().LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME.set(0L);
ctx.getSettings().LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN.set(false);
notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES); notifyDismissProgress(InAppPurchaseTaskType.PURCHASE_LIVE_UPDATES);
notifyItemPurchased(sku, active); notifyItemPurchased(sku, active);

View file

@ -24,6 +24,8 @@ import java.text.NumberFormat;
import java.text.ParseException; import java.text.ParseException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Currency; import java.util.Currency;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -190,6 +192,28 @@ public abstract class InAppPurchases {
} }
return null; return null;
} }
@Nullable
public InAppSubscription getTopExpiredSubscription() {
List<InAppSubscription> expiredSubscriptions = new ArrayList<>();
for (InAppSubscription s : getAllSubscriptions()) {
if (s.getState().isGone()) {
expiredSubscriptions.add(s);
}
}
Collections.sort(expiredSubscriptions, new Comparator<InAppSubscription>() {
@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 { public abstract static class InAppPurchase {
@ -554,9 +578,49 @@ public abstract class InAppPurchases {
private String subscriptionPeriodString; private String subscriptionPeriodString;
private Period subscriptionPeriod; private Period subscriptionPeriod;
private boolean upgrade = false; private boolean upgrade = false;
private SubscriptionState state = SubscriptionState.UNDEFINED;
private SubscriptionState prevState = SubscriptionState.UNDEFINED;
private InAppSubscriptionIntroductoryInfo introductoryInfo; 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) { InAppSubscription(@NonNull String skuNoVersion, int version) {
super(skuNoVersion + "_v" + version); super(skuNoVersion + "_v" + version);
this.skuNoVersion = skuNoVersion; this.skuNoVersion = skuNoVersion;
@ -592,6 +656,28 @@ public abstract class InAppPurchases {
return upgrade; 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() { public boolean isAnyPurchased() {
if (isPurchased()) { if (isPurchased()) {
return true; return true;

View file

@ -1104,9 +1104,8 @@ public class OsmandSettings {
public final OsmandPreference<Boolean> BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference(this, "billing_purchase_token_sent", false).makeGlobal(); public final OsmandPreference<Boolean> BILLING_PURCHASE_TOKEN_SENT = new BooleanPreference(this, "billing_purchase_token_sent", false).makeGlobal();
public final OsmandPreference<String> BILLING_PURCHASE_TOKENS_SENT = new StringPreference(this, "billing_purchase_tokens_sent", "").makeGlobal(); public final OsmandPreference<String> BILLING_PURCHASE_TOKENS_SENT = new StringPreference(this, "billing_purchase_tokens_sent", "").makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASED = new BooleanPreference(this, "billing_live_updates_purchased", false).makeGlobal(); public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASED = new BooleanPreference(this, "billing_live_updates_purchased", false).makeGlobal();
public final OsmandPreference<Long> LIVE_UPDATES_PURCHASE_CANCELLED_TIME = new LongPreference(this, "live_updates_purchase_cancelled_time", 0).makeGlobal(); public final OsmandPreference<Long> LIVE_UPDATES_EXPIRED_FIRST_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_first_dlg_shown_time", 0).makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASE_CANCELLED_FIRST_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_first_dlg_shown", false).makeGlobal(); public final OsmandPreference<Long> LIVE_UPDATES_EXPIRED_SECOND_DLG_SHOWN_TIME = new LongPreference(this, "live_updates_expired_second_dlg_shown_time", 0).makeGlobal();
public final OsmandPreference<Boolean> LIVE_UPDATES_PURCHASE_CANCELLED_SECOND_DLG_SHOWN = new BooleanPreference(this, "live_updates_purchase_cancelled_second_dlg_shown", false).makeGlobal();
public final OsmandPreference<Boolean> FULL_VERSION_PURCHASED = new BooleanPreference(this, "billing_full_version_purchased", false).makeGlobal(); public final OsmandPreference<Boolean> FULL_VERSION_PURCHASED = new BooleanPreference(this, "billing_full_version_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> DEPTH_CONTOURS_PURCHASED = new BooleanPreference(this, "billing_sea_depth_purchased", false).makeGlobal(); public final OsmandPreference<Boolean> DEPTH_CONTOURS_PURCHASED = new BooleanPreference(this, "billing_sea_depth_purchased", false).makeGlobal();
public final OsmandPreference<Boolean> CONTOUR_LINES_PURCHASED = new BooleanPreference(this, "billing_srtm_purchased", false).makeGlobal(); public final OsmandPreference<Boolean> CONTOUR_LINES_PURCHASED = new BooleanPreference(this, "billing_srtm_purchased", false).makeGlobal();