blob: 3490b1d1895393aa008360101d4b9d676093eb83 [file] [log] [blame]
Makoto Onukifc73d792017-03-22 14:22:35 -07001/*
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 */
16package com.android.server.am;
17
18import android.annotation.NonNull;
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.os.Handler;
24import android.os.IBinder;
Makoto Onuki09c529a2017-05-01 10:05:28 -070025import android.os.SystemClock;
Makoto Onukifc73d792017-03-22 14:22:35 -070026import android.os.UserHandle;
Makoto Onuki87d260a2018-09-26 16:58:32 -070027import android.util.Log;
Makoto Onuki09c529a2017-05-01 10:05:28 -070028import android.util.TimeUtils;
Makoto Onukifc73d792017-03-22 14:22:35 -070029
30import com.android.internal.annotations.GuardedBy;
Makoto Onuki09c529a2017-05-01 10:05:28 -070031import com.android.internal.annotations.VisibleForTesting;
Makoto Onukifc73d792017-03-22 14:22:35 -070032
33import java.io.PrintWriter;
34
35/**
36 * Connects to a given service component on a given user.
37 *
Makoto Onuki09c529a2017-05-01 10:05:28 -070038 * - Call {@link #bind()} to create a connection.
39 * - Call {@link #unbind()} to disconnect. Make sure to disconnect when the user stops.
Makoto Onukifc73d792017-03-22 14:22:35 -070040 *
41 * Add onConnected/onDisconnected callbacks as needed.
Makoto Onuki09c529a2017-05-01 10:05:28 -070042 *
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.
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -070062 *
63 * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java
Makoto Onukifc73d792017-03-22 14:22:35 -070064 */
65public abstract class PersistentConnection<T> {
66 private final Object mLock = new Object();
67
Makoto Onuki09c529a2017-05-01 10:05:28 -070068 private final static boolean DEBUG = false;
69
Makoto Onukifc73d792017-03-22 14:22:35 -070070 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
Makoto Onuki09c529a2017-05-01 10:05:28 -070076 private long mNextBackoffMs;
77
78 private final long mRebindBackoffMs;
79 private final double mRebindBackoffIncrease;
80 private final long mRebindMaxBackoffMs;
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -070081 private final long mResetBackoffDelay;
Makoto Onuki09c529a2017-05-01 10:05:28 -070082
83 private long mReconnectTime;
84
85 // TODO too many booleans... Should clean up.
86
Makoto Onukifc73d792017-03-22 14:22:35 -070087 @GuardedBy("mLock")
Makoto Onuki09c529a2017-05-01 10:05:28 -070088 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;
Makoto Onukifc73d792017-03-22 14:22:35 -070099
100 @GuardedBy("mLock")
101 private boolean mIsConnected;
102
103 @GuardedBy("mLock")
104 private T mService;
105
Makoto Onuki87d260a2018-09-26 16:58:32 -0700106 @GuardedBy("mLock")
107 private int mNumConnected;
108
109 @GuardedBy("mLock")
110 private int mNumDisconnected;
111
112 @GuardedBy("mLock")
113 private int mNumBindingDied;
114
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700115 @GuardedBy("mLock")
116 private long mLastConnectedTime;
117
Makoto Onukifc73d792017-03-22 14:22:35 -0700118 private final ServiceConnection mServiceConnection = new ServiceConnection() {
119 @Override
120 public void onServiceConnected(ComponentName name, IBinder service) {
121 synchronized (mLock) {
Makoto Onuki09c529a2017-05-01 10:05:28 -0700122 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)
Makoto Onuki87d260a2018-09-26 16:58:32 -0700126 Log.w(mTag, "Connected: " + mComponentName.flattenToShortString()
Makoto Onuki09c529a2017-05-01 10:05:28 -0700127 + " u" + mUserId + " but not bound, ignore.");
128 return;
129 }
Makoto Onuki87d260a2018-09-26 16:58:32 -0700130 Log.i(mTag, "Connected: " + mComponentName.flattenToShortString()
Makoto Onukifc73d792017-03-22 14:22:35 -0700131 + " u" + mUserId);
132
Makoto Onuki87d260a2018-09-26 16:58:32 -0700133 mNumConnected++;
134
Makoto Onukifc73d792017-03-22 14:22:35 -0700135 mIsConnected = true;
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700136 mLastConnectedTime = injectUptimeMillis();
Makoto Onukifc73d792017-03-22 14:22:35 -0700137 mService = asInterface(service);
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700138
139 scheduleStableCheckLocked();
Makoto Onukifc73d792017-03-22 14:22:35 -0700140 }
141 }
142
143 @Override
144 public void onServiceDisconnected(ComponentName name) {
145 synchronized (mLock) {
Makoto Onuki87d260a2018-09-26 16:58:32 -0700146 Log.i(mTag, "Disconnected: " + mComponentName.flattenToShortString()
Makoto Onukifc73d792017-03-22 14:22:35 -0700147 + " u" + mUserId);
148
Makoto Onuki87d260a2018-09-26 16:58:32 -0700149 mNumDisconnected++;
150
Makoto Onukifc73d792017-03-22 14:22:35 -0700151 cleanUpConnectionLocked();
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700152
153 // Note we won't increase the rebind timeout here, because we don't explicitly
154 // rebind in this case.
Makoto Onukifc73d792017-03-22 14:22:35 -0700155 }
156 }
Makoto Onuki09c529a2017-05-01 10:05:28 -0700157
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?
Makoto Onuki87d260a2018-09-26 16:58:32 -0700164 Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
Makoto Onuki09c529a2017-05-01 10:05:28 -0700165 + " u" + mUserId + " but not bound, ignore.");
166 return;
167 }
168
Makoto Onuki87d260a2018-09-26 16:58:32 -0700169 Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
Makoto Onuki09c529a2017-05-01 10:05:28 -0700170 + " u" + mUserId);
Makoto Onuki87d260a2018-09-26 16:58:32 -0700171
172 mNumBindingDied++;
173
Makoto Onuki09c529a2017-05-01 10:05:28 -0700174 scheduleRebindLocked();
175 }
176 }
Makoto Onukifc73d792017-03-22 14:22:35 -0700177 };
178
Makoto Onuki09c529a2017-05-01 10:05:28 -0700179 private final Runnable mBindForBackoffRunnable = () -> bindForBackoff();
180
Makoto Onukifc73d792017-03-22 14:22:35 -0700181 public PersistentConnection(@NonNull String tag, @NonNull Context context,
Makoto Onuki09c529a2017-05-01 10:05:28 -0700182 @NonNull Handler handler, int userId, @NonNull ComponentName componentName,
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700183 long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds,
184 long resetBackoffDelay) {
Makoto Onukifc73d792017-03-22 14:22:35 -0700185 mTag = tag;
186 mContext = context;
187 mHandler = handler;
188 mUserId = userId;
189 mComponentName = componentName;
Makoto Onuki09c529a2017-05-01 10:05:28 -0700190
191 mRebindBackoffMs = rebindBackoffSeconds * 1000;
192 mRebindBackoffIncrease = rebindBackoffIncrease;
193 mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000;
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700194 mResetBackoffDelay = resetBackoffDelay * 1000;
Makoto Onuki09c529a2017-05-01 10:05:28 -0700195
196 mNextBackoffMs = mRebindBackoffMs;
Makoto Onukifc73d792017-03-22 14:22:35 -0700197 }
198
199 public final ComponentName getComponentName() {
200 return mComponentName;
201 }
202
Makoto Onuki87d260a2018-09-26 16:58:32 -0700203 public final int getUserId() {
204 return mUserId;
205 }
206
207 protected abstract int getBindFlags();
208
Makoto Onukifc73d792017-03-22 14:22:35 -0700209 /**
Makoto Onuki09c529a2017-05-01 10:05:28 -0700210 * @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 */
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 */
224 public final boolean isRebindScheduled() {
225 synchronized (mLock) {
226 return mRebindScheduled;
227 }
228 }
229
230 /**
Makoto Onukifc73d792017-03-22 14:22:35 -0700231 * @return whether connected.
232 */
233 public final boolean isConnected() {
234 synchronized (mLock) {
235 return mIsConnected;
236 }
237 }
238
239 /**
240 * @return the service binder interface.
241 */
242 public final T getServiceBinder() {
243 synchronized (mLock) {
244 return mService;
245 }
246 }
247
248 /**
249 * Connects to the service.
250 */
Makoto Onuki09c529a2017-05-01 10:05:28 -0700251 public final void bind() {
Makoto Onukifc73d792017-03-22 14:22:35 -0700252 synchronized (mLock) {
Makoto Onuki09c529a2017-05-01 10:05:28 -0700253 mShouldBeBound = true;
254
255 bindInnerLocked(/* resetBackoff= */ true);
256 }
257 }
258
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700259 /** Return the next back-off time */
260 public long getNextBackoffMs() {
261 synchronized (mLock) {
262 return mNextBackoffMs;
263 }
264 }
265
266 /** Return the number of times the connected callback called. */
267 public int getNumConnected() {
268 synchronized (mLock) {
269 return mNumConnected;
270 }
271 }
272
273 /** Return the number of times the disconnected callback called. */
274 public int getNumDisconnected() {
275 synchronized (mLock) {
276 return mNumDisconnected;
277 }
278 }
279
280 /** Return the number of times the binding died callback called. */
281 public int getNumBindingDied() {
282 synchronized (mLock) {
283 return mNumBindingDied;
284 }
285 }
286
287 @GuardedBy("mLock")
288 private void resetBackoffLocked() {
289 if (mNextBackoffMs != mRebindBackoffMs) {
290 mNextBackoffMs = mRebindBackoffMs;
291 Log.i(mTag, "Backoff reset to " + mNextBackoffMs);
292 }
293 }
294
Andreas Gampea36dc622018-02-05 17:19:22 -0800295 @GuardedBy("mLock")
Makoto Onuki09c529a2017-05-01 10:05:28 -0700296 public final void bindInnerLocked(boolean resetBackoff) {
297 unscheduleRebindLocked();
298
299 if (mBound) {
300 return;
301 }
302 mBound = true;
303
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700304 unscheduleStableCheckLocked();
305
Makoto Onuki09c529a2017-05-01 10:05:28 -0700306 if (resetBackoff) {
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700307 resetBackoffLocked();
Makoto Onuki09c529a2017-05-01 10:05:28 -0700308 }
309
310 final Intent service = new Intent().setComponent(mComponentName);
311
312 if (DEBUG) {
Makoto Onuki87d260a2018-09-26 16:58:32 -0700313 Log.d(mTag, "Attempting to connect to " + mComponentName);
Makoto Onuki09c529a2017-05-01 10:05:28 -0700314 }
315
316 final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
Makoto Onuki87d260a2018-09-26 16:58:32 -0700317 Context.BIND_AUTO_CREATE | getBindFlags(),
Makoto Onuki09c529a2017-05-01 10:05:28 -0700318 mHandler, UserHandle.of(mUserId));
319
320 if (!success) {
Makoto Onuki87d260a2018-09-26 16:58:32 -0700321 Log.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
Makoto Onuki09c529a2017-05-01 10:05:28 -0700322 + " failed.");
323 }
324 }
325
326 final void bindForBackoff() {
327 synchronized (mLock) {
328 if (!mShouldBeBound) {
329 // Race condition -- by the time we got here, unbind() has already been called.
Makoto Onukifc73d792017-03-22 14:22:35 -0700330 return;
331 }
Makoto Onukifc73d792017-03-22 14:22:35 -0700332
Makoto Onuki09c529a2017-05-01 10:05:28 -0700333 bindInnerLocked(/* resetBackoff= */ false);
Makoto Onukifc73d792017-03-22 14:22:35 -0700334 }
335 }
336
Andreas Gampea36dc622018-02-05 17:19:22 -0800337 @GuardedBy("mLock")
Makoto Onukifc73d792017-03-22 14:22:35 -0700338 private void cleanUpConnectionLocked() {
339 mIsConnected = false;
340 mService = null;
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700341
342 unscheduleStableCheckLocked();
Makoto Onukifc73d792017-03-22 14:22:35 -0700343 }
344
345 /**
346 * Disconnect from the service.
347 */
Makoto Onuki09c529a2017-05-01 10:05:28 -0700348 public final void unbind() {
Makoto Onukifc73d792017-03-22 14:22:35 -0700349 synchronized (mLock) {
Makoto Onuki09c529a2017-05-01 10:05:28 -0700350 mShouldBeBound = false;
Makoto Onukifc73d792017-03-22 14:22:35 -0700351
Makoto Onuki09c529a2017-05-01 10:05:28 -0700352 unbindLocked();
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700353 unscheduleStableCheckLocked();
Makoto Onuki09c529a2017-05-01 10:05:28 -0700354 }
355 }
356
Andreas Gampea36dc622018-02-05 17:19:22 -0800357 @GuardedBy("mLock")
Makoto Onuki09c529a2017-05-01 10:05:28 -0700358 private final void unbindLocked() {
359 unscheduleRebindLocked();
360
361 if (!mBound) {
362 return;
363 }
Makoto Onuki87d260a2018-09-26 16:58:32 -0700364 Log.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
Makoto Onuki09c529a2017-05-01 10:05:28 -0700365 mBound = false;
366 mContext.unbindService(mServiceConnection);
367
368 cleanUpConnectionLocked();
369 }
370
Andreas Gampea36dc622018-02-05 17:19:22 -0800371 @GuardedBy("mLock")
Makoto Onuki09c529a2017-05-01 10:05:28 -0700372 void unscheduleRebindLocked() {
373 injectRemoveCallbacks(mBindForBackoffRunnable);
374 mRebindScheduled = false;
375 }
376
Andreas Gampea36dc622018-02-05 17:19:22 -0800377 @GuardedBy("mLock")
Makoto Onuki09c529a2017-05-01 10:05:28 -0700378 void scheduleRebindLocked() {
379 unbindLocked();
380
381 if (!mRebindScheduled) {
Makoto Onuki87d260a2018-09-26 16:58:32 -0700382 Log.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)");
Makoto Onuki09c529a2017-05-01 10:05:28 -0700383
384 mReconnectTime = injectUptimeMillis() + mNextBackoffMs;
385
386 injectPostAtTime(mBindForBackoffRunnable, mReconnectTime);
387
388 mNextBackoffMs = Math.min(mRebindMaxBackoffMs,
389 (long) (mNextBackoffMs * mRebindBackoffIncrease));
390
391 mRebindScheduled = true;
Makoto Onukifc73d792017-03-22 14:22:35 -0700392 }
393 }
394
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700395 private final Runnable mStableCheck = this::stableConnectionCheck;
396
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")
412 private void unscheduleStableCheckLocked() {
413 injectRemoveCallbacks(mStableCheck);
414 }
415
416 @GuardedBy("mLock")
417 private void scheduleStableCheckLocked() {
418 unscheduleStableCheckLocked();
419 injectPostAtTime(mStableCheck, injectUptimeMillis() + mResetBackoffDelay);
420 }
421
Makoto Onukifc73d792017-03-22 14:22:35 -0700422 /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */
423 protected abstract T asInterface(IBinder binder);
424
425 public void dump(String prefix, PrintWriter pw) {
426 synchronized (mLock) {
427 pw.print(prefix);
428 pw.print(mComponentName.flattenToShortString());
Makoto Onuki87d260a2018-09-26 16:58:32 -0700429 pw.print(" u");
430 pw.print(mUserId);
431 pw.print(mBound ? " [bound]" : " [not bound]");
432 pw.print(mIsConnected ? " [connected]" : " [not connected]");
Makoto Onuki09c529a2017-05-01 10:05:28 -0700433 if (mRebindScheduled) {
Makoto Onuki87d260a2018-09-26 16:58:32 -0700434 pw.print(" reconnect in ");
Makoto Onuki09c529a2017-05-01 10:05:28 -0700435 TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw);
436 }
Makoto Onukifc73d792017-03-22 14:22:35 -0700437 pw.println();
Makoto Onuki09c529a2017-05-01 10:05:28 -0700438
439 pw.print(prefix);
440 pw.print(" Next backoff(sec): ");
441 pw.print(mNextBackoffMs / 1000);
Makoto Onuki87d260a2018-09-26 16:58:32 -0700442 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);
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700451 if (mIsConnected) {
452 pw.print(" Duration: ");
453 TimeUtils.formatDuration((injectUptimeMillis() - mLastConnectedTime), pw);
454 }
Makoto Onuki87d260a2018-09-26 16:58:32 -0700455 pw.println();
Makoto Onukifc73d792017-03-22 14:22:35 -0700456 }
457 }
Makoto Onuki09c529a2017-05-01 10:05:28 -0700458
459 @VisibleForTesting
460 void injectRemoveCallbacks(Runnable r) {
461 mHandler.removeCallbacks(r);
462 }
463
464 @VisibleForTesting
465 void injectPostAtTime(Runnable r, long uptimeMillis) {
466 mHandler.postAtTime(r, uptimeMillis);
467 }
468
469 @VisibleForTesting
470 long injectUptimeMillis() {
471 return SystemClock.uptimeMillis();
472 }
473
474 @VisibleForTesting
475 long getNextBackoffMsForTest() {
476 return mNextBackoffMs;
477 }
478
479 @VisibleForTesting
480 long getReconnectTimeForTest() {
481 return mReconnectTime;
482 }
483
484 @VisibleForTesting
485 ServiceConnection getServiceConnectionForTest() {
486 return mServiceConnection;
487 }
488
489 @VisibleForTesting
490 Runnable getBindForBackoffRunnableForTest() {
491 return mBindForBackoffRunnable;
492 }
493
494 @VisibleForTesting
Makoto Onuki8c7c5cc2018-10-02 09:29:58 -0700495 Runnable getStableCheckRunnableForTest() {
496 return mStableCheck;
497 }
498
499 @VisibleForTesting
Makoto Onuki09c529a2017-05-01 10:05:28 -0700500 boolean shouldBeBoundForTest() {
501 return mShouldBeBound;
502 }
Makoto Onukifc73d792017-03-22 14:22:35 -0700503}