diff options
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; } |