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();