chromium/chrome/browser/commerce/subscriptions/android/java/src/org/chromium/chrome/browser/subscriptions/CommerceSubscriptionsService.java

// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.subscriptions;

import androidx.annotation.VisibleForTesting;

import org.chromium.base.lifetime.Destroyable;
import org.chromium.base.shared_preferences.SharedPreferencesManager;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.lifecycle.PauseResumeWithNativeObserver;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.price_tracking.PriceDropNotificationManager;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.components.commerce.core.ShoppingService;

import java.util.concurrent.TimeUnit;

/**
 * Commerce Subscriptions Service. TODO(crbug.com/40245507): This service is now only used to manage
 * implicit tracking and to record notification metrics, both of which are Android-specific. The
 * ImplicitPriceDropSubscriptionsManager should be profile-independent and we should decouple
 * subscriptions and notifications. Some logic here like observing Android activity lifecycle can be
 * moved to ShoppingServiceFactory.
 */
public class CommerceSubscriptionsService implements Destroyable {
    @VisibleForTesting
    public static final String CHROME_MANAGED_SUBSCRIPTIONS_TIMESTAMP =
            ChromePreferenceKeys.COMMERCE_SUBSCRIPTIONS_CHROME_MANAGED_TIMESTAMP;

    private final SharedPreferencesManager mSharedPreferencesManager;
    private final PriceDropNotificationManager mPriceDropNotificationManager;
    private ImplicitPriceDropSubscriptionsManager mImplicitPriceDropSubscriptionsManager;
    private ActivityLifecycleDispatcher mActivityLifecycleDispatcher;
    private PauseResumeWithNativeObserver mPauseResumeWithNativeObserver;
    private ShoppingService mShoppingService;

    /** Creates a new instance. */
    CommerceSubscriptionsService(
            ShoppingService shoppingService,
            PriceDropNotificationManager priceDropNotificationManager) {
        mShoppingService = shoppingService;
        mSharedPreferencesManager = ChromeSharedPreferences.getInstance();
        mPriceDropNotificationManager = priceDropNotificationManager;
    }

    /** Performs any deferred startup tasks required by {@link Subscriptions}. */
    public void initDeferredStartupForActivity(
            TabModelSelector tabModelSelector,
            ActivityLifecycleDispatcher activityLifecycleDispatcher) {
        mActivityLifecycleDispatcher = activityLifecycleDispatcher;
        mPauseResumeWithNativeObserver =
                new PauseResumeWithNativeObserver() {
                    @Override
                    public void onResumeWithNative() {
                        maybeRecordMetricsAndInitializeSubscriptions();
                    }

                    @Override
                    public void onPauseWithNative() {}
                };
        mActivityLifecycleDispatcher.register(mPauseResumeWithNativeObserver);

        if (CommerceSubscriptionsServiceConfig.isImplicitSubscriptionsEnabled()
                && mImplicitPriceDropSubscriptionsManager == null) {
            mImplicitPriceDropSubscriptionsManager =
                    new ImplicitPriceDropSubscriptionsManager(tabModelSelector, mShoppingService);
        }
    }

    /**
     * Cleans up internal resources. Currently this method calls SubscriptionsManagerImpl#destroy.
     */
    @Override
    public void destroy() {
        if (mActivityLifecycleDispatcher != null) {
            mActivityLifecycleDispatcher.unregister(mPauseResumeWithNativeObserver);
        }
        if (mImplicitPriceDropSubscriptionsManager != null) {
            mImplicitPriceDropSubscriptionsManager.destroy();
            mImplicitPriceDropSubscriptionsManager = null;
        }
    }

    private void maybeRecordMetricsAndInitializeSubscriptions() {
        if (System.currentTimeMillis()
                        - mSharedPreferencesManager.readLong(
                                CHROME_MANAGED_SUBSCRIPTIONS_TIMESTAMP, -1)
                < TimeUnit.SECONDS.toMillis(
                        CommerceSubscriptionsServiceConfig.getStaleTabLowerBoundSeconds())) {
            return;
        }
        mSharedPreferencesManager.writeLong(
                CHROME_MANAGED_SUBSCRIPTIONS_TIMESTAMP, System.currentTimeMillis());
        if (!mShoppingService.isShoppingListEligible()) return;
        recordMetricsForEligibleAccount();
        if (mImplicitPriceDropSubscriptionsManager != null) {
            mImplicitPriceDropSubscriptionsManager.initializeSubscriptions();
        }
    }

    private void recordMetricsForEligibleAccount() {
        // Record notification opt-in metrics.
        mPriceDropNotificationManager.canPostNotificationWithMetricsRecorded();
        mPriceDropNotificationManager.recordMetricsForNotificationCounts();
    }

    void setImplicitSubscriptionsManagerForTesting(ImplicitPriceDropSubscriptionsManager manager) {
        mImplicitPriceDropSubscriptionsManager = manager;
    }
}