1 /*
2  * Copyright (C) 2017 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 package com.android.server.am;
17 
18 import android.annotation.NonNull;
19 import android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.os.Handler;
24 import android.os.IBinder;
25 import android.os.SystemClock;
26 import android.os.UserHandle;
27 import android.util.Log;
28 import android.util.TimeUtils;
29 
30 import com.android.internal.annotations.GuardedBy;
31 import com.android.internal.annotations.VisibleForTesting;
32 
33 import java.io.PrintWriter;
34 
35 /**
36  * Connects to a given service component on a given user.
37  *
38  * - Call {@link #bind()} to create a connection.
39  * - Call {@link #unbind()} to disconnect.  Make sure to disconnect when the user stops.
40  *
41  * Add onConnected/onDisconnected callbacks as needed.
42  *
43  * When the target process gets killed (by OOM-killer, etc), then the activity manager will
44  * re-connect the connection automatically, in which case onServiceDisconnected() gets called
45  * and then onServiceConnected().
46  *
47  * However sometimes the activity manager just "kills" the connection -- like when the target
48  * package gets updated or the target process crashes multiple times in a row, in which case
49  * onBindingDied() gets called.  This class handles this case by re-connecting in the time
50  * {@link #mRebindBackoffMs}.  If this happens again, this class increases the back-off time
51  * by {@link #mRebindBackoffIncrease} and retry.  The back-off time is capped at
52  * {@link #mRebindMaxBackoffMs}.
53  *
54  * The back-off time will never be reset until {@link #unbind()} and {@link #bind()} are called
55  * explicitly.
56  *
57  * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to
58  * the target package being updated, this class won't reconnect.  This is because this class doesn't
59  * know what to do when the service component has gone missing, for example.  If the user of this
60  * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind}
61  * explicitly.
62  *
63  * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java
64  */
65 public abstract class PersistentConnection<T> {
66     private final Object mLock = new Object();
67 
68     private final static boolean DEBUG = false;
69 
70     private final String mTag;
71     private final Context mContext;
72     private final Handler mHandler;
73     private final int mUserId;
74     private final ComponentName mComponentName;
75 
76     private long mNextBackoffMs;
77 
78     private final long mRebindBackoffMs;
79     private final double mRebindBackoffIncrease;
80     private final long mRebindMaxBackoffMs;
81     private final long mResetBackoffDelay;
82 
83     private long mReconnectTime;
84 
85     // TODO too many booleans... Should clean up.
86 
87     @GuardedBy("mLock")
88     private boolean mBound;
89 
90     /**
91      * Whether {@link #bind()} has been called and {@link #unbind()} hasn't been yet; meaning this
92      * is the expected bind state from the caller's point of view.
93      */
94     @GuardedBy("mLock")
95     private boolean mShouldBeBound;
96 
97     @GuardedBy("mLock")
98     private boolean mRebindScheduled;
99 
100     @GuardedBy("mLock")
101     private boolean mIsConnected;
102 
103     @GuardedBy("mLock")
104     private T mService;
105 
106     @GuardedBy("mLock")
107     private int mNumConnected;
108 
109     @GuardedBy("mLock")
110     private int mNumDisconnected;
111 
112     @GuardedBy("mLock")
113     private int mNumBindingDied;
114 
115     @GuardedBy("mLock")
116     private long mLastConnectedTime;
117 
118     private final ServiceConnection mServiceConnection = new ServiceConnection() {
119         @Override
120         public void onServiceConnected(ComponentName name, IBinder service) {
121             synchronized (mLock) {
122                 if (!mBound) {
123                     // Callback came in after PersistentConnection.unbind() was called.
124                     // We just ignore this.
125                     // (We've already called unbindService() already in unbind)
126                     Log.w(mTag, "Connected: " + mComponentName.flattenToShortString()
127                             + " u" + mUserId + " but not bound, ignore.");
128                     return;
129                 }
130                 Log.i(mTag, "Connected: " + mComponentName.flattenToShortString()
131                         + " u" + mUserId);
132 
133                 mNumConnected++;
134 
135                 mIsConnected = true;
136                 mLastConnectedTime = injectUptimeMillis();
137                 mService = asInterface(service);
138 
139                 scheduleStableCheckLocked();
140             }
141         }
142 
143         @Override
144         public void onServiceDisconnected(ComponentName name) {
145             synchronized (mLock) {
146                 Log.i(mTag, "Disconnected: " + mComponentName.flattenToShortString()
147                         + " u" + mUserId);
148 
149                 mNumDisconnected++;
150 
151                 cleanUpConnectionLocked();
152 
153                 // Note we won't increase the rebind timeout here, because we don't explicitly
154                 // rebind in this case.
155             }
156         }
157 
158         @Override
159         public void onBindingDied(ComponentName name) {
160             // Activity manager gave up; we'll schedule a re-connect by ourselves.
161             synchronized (mLock) {
162                 if (!mBound) {
163                     // Callback came in late?
164                     Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
165                             + " u" + mUserId + " but not bound, ignore.");
166                     return;
167                 }
168 
169                 Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
170                         + " u" + mUserId);
171 
172                 mNumBindingDied++;
173 
174                 scheduleRebindLocked();
175             }
176         }
177     };
178 
179     private final Runnable mBindForBackoffRunnable = () -> bindForBackoff();
180 
PersistentConnection(@onNull String tag, @NonNull Context context, @NonNull Handler handler, int userId, @NonNull ComponentName componentName, long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds, long resetBackoffDelay)181     public PersistentConnection(@NonNull String tag, @NonNull Context context,
182             @NonNull Handler handler, int userId, @NonNull ComponentName componentName,
183             long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds,
184             long resetBackoffDelay) {
185         mTag = tag;
186         mContext = context;
187         mHandler = handler;
188         mUserId = userId;
189         mComponentName = componentName;
190 
191         mRebindBackoffMs = rebindBackoffSeconds * 1000;
192         mRebindBackoffIncrease = rebindBackoffIncrease;
193         mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000;
194         mResetBackoffDelay = resetBackoffDelay * 1000;
195 
196         mNextBackoffMs = mRebindBackoffMs;
197     }
198 
getComponentName()199     public final ComponentName getComponentName() {
200         return mComponentName;
201     }
202 
getUserId()203     public final int getUserId() {
204         return mUserId;
205     }
206 
getBindFlags()207     protected abstract int getBindFlags();
208 
209     /**
210      * @return whether {@link #bind()} has been called and {@link #unbind()} hasn't.
211      *
212      * Note when the AM gives up on connection, this class detects it and un-bind automatically,
213      * and schedule rebind, and {@link #isBound} returns false when it's waiting for a retry.
214      */
isBound()215     public final boolean isBound() {
216         synchronized (mLock) {
217             return mBound;
218         }
219     }
220 
221     /**
222      * @return whether re-bind is scheduled after the AM gives up on a connection.
223      */
isRebindScheduled()224     public final boolean isRebindScheduled() {
225         synchronized (mLock) {
226             return mRebindScheduled;
227         }
228     }
229 
230     /**
231      * @return whether connected.
232      */
isConnected()233     public final boolean isConnected() {
234         synchronized (mLock) {
235             return mIsConnected;
236         }
237     }
238 
239     /**
240      * @return the service binder interface.
241      */
getServiceBinder()242     public final T getServiceBinder() {
243         synchronized (mLock) {
244             return mService;
245         }
246     }
247 
248     /**
249      * Connects to the service.
250      */
bind()251     public final void bind() {
252         synchronized (mLock) {
253             mShouldBeBound = true;
254 
255             bindInnerLocked(/* resetBackoff= */ true);
256         }
257     }
258 
259     /** Return the next back-off time */
getNextBackoffMs()260     public long getNextBackoffMs() {
261         synchronized (mLock) {
262             return mNextBackoffMs;
263         }
264     }
265 
266     /** Return the number of times the connected callback called. */
getNumConnected()267     public int getNumConnected() {
268         synchronized (mLock) {
269             return mNumConnected;
270         }
271     }
272 
273     /** Return the number of times the disconnected callback called. */
getNumDisconnected()274     public int getNumDisconnected() {
275         synchronized (mLock) {
276             return mNumDisconnected;
277         }
278     }
279 
280     /** Return the number of times the binding died callback called. */
getNumBindingDied()281     public int getNumBindingDied() {
282         synchronized (mLock) {
283             return mNumBindingDied;
284         }
285     }
286 
287     @GuardedBy("mLock")
resetBackoffLocked()288     private void resetBackoffLocked() {
289         if (mNextBackoffMs != mRebindBackoffMs) {
290             mNextBackoffMs = mRebindBackoffMs;
291             Log.i(mTag, "Backoff reset to " + mNextBackoffMs);
292         }
293     }
294 
295     @GuardedBy("mLock")
bindInnerLocked(boolean resetBackoff)296     public final void bindInnerLocked(boolean resetBackoff) {
297         unscheduleRebindLocked();
298 
299         if (mBound) {
300             return;
301         }
302         mBound = true;
303 
304         unscheduleStableCheckLocked();
305 
306         if (resetBackoff) {
307             resetBackoffLocked();
308         }
309 
310         final Intent service = new Intent().setComponent(mComponentName);
311 
312         if (DEBUG) {
313             Log.d(mTag, "Attempting to connect to " + mComponentName);
314         }
315 
316         final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
317                 Context.BIND_AUTO_CREATE | getBindFlags(),
318                 mHandler, UserHandle.of(mUserId));
319 
320         if (!success) {
321             Log.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
322                     + " failed.");
323         }
324     }
325 
bindForBackoff()326     final void bindForBackoff() {
327         synchronized (mLock) {
328             if (!mShouldBeBound) {
329                 // Race condition -- by the time we got here, unbind() has already been called.
330                 return;
331             }
332 
333             bindInnerLocked(/* resetBackoff= */ false);
334         }
335     }
336 
337     @GuardedBy("mLock")
cleanUpConnectionLocked()338     private void cleanUpConnectionLocked() {
339         mIsConnected = false;
340         mService = null;
341 
342         unscheduleStableCheckLocked();
343     }
344 
345     /**
346      * Disconnect from the service.
347      */
unbind()348     public final void unbind() {
349         synchronized (mLock) {
350             mShouldBeBound = false;
351 
352             unbindLocked();
353             unscheduleStableCheckLocked();
354         }
355     }
356 
357     @GuardedBy("mLock")
unbindLocked()358     private final void unbindLocked() {
359         unscheduleRebindLocked();
360 
361         if (!mBound) {
362             return;
363         }
364         Log.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
365         mBound = false;
366         mContext.unbindService(mServiceConnection);
367 
368         cleanUpConnectionLocked();
369     }
370 
371     @GuardedBy("mLock")
unscheduleRebindLocked()372     void unscheduleRebindLocked() {
373         injectRemoveCallbacks(mBindForBackoffRunnable);
374         mRebindScheduled = false;
375     }
376 
377     @GuardedBy("mLock")
scheduleRebindLocked()378     void scheduleRebindLocked() {
379         unbindLocked();
380 
381         if (!mRebindScheduled) {
382             Log.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)");
383 
384             mReconnectTime = injectUptimeMillis() + mNextBackoffMs;
385 
386             injectPostAtTime(mBindForBackoffRunnable, mReconnectTime);
387 
388             mNextBackoffMs = Math.min(mRebindMaxBackoffMs,
389                     (long) (mNextBackoffMs * mRebindBackoffIncrease));
390 
391             mRebindScheduled = true;
392         }
393     }
394 
395     private final Runnable mStableCheck = this::stableConnectionCheck;
396 
stableConnectionCheck()397     private void stableConnectionCheck() {
398         synchronized (mLock) {
399             final long now = injectUptimeMillis();
400             final long timeRemaining = (mLastConnectedTime + mResetBackoffDelay) - now;
401             if (DEBUG) {
402                 Log.d(mTag, "stableConnectionCheck: bound=" + mBound + " connected=" + mIsConnected
403                         + " remaining=" + timeRemaining);
404             }
405             if (mBound && mIsConnected && timeRemaining <= 0) {
406                 resetBackoffLocked();
407             }
408         }
409     }
410 
411     @GuardedBy("mLock")
unscheduleStableCheckLocked()412     private void unscheduleStableCheckLocked() {
413         injectRemoveCallbacks(mStableCheck);
414     }
415 
416     @GuardedBy("mLock")
scheduleStableCheckLocked()417     private void scheduleStableCheckLocked() {
418         unscheduleStableCheckLocked();
419         injectPostAtTime(mStableCheck, injectUptimeMillis() + mResetBackoffDelay);
420     }
421 
422     /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */
asInterface(IBinder binder)423     protected abstract T asInterface(IBinder binder);
424 
dump(String prefix, PrintWriter pw)425     public void dump(String prefix, PrintWriter pw) {
426         synchronized (mLock) {
427             pw.print(prefix);
428             pw.print(mComponentName.flattenToShortString());
429             pw.print(" u");
430             pw.print(mUserId);
431             pw.print(mBound ? " [bound]" : " [not bound]");
432             pw.print(mIsConnected ? " [connected]" : " [not connected]");
433             if (mRebindScheduled) {
434                 pw.print(" reconnect in ");
435                 TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw);
436             }
437             pw.println();
438 
439             pw.print(prefix);
440             pw.print("  Next backoff(sec): ");
441             pw.print(mNextBackoffMs / 1000);
442             pw.println();
443 
444             pw.print(prefix);
445             pw.print("  Connected: ");
446             pw.print(mNumConnected);
447             pw.print("  Disconnected: ");
448             pw.print(mNumDisconnected);
449             pw.print("  Died: ");
450             pw.print(mNumBindingDied);
451             if (mIsConnected) {
452                 pw.print("  Duration: ");
453                 TimeUtils.formatDuration((injectUptimeMillis() - mLastConnectedTime), pw);
454             }
455             pw.println();
456         }
457     }
458 
459     @VisibleForTesting
injectRemoveCallbacks(Runnable r)460     void injectRemoveCallbacks(Runnable r) {
461         mHandler.removeCallbacks(r);
462     }
463 
464     @VisibleForTesting
injectPostAtTime(Runnable r, long uptimeMillis)465     void injectPostAtTime(Runnable r, long uptimeMillis) {
466         mHandler.postAtTime(r, uptimeMillis);
467     }
468 
469     @VisibleForTesting
injectUptimeMillis()470     long injectUptimeMillis() {
471         return SystemClock.uptimeMillis();
472     }
473 
474     @VisibleForTesting
getNextBackoffMsForTest()475     long getNextBackoffMsForTest() {
476         return mNextBackoffMs;
477     }
478 
479     @VisibleForTesting
getReconnectTimeForTest()480     long getReconnectTimeForTest() {
481         return mReconnectTime;
482     }
483 
484     @VisibleForTesting
getServiceConnectionForTest()485     ServiceConnection getServiceConnectionForTest() {
486         return mServiceConnection;
487     }
488 
489     @VisibleForTesting
getBindForBackoffRunnableForTest()490     Runnable getBindForBackoffRunnableForTest() {
491         return mBindForBackoffRunnable;
492     }
493 
494     @VisibleForTesting
getStableCheckRunnableForTest()495     Runnable getStableCheckRunnableForTest() {
496         return mStableCheck;
497     }
498 
499     @VisibleForTesting
shouldBeBoundForTest()500     boolean shouldBeBoundForTest() {
501         return mShouldBeBound;
502     }
503 }
504