blob: 292c062369c1de9be4d002a621c91548d9a35143 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.util.service;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
import android.util.Log;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.time.SystemClock;
import javax.inject.Inject;
import javax.inject.Named;
/**
* The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
* {@link ObservableServiceConnection}.
* @param <T> The transformed connection type handled by the service.
*/
public class PersistentConnectionManager<T> {
private static final String TAG = "PersistentConnManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final SystemClock mSystemClock;
private final DelayableExecutor mMainExecutor;
private final int mBaseReconnectDelayMs;
private final int mMaxReconnectAttempts;
private final int mMinConnectionDuration;
private final Observer mObserver;
private int mReconnectAttempts = 0;
private Runnable mCurrentReconnectCancelable;
private final ObservableServiceConnection<T> mConnection;
private final Runnable mConnectRunnable = new Runnable() {
@Override
public void run() {
mCurrentReconnectCancelable = null;
mConnection.bind();
}
};
private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
private final ObservableServiceConnection.Callback mConnectionCallback =
new ObservableServiceConnection.Callback() {
private long mStartTime;
@Override
public void onConnected(ObservableServiceConnection connection, Object proxy) {
mStartTime = mSystemClock.currentTimeMillis();
}
@Override
public void onDisconnected(ObservableServiceConnection connection, int reason) {
if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
initiateConnectionAttempt();
} else {
scheduleConnectionAttempt();
}
}
};
@Inject
public PersistentConnectionManager(
SystemClock clock,
DelayableExecutor mainExecutor,
@Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
@Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
@Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
@Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
@Named(OBSERVER) Observer observer) {
mSystemClock = clock;
mMainExecutor = mainExecutor;
mConnection = serviceConnection;
mObserver = observer;
mMaxReconnectAttempts = maxReconnectAttempts;
mBaseReconnectDelayMs = baseReconnectDelayMs;
mMinConnectionDuration = minConnectionDurationMs;
}
/**
* Begins the {@link PersistentConnectionManager} by connecting to the associated service.
*/
public void start() {
mConnection.addCallback(mConnectionCallback);
mObserver.addCallback(mObserverCallback);
initiateConnectionAttempt();
}
/**
* Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
*/
public void stop() {
mConnection.removeCallback(mConnectionCallback);
mObserver.removeCallback(mObserverCallback);
mConnection.unbind();
}
private void initiateConnectionAttempt() {
// Reset attempts
mReconnectAttempts = 0;
// The first attempt is always a direct invocation rather than delayed.
mConnection.bind();
}
private void scheduleConnectionAttempt() {
// always clear cancelable if present.
if (mCurrentReconnectCancelable != null) {
mCurrentReconnectCancelable.run();
mCurrentReconnectCancelable = null;
}
if (mReconnectAttempts >= mMaxReconnectAttempts) {
if (DEBUG) {
Log.d(TAG, "exceeded max connection attempts.");
}
return;
}
final long reconnectDelayMs =
(long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
if (DEBUG) {
Log.d(TAG,
"scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
}
mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
reconnectDelayMs);
mReconnectAttempts++;
}
}