// 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.gcore; import org.chromium.base.Log; import org.chromium.base.TraceEvent; import org.chromium.base.VisibleForTesting; import org.chromium.base.annotations.RemovableInRelease; import java.util.concurrent.TimeUnit; /** * Base class for tasks which connects to Google Play Services using given GoogleApiClient, * performs action specified in doWhenConnected method, disconnects the client and cleans up * by invoking cleanUp method. * * <p> * Using the same client for tasks running in more than one thread is a serious error, as * the state can then be modified while other threads are still using the client. The * recommended way to use these tasks is with a {@link java.util.concurrent.ThreadPoolExecutor} * having a pool size of 1. * </p> * <p> * This class waits {@link #CONNECTION_TIMEOUT_MS} milliseconds for connection to be established. * If connection is unsuccessful then it will retry after {@link #CONNECTION_RETRY_TIME_MS} * milliseconds as long as Google Play Services is available. Number of retries is limited to * {@link #RETRY_NUMBER_LIMIT}. * </p> * * @param <T> type of {@link ChromeGoogleApiClient} to use for the tasks */ public abstract class ConnectedTask<T extends ChromeGoogleApiClient> implements Runnable { private static final String TAG = "GCore"; public static final long CONNECTION_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(5); public static final long CONNECTION_RETRY_TIME_MS = TimeUnit.SECONDS.toMillis(10); public static final int RETRY_NUMBER_LIMIT = 5; private final T mClient; private int mRetryNumber; /** * Used for logging and tracing. * <ul> * <li>Log format: "{logPrefix}| {{@link #getName()}} {message}"</li> * <li>Trace format: "ConnectedTask:{logPrefix}:{traceEventName}"</li> * </ul> */ private final String mLogPrefix; /** * @param client * @param logPrefix used for logging and tracing. */ public ConnectedTask(T client, String logPrefix) { assert logPrefix != null; mClient = client; mLogPrefix = logPrefix; } /** Creates a connected task with an empty log prefix. */ @VisibleForTesting public ConnectedTask(T client) { this(client, ""); } /** * Executed with client connected to Google Play Services. * This method is intended to be overridden by a subclass. */ protected abstract void doWhenConnected(T client); /** * Returns a name of a task. Implementations should not have side effects * as we want to have the logging related calls removed. */ @RemovableInRelease protected abstract String getName(); /** * Executed after doWhenConnected was done and client was disconnected. * May also be executed when Google Play Services is no longer available, which means connection * was unsuccessful and won't be retried. * This method is intended to be overridden by a subclass. */ protected void cleanUp() {} /** * Executed if the connection was unsuccessful. * This method is intended to be overridden by a subclass. */ protected void connectionFailed() {} @Override @VisibleForTesting public final void run() { TraceEvent.begin("GCore:" + mLogPrefix + ":run"); try { Log.d(TAG, "%s:%s started", mLogPrefix, getName()); if (mClient.connectWithTimeout(CONNECTION_TIMEOUT_MS)) { try { Log.d(TAG, "%s:%s connected", mLogPrefix, getName()); doWhenConnected(mClient); Log.d(TAG, "%s:%s finished", mLogPrefix, getName()); } finally { mClient.disconnect(); Log.d(TAG, "%s:%s disconnected", mLogPrefix, getName()); cleanUp(); Log.d(TAG, "%s:%s cleaned up", mLogPrefix, getName()); } } else { mRetryNumber++; if (mRetryNumber < RETRY_NUMBER_LIMIT && mClient.isGooglePlayServicesAvailable()) { Log.d(TAG, "%s:%s calling retry", mLogPrefix, getName()); retry(this, CONNECTION_RETRY_TIME_MS); } else { connectionFailed(); Log.d(TAG, "%s:%s number of retries exceeded", mLogPrefix, getName()); cleanUp(); Log.d(TAG, "%s:%s cleaned up", mLogPrefix, getName()); } } } catch (RuntimeException e) { Log.e(TAG, "%s:%s runtime exception %s: %s", mLogPrefix, getName(), e.getClass().getName(), e.getMessage()); throw e; } finally { TraceEvent.end("GCore:" + mLogPrefix + ":run"); } } /** Method to implement to determine how to run the retry task. */ protected abstract void retry(Runnable task, long delayMs); }