// Copyright 2015 The Chromium Authors. All rights reserved.
// 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.precache;
import android.content.Context;
import org.chromium.base.Log;
import org.chromium.base.ThreadUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import java.util.EnumSet;
/** Class that interacts with the PrecacheManager to control precache cycles. */
public abstract class PrecacheLauncher {
private static final String TAG = "Precache";
private static final PrecacheLauncher sInstance = new PrecacheLauncher() {
/** A null implementation, as it is not needed by clients of sInstance. */
@Override
protected void onPrecacheCompleted(boolean tryAgainSoon) {}
};
/** Returns the singleton instance of PrecacheLauncher. */
public static PrecacheLauncher get() {
return sInstance;
}
/** Pointer to the native PrecacheLauncher object. Set to 0 when uninitialized. */
private long mNativePrecacheLauncher;
/**
* Initialized by updateEnabled to call updateEnabledSync when the sync backend is initialized.
* Only accessed on the UI thread.
*/
private ProfileSyncService.SyncStateChangedListener mListener = null;
/**
* Boolean failure indicators, reflecting the state of the last call to updatePrecachingEnabled.
* Access must occur on the UI thread. Values default to false -- so if mCalled is false, the
* value of the other booleans is not necessarily valid.
*/
private boolean mCalled = false;
private boolean mSyncInitialized = false;
private boolean mNetworkPredictionsAllowed = false;
private boolean mShouldRun = false;
/** Destroy the native PrecacheLauncher, releasing the memory that it was using. */
public void destroy() {
if (mNativePrecacheLauncher != 0) {
nativeDestroy(mNativePrecacheLauncher);
mNativePrecacheLauncher = 0;
}
}
/** Starts a precache cycle. */
public void start() {
// Lazily initialize the native PrecacheLauncher.
if (mNativePrecacheLauncher == 0) {
mNativePrecacheLauncher = nativeInit();
}
nativeStart(mNativePrecacheLauncher);
}
/** Cancel the precache cycle if one is ongoing. */
public void cancel() {
// Lazily initialize the native PrecacheLauncher.
if (mNativePrecacheLauncher == 0) {
mNativePrecacheLauncher = nativeInit();
}
nativeCancel(mNativePrecacheLauncher);
}
/**
* Called when a precache cycle completes.
*
* @param tryAgainSoon true iff the precache failed to start due to a transient error and should
* be attempted again soon
*/
protected abstract void onPrecacheCompleted(boolean tryAgainSoon);
/**
* Called by native code when the precache cycle completes. This method exists because an
* abstract method cannot be directly called from native.
*
* @param tryAgainSoon true iff the precache failed to start due to a transient error and should
* be attempted again soon
*/
@CalledByNative
private void onPrecacheCompletedCallback(boolean tryAgainSoon) {
onPrecacheCompleted(tryAgainSoon);
}
/**
* Updates the PrecacheController with whether conditions are right for precaching. All of
* the following must be true:
*
* <ul>
* <li>The predictive network actions preference is enabled.</li>
* <li>Sync is enabled for sessions and it is not encrypted with a secondary passphrase.</li>
* <li>Either the Precache field trial or the precache commandline flag is enabled.</li>
* </ul>
*
* This should be called only after the sync backend has been initialized. Must be called on the
* UI thread.
*
* @param context any context within the application
*/
private void updateEnabledSync(Context context) {
// PrefServiceBridge.getInstance() and nativeShouldRun() can only be executed on the UI
// thread.
ThreadUtils.assertOnUiThread();
boolean networkPredictionEnabledPref =
PrefServiceBridge.getInstance().getNetworkPredictionEnabled();
boolean shouldRun = nativeShouldRun();
mNetworkPredictionsAllowed = networkPredictionEnabledPref;
mShouldRun = shouldRun;
PrecacheController.setIsPrecachingEnabled(
context, networkPredictionEnabledPref && shouldRun);
Log.v(TAG, "updateEnabledSync complete");
}
/**
* If precaching is enabled, then allow the PrecacheController to be launched and signal Chrome
* when conditions are right to start precaching. If precaching is disabled, prevent the
* PrecacheController from ever starting.
*
* @param context any context within the application
*/
@VisibleForTesting
void updateEnabled(final Context context) {
Log.v(TAG, "updateEnabled starting");
ThreadUtils.postOnUiThread(new Runnable() {
@Override
public void run() {
mCalled = true;
final ProfileSyncService sync = ProfileSyncService.get();
if (mListener == null && sync != null) {
mListener = new ProfileSyncService.SyncStateChangedListener() {
public void syncStateChanged() {
if (sync.isBackendInitialized()) {
mSyncInitialized = true;
updateEnabledSync(context);
}
}
};
sync.addSyncStateChangedListener(mListener);
}
if (mListener != null) {
// Call the listener once, in case the sync backend is already initialized.
mListener.syncStateChanged();
}
Log.v(TAG, "updateEnabled complete");
}
});
}
/**
* If precaching is enabled, then allow the PrecacheController to be launched and signal Chrome
* when conditions are right to start precaching. If precaching is disabled, prevent the
* PrecacheController from ever starting.
*
* @param context any context within the application
*/
public static void updatePrecachingEnabled(final Context context) {
sInstance.updateEnabled(context);
}
/** Returns the set of reasons that the "precache.is_precaching_enabled" pref is false. */
public EnumSet<FailureReason> failureReasons() {
ThreadUtils.assertOnUiThread();
EnumSet<FailureReason> reasons = EnumSet.noneOf(FailureReason.class);
if (!mCalled) reasons.add(FailureReason.UPDATE_PRECACHING_ENABLED_NEVER_CALLED);
if (!mSyncInitialized) reasons.add(FailureReason.SYNC_NOT_INITIALIZED);
if (!mNetworkPredictionsAllowed) {
reasons.add(FailureReason.PRERENDER_PRIVACY_PREFERENCE_NOT_ENABLED);
}
if (!mShouldRun) reasons.add(FailureReason.NATIVE_SHOULD_RUN_IS_FALSE);
return reasons;
}
private native long nativeInit();
private native void nativeDestroy(long nativePrecacheLauncher);
private native void nativeStart(long nativePrecacheLauncher);
private native void nativeCancel(long nativePrecacheLauncher);
@VisibleForTesting native boolean nativeShouldRun();
}