Loading core/java/android/app/SmsAppService.java +25 −4 Original line number Diff line number Diff line Loading @@ -24,21 +24,42 @@ import android.os.IBinder; * it so that the process is always running, which allows the app to have a persistent connection * to the server. * * <p>The service must have {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} * <p>The service must have an {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} * action in the intent handler, and be protected with * {@link android.Manifest.permission#BIND_SMS_APP_SERVICE}. However the service does not have to * be exported. * * <p>Apps can use * <p>The service must be associated with a non-main process, meaning it must have an * {@code android:process} tag in its manifest entry. * * <p>An app can use * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)} * to disable/enable the service. Apps should use it to disable the service when it no longer needs * to be running. * to disable or enable the service. An app should use it to disable the service when it no longer * needs to be running. * * <p>When the owner process crashes, the service will be re-bound automatically after a * back-off. * * <p>Note the process may still be killed if the system is under heavy memory pressure, in which * case the process will be re-started later. * * <p>Example: First, define a subclass in the application: * <pre> * public class MySmsAppService extends SmsAppService { * } * </pre> * Then, declare it in its {@code AndroidManifest.xml}: * <pre> * <service * android:name=".MySmsAppService" * android:exported="false" * android:process=":persistent" * android:permission="android.permission.BIND_SMS_APP_SERVICE"> * <intent-filter> * <action android:name="android.telephony.action.SMS_APP_SERVICE" /> * </intent-filter> * </service> * </pre> */ public class SmsAppService extends Service { private final ISmsAppService mImpl; Loading services/core/java/com/android/server/am/PersistentConnection.java +93 −3 Original line number Diff line number Diff line Loading @@ -59,6 +59,8 @@ import java.io.PrintWriter; * know what to do when the service component has gone missing, for example. If the user of this * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind} * explicitly. * * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java */ public abstract class PersistentConnection<T> { private final Object mLock = new Object(); Loading @@ -76,6 +78,7 @@ public abstract class PersistentConnection<T> { private final long mRebindBackoffMs; private final double mRebindBackoffIncrease; private final long mRebindMaxBackoffMs; private final long mResetBackoffDelay; private long mReconnectTime; Loading Loading @@ -109,6 +112,9 @@ public abstract class PersistentConnection<T> { @GuardedBy("mLock") private int mNumBindingDied; @GuardedBy("mLock") private long mLastConnectedTime; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Loading @@ -127,7 +133,10 @@ public abstract class PersistentConnection<T> { mNumConnected++; mIsConnected = true; mLastConnectedTime = injectUptimeMillis(); mService = asInterface(service); scheduleStableCheckLocked(); } } Loading @@ -140,6 +149,9 @@ public abstract class PersistentConnection<T> { mNumDisconnected++; cleanUpConnectionLocked(); // Note we won't increase the rebind timeout here, because we don't explicitly // rebind in this case. } } Loading Loading @@ -168,7 +180,8 @@ public abstract class PersistentConnection<T> { public PersistentConnection(@NonNull String tag, @NonNull Context context, @NonNull Handler handler, int userId, @NonNull ComponentName componentName, long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds, long resetBackoffDelay) { mTag = tag; mContext = context; mHandler = handler; Loading @@ -178,6 +191,7 @@ public abstract class PersistentConnection<T> { mRebindBackoffMs = rebindBackoffSeconds * 1000; mRebindBackoffIncrease = rebindBackoffIncrease; mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000; mResetBackoffDelay = resetBackoffDelay * 1000; mNextBackoffMs = mRebindBackoffMs; } Loading Loading @@ -242,6 +256,42 @@ public abstract class PersistentConnection<T> { } } /** Return the next back-off time */ public long getNextBackoffMs() { synchronized (mLock) { return mNextBackoffMs; } } /** Return the number of times the connected callback called. */ public int getNumConnected() { synchronized (mLock) { return mNumConnected; } } /** Return the number of times the disconnected callback called. */ public int getNumDisconnected() { synchronized (mLock) { return mNumDisconnected; } } /** Return the number of times the binding died callback called. */ public int getNumBindingDied() { synchronized (mLock) { return mNumBindingDied; } } @GuardedBy("mLock") private void resetBackoffLocked() { if (mNextBackoffMs != mRebindBackoffMs) { mNextBackoffMs = mRebindBackoffMs; Log.i(mTag, "Backoff reset to " + mNextBackoffMs); } } @GuardedBy("mLock") public final void bindInnerLocked(boolean resetBackoff) { unscheduleRebindLocked(); Loading @@ -251,9 +301,10 @@ public abstract class PersistentConnection<T> { } mBound = true; unscheduleStableCheckLocked(); if (resetBackoff) { // Note this is the only place we reset the backoff time. mNextBackoffMs = mRebindBackoffMs; resetBackoffLocked(); } final Intent service = new Intent().setComponent(mComponentName); Loading Loading @@ -287,6 +338,8 @@ public abstract class PersistentConnection<T> { private void cleanUpConnectionLocked() { mIsConnected = false; mService = null; unscheduleStableCheckLocked(); } /** Loading @@ -297,6 +350,7 @@ public abstract class PersistentConnection<T> { mShouldBeBound = false; unbindLocked(); unscheduleStableCheckLocked(); } } Loading Loading @@ -338,6 +392,33 @@ public abstract class PersistentConnection<T> { } } private final Runnable mStableCheck = this::stableConnectionCheck; private void stableConnectionCheck() { synchronized (mLock) { final long now = injectUptimeMillis(); final long timeRemaining = (mLastConnectedTime + mResetBackoffDelay) - now; if (DEBUG) { Log.d(mTag, "stableConnectionCheck: bound=" + mBound + " connected=" + mIsConnected + " remaining=" + timeRemaining); } if (mBound && mIsConnected && timeRemaining <= 0) { resetBackoffLocked(); } } } @GuardedBy("mLock") private void unscheduleStableCheckLocked() { injectRemoveCallbacks(mStableCheck); } @GuardedBy("mLock") private void scheduleStableCheckLocked() { unscheduleStableCheckLocked(); injectPostAtTime(mStableCheck, injectUptimeMillis() + mResetBackoffDelay); } /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */ protected abstract T asInterface(IBinder binder); Loading Loading @@ -367,6 +448,10 @@ public abstract class PersistentConnection<T> { pw.print(mNumDisconnected); pw.print(" Died: "); pw.print(mNumBindingDied); if (mIsConnected) { pw.print(" Duration: "); TimeUtils.formatDuration((injectUptimeMillis() - mLastConnectedTime), pw); } pw.println(); } } Loading Loading @@ -406,6 +491,11 @@ public abstract class PersistentConnection<T> { return mBindForBackoffRunnable; } @VisibleForTesting Runnable getStableCheckRunnableForTest() { return mStableCheck; } @VisibleForTesting boolean shouldBeBoundForTest() { return mShouldBeBound; Loading services/core/java/com/android/server/appbinding/AppBindingConstants.java +37 −3 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.server.appbinding; import android.content.Context; import android.util.KeyValueListParser; import android.util.Slog; Loading @@ -24,7 +25,7 @@ import java.util.concurrent.TimeUnit; /** * Constants that are configurable via the global settings for {@link AppBindingService}. */ class AppBindingConstants { public class AppBindingConstants { private static final String TAG = AppBindingService.TAG; private static final String SERVICE_RECONNECT_BACKOFF_SEC_KEY = Loading @@ -36,6 +37,12 @@ class AppBindingConstants { private static final String SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY = "service_reconnect_max_backoff_sec"; private static final String SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY = "service_stable_connection_threshold_sec"; private static final String SMS_APP_BIND_FLAGS_KEY = "sms_app_bind_flags"; public final String sourceSettings; /** Loading @@ -54,6 +61,16 @@ class AppBindingConstants { */ public final long SERVICE_RECONNECT_MAX_BACKOFF_SEC; /** * If a connection lasts more than this duration, we reset the re-connect back-off time. */ public final long SERVICE_STABLE_CONNECTION_THRESHOLD_SEC; /** * Extra binding flags for SMS service. */ public final int SMS_APP_BIND_FLAGS; private AppBindingConstants(String settings) { sourceSettings = settings; Loading @@ -67,13 +84,20 @@ class AppBindingConstants { } long serviceReconnectBackoffSec = parser.getLong( SERVICE_RECONNECT_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1)); SERVICE_RECONNECT_BACKOFF_SEC_KEY, 10); double serviceReconnectBackoffIncrease = parser.getFloat( SERVICE_RECONNECT_BACKOFF_INCREASE_KEY, 2f); long serviceReconnectMaxBackoffSec = parser.getLong( SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.DAYS.toSeconds(1)); SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1)); int smsAppBindFlags = parser.getInt( SMS_APP_BIND_FLAGS_KEY, Context.BIND_NOT_VISIBLE | Context.BIND_FOREGROUND_SERVICE); long serviceStableConnectionThresholdSec = parser.getLong( SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY, TimeUnit.MINUTES.toSeconds(2)); // Set minimum: 5 seconds. serviceReconnectBackoffSec = Math.max(5, serviceReconnectBackoffSec); Loading @@ -89,6 +113,8 @@ class AppBindingConstants { SERVICE_RECONNECT_BACKOFF_SEC = serviceReconnectBackoffSec; SERVICE_RECONNECT_BACKOFF_INCREASE = serviceReconnectBackoffIncrease; SERVICE_RECONNECT_MAX_BACKOFF_SEC = serviceReconnectMaxBackoffSec; SERVICE_STABLE_CONNECTION_THRESHOLD_SEC = serviceStableConnectionThresholdSec; SMS_APP_BIND_FLAGS = smsAppBindFlags; } /** Loading Loading @@ -116,5 +142,13 @@ class AppBindingConstants { pw.print(prefix); pw.print(" SERVICE_RECONNECT_MAX_BACKOFF_SEC: "); pw.println(SERVICE_RECONNECT_MAX_BACKOFF_SEC); pw.print(prefix); pw.print(" SERVICE_STABLE_CONNECTION_THRESHOLD_SEC: "); pw.println(SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); pw.print(prefix); pw.print(" SMS_APP_BIND_FLAGS: 0x"); pw.println(Integer.toHexString(SMS_APP_BIND_FLAGS)); } } services/core/java/com/android/server/appbinding/AppBindingService.java +35 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.AppGlobals; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; Loading Loading @@ -58,10 +59,11 @@ import java.util.function.Consumer; * * <p>As of android Q, we only use it for the default SMS app. * * TODO Unit tests * TODO How do we handle force stop?? * TODO Change OOM adjustment to 200 or so * TODO Only allow it when the service is associated with a secondary process. * Relevant tests: * atest CtsAppBindingHostTestCases * * TODO Maybe handle force-stop differently. Right now we just get "binder died" and re-bind * after a timeout. b/116813347 */ public class AppBindingService extends Binder { public static final String TAG = "AppBindingService"; Loading Loading @@ -91,17 +93,25 @@ public class AppBindingService extends Binder { public IPackageManager getIPackageManager() { return AppGlobals.getPackageManager(); } public String getGlobalSettingString(ContentResolver resolver, String key) { return Settings.Global.getString(resolver, key); } } /** * {@link SystemService} for this service. */ public static final class Lifecycle extends SystemService { public static class Lifecycle extends SystemService { final AppBindingService mService; public Lifecycle(Context context) { this(context, new Injector()); } Lifecycle(Context context, Injector injector) { super(context); mService = new AppBindingService(new Injector(), context); mService = new AppBindingService(injector, context); } @Override Loading Loading @@ -171,7 +181,6 @@ public class AppBindingService extends Binder { packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); packageFilter.addDataScheme("package"); packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); Loading @@ -197,7 +206,7 @@ public class AppBindingService extends Binder { }; private void refreshConstants() { final String newSetting = Settings.Global.getString( final String newSetting = mInjector.getGlobalSettingString( mContext.getContentResolver(), Global.APP_BINDING_CONSTANTS); synchronized (mLock) { Loading @@ -215,6 +224,9 @@ public class AppBindingService extends Binder { final BroadcastReceiver mPackageUserMonitor = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Broadcast received: " + intent); } final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId == UserHandle.USER_NULL) { Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); Loading Loading @@ -454,14 +466,15 @@ public class AppBindingService extends Binder { super(TAG, context, handler, userId, componentName, constants.SERVICE_RECONNECT_BACKOFF_SEC, constants.SERVICE_RECONNECT_BACKOFF_INCREASE, constants.SERVICE_RECONNECT_MAX_BACKOFF_SEC); constants.SERVICE_RECONNECT_MAX_BACKOFF_SEC, constants.SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); mFinder = finder; mConstants = constants; } @Override protected int getBindFlags() { return Context.BIND_FOREGROUND_SERVICE; return mFinder.getBindFlags(mConstants); } @Override Loading Loading @@ -535,9 +548,21 @@ public class AppBindingService extends Binder { pw.print(conn.isBound() ? "bound" : "not-bound"); pw.print(","); pw.print(conn.isConnected() ? "connected" : "not-connected"); pw.print(",#con="); pw.print(conn.getNumConnected()); pw.print(",#dis="); pw.print(conn.getNumDisconnected()); pw.print(",#died="); pw.print(conn.getNumBindingDied()); pw.print(",backoff="); pw.print(conn.getNextBackoffMs()); pw.println(); } forAllAppsLocked((app) -> app.dumpSimple(pw)); } } AppBindingConstants getConstantsForTest() { return mConstants; } } services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java +35 −11 Original line number Diff line number Diff line Loading @@ -24,10 +24,12 @@ import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.IBinder; import android.os.IInterface; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.appbinding.AppBindingConstants; import com.android.server.appbinding.AppBindingService; import com.android.server.appbinding.AppBindingUtils; Loading @@ -51,13 +53,13 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten private final Object mLock = new Object(); @GuardedBy("mLock") private final SparseArray<String> mTargetPackages = new SparseArray(1); private final SparseArray<String> mTargetPackages = new SparseArray(4); @GuardedBy("mLock") private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(1); private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(4); @GuardedBy("mLock") private final SparseArray<String> mLastMessages = new SparseArray(1); private final SparseArray<String> mLastMessages = new SparseArray(4); public AppServiceFinder(Context context, BiConsumer<AppServiceFinder, Integer> listener, Loading @@ -80,6 +82,7 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten synchronized (mLock) { mTargetPackages.delete(userId); mTargetServices.delete(userId); mLastMessages.delete(userId); } } Loading @@ -91,6 +94,7 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten synchronized (mLock) { mTargetPackages.put(userId, null); mTargetServices.put(userId, null); mLastMessages.put(userId, null); final String targetPackage = getTargetPackage(userId); if (DEBUG) { Loading Loading @@ -118,11 +122,18 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten final String message = errorMessage.toString(); mLastMessages.put(userId, message); if (DEBUG) { // This log is optional because findService() already did Log.e(). Slog.w(TAG, getAppDescription() + " package " + targetPackage + " u" + userId + " " + message); } return null; } final String error = validateService(service); if (error != null) { mLastMessages.put(userId, error); Log.e(TAG, error); return null; } final String message = "Valid service found"; mLastMessages.put(userId, message); Loading Loading @@ -156,6 +167,17 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten @NonNull protected abstract String getServicePermission(); /** * Subclass can implement it to decide whether to accept a service (by returning null) or not * (by returning an error message.) */ protected String validateService(ServiceInfo service) { return null; } /** Return the bind flags for this service. */ public abstract int getBindFlags(AppBindingConstants constants); /** Dumpsys support. */ public void dump(String prefix, PrintWriter pw) { pw.print(prefix); Loading @@ -165,24 +187,25 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten synchronized (mLock) { for (int i = 0; i < mTargetPackages.size(); i++) { final int userId = mTargetPackages.keyAt(i); pw.print(prefix); pw.print(" User: "); pw.print(mTargetPackages.keyAt(i)); pw.print(userId); pw.println(); pw.print(prefix); pw.print(" Package: "); pw.print(mTargetPackages.valueAt(i)); pw.print(mTargetPackages.get(userId)); pw.println(); pw.print(prefix); pw.print(" Service: "); pw.print(mTargetServices.valueAt(i)); pw.print(mTargetServices.get(userId)); pw.println(); pw.print(prefix); pw.print(" Message: "); pw.print(mLastMessages.valueAt(i)); pw.print(mLastMessages.get(userId)); pw.println(); } } Loading @@ -192,16 +215,17 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten public void dumpSimple(PrintWriter pw) { synchronized (mLock) { for (int i = 0; i < mTargetPackages.size(); i++) { final int userId = mTargetPackages.keyAt(i); pw.print("finder,"); pw.print(getAppDescription()); pw.print(","); pw.print(mTargetPackages.keyAt(i)); // User-id pw.print(userId); pw.print(","); pw.print(mTargetPackages.valueAt(i)); pw.print(mTargetPackages.get(userId)); pw.print(","); pw.print(mTargetServices.valueAt(i)); pw.print(mTargetServices.get(userId)); pw.print(","); pw.print(mLastMessages.valueAt(i)); pw.print(mLastMessages.get(userId)); pw.println(); } } Loading Loading
core/java/android/app/SmsAppService.java +25 −4 Original line number Diff line number Diff line Loading @@ -24,21 +24,42 @@ import android.os.IBinder; * it so that the process is always running, which allows the app to have a persistent connection * to the server. * * <p>The service must have {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} * <p>The service must have an {@link android.telephony.TelephonyManager#ACTION_SMS_APP_SERVICE} * action in the intent handler, and be protected with * {@link android.Manifest.permission#BIND_SMS_APP_SERVICE}. However the service does not have to * be exported. * * <p>Apps can use * <p>The service must be associated with a non-main process, meaning it must have an * {@code android:process} tag in its manifest entry. * * <p>An app can use * {@link android.content.pm.PackageManager#setComponentEnabledSetting(ComponentName, int, int)} * to disable/enable the service. Apps should use it to disable the service when it no longer needs * to be running. * to disable or enable the service. An app should use it to disable the service when it no longer * needs to be running. * * <p>When the owner process crashes, the service will be re-bound automatically after a * back-off. * * <p>Note the process may still be killed if the system is under heavy memory pressure, in which * case the process will be re-started later. * * <p>Example: First, define a subclass in the application: * <pre> * public class MySmsAppService extends SmsAppService { * } * </pre> * Then, declare it in its {@code AndroidManifest.xml}: * <pre> * <service * android:name=".MySmsAppService" * android:exported="false" * android:process=":persistent" * android:permission="android.permission.BIND_SMS_APP_SERVICE"> * <intent-filter> * <action android:name="android.telephony.action.SMS_APP_SERVICE" /> * </intent-filter> * </service> * </pre> */ public class SmsAppService extends Service { private final ISmsAppService mImpl; Loading
services/core/java/com/android/server/am/PersistentConnection.java +93 −3 Original line number Diff line number Diff line Loading @@ -59,6 +59,8 @@ import java.io.PrintWriter; * know what to do when the service component has gone missing, for example. If the user of this * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind} * explicitly. * * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java */ public abstract class PersistentConnection<T> { private final Object mLock = new Object(); Loading @@ -76,6 +78,7 @@ public abstract class PersistentConnection<T> { private final long mRebindBackoffMs; private final double mRebindBackoffIncrease; private final long mRebindMaxBackoffMs; private final long mResetBackoffDelay; private long mReconnectTime; Loading Loading @@ -109,6 +112,9 @@ public abstract class PersistentConnection<T> { @GuardedBy("mLock") private int mNumBindingDied; @GuardedBy("mLock") private long mLastConnectedTime; private final ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { Loading @@ -127,7 +133,10 @@ public abstract class PersistentConnection<T> { mNumConnected++; mIsConnected = true; mLastConnectedTime = injectUptimeMillis(); mService = asInterface(service); scheduleStableCheckLocked(); } } Loading @@ -140,6 +149,9 @@ public abstract class PersistentConnection<T> { mNumDisconnected++; cleanUpConnectionLocked(); // Note we won't increase the rebind timeout here, because we don't explicitly // rebind in this case. } } Loading Loading @@ -168,7 +180,8 @@ public abstract class PersistentConnection<T> { public PersistentConnection(@NonNull String tag, @NonNull Context context, @NonNull Handler handler, int userId, @NonNull ComponentName componentName, long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds) { long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds, long resetBackoffDelay) { mTag = tag; mContext = context; mHandler = handler; Loading @@ -178,6 +191,7 @@ public abstract class PersistentConnection<T> { mRebindBackoffMs = rebindBackoffSeconds * 1000; mRebindBackoffIncrease = rebindBackoffIncrease; mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000; mResetBackoffDelay = resetBackoffDelay * 1000; mNextBackoffMs = mRebindBackoffMs; } Loading Loading @@ -242,6 +256,42 @@ public abstract class PersistentConnection<T> { } } /** Return the next back-off time */ public long getNextBackoffMs() { synchronized (mLock) { return mNextBackoffMs; } } /** Return the number of times the connected callback called. */ public int getNumConnected() { synchronized (mLock) { return mNumConnected; } } /** Return the number of times the disconnected callback called. */ public int getNumDisconnected() { synchronized (mLock) { return mNumDisconnected; } } /** Return the number of times the binding died callback called. */ public int getNumBindingDied() { synchronized (mLock) { return mNumBindingDied; } } @GuardedBy("mLock") private void resetBackoffLocked() { if (mNextBackoffMs != mRebindBackoffMs) { mNextBackoffMs = mRebindBackoffMs; Log.i(mTag, "Backoff reset to " + mNextBackoffMs); } } @GuardedBy("mLock") public final void bindInnerLocked(boolean resetBackoff) { unscheduleRebindLocked(); Loading @@ -251,9 +301,10 @@ public abstract class PersistentConnection<T> { } mBound = true; unscheduleStableCheckLocked(); if (resetBackoff) { // Note this is the only place we reset the backoff time. mNextBackoffMs = mRebindBackoffMs; resetBackoffLocked(); } final Intent service = new Intent().setComponent(mComponentName); Loading Loading @@ -287,6 +338,8 @@ public abstract class PersistentConnection<T> { private void cleanUpConnectionLocked() { mIsConnected = false; mService = null; unscheduleStableCheckLocked(); } /** Loading @@ -297,6 +350,7 @@ public abstract class PersistentConnection<T> { mShouldBeBound = false; unbindLocked(); unscheduleStableCheckLocked(); } } Loading Loading @@ -338,6 +392,33 @@ public abstract class PersistentConnection<T> { } } private final Runnable mStableCheck = this::stableConnectionCheck; private void stableConnectionCheck() { synchronized (mLock) { final long now = injectUptimeMillis(); final long timeRemaining = (mLastConnectedTime + mResetBackoffDelay) - now; if (DEBUG) { Log.d(mTag, "stableConnectionCheck: bound=" + mBound + " connected=" + mIsConnected + " remaining=" + timeRemaining); } if (mBound && mIsConnected && timeRemaining <= 0) { resetBackoffLocked(); } } } @GuardedBy("mLock") private void unscheduleStableCheckLocked() { injectRemoveCallbacks(mStableCheck); } @GuardedBy("mLock") private void scheduleStableCheckLocked() { unscheduleStableCheckLocked(); injectPostAtTime(mStableCheck, injectUptimeMillis() + mResetBackoffDelay); } /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */ protected abstract T asInterface(IBinder binder); Loading Loading @@ -367,6 +448,10 @@ public abstract class PersistentConnection<T> { pw.print(mNumDisconnected); pw.print(" Died: "); pw.print(mNumBindingDied); if (mIsConnected) { pw.print(" Duration: "); TimeUtils.formatDuration((injectUptimeMillis() - mLastConnectedTime), pw); } pw.println(); } } Loading Loading @@ -406,6 +491,11 @@ public abstract class PersistentConnection<T> { return mBindForBackoffRunnable; } @VisibleForTesting Runnable getStableCheckRunnableForTest() { return mStableCheck; } @VisibleForTesting boolean shouldBeBoundForTest() { return mShouldBeBound; Loading
services/core/java/com/android/server/appbinding/AppBindingConstants.java +37 −3 Original line number Diff line number Diff line Loading @@ -15,6 +15,7 @@ */ package com.android.server.appbinding; import android.content.Context; import android.util.KeyValueListParser; import android.util.Slog; Loading @@ -24,7 +25,7 @@ import java.util.concurrent.TimeUnit; /** * Constants that are configurable via the global settings for {@link AppBindingService}. */ class AppBindingConstants { public class AppBindingConstants { private static final String TAG = AppBindingService.TAG; private static final String SERVICE_RECONNECT_BACKOFF_SEC_KEY = Loading @@ -36,6 +37,12 @@ class AppBindingConstants { private static final String SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY = "service_reconnect_max_backoff_sec"; private static final String SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY = "service_stable_connection_threshold_sec"; private static final String SMS_APP_BIND_FLAGS_KEY = "sms_app_bind_flags"; public final String sourceSettings; /** Loading @@ -54,6 +61,16 @@ class AppBindingConstants { */ public final long SERVICE_RECONNECT_MAX_BACKOFF_SEC; /** * If a connection lasts more than this duration, we reset the re-connect back-off time. */ public final long SERVICE_STABLE_CONNECTION_THRESHOLD_SEC; /** * Extra binding flags for SMS service. */ public final int SMS_APP_BIND_FLAGS; private AppBindingConstants(String settings) { sourceSettings = settings; Loading @@ -67,13 +84,20 @@ class AppBindingConstants { } long serviceReconnectBackoffSec = parser.getLong( SERVICE_RECONNECT_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1)); SERVICE_RECONNECT_BACKOFF_SEC_KEY, 10); double serviceReconnectBackoffIncrease = parser.getFloat( SERVICE_RECONNECT_BACKOFF_INCREASE_KEY, 2f); long serviceReconnectMaxBackoffSec = parser.getLong( SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.DAYS.toSeconds(1)); SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1)); int smsAppBindFlags = parser.getInt( SMS_APP_BIND_FLAGS_KEY, Context.BIND_NOT_VISIBLE | Context.BIND_FOREGROUND_SERVICE); long serviceStableConnectionThresholdSec = parser.getLong( SERVICE_STABLE_CONNECTION_THRESHOLD_SEC_KEY, TimeUnit.MINUTES.toSeconds(2)); // Set minimum: 5 seconds. serviceReconnectBackoffSec = Math.max(5, serviceReconnectBackoffSec); Loading @@ -89,6 +113,8 @@ class AppBindingConstants { SERVICE_RECONNECT_BACKOFF_SEC = serviceReconnectBackoffSec; SERVICE_RECONNECT_BACKOFF_INCREASE = serviceReconnectBackoffIncrease; SERVICE_RECONNECT_MAX_BACKOFF_SEC = serviceReconnectMaxBackoffSec; SERVICE_STABLE_CONNECTION_THRESHOLD_SEC = serviceStableConnectionThresholdSec; SMS_APP_BIND_FLAGS = smsAppBindFlags; } /** Loading Loading @@ -116,5 +142,13 @@ class AppBindingConstants { pw.print(prefix); pw.print(" SERVICE_RECONNECT_MAX_BACKOFF_SEC: "); pw.println(SERVICE_RECONNECT_MAX_BACKOFF_SEC); pw.print(prefix); pw.print(" SERVICE_STABLE_CONNECTION_THRESHOLD_SEC: "); pw.println(SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); pw.print(prefix); pw.print(" SMS_APP_BIND_FLAGS: 0x"); pw.println(Integer.toHexString(SMS_APP_BIND_FLAGS)); } }
services/core/java/com/android/server/appbinding/AppBindingService.java +35 −10 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.app.AppGlobals; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; Loading Loading @@ -58,10 +59,11 @@ import java.util.function.Consumer; * * <p>As of android Q, we only use it for the default SMS app. * * TODO Unit tests * TODO How do we handle force stop?? * TODO Change OOM adjustment to 200 or so * TODO Only allow it when the service is associated with a secondary process. * Relevant tests: * atest CtsAppBindingHostTestCases * * TODO Maybe handle force-stop differently. Right now we just get "binder died" and re-bind * after a timeout. b/116813347 */ public class AppBindingService extends Binder { public static final String TAG = "AppBindingService"; Loading Loading @@ -91,17 +93,25 @@ public class AppBindingService extends Binder { public IPackageManager getIPackageManager() { return AppGlobals.getPackageManager(); } public String getGlobalSettingString(ContentResolver resolver, String key) { return Settings.Global.getString(resolver, key); } } /** * {@link SystemService} for this service. */ public static final class Lifecycle extends SystemService { public static class Lifecycle extends SystemService { final AppBindingService mService; public Lifecycle(Context context) { this(context, new Injector()); } Lifecycle(Context context, Injector injector) { super(context); mService = new AppBindingService(new Injector(), context); mService = new AppBindingService(injector, context); } @Override Loading Loading @@ -171,7 +181,6 @@ public class AppBindingService extends Binder { packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); packageFilter.addDataScheme("package"); packageFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY); Loading @@ -197,7 +206,7 @@ public class AppBindingService extends Binder { }; private void refreshConstants() { final String newSetting = Settings.Global.getString( final String newSetting = mInjector.getGlobalSettingString( mContext.getContentResolver(), Global.APP_BINDING_CONSTANTS); synchronized (mLock) { Loading @@ -215,6 +224,9 @@ public class AppBindingService extends Binder { final BroadcastReceiver mPackageUserMonitor = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Broadcast received: " + intent); } final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); if (userId == UserHandle.USER_NULL) { Slog.w(TAG, "Intent broadcast does not contain user handle: " + intent); Loading Loading @@ -454,14 +466,15 @@ public class AppBindingService extends Binder { super(TAG, context, handler, userId, componentName, constants.SERVICE_RECONNECT_BACKOFF_SEC, constants.SERVICE_RECONNECT_BACKOFF_INCREASE, constants.SERVICE_RECONNECT_MAX_BACKOFF_SEC); constants.SERVICE_RECONNECT_MAX_BACKOFF_SEC, constants.SERVICE_STABLE_CONNECTION_THRESHOLD_SEC); mFinder = finder; mConstants = constants; } @Override protected int getBindFlags() { return Context.BIND_FOREGROUND_SERVICE; return mFinder.getBindFlags(mConstants); } @Override Loading Loading @@ -535,9 +548,21 @@ public class AppBindingService extends Binder { pw.print(conn.isBound() ? "bound" : "not-bound"); pw.print(","); pw.print(conn.isConnected() ? "connected" : "not-connected"); pw.print(",#con="); pw.print(conn.getNumConnected()); pw.print(",#dis="); pw.print(conn.getNumDisconnected()); pw.print(",#died="); pw.print(conn.getNumBindingDied()); pw.print(",backoff="); pw.print(conn.getNextBackoffMs()); pw.println(); } forAllAppsLocked((app) -> app.dumpSimple(pw)); } } AppBindingConstants getConstantsForTest() { return mConstants; } }
services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java +35 −11 Original line number Diff line number Diff line Loading @@ -24,10 +24,12 @@ import android.content.pm.ServiceInfo; import android.os.Handler; import android.os.IBinder; import android.os.IInterface; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import com.android.server.appbinding.AppBindingConstants; import com.android.server.appbinding.AppBindingService; import com.android.server.appbinding.AppBindingUtils; Loading @@ -51,13 +53,13 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten private final Object mLock = new Object(); @GuardedBy("mLock") private final SparseArray<String> mTargetPackages = new SparseArray(1); private final SparseArray<String> mTargetPackages = new SparseArray(4); @GuardedBy("mLock") private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(1); private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(4); @GuardedBy("mLock") private final SparseArray<String> mLastMessages = new SparseArray(1); private final SparseArray<String> mLastMessages = new SparseArray(4); public AppServiceFinder(Context context, BiConsumer<AppServiceFinder, Integer> listener, Loading @@ -80,6 +82,7 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten synchronized (mLock) { mTargetPackages.delete(userId); mTargetServices.delete(userId); mLastMessages.delete(userId); } } Loading @@ -91,6 +94,7 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten synchronized (mLock) { mTargetPackages.put(userId, null); mTargetServices.put(userId, null); mLastMessages.put(userId, null); final String targetPackage = getTargetPackage(userId); if (DEBUG) { Loading Loading @@ -118,11 +122,18 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten final String message = errorMessage.toString(); mLastMessages.put(userId, message); if (DEBUG) { // This log is optional because findService() already did Log.e(). Slog.w(TAG, getAppDescription() + " package " + targetPackage + " u" + userId + " " + message); } return null; } final String error = validateService(service); if (error != null) { mLastMessages.put(userId, error); Log.e(TAG, error); return null; } final String message = "Valid service found"; mLastMessages.put(userId, message); Loading Loading @@ -156,6 +167,17 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten @NonNull protected abstract String getServicePermission(); /** * Subclass can implement it to decide whether to accept a service (by returning null) or not * (by returning an error message.) */ protected String validateService(ServiceInfo service) { return null; } /** Return the bind flags for this service. */ public abstract int getBindFlags(AppBindingConstants constants); /** Dumpsys support. */ public void dump(String prefix, PrintWriter pw) { pw.print(prefix); Loading @@ -165,24 +187,25 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten synchronized (mLock) { for (int i = 0; i < mTargetPackages.size(); i++) { final int userId = mTargetPackages.keyAt(i); pw.print(prefix); pw.print(" User: "); pw.print(mTargetPackages.keyAt(i)); pw.print(userId); pw.println(); pw.print(prefix); pw.print(" Package: "); pw.print(mTargetPackages.valueAt(i)); pw.print(mTargetPackages.get(userId)); pw.println(); pw.print(prefix); pw.print(" Service: "); pw.print(mTargetServices.valueAt(i)); pw.print(mTargetServices.get(userId)); pw.println(); pw.print(prefix); pw.print(" Message: "); pw.print(mLastMessages.valueAt(i)); pw.print(mLastMessages.get(userId)); pw.println(); } } Loading @@ -192,16 +215,17 @@ public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType exten public void dumpSimple(PrintWriter pw) { synchronized (mLock) { for (int i = 0; i < mTargetPackages.size(); i++) { final int userId = mTargetPackages.keyAt(i); pw.print("finder,"); pw.print(getAppDescription()); pw.print(","); pw.print(mTargetPackages.keyAt(i)); // User-id pw.print(userId); pw.print(","); pw.print(mTargetPackages.valueAt(i)); pw.print(mTargetPackages.get(userId)); pw.print(","); pw.print(mTargetServices.valueAt(i)); pw.print(mTargetServices.get(userId)); pw.print(","); pw.print(mLastMessages.valueAt(i)); pw.print(mLastMessages.get(userId)); pw.println(); } } Loading