summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/provider/Settings.java6
-rw-r--r--core/tests/coretests/src/android/provider/SettingsBackupTest.java1
-rw-r--r--services/core/java/com/android/server/am/PersistentConnection.java62
-rw-r--r--services/core/java/com/android/server/appbinding/AppBindingConstants.java120
-rw-r--r--services/core/java/com/android/server/appbinding/AppBindingService.java466
-rw-r--r--services/core/java/com/android/server/appbinding/AppBindingUtils.java89
-rw-r--r--services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java209
-rw-r--r--services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java95
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java55
-rw-r--r--services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java5
10 files changed, 1052 insertions, 56 deletions
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 1d3cf19e18f0..e1c0fe52de37 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10821,6 +10821,12 @@ public final class Settings {
= "activity_starts_logging_enabled";
/**
+ * @hide
+ * @see com.android.server.appbinding.AppBindingConstants
+ */
+ public static final String APP_BINDING_CONSTANTS = "app_binding_constants";
+
+ /**
* App ops specific settings.
* This is encoded as a key=value list, separated by commas. Ex:
*
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 8f0e76bdc0c9..d2420ee1ef10 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -114,6 +114,7 @@ public class SettingsBackupTest {
Settings.Global.ANOMALY_CONFIG_VERSION,
Settings.Global.APN_DB_UPDATE_CONTENT_URL,
Settings.Global.APN_DB_UPDATE_METADATA_URL,
+ Settings.Global.APP_BINDING_CONSTANTS,
Settings.Global.APP_IDLE_CONSTANTS,
Settings.Global.APP_OPS_CONSTANTS,
Settings.Global.APP_STANDBY_ENABLED,
diff --git a/services/core/java/com/android/server/am/PersistentConnection.java b/services/core/java/com/android/server/am/PersistentConnection.java
index c5edb26892f8..080fa2a2ec5e 100644
--- a/services/core/java/com/android/server/am/PersistentConnection.java
+++ b/services/core/java/com/android/server/am/PersistentConnection.java
@@ -24,7 +24,7 @@ import android.os.Handler;
import android.os.IBinder;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.util.Slog;
+import android.util.Log;
import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
@@ -100,6 +100,15 @@ public abstract class PersistentConnection<T> {
@GuardedBy("mLock")
private T mService;
+ @GuardedBy("mLock")
+ private int mNumConnected;
+
+ @GuardedBy("mLock")
+ private int mNumDisconnected;
+
+ @GuardedBy("mLock")
+ private int mNumBindingDied;
+
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
@@ -108,13 +117,15 @@ public abstract class PersistentConnection<T> {
// Callback came in after PersistentConnection.unbind() was called.
// We just ignore this.
// (We've already called unbindService() already in unbind)
- Slog.w(mTag, "Connected: " + mComponentName.flattenToShortString()
+ Log.w(mTag, "Connected: " + mComponentName.flattenToShortString()
+ " u" + mUserId + " but not bound, ignore.");
return;
}
- Slog.i(mTag, "Connected: " + mComponentName.flattenToShortString()
+ Log.i(mTag, "Connected: " + mComponentName.flattenToShortString()
+ " u" + mUserId);
+ mNumConnected++;
+
mIsConnected = true;
mService = asInterface(service);
}
@@ -123,9 +134,11 @@ public abstract class PersistentConnection<T> {
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mLock) {
- Slog.i(mTag, "Disconnected: " + mComponentName.flattenToShortString()
+ Log.i(mTag, "Disconnected: " + mComponentName.flattenToShortString()
+ " u" + mUserId);
+ mNumDisconnected++;
+
cleanUpConnectionLocked();
}
}
@@ -136,13 +149,16 @@ public abstract class PersistentConnection<T> {
synchronized (mLock) {
if (!mBound) {
// Callback came in late?
- Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
+ Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
+ " u" + mUserId + " but not bound, ignore.");
return;
}
- Slog.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
+ Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString()
+ " u" + mUserId);
+
+ mNumBindingDied++;
+
scheduleRebindLocked();
}
}
@@ -170,6 +186,12 @@ public abstract class PersistentConnection<T> {
return mComponentName;
}
+ public final int getUserId() {
+ return mUserId;
+ }
+
+ protected abstract int getBindFlags();
+
/**
* @return whether {@link #bind()} has been called and {@link #unbind()} hasn't.
*
@@ -237,15 +259,15 @@ public abstract class PersistentConnection<T> {
final Intent service = new Intent().setComponent(mComponentName);
if (DEBUG) {
- Slog.d(mTag, "Attempting to connect to " + mComponentName);
+ Log.d(mTag, "Attempting to connect to " + mComponentName);
}
final boolean success = mContext.bindServiceAsUser(service, mServiceConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+ Context.BIND_AUTO_CREATE | getBindFlags(),
mHandler, UserHandle.of(mUserId));
if (!success) {
- Slog.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
+ Log.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId
+ " failed.");
}
}
@@ -285,7 +307,7 @@ public abstract class PersistentConnection<T> {
if (!mBound) {
return;
}
- Slog.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
+ Log.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId);
mBound = false;
mContext.unbindService(mServiceConnection);
@@ -303,7 +325,7 @@ public abstract class PersistentConnection<T> {
unbindLocked();
if (!mRebindScheduled) {
- Slog.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)");
+ Log.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)");
mReconnectTime = injectUptimeMillis() + mNextBackoffMs;
@@ -323,10 +345,12 @@ public abstract class PersistentConnection<T> {
synchronized (mLock) {
pw.print(prefix);
pw.print(mComponentName.flattenToShortString());
- pw.print(mBound ? " [bound]" : " [not bound]");
- pw.print(mIsConnected ? " [connected]" : " [not connected]");
+ pw.print(" u");
+ pw.print(mUserId);
+ pw.print(mBound ? " [bound]" : " [not bound]");
+ pw.print(mIsConnected ? " [connected]" : " [not connected]");
if (mRebindScheduled) {
- pw.print(" reconnect in ");
+ pw.print(" reconnect in ");
TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw);
}
pw.println();
@@ -334,6 +358,16 @@ public abstract class PersistentConnection<T> {
pw.print(prefix);
pw.print(" Next backoff(sec): ");
pw.print(mNextBackoffMs / 1000);
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" Connected: ");
+ pw.print(mNumConnected);
+ pw.print(" Disconnected: ");
+ pw.print(mNumDisconnected);
+ pw.print(" Died: ");
+ pw.print(mNumBindingDied);
+ pw.println();
}
}
diff --git a/services/core/java/com/android/server/appbinding/AppBindingConstants.java b/services/core/java/com/android/server/appbinding/AppBindingConstants.java
new file mode 100644
index 000000000000..c2655a289b49
--- /dev/null
+++ b/services/core/java/com/android/server/appbinding/AppBindingConstants.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2018 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.server.appbinding;
+
+import android.util.KeyValueListParser;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Constants that are configurable via the global settings for {@link AppBindingService}.
+ */
+class AppBindingConstants {
+ private static final String TAG = AppBindingService.TAG;
+
+ private static final String SERVICE_RECONNECT_BACKOFF_SEC_KEY =
+ "service_reconnect_backoff_sec";
+
+ private static final String SERVICE_RECONNECT_BACKOFF_INCREASE_KEY =
+ "service_reconnect_backoff_increase";
+
+ private static final String SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY =
+ "service_reconnect_max_backoff_sec";
+
+ public final String sourceSettings;
+
+ /**
+ * The back-off before re-connecting, when a service binding died, due to the app
+ * crashing repeatedly.
+ */
+ public final long SERVICE_RECONNECT_BACKOFF_SEC;
+
+ /**
+ * The exponential back-off increase factor when a binding dies multiple times.
+ */
+ public final double SERVICE_RECONNECT_BACKOFF_INCREASE;
+
+ /**
+ * The max back-off
+ */
+ public final long SERVICE_RECONNECT_MAX_BACKOFF_SEC;
+
+ private AppBindingConstants(String settings) {
+ sourceSettings = settings;
+
+ final KeyValueListParser parser = new KeyValueListParser(',');
+ try {
+ parser.setString(settings);
+ } catch (IllegalArgumentException e) {
+ // Failed to parse the settings string, log this and move on
+ // with defaults.
+ Slog.e(TAG, "Bad setting: " + settings);
+ }
+
+ long serviceReconnectBackoffSec = parser.getLong(
+ SERVICE_RECONNECT_BACKOFF_SEC_KEY, TimeUnit.HOURS.toSeconds(1));
+
+ double serviceReconnectBackoffIncrease = parser.getFloat(
+ SERVICE_RECONNECT_BACKOFF_INCREASE_KEY, 2f);
+
+ long serviceReconnectMaxBackoffSec = parser.getLong(
+ SERVICE_RECONNECT_MAX_BACKOFF_SEC_KEY, TimeUnit.DAYS.toSeconds(1));
+
+ // Set minimum: 5 seconds.
+ serviceReconnectBackoffSec = Math.max(5, serviceReconnectBackoffSec);
+
+ // Set minimum: 1.0.
+ serviceReconnectBackoffIncrease =
+ Math.max(1, serviceReconnectBackoffIncrease);
+
+ // Make sure max >= default back off.
+ serviceReconnectMaxBackoffSec = Math.max(serviceReconnectBackoffSec,
+ serviceReconnectMaxBackoffSec);
+
+ SERVICE_RECONNECT_BACKOFF_SEC = serviceReconnectBackoffSec;
+ SERVICE_RECONNECT_BACKOFF_INCREASE = serviceReconnectBackoffIncrease;
+ SERVICE_RECONNECT_MAX_BACKOFF_SEC = serviceReconnectMaxBackoffSec;
+ }
+
+ /**
+ * Create a new instance from a settings string.
+ */
+ public static AppBindingConstants initializeFromString(String settings) {
+ return new AppBindingConstants(settings);
+ }
+
+ /**
+ * dumpsys support.
+ */
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.println("Constants:");
+
+ pw.print(prefix);
+ pw.print(" SERVICE_RECONNECT_BACKOFF_SEC: ");
+ pw.println(SERVICE_RECONNECT_BACKOFF_SEC);
+
+ pw.print(prefix);
+ pw.print(" SERVICE_RECONNECT_BACKOFF_INCREASE: ");
+ pw.println(SERVICE_RECONNECT_BACKOFF_INCREASE);
+
+ pw.print(prefix);
+ pw.print(" SERVICE_RECONNECT_MAX_BACKOFF_SEC: ");
+ pw.println(SERVICE_RECONNECT_MAX_BACKOFF_SEC);
+ }
+}
diff --git a/services/core/java/com/android/server/appbinding/AppBindingService.java b/services/core/java/com/android/server/appbinding/AppBindingService.java
index 91b3b21632c6..c5cb2a4f5814 100644
--- a/services/core/java/com/android/server/appbinding/AppBindingService.java
+++ b/services/core/java/com/android/server/appbinding/AppBindingService.java
@@ -16,26 +16,57 @@
package com.android.server.appbinding;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AppGlobals;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
+import android.database.ContentObserver;
+import android.net.Uri;
import android.os.Binder;
import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.provider.Settings.Global;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseBooleanArray;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.SystemService;
+import com.android.server.am.PersistentConnection;
+import com.android.server.appbinding.finders.AppServiceFinder;
+import com.android.server.appbinding.finders.SmsAppServiceFinder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.function.Consumer;
/**
* System server that keeps a binding to an app to keep it always running.
+ *
+ * <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.
*/
public class AppBindingService extends Binder {
public static final String TAG = "AppBindingService";
- private static final boolean DEBUG = false;
+ public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE
private final Object mLock = new Object();
@@ -44,6 +75,18 @@ public class AppBindingService extends Binder {
private final Handler mHandler;
private final IPackageManager mIPackageManager;
+ @GuardedBy("mLock")
+ private AppBindingConstants mConstants;
+
+ @GuardedBy("mLock")
+ private final SparseBooleanArray mRunningUsers = new SparseBooleanArray(2);
+
+ @GuardedBy("mLock")
+ private final ArrayList<AppServiceFinder> mApps = new ArrayList<>();
+
+ @GuardedBy("mLock")
+ private final ArrayList<AppServiceConnection> mConnections = new ArrayList<>();
+
static class Injector {
public IPackageManager getIPackageManager() {
return AppGlobals.getPackageManager();
@@ -51,7 +94,7 @@ public class AppBindingService extends Binder {
}
/**
- * System service interacts with this service via this class.
+ * {@link SystemService} for this service.
*/
public static final class Lifecycle extends SystemService {
final AppBindingService mService;
@@ -65,17 +108,436 @@ public class AppBindingService extends Binder {
public void onStart() {
publishBinderService(Context.APP_BINDING_SERVICE, mService);
}
+
+ @Override
+ public void onBootPhase(int phase) {
+ mService.onBootPhase(phase);
+ }
+
+ @Override
+ public void onStartUser(int userHandle) {
+ mService.onStartUser(userHandle);
+ }
+
+ @Override
+ public void onUnlockUser(int userId) {
+ mService.onUnlockUser(userId);
+ }
+
+ @Override
+ public void onStopUser(int userHandle) {
+ mService.onStopUser(userHandle);
+ }
}
private AppBindingService(Injector injector, Context context) {
mInjector = injector;
mContext = context;
+
mIPackageManager = injector.getIPackageManager();
+
mHandler = BackgroundThread.getHandler();
+ mApps.add(new SmsAppServiceFinder(context, this::onAppChanged, mHandler));
+
+ // Initialize with the default value to make it non-null.
+ mConstants = AppBindingConstants.initializeFromString("");
+ }
+
+ private void forAllAppsLocked(Consumer<AppServiceFinder> consumer) {
+ for (int i = 0; i < mApps.size(); i++) {
+ consumer.accept(mApps.get(i));
+ }
+ }
+
+ private void onBootPhase(int phase) {
+ if (DEBUG) {
+ Slog.d(TAG, "onBootPhase: " + phase);
+ }
+ switch (phase) {
+ case SystemService.PHASE_ACTIVITY_MANAGER_READY:
+ onPhaseActivityManagerReady();
+ break;
+ case SystemService.PHASE_THIRD_PARTY_APPS_CAN_START:
+ onPhaseThirdPartyAppsCanStart();
+ break;
+ }
+ }
+
+ /**
+ * Handle boot phase PHASE_ACTIVITY_MANAGER_READY.
+ */
+ private void onPhaseActivityManagerReady() {
+ final IntentFilter packageFilter = new IntentFilter();
+ 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);
+ mContext.registerReceiverAsUser(mPackageUserMonitor, UserHandle.ALL,
+ packageFilter, null, mHandler);
+
+ final IntentFilter userFilter = new IntentFilter();
+ userFilter.addAction(Intent.ACTION_USER_REMOVED);
+ mContext.registerReceiverAsUser(mPackageUserMonitor, UserHandle.ALL,
+ userFilter, null, mHandler);
+
+ mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+ Settings.Global.APP_BINDING_CONSTANTS), false, mSettingsObserver);
+
+ refreshConstants();
+ }
+
+ private final ContentObserver mSettingsObserver = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ refreshConstants();
+ }
+ };
+
+ private void refreshConstants() {
+ final String newSetting = Settings.Global.getString(
+ mContext.getContentResolver(), Global.APP_BINDING_CONSTANTS);
+
+ synchronized (mLock) {
+ if (TextUtils.equals(mConstants.sourceSettings, newSetting)) {
+ return;
+ }
+ Slog.i(TAG, "Updating constants with: " + newSetting);
+ mConstants = AppBindingConstants.initializeFromString(newSetting);
+
+ rebindAllLocked("settings update");
+ }
+ }
+
+ @VisibleForTesting
+ final BroadcastReceiver mPackageUserMonitor = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent 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);
+ return;
+ }
+
+ final String action = intent.getAction();
+
+ if (Intent.ACTION_USER_REMOVED.equals(action)) {
+ onUserRemoved(userId);
+ return;
+ }
+
+ final Uri intentUri = intent.getData();
+ final String packageName = (intentUri != null) ? intentUri.getSchemeSpecificPart()
+ : null;
+ if (packageName == null) {
+ Slog.w(TAG, "Intent broadcast does not contain package name: " + intent);
+ return;
+ }
+
+ final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
+
+ switch (action) {
+ case Intent.ACTION_PACKAGE_ADDED:
+ if (replacing) {
+ handlePackageAddedReplacing(packageName, userId);
+ }
+ break;
+ case Intent.ACTION_PACKAGE_REMOVED:
+ if (!replacing) {
+ handlePackageRemoved(packageName, userId);
+ }
+ break;
+ case Intent.ACTION_PACKAGE_CHANGED:
+ handlePackageChanged(packageName, userId);
+ break;
+ }
+ }
+ };
+
+ /**
+ * Handle boot phase PHASE_THIRD_PARTY_APPS_CAN_START.
+ */
+ private void onPhaseThirdPartyAppsCanStart() {
+ synchronized (mLock) {
+ forAllAppsLocked(AppServiceFinder::startMonitoring);
+ }
+ }
+
+ /** User lifecycle callback. */
+ private void onStartUser(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onStartUser: u" + userId);
+ }
+ synchronized (mLock) {
+ mRunningUsers.append(userId, true);
+ bindServicesLocked(userId, null, "user start");
+ }
+ }
+
+ /** User lifecycle callback. */
+ private void onUnlockUser(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onUnlockUser: u" + userId);
+ }
+ synchronized (mLock) {
+ bindServicesLocked(userId, null, "user unlock");
+ }
+ }
+
+ /** User lifecycle callback. */
+ private void onStopUser(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onStopUser: u" + userId);
+ }
+ synchronized (mLock) {
+ unbindServicesLocked(userId, null, "user stop");
+
+ mRunningUsers.delete(userId);
+ }
+ }
+
+ private void onUserRemoved(int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onUserRemoved: u" + userId);
+ }
+ synchronized (mLock) {
+ forAllAppsLocked((app) -> app.onUserRemoved(userId));
+
+ mRunningUsers.delete(userId);
+ }
+ }
+
+ /**
+ * Called when a target package changes; e.g. when the user changes the default SMS app.
+ */
+ private void onAppChanged(AppServiceFinder finder, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "onAppChanged: u" + userId + " " + finder.getAppDescription());
+ }
+ synchronized (mLock) {
+ final String reason = finder.getAppDescription() + " changed";
+ unbindServicesLocked(userId, finder, reason);
+ bindServicesLocked(userId, finder, reason);
+ }
+ }
+
+ @Nullable
+ private AppServiceFinder findFinderLocked(int userId, @NonNull String packageName) {
+ for (int i = 0; i < mApps.size(); i++) {
+ final AppServiceFinder app = mApps.get(i);
+ if (packageName.equals(app.getTargetPackage(userId))) {
+ return app;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private AppServiceConnection findConnectionLock(
+ int userId, @NonNull AppServiceFinder target) {
+ for (int i = 0; i < mConnections.size(); i++) {
+ final AppServiceConnection conn = mConnections.get(i);
+ if ((conn.getUserId() == userId) && (conn.getFinder() == target)) {
+ return conn;
+ }
+ }
+ return null;
+ }
+
+ private void handlePackageAddedReplacing(String packageName, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "handlePackageAddedReplacing: u" + userId + " " + packageName);
+ }
+ synchronized (mLock) {
+ final AppServiceFinder finder = findFinderLocked(userId, packageName);
+ if (finder != null) {
+ unbindServicesLocked(userId, finder, "package update");
+ bindServicesLocked(userId, finder, "package update");
+ }
+ }
+ }
+
+ private void handlePackageRemoved(String packageName, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "handlePackageRemoved: u" + userId + " " + packageName);
+ }
+ synchronized (mLock) {
+ final AppServiceFinder finder = findFinderLocked(userId, packageName);
+ if (finder != null) {
+ unbindServicesLocked(userId, finder, "package uninstall");
+ }
+ }
+ }
+
+ private void handlePackageChanged(String packageName, int userId) {
+ if (DEBUG) {
+ Slog.d(TAG, "handlePackageChanged: u" + userId + " " + packageName);
+ }
+ synchronized (mLock) {
+ final AppServiceFinder finder = findFinderLocked(userId, packageName);
+ if (finder != null) {
+ unbindServicesLocked(userId, finder, "package changed");
+ bindServicesLocked(userId, finder, "package changed");
+ }
+ }
+ }
+
+ private void rebindAllLocked(String reason) {
+ for (int i = 0; i < mRunningUsers.size(); i++) {
+ if (!mRunningUsers.valueAt(i)) {
+ continue;
+ }
+ final int userId = mRunningUsers.keyAt(i);
+
+ unbindServicesLocked(userId, null, reason);
+ bindServicesLocked(userId, null, reason);
+ }
+ }
+
+ private void bindServicesLocked(int userId, @Nullable AppServiceFinder target,
+ @NonNull String reasonForLog) {
+ for (int i = 0; i < mApps.size(); i++) {
+ final AppServiceFinder app = mApps.get(i);
+ if (target != null && target != app) {
+ continue;
+ }
+
+ // Disconnect from existing binding.
+ final AppServiceConnection existingConn = findConnectionLock(userId, app);
+ if (existingConn != null) {
+ unbindServicesLocked(userId, target, reasonForLog);
+ }
+
+ final ServiceInfo service = app.findService(userId, mIPackageManager);
+ if (service == null) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "bindServicesLocked: u" + userId + " " + app.getAppDescription()
+ + " binding " + service.getComponentName() + " for " + reasonForLog);
+ }
+ final AppServiceConnection conn =
+ new AppServiceConnection(mContext, userId, mConstants, mHandler,
+ app, service.getComponentName());
+ mConnections.add(conn);
+ conn.bind();
+ }
+ }
+
+ private void unbindServicesLocked(int userId, @Nullable AppServiceFinder target,
+ @NonNull String reasonForLog) {
+ for (int i = mConnections.size() - 1; i >= 0; i--) {
+ final AppServiceConnection conn = mConnections.get(i);
+ if ((conn.getUserId() != userId)
+ || (target != null && conn.getFinder() != target)) {
+ continue;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "unbindServicesLocked: u" + userId
+ + " " + conn.getFinder().getAppDescription()
+ + " unbinding " + conn.getComponentName() + " for " + reasonForLog);
+ }
+ mConnections.remove(i);
+ conn.unbind();
+ }
+ }
+
+ private static class AppServiceConnection extends PersistentConnection<IInterface> {
+ private final AppBindingConstants mConstants;
+ private final AppServiceFinder mFinder;
+
+ AppServiceConnection(Context context, int userId, AppBindingConstants constants,
+ Handler handler, AppServiceFinder finder,
+ @NonNull ComponentName componentName) {
+ super(TAG, context, handler, userId, componentName,
+ constants.SERVICE_RECONNECT_BACKOFF_SEC,
+ constants.SERVICE_RECONNECT_BACKOFF_INCREASE,
+ constants.SERVICE_RECONNECT_MAX_BACKOFF_SEC);
+ mFinder = finder;
+ mConstants = constants;
+ }
+
+ @Override
+ protected int getBindFlags() {
+ return Context.BIND_FOREGROUND_SERVICE;
+ }
+
+ @Override
+ protected IInterface asInterface(IBinder obj) {
+ return mFinder.asInterface(obj);
+ }
+
+ public AppServiceFinder getFinder() {
+ return mFinder;
+ }
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+
+ if (args.length > 0 && "-s".equals(args[0])) {
+ dumpSimple(pw);
+ return;
+ }
+
+ synchronized (mLock) {
+ mConstants.dump(" ", pw);
+
+ pw.println();
+ pw.print(" Running users:");
+ for (int i = 0; i < mRunningUsers.size(); i++) {
+ if (mRunningUsers.valueAt(i)) {
+ pw.print(" ");
+ pw.print(mRunningUsers.keyAt(i));
+ }
+ }
+
+ pw.println();
+ pw.println(" Connections:");
+ for (int i = 0; i < mConnections.size(); i++) {
+ final AppServiceConnection conn = mConnections.get(i);
+ pw.print(" App type: ");
+ pw.print(conn.getFinder().getAppDescription());
+ pw.println();
+
+ conn.dump(" ", pw);
+ }
+ if (mConnections.size() == 0) {
+ pw.println(" None:");
+ }
+
+ pw.println();
+ pw.println(" Finders:");
+ forAllAppsLocked((app) -> app.dump(" ", pw));
+ }
+ }
+
+ /**
+ * Print simple output for CTS.
+ */
+ private void dumpSimple(PrintWriter pw) {
+ synchronized (mLock) {
+ for (int i = 0; i < mConnections.size(); i++) {
+ final AppServiceConnection conn = mConnections.get(i);
+
+ pw.print("conn,");
+ pw.print(conn.getFinder().getAppDescription());
+ pw.print(",");
+ pw.print(conn.getUserId());
+ pw.print(",");
+ pw.print(conn.getComponentName().getPackageName());
+ pw.print(",");
+ pw.print(conn.getComponentName().getClassName());
+ pw.print(",");
+ pw.print(conn.isBound() ? "bound" : "not-bound");
+ pw.print(",");
+ pw.print(conn.isConnected() ? "connected" : "not-connected");
+ pw.println();
+ }
+ forAllAppsLocked((app) -> app.dumpSimple(pw));
+ }
}
}
diff --git a/services/core/java/com/android/server/appbinding/AppBindingUtils.java b/services/core/java/com/android/server/appbinding/AppBindingUtils.java
new file mode 100644
index 000000000000..fcbaecf7e059
--- /dev/null
+++ b/services/core/java/com/android/server/appbinding/AppBindingUtils.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2018 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.server.appbinding;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Intent;
+import android.content.pm.IPackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Utility class to find a persistent bound service within an app.
+ */
+public class AppBindingUtils {
+ private static final String TAG = "AppBindingUtils";
+ private AppBindingUtils() {
+ }
+
+ /**
+ * Find a service with the action {@code serviceAction} in the package {@code packageName}.
+ * Returns null in any of the following cases.
+ * - No service with the action is found.
+ * - More than 1 service with the action is found.
+ * - Found service is not protected with the permission {@code servicePermission}.
+ */
+ @Nullable
+ public static ServiceInfo findService(@NonNull String packageName, int userId,
+ String serviceAction, String servicePermission,
+ Class<?> serviceClassForLogging,
+ IPackageManager ipm,
+ StringBuilder errorMessage) {
+ final String simpleClassName = serviceClassForLogging.getSimpleName();
+ final Intent intent = new Intent(serviceAction);
+ intent.setPackage(packageName);
+
+ errorMessage.setLength(0); // Clear it.
+ try {
+ final ParceledListSlice<ResolveInfo> pls = ipm
+ .queryIntentServices(intent, null, /* flags=*/ 0, userId);
+ if (pls == null || pls.getList().size() == 0) {
+ errorMessage.append("Service with " + serviceAction + " not found.");
+ return null;
+ }
+ final List<ResolveInfo> list = pls.getList();
+ // Note if multiple services are found, that's an error, even if only one of them
+ // is exported.
+ if (list.size() > 1) {
+ errorMessage.append("More than one " + simpleClassName + "'s found in package "
+ + packageName + ". They'll all be ignored.");
+ Log.e(TAG, errorMessage.toString());
+ return null;
+ }
+ final ServiceInfo si = list.get(0).serviceInfo;
+
+ if (!servicePermission.equals(si.permission)) {
+ errorMessage.append(simpleClassName + " "
+ + si.getComponentName().flattenToShortString()
+ + " must be protected with " + servicePermission
+ + ".");
+ Log.e(TAG, errorMessage.toString());
+ return null;
+ }
+ return si;
+ } catch (RemoteException e) {
+ // Shouldn't happen
+ }
+ return null;
+ }
+}
diff --git a/services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java b/services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java
new file mode 100644
index 000000000000..68c5e496cc32
--- /dev/null
+++ b/services/core/java/com/android/server/appbinding/finders/AppServiceFinder.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2018 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.server.appbinding.finders;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.IPackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IInterface;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.appbinding.AppBindingService;
+import com.android.server.appbinding.AppBindingUtils;
+
+import java.io.PrintWriter;
+import java.util.function.BiConsumer;
+
+/**
+ * Baseclss that finds "persistent" service from a type of an app.
+ *
+ * @param <TServiceType> Type of the target service class.
+ * @param <TServiceInterfaceType> Type of the IInterface class used by TServiceType.
+ */
+public abstract class AppServiceFinder<TServiceType, TServiceInterfaceType extends IInterface> {
+ protected static final String TAG = AppBindingService.TAG;
+ protected static final boolean DEBUG = AppBindingService.DEBUG;
+
+ protected final Context mContext;
+ protected final BiConsumer<AppServiceFinder, Integer> mListener;
+ protected final Handler mHandler;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final SparseArray<String> mTargetPackages = new SparseArray(1);
+
+ @GuardedBy("mLock")
+ private final SparseArray<ServiceInfo> mTargetServices = new SparseArray(1);
+
+ @GuardedBy("mLock")
+ private final SparseArray<String> mLastMessages = new SparseArray(1);
+
+ public AppServiceFinder(Context context,
+ BiConsumer<AppServiceFinder, Integer> listener,
+ Handler callbackHandler) {
+ mContext = context;
+ mListener = listener;
+ mHandler = callbackHandler;
+ }
+
+ /** Human readable description of the type of apps; e.g. [Default SMS app] */
+ @NonNull
+ public abstract String getAppDescription();
+
+ /** Start monitoring apps. (e.g. Start watching the default SMS app changes.) */
+ public void startMonitoring() {
+ }
+
+ /** Called when a user is removed. */
+ public void onUserRemoved(int userId) {
+ synchronized (mLock) {
+ mTargetPackages.delete(userId);
+ mTargetServices.delete(userId);
+ }
+ }
+
+ /**
+ * Find the target service from the target app on a given user.
+ */
+ @Nullable
+ public final ServiceInfo findService(int userId, IPackageManager ipm) {
+ synchronized (mLock) {
+ mTargetPackages.put(userId, null);
+ mTargetServices.put(userId, null);
+
+ final String targetPackage = getTargetPackage(userId);
+ if (DEBUG) {
+ Slog.d(TAG, getAppDescription() + " package=" + targetPackage);
+ }
+ if (targetPackage == null) {
+ final String message = "Target package not found";
+ mLastMessages.put(userId, message);
+ Slog.w(TAG, getAppDescription() + " u" + userId + " " + message);
+ return null;
+ }
+ mTargetPackages.put(userId, targetPackage);
+
+ final StringBuilder errorMessage = new StringBuilder();
+ final ServiceInfo service = AppBindingUtils.findService(
+ targetPackage,
+ userId,
+ getServiceAction(),
+ getServicePermission(),
+ getServiceClass(),
+ ipm,
+ errorMessage);
+
+ if (service == null) {
+ final String message = errorMessage.toString();
+ mLastMessages.put(userId, message);
+ if (DEBUG) {
+ Slog.w(TAG, getAppDescription() + " package " + targetPackage + " u" + userId
+ + " " + message);
+ }
+ return null;
+ }
+
+ final String message = "Valid service found";
+ mLastMessages.put(userId, message);
+ mTargetServices.put(userId, service);
+ return service;
+ }
+ }
+
+ protected abstract Class<TServiceType> getServiceClass();
+
+ /**
+ * Convert a binder reference to a service interface type.
+ */
+ public abstract TServiceInterfaceType asInterface(IBinder obj);
+
+ /**
+ * @return the target package on a given user.
+ */
+ @Nullable
+ public abstract String getTargetPackage(int userId);
+
+ /**
+ * @return the intent action that identifies the target service in the target app.
+ */
+ @NonNull
+ protected abstract String getServiceAction();
+
+ /**
+ * @return the permission that the target service must be protected with.
+ */
+ @NonNull
+ protected abstract String getServicePermission();
+
+ /** Dumpsys support. */
+ public void dump(String prefix, PrintWriter pw) {
+ pw.print(prefix);
+ pw.print("App type: ");
+ pw.print(getAppDescription());
+ pw.println();
+
+ synchronized (mLock) {
+ for (int i = 0; i < mTargetPackages.size(); i++) {
+ pw.print(prefix);
+ pw.print(" User: ");
+ pw.print(mTargetPackages.keyAt(i));
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" Package: ");
+ pw.print(mTargetPackages.valueAt(i));
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" Service: ");
+ pw.print(mTargetServices.valueAt(i));
+ pw.println();
+
+ pw.print(prefix);
+ pw.print(" Message: ");
+ pw.print(mLastMessages.valueAt(i));
+ pw.println();
+ }
+ }
+ }
+
+ /** Dumpys support */
+ public void dumpSimple(PrintWriter pw) {
+ synchronized (mLock) {
+ for (int i = 0; i < mTargetPackages.size(); i++) {
+ pw.print("finder,");
+ pw.print(getAppDescription());
+ pw.print(",");
+ pw.print(mTargetPackages.keyAt(i)); // User-id
+ pw.print(",");
+ pw.print(mTargetPackages.valueAt(i));
+ pw.print(",");
+ pw.print(mTargetServices.valueAt(i));
+ pw.print(",");
+ pw.print(mLastMessages.valueAt(i));
+ pw.println();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java b/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java
new file mode 100644
index 000000000000..c908bd919748
--- /dev/null
+++ b/services/core/java/com/android/server/appbinding/finders/SmsAppServiceFinder.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2018 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.server.appbinding.finders;
+
+import static android.provider.Telephony.Sms.Intents.ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL;
+
+import android.Manifest.permission;
+import android.app.ISmsAppService;
+import android.app.SmsAppService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.SmsApplication;
+
+import java.util.function.BiConsumer;
+
+/**
+ * Find the SmsAppService service within the default SMS app.
+ */
+public class SmsAppServiceFinder extends AppServiceFinder<SmsAppService, ISmsAppService> {
+ public SmsAppServiceFinder(Context context,
+ BiConsumer<AppServiceFinder, Integer> listener,
+ Handler callbackHandler) {
+ super(context, listener, callbackHandler);
+ }
+
+ @Override
+ public String getAppDescription() {
+ return "[Default SMS app]";
+ }
+
+ @Override
+ protected Class<SmsAppService> getServiceClass() {
+ return SmsAppService.class;
+ }
+
+ @Override
+ public ISmsAppService asInterface(IBinder obj) {
+ return ISmsAppService.Stub.asInterface(obj);
+ }
+
+ @Override
+ protected String getServiceAction() {
+ return TelephonyManager.ACTION_SMS_APP_SERVICE;
+ }
+
+ @Override
+ protected String getServicePermission() {
+ return permission.BIND_SMS_APP_SERVICE;
+ }
+
+ @Override
+ public String getTargetPackage(int userId) {
+ final ComponentName cn = SmsApplication.getDefaultSmsApplicationAsUser(
+ mContext, /* updateIfNeeded= */ true, userId);
+ return cn == null ? null : cn.getPackageName();
+ }
+
+ @Override
+ public void startMonitoring() {
+ final IntentFilter filter = new IntentFilter(ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL);
+ mContext.registerReceiverAsUser(mSmsAppChangedWatcher, UserHandle.ALL, filter,
+ /* permission= */ null, mHandler);
+ }
+
+ private final BroadcastReceiver mSmsAppChangedWatcher = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (ACTION_DEFAULT_SMS_PACKAGE_CHANGED_INTERNAL.equals(intent.getAction())) {
+ mListener.accept(SmsAppServiceFinder.this, getSendingUserId());
+ }
+ }
+ };
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
index 0c0ce8dd5174..a8b9b0c910ac 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DeviceAdminServiceController.java
@@ -18,27 +18,23 @@ package com.android.server.devicepolicy;
import android.Manifest.permission;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.admin.DeviceAdminService;
import android.app.admin.DevicePolicyManager;
import android.app.admin.IDeviceAdminService;
import android.content.ComponentName;
import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ParceledListSlice;
-import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Handler;
import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.server.am.PersistentConnection;
+import com.android.server.appbinding.AppBindingUtils;
import java.io.PrintWriter;
-import java.util.List;
/**
* Manages connections to persistent services in owner packages.
@@ -74,6 +70,11 @@ public class DeviceAdminServiceController {
}
@Override
+ protected int getBindFlags() {
+ return Context.BIND_FOREGROUND_SERVICE;
+ }
+
+ @Override
protected IDeviceAdminService asInterface(IBinder binder) {
return IDeviceAdminService.Stub.asInterface(binder);
}
@@ -100,40 +101,14 @@ public class DeviceAdminServiceController {
*/
@Nullable
private ServiceInfo findService(@NonNull String packageName, int userId) {
- final Intent intent = new Intent(DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE);
- intent.setPackage(packageName);
-
- try {
- final ParceledListSlice<ResolveInfo> pls = mInjector.getIPackageManager()
- .queryIntentServices(intent, null, /* flags=*/ 0, userId);
- if (pls == null) {
- return null;
- }
- final List<ResolveInfo> list = pls.getList();
- if (list.size() == 0) {
- return null;
- }
- // Note if multiple services are found, that's an error, even if only one of them
- // is exported.
- if (list.size() > 1) {
- Log.e(TAG, "More than one DeviceAdminService's found in package "
- + packageName
- + ". They'll all be ignored.");
- return null;
- }
- final ServiceInfo si = list.get(0).serviceInfo;
-
- if (!permission.BIND_DEVICE_ADMIN.equals(si.permission)) {
- Log.e(TAG, "DeviceAdminService "
- + si.getComponentName().flattenToShortString()
- + " must be protected with " + permission.BIND_DEVICE_ADMIN
- + ".");
- return null;
- }
- return si;
- } catch (RemoteException e) {
- }
- return null;
+ return AppBindingUtils.findService(
+ packageName,
+ userId,
+ DevicePolicyManager.ACTION_DEVICE_ADMIN_SERVICE,
+ permission.BIND_DEVICE_ADMIN,
+ DeviceAdminService.class,
+ mInjector.getIPackageManager(),
+ new StringBuilder() /* ignore error message */);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
index 54f93a88a387..39cab8d2888a 100644
--- a/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/PersistentConnectionTest.java
@@ -56,6 +56,11 @@ public class PersistentConnectionTest extends AndroidTestCase {
}
@Override
+ protected int getBindFlags() {
+ return Context.BIND_FOREGROUND_SERVICE;
+ }
+
+ @Override
protected IDeviceAdminService asInterface(IBinder binder) {
return (IDeviceAdminService) binder;
}