1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.util.service;
18 
19 import static com.android.systemui.util.service.dagger.ObservableServiceModule.BASE_RECONNECT_DELAY_MS;
20 import static com.android.systemui.util.service.dagger.ObservableServiceModule.MAX_RECONNECT_ATTEMPTS;
21 import static com.android.systemui.util.service.dagger.ObservableServiceModule.MIN_CONNECTION_DURATION_MS;
22 import static com.android.systemui.util.service.dagger.ObservableServiceModule.OBSERVER;
23 import static com.android.systemui.util.service.dagger.ObservableServiceModule.SERVICE_CONNECTION;
24 
25 import android.util.Log;
26 
27 import com.android.systemui.util.concurrency.DelayableExecutor;
28 import com.android.systemui.util.time.SystemClock;
29 
30 import javax.inject.Inject;
31 import javax.inject.Named;
32 
33 /**
34  * The {@link PersistentConnectionManager} is responsible for maintaining a connection to a
35  * {@link ObservableServiceConnection}.
36  * @param <T> The transformed connection type handled by the service.
37  */
38 public class PersistentConnectionManager<T> {
39     private static final String TAG = "PersistentConnManager";
40     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
41 
42     private final SystemClock mSystemClock;
43     private final DelayableExecutor mMainExecutor;
44     private final int mBaseReconnectDelayMs;
45     private final int mMaxReconnectAttempts;
46     private final int mMinConnectionDuration;
47     private final Observer mObserver;
48 
49     private int mReconnectAttempts = 0;
50     private Runnable mCurrentReconnectCancelable;
51 
52     private final ObservableServiceConnection<T> mConnection;
53 
54     private final Runnable mConnectRunnable = new Runnable() {
55         @Override
56         public void run() {
57             mCurrentReconnectCancelable = null;
58             mConnection.bind();
59         }
60     };
61 
62     private final Observer.Callback mObserverCallback = () -> initiateConnectionAttempt();
63 
64     private final ObservableServiceConnection.Callback mConnectionCallback =
65             new ObservableServiceConnection.Callback() {
66         private long mStartTime;
67 
68         @Override
69         public void onConnected(ObservableServiceConnection connection, Object proxy) {
70             mStartTime = mSystemClock.currentTimeMillis();
71         }
72 
73         @Override
74         public void onDisconnected(ObservableServiceConnection connection, int reason) {
75             // Do not attempt to reconnect if we were manually unbound
76             if (reason == ObservableServiceConnection.DISCONNECT_REASON_UNBIND) {
77                 return;
78             }
79 
80             if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
81                 initiateConnectionAttempt();
82             } else {
83                 scheduleConnectionAttempt();
84             }
85         }
86     };
87 
88     @Inject
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)89     public PersistentConnectionManager(
90             SystemClock clock,
91             DelayableExecutor mainExecutor,
92             @Named(SERVICE_CONNECTION) ObservableServiceConnection<T> serviceConnection,
93             @Named(MAX_RECONNECT_ATTEMPTS) int maxReconnectAttempts,
94             @Named(BASE_RECONNECT_DELAY_MS) int baseReconnectDelayMs,
95             @Named(MIN_CONNECTION_DURATION_MS) int minConnectionDurationMs,
96             @Named(OBSERVER) Observer observer) {
97         mSystemClock = clock;
98         mMainExecutor = mainExecutor;
99         mConnection = serviceConnection;
100         mObserver = observer;
101 
102         mMaxReconnectAttempts = maxReconnectAttempts;
103         mBaseReconnectDelayMs = baseReconnectDelayMs;
104         mMinConnectionDuration = minConnectionDurationMs;
105     }
106 
107     /**
108      * Begins the {@link PersistentConnectionManager} by connecting to the associated service.
109      */
start()110     public void start() {
111         mConnection.addCallback(mConnectionCallback);
112         mObserver.addCallback(mObserverCallback);
113         initiateConnectionAttempt();
114     }
115 
116     /**
117      * Brings down the {@link PersistentConnectionManager}, disconnecting from the service.
118      */
stop()119     public void stop() {
120         mConnection.removeCallback(mConnectionCallback);
121         mObserver.removeCallback(mObserverCallback);
122         mConnection.unbind();
123     }
124 
initiateConnectionAttempt()125     private void initiateConnectionAttempt() {
126         // Reset attempts
127         mReconnectAttempts = 0;
128 
129         // The first attempt is always a direct invocation rather than delayed.
130         mConnection.bind();
131     }
132 
scheduleConnectionAttempt()133     private void scheduleConnectionAttempt() {
134         // always clear cancelable if present.
135         if (mCurrentReconnectCancelable != null) {
136             mCurrentReconnectCancelable.run();
137             mCurrentReconnectCancelable = null;
138         }
139 
140         if (mReconnectAttempts >= mMaxReconnectAttempts) {
141             if (DEBUG) {
142                 Log.d(TAG, "exceeded max connection attempts.");
143             }
144             return;
145         }
146 
147         final long reconnectDelayMs =
148                 (long) Math.scalb(mBaseReconnectDelayMs, mReconnectAttempts);
149 
150         if (DEBUG) {
151             Log.d(TAG,
152                     "scheduling connection attempt in " + reconnectDelayMs + "milliseconds");
153         }
154 
155         mCurrentReconnectCancelable = mMainExecutor.executeDelayed(mConnectRunnable,
156                 reconnectDelayMs);
157 
158         mReconnectAttempts++;
159     }
160 }
161