Merge "Refactor ServiceWatcher" into sc-dev

This commit is contained in:
TreeHugger Robot
2021-03-23 06:23:33 +00:00
committed by Android (Google) Code Review
13 changed files with 909 additions and 607 deletions

View File

@@ -24,7 +24,7 @@ import android.location.provider.ProviderProperties;
* @hide
*/
interface ILocationProviderManager {
void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String packageName, @nullable String attributionTag);
void onInitialize(boolean allowed, in ProviderProperties properties, @nullable String attributionTag);
void onSetAllowed(boolean allowed);
void onSetProperties(in ProviderProperties properties);

View File

@@ -96,7 +96,6 @@ public abstract class LocationProviderBase {
"com.android.location.service.FusedLocationProvider";
private final String mTag;
private final @Nullable String mPackageName;
private final @Nullable String mAttributionTag;
private final IBinder mBinder;
@@ -108,7 +107,6 @@ public abstract class LocationProviderBase {
public LocationProviderBase(@NonNull Context context, @NonNull String tag,
@NonNull ProviderProperties properties) {
mTag = tag;
mPackageName = context.getPackageName();
mAttributionTag = context.getAttributionTag();
mBinder = new Service();
@@ -305,7 +303,7 @@ public abstract class LocationProviderBase {
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {

View File

@@ -96,7 +96,6 @@ public abstract class LocationProviderBase {
public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER;
final String mTag;
@Nullable final String mPackageName;
@Nullable final String mAttributionTag;
final IBinder mBinder;
@@ -133,7 +132,6 @@ public abstract class LocationProviderBase {
public LocationProviderBase(Context context, String tag,
ProviderPropertiesUnbundled properties) {
mTag = tag;
mPackageName = context != null ? context.getPackageName() : null;
mAttributionTag = context != null ? context.getAttributionTag() : null;
mBinder = new Service();
@@ -370,7 +368,7 @@ public abstract class LocationProviderBase {
public void setLocationProviderManager(ILocationProviderManager manager) {
synchronized (mBinder) {
try {
manager.onInitialize(mAllowed, mProperties, mPackageName, mAttributionTag);
manager.onInitialize(mAllowed, mProperties, mAttributionTag);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (RuntimeException e) {

View File

@@ -151,20 +151,14 @@ public class FusedLocationServiceTest {
}
@Override
public void onInitialize(boolean allowed, ProviderProperties properties, String packageName,
String attributionTag) {
}
public void onInitialize(boolean allowed, ProviderProperties properties,
String attributionTag) {}
@Override
public void onSetAllowed(boolean allowed) {
}
public void onSetAllowed(boolean allowed) {}
@Override
public void onSetProperties(ProviderProperties properties) {
}
public void onSetProperties(ProviderProperties properties) {}
@Override
public void onReportLocation(Location location) {
@@ -177,9 +171,7 @@ public class FusedLocationServiceTest {
}
@Override
public void onFlushComplete() {
}
public void onFlushComplete() {}
public Location getNextLocation(long timeoutMs) throws InterruptedException {
return mLocations.poll(timeoutMs, TimeUnit.MILLISECONDS);

View File

@@ -24,6 +24,7 @@ import android.location.IGeocodeProvider;
import android.os.IBinder;
import android.os.RemoteException;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
import java.util.Collections;
@@ -54,9 +55,11 @@ public class GeocoderProxy {
private final ServiceWatcher mServiceWatcher;
private GeocoderProxy(Context context) {
mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION, null, null,
com.android.internal.R.bool.config_enableGeocoderOverlay,
com.android.internal.R.string.config_geocoderProviderPackageName);
mServiceWatcher = ServiceWatcher.create(context, "GeocoderProxy",
new CurrentUserServiceSupplier(context, SERVICE_ACTION,
com.android.internal.R.bool.config_enableGeocoderOverlay,
com.android.internal.R.string.config_geocoderProviderPackageName),
null);
}
private boolean register() {
@@ -72,7 +75,7 @@ public class GeocoderProxy {
*/
public void getFromLocation(double latitude, double longitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);
@@ -97,7 +100,7 @@ public class GeocoderProxy {
double lowerLeftLatitude, double lowerLeftLongitude,
double upperRightLatitude, double upperRightLongitude, int maxResults,
GeocoderParams params, IGeocodeListener listener) {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
IGeocodeProvider provider = IGeocodeProvider.Stub.asInterface(binder);

View File

@@ -25,15 +25,15 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.servicewatcher.ServiceWatcher.BoundService;
import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
/**
* Proxy class to bind GmsCore to the ActivityRecognitionHardware.
*
* @hide
*/
public class HardwareActivityRecognitionProxy {
public class HardwareActivityRecognitionProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "ARProxy";
private static final String SERVICE_ACTION =
@@ -66,12 +66,16 @@ public class HardwareActivityRecognitionProxy {
mInstance = null;
}
mServiceWatcher = new ServiceWatcher(context,
SERVICE_ACTION,
this::onBind,
null,
com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay,
com.android.internal.R.string.config_activityRecognitionHardwarePackageName);
int useOverlayResId =
com.android.internal.R.bool.config_enableActivityRecognitionHardwareOverlay;
int nonOverlayPackageResId =
com.android.internal.R.string.config_activityRecognitionHardwarePackageName;
mServiceWatcher = ServiceWatcher.create(context,
"HardwareActivityRecognitionProxy",
new CurrentUserServiceSupplier(context, SERVICE_ACTION, useOverlayResId,
nonOverlayPackageResId),
this);
}
private boolean register() {
@@ -82,7 +86,8 @@ public class HardwareActivityRecognitionProxy {
return resolves;
}
private void onBind(IBinder binder, BoundService service) throws RemoteException {
@Override
public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
String descriptor = binder.getInterfaceDescriptor();
if (IActivityRecognitionHardwareWatcher.class.getCanonicalName().equals(descriptor)) {
@@ -99,4 +104,7 @@ public class HardwareActivityRecognitionProxy {
Log.e(TAG, "Unknown descriptor: " + descriptor);
}
}
@Override
public void onUnbind() {}
}

View File

@@ -378,6 +378,7 @@ public class LocationManagerService extends ILocationManager.Stub {
// provider has unfortunate hard dependencies on the network provider
ProxyLocationProvider networkProvider = ProxyLocationProvider.create(
mContext,
NETWORK_PROVIDER,
ACTION_NETWORK_PROVIDER,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName);
@@ -397,6 +398,7 @@ public class LocationManagerService extends ILocationManager.Stub {
ProxyLocationProvider fusedProvider = ProxyLocationProvider.create(
mContext,
FUSED_PROVIDER,
ACTION_FUSED_PROVIDER,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName);

View File

@@ -29,14 +29,17 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
/**
* @hide
*/
public final class GeofenceProxy {
public final class GeofenceProxy implements ServiceListener<BoundServiceInfo> {
private static final String TAG = "GeofenceProxy";
private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider";
@@ -62,10 +65,12 @@ public final class GeofenceProxy {
private GeofenceProxy(Context context, IGpsGeofenceHardware gpsGeofence) {
mGpsGeofenceHardware = Objects.requireNonNull(gpsGeofence);
mServiceWatcher = new ServiceWatcher(context, SERVICE_ACTION,
(binder, service) -> updateGeofenceHardware(binder), null,
com.android.internal.R.bool.config_enableGeofenceOverlay,
com.android.internal.R.string.config_geofenceProviderPackageName);
mServiceWatcher = ServiceWatcher.create(context,
"GeofenceProxy",
new CurrentUserServiceSupplier(context, SERVICE_ACTION,
com.android.internal.R.bool.config_enableGeofenceOverlay,
com.android.internal.R.string.config_geofenceProviderPackageName),
this);
mGeofenceHardware = null;
}
@@ -87,6 +92,14 @@ public final class GeofenceProxy {
return resolves;
}
@Override
public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
updateGeofenceHardware(binder);
}
@Override
public void onUnbind() {}
private class GeofenceProxyServiceConnection implements ServiceConnection {
GeofenceProxyServiceConnection() {}

View File

@@ -19,7 +19,6 @@ package com.android.server.location.provider.proxy;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
@@ -28,33 +27,34 @@ import android.location.provider.ILocationProviderManager;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.ArraySet;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.FgThread;
import com.android.server.location.provider.AbstractLocationProvider;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.servicewatcher.ServiceWatcher.BoundService;
import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Proxy for ILocationProvider implementations.
*/
public class ProxyLocationProvider extends AbstractLocationProvider {
public class ProxyLocationProvider extends AbstractLocationProvider implements
ServiceListener<BoundServiceInfo> {
private static final String KEY_EXTRA_ATTRIBUTION_TAGS = "android:location_allow_listed_tags";
private static final String EXTRA_ATTRIBUTION_TAGS_SEPARATOR = ";";
private static final String EXTRA_LOCATION_TAGS = "android:location_allow_listed_tags";
private static final String LOCATION_TAGS_SEPARATOR = ";";
private static final long RESET_DELAY_MS = 1000;
@@ -63,10 +63,10 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
* null.
*/
@Nullable
public static ProxyLocationProvider create(Context context, String action,
public static ProxyLocationProvider create(Context context, String provider, String action,
int enableOverlayResId, int nonOverlayPackageResId) {
ProxyLocationProvider proxy = new ProxyLocationProvider(context, action, enableOverlayResId,
nonOverlayPackageResId);
ProxyLocationProvider proxy = new ProxyLocationProvider(context, provider, action,
enableOverlayResId, nonOverlayPackageResId);
if (proxy.checkServiceResolves()) {
return proxy;
} else {
@@ -84,23 +84,23 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
@GuardedBy("mLock")
@Nullable Runnable mResetter;
@GuardedBy("mLock")
Proxy mProxy;
@Nullable Proxy mProxy;
@GuardedBy("mLock")
@Nullable ComponentName mService;
@Nullable BoundServiceInfo mBoundServiceInfo;
private volatile ProviderRequest mRequest;
private ProxyLocationProvider(Context context, String action, int enableOverlayResId,
int nonOverlayPackageResId) {
private ProxyLocationProvider(Context context, String provider, String action,
int enableOverlayResId, int nonOverlayPackageResId) {
// safe to use direct executor since our locks are not acquired in a code path invoked by
// our owning provider
super(DIRECT_EXECUTOR, null, null, Collections.emptySet());
mContext = context;
mServiceWatcher = new ServiceWatcher(context, action, this::onBind,
this::onUnbind, enableOverlayResId, nonOverlayPackageResId);
mServiceWatcher = ServiceWatcher.create(context, provider,
new CurrentUserServiceSupplier(context, action, enableOverlayResId,
nonOverlayPackageResId), this);
mProxy = null;
mRequest = ProviderRequest.EMPTY_REQUEST;
@@ -110,26 +110,13 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
return mServiceWatcher.checkServiceResolves();
}
private void onBind(IBinder binder, BoundService boundService) throws RemoteException {
@Override
public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
synchronized (mLock) {
mProxy = new Proxy();
mService = boundService.component;
if (mResetter != null) {
FgThread.getHandler().removeCallbacks(mResetter);
mResetter = null;
}
// update extra attribution tag info from manifest
if (boundService.metadata != null) {
String tagsList = boundService.metadata.getString(KEY_EXTRA_ATTRIBUTION_TAGS);
if (tagsList != null) {
setExtraAttributionTags(
new ArraySet<>(tagsList.split(EXTRA_ATTRIBUTION_TAGS_SEPARATOR)));
}
}
mBoundServiceInfo = boundServiceInfo;
provider.setLocationProviderManager(mProxy);
@@ -140,26 +127,29 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
}
}
private void onUnbind() {
@Override
public void onUnbind() {
Runnable[] flushListeners;
synchronized (mLock) {
mProxy = null;
mService = null;
mBoundServiceInfo = null;
// we need to clear the state - but most disconnections are very temporary. we give a
// grace period where we don't clear the state immediately so that transient
// interruptions are not visible to clients
mResetter = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (mResetter == this) {
setState(prevState -> State.EMPTY_STATE);
// interruptions are not necessarily visible to downstream clients
if (mResetter == null) {
mResetter = new Runnable() {
@Override
public void run() {
synchronized (mLock) {
if (mResetter == this) {
setState(prevState -> State.EMPTY_STATE);
}
}
}
}
};
FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
};
FgThread.getHandler().postDelayed(mResetter, RESET_DELAY_MS);
}
flushListeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
@@ -192,7 +182,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
@Override
protected void onFlush(Runnable callback) {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderRunner() {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
@@ -232,20 +222,7 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mServiceWatcher.dump(fd, pw, args);
}
private static String guessPackageName(Context context, int uid, String packageName) {
String[] packageNames = context.getPackageManager().getPackagesForUid(uid);
if (packageNames == null || packageNames.length == 0) {
// illegal state exception will propagate back through binders
throw new IllegalStateException(
"location provider from uid " + uid + " has no package information");
} else if (ArrayUtils.contains(packageNames, packageName)) {
return packageName;
} else {
return packageNames[0];
}
mServiceWatcher.dump(pw);
}
private class Proxy extends ILocationProviderManager.Stub {
@@ -255,27 +232,37 @@ public class ProxyLocationProvider extends AbstractLocationProvider {
// executed on binder thread
@Override
public void onInitialize(boolean allowed, ProviderProperties properties,
@Nullable String packageName, @Nullable String attributionTag) {
@Nullable String attributionTag) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
CallerIdentity identity;
if (packageName == null) {
packageName = guessPackageName(mContext, Binder.getCallingUid(),
Objects.requireNonNull(mService).getPackageName());
// unsafe is ok since the package is coming direct from the package manager here
identity = CallerIdentity.fromBinderUnsafe(packageName, attributionTag);
} else {
identity = CallerIdentity.fromBinder(mContext, packageName, attributionTag);
if (mResetter != null) {
FgThread.getHandler().removeCallbacks(mResetter);
mResetter = null;
}
// set extra attribution tags from manifest if necessary
String[] attributionTags = new String[0];
if (mBoundServiceInfo.getMetadata() != null) {
String tagsStr = mBoundServiceInfo.getMetadata().getString(EXTRA_LOCATION_TAGS);
if (!TextUtils.isEmpty(tagsStr)) {
attributionTags = tagsStr.split(LOCATION_TAGS_SEPARATOR);
}
}
ArraySet<String> extraAttributionTags = new ArraySet<>(attributionTags);
// unsafe is ok since we trust the package name already
CallerIdentity identity = CallerIdentity.fromBinderUnsafe(
mBoundServiceInfo.getComponentName().getPackageName(),
attributionTag);
setState(prevState -> State.EMPTY_STATE
.withExtraAttributionTags(prevState.extraAttributionTags)
.withAllowed(allowed)
.withProperties(properties)
.withIdentity(identity));
.withIdentity(identity)
.withExtraAttributionTags(extraAttributionTags));
}
}

View File

@@ -0,0 +1,302 @@
/*
* Copyright (C) 2021 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.servicewatcher;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.UserHandle.USER_SYSTEM;
import android.annotation.BoolRes;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.app.ActivityManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.UserHandle;
import android.permission.PermissionManager;
import android.util.Log;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
import com.android.server.servicewatcher.ServiceWatcher.ServiceSupplier;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
/**
* Supplies services based on the current active user and version as defined in the service
* manifest. This implementation uses {@link android.content.pm.PackageManager#MATCH_SYSTEM_ONLY} to
* ensure only system (ie, privileged) services are matched. It also handles services that are not
* direct boot aware, and will automatically pick the best service as the user's direct boot state
* changes.
*
* <p>Optionally, two permissions may be specified: (1) a caller permission - any service that does
* not require callers to hold this permission is rejected (2) a service permission - any service
* whose package does not hold this permission is rejected.
*/
public class CurrentUserServiceSupplier extends BroadcastReceiver implements
ServiceSupplier<CurrentUserServiceSupplier.BoundServiceInfo> {
private static final String TAG = "CurrentUserServiceSupplier";
private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
private static final Comparator<BoundServiceInfo> sBoundServiceInfoComparator = (o1, o2) -> {
if (o1 == o2) {
return 0;
} else if (o1 == null) {
return -1;
} else if (o2 == null) {
return 1;
}
// ServiceInfos with higher version numbers always win. if version numbers are equal
// then we prefer components that work for all users vs components that only work for a
// single user at a time. otherwise everything's equal.
int ret = Integer.compare(o1.getVersion(), o2.getVersion());
if (ret == 0) {
if (o1.getUserId() != USER_SYSTEM && o2.getUserId() == USER_SYSTEM) {
ret = -1;
} else if (o1.getUserId() == USER_SYSTEM && o2.getUserId() != USER_SYSTEM) {
ret = 1;
}
}
return ret;
};
/** Bound service information with version information. */
public static class BoundServiceInfo extends ServiceWatcher.BoundServiceInfo {
private static int parseUid(ResolveInfo resolveInfo) {
int uid = resolveInfo.serviceInfo.applicationInfo.uid;
Bundle metadata = resolveInfo.serviceInfo.metaData;
if (metadata != null && metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false)) {
// reconstruct a uid for the same app but with the system user - hope this exists
uid = UserHandle.getUid(USER_SYSTEM, UserHandle.getAppId(uid));
}
return uid;
}
private static int parseVersion(ResolveInfo resolveInfo) {
int version = Integer.MIN_VALUE;
if (resolveInfo.serviceInfo.metaData != null) {
version = resolveInfo.serviceInfo.metaData.getInt(EXTRA_SERVICE_VERSION, version);
}
return version;
}
private final int mVersion;
private final @Nullable Bundle mMetadata;
protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
this(action, parseUid(resolveInfo), resolveInfo.serviceInfo.getComponentName(),
parseVersion(resolveInfo), resolveInfo.serviceInfo.metaData);
}
protected BoundServiceInfo(String action, int uid, ComponentName componentName, int version,
@Nullable Bundle metadata) {
super(action, uid, componentName);
mVersion = version;
mMetadata = metadata;
}
public int getVersion() {
return mVersion;
}
public @Nullable Bundle getMetadata() {
return mMetadata;
}
@Override
public String toString() {
return super.toString() + "@" + mVersion;
}
}
private static @Nullable String retrieveExplicitPackage(Context context,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
Resources resources = context.getResources();
boolean enableOverlay = resources.getBoolean(enableOverlayResId);
if (!enableOverlay) {
return resources.getString(nonOverlayPackageResId);
} else {
return null;
}
}
private final Context mContext;
private final ActivityManagerInternal mActivityManager;
private final Intent mIntent;
// a permission that the service forces callers (ie ServiceWatcher/system server) to hold
private final @Nullable String mCallerPermission;
// a permission that the service package should hold
private final @Nullable String mServicePermission;
private volatile ServiceChangedListener mListener;
public CurrentUserServiceSupplier(Context context, String action) {
this(context, action, null, null, null);
}
public CurrentUserServiceSupplier(Context context, String action,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
this(context, action,
retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId), null,
null);
}
public CurrentUserServiceSupplier(Context context, String action,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
@Nullable String callerPermission, @Nullable String servicePermission) {
this(context, action,
retrieveExplicitPackage(context, enableOverlayResId, nonOverlayPackageResId),
callerPermission, servicePermission);
}
public CurrentUserServiceSupplier(Context context, String action,
@Nullable String explicitPackage, @Nullable String callerPermission,
@Nullable String servicePermission) {
mContext = context;
mActivityManager = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
mIntent = new Intent(action);
if (explicitPackage != null) {
mIntent.setPackage(explicitPackage);
}
mCallerPermission = callerPermission;
mServicePermission = servicePermission;
}
@Override
public boolean hasMatchingService() {
List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentServicesAsUser(mIntent,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
UserHandle.USER_SYSTEM);
return !resolveInfos.isEmpty();
}
@Override
public void register(ServiceChangedListener listener) {
Preconditions.checkState(mListener == null);
mListener = listener;
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(this, UserHandle.ALL, intentFilter, null,
FgThread.getHandler());
}
@Override
public void unregister() {
Preconditions.checkArgument(mListener != null);
mListener = null;
mContext.unregisterReceiver(this);
}
@Override
public BoundServiceInfo getServiceInfo() {
BoundServiceInfo bestServiceInfo = null;
// only allow privileged services in the correct direct boot state to match
int currentUserId = mActivityManager.getCurrentUserId();
List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
mIntent,
GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
currentUserId);
for (ResolveInfo resolveInfo : resolveInfos) {
ServiceInfo service = Objects.requireNonNull(resolveInfo.serviceInfo);
if (mCallerPermission != null) {
if (!mCallerPermission.equals(service.permission)) {
Log.d(TAG, service.getComponentName().flattenToShortString()
+ " disqualified due to not requiring " + mCallerPermission);
continue;
}
}
BoundServiceInfo serviceInfo = new BoundServiceInfo(mIntent.getAction(), resolveInfo);
if (mServicePermission != null) {
if (PermissionManager.checkPackageNamePermission(mServicePermission,
service.packageName, serviceInfo.getUserId()) != PERMISSION_GRANTED) {
Log.d(TAG, serviceInfo.getComponentName().flattenToShortString()
+ " disqualified due to not holding " + mCallerPermission);
continue;
}
}
if (sBoundServiceInfoComparator.compare(serviceInfo, bestServiceInfo) > 0) {
bestServiceInfo = serviceInfo;
}
}
return bestServiceInfo;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
return;
}
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId == UserHandle.USER_NULL) {
return;
}
ServiceChangedListener listener = mListener;
if (listener == null) {
return;
}
switch (action) {
case Intent.ACTION_USER_SWITCHED:
listener.onServiceChanged();
break;
case Intent.ACTION_USER_UNLOCKED:
// user unlocked implies direct boot mode may have changed
if (userId == mActivityManager.getCurrentUserId()) {
listener.onServiceChanged();
}
break;
default:
break;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2012 The Android Open Source Project
* Copyright (C) 2021 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.
@@ -16,516 +16,244 @@
package com.android.server.servicewatcher;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_NOT_FOREGROUND;
import static android.content.Context.BIND_NOT_VISIBLE;
import static android.content.pm.PackageManager.GET_META_DATA;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AUTO;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import android.annotation.BoolRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.Immutable;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Predicate;
/**
* Maintains a binding to the best service that matches the given intent information. Bind and
* unbind callbacks, as well as all binder operations, will all be run on a single thread.
* A ServiceWatcher is responsible for continuously maintaining an active binding to a service
* selected by it's {@link ServiceSupplier}. The {@link ServiceSupplier} may change the service it
* selects over time, and the currently bound service may crash, restart, have a user change, have
* changes made to its package, and so on and so forth. The ServiceWatcher is responsible for
* maintaining the binding across all these changes.
*
* <p>Clients may invoke {@link BinderOperation}s on the ServiceWatcher, and it will make a best
* effort to run these on the currently bound service, but individual operations may fail (if there
* is no service currently bound for instance). In order to help clients maintain the correct state,
* clients may supply a {@link ServiceListener}, which is informed when the ServiceWatcher connects
* and disconnects from a service. This allows clients to bring a bound service back into a known
* state on connection, and then run binder operations from there. In order to help clients
* accomplish this, ServiceWatcher guarantees that {@link BinderOperation}s and the
* {@link ServiceListener} will always be run on the same thread, so that strong ordering guarantees
* can be established between them.
*
* There is never any guarantee of whether a ServiceWatcher is currently connected to a service, and
* whether any particular {@link BinderOperation} will succeed. Clients must ensure they do not rely
* on this, and instead use {@link ServiceListener} notifications as necessary to recover from
* failures.
*/
public class ServiceWatcher implements ServiceConnection {
public interface ServiceWatcher {
private static final String TAG = "ServiceWatcher";
private static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
private static final String EXTRA_SERVICE_VERSION = "serviceVersion";
private static final String EXTRA_SERVICE_IS_MULTIUSER = "serviceIsMultiuser";
private static final long RETRY_DELAY_MS = 15 * 1000;
private static final Predicate<ResolveInfo> DEFAULT_SERVICE_CHECK_PREDICATE = x -> true;
/** Function to run on binder interface. */
public interface BinderRunner {
/** Called to run client code with the binder. */
/**
* Operation to run on a binder interface. All operations will be run on the thread used by the
* ServiceWatcher this is run with.
*/
interface BinderOperation {
/** Invoked to run the operation. Run on the ServiceWatcher thread. */
void run(IBinder binder) throws RemoteException;
/**
* Called if an error occurred and the function could not be run. This callback is only
* intended for resource deallocation and cleanup in response to a single binder operation,
* it should not be used to propagate errors further.
* Invoked if {@link #run(IBinder)} could not be invoked because there was no current
* binding, or if {@link #run(IBinder)} threw an exception ({@link RemoteException} or
* {@link RuntimeException}). This callback is only intended for resource deallocation and
* cleanup in response to a single binder operation, it should not be used to propagate
* errors further. Run on the ServiceWatcher thread.
*/
default void onError() {}
}
/** Function to run on binder interface when first bound. */
public interface OnBindRunner {
/** Called to run client code with the binder. */
void run(IBinder binder, BoundService service) throws RemoteException;
/**
* Listener for bind and unbind events. All operations will be run on the thread used by the
* ServiceWatcher this is run with.
*
* @param <TBoundServiceInfo> type of bound service
*/
interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
/** Invoked when a service is bound. Run on the ServiceWatcher thread. */
void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
/** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
void onUnbind();
}
/**
* Information on the service ServiceWatcher has selected as the best option for binding.
* A listener for when a {@link ServiceSupplier} decides that the current service has changed.
*/
@Immutable
public static final class BoundService implements Comparable<BoundService> {
interface ServiceChangedListener {
/**
* Should be invoked when the current service may have changed.
*/
void onServiceChanged();
}
public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null,
false, null, -1);
/**
* This supplier encapsulates the logic of deciding what service a {@link ServiceWatcher} should
* be bound to at any given moment.
*
* @param <TBoundServiceInfo> type of bound service
*/
interface ServiceSupplier<TBoundServiceInfo extends BoundServiceInfo> {
/**
* Should return true if there exists at least one service capable of meeting the criteria
* of this supplier. This does not imply that {@link #getServiceInfo()} will always return a
* non-null result, as any service may be disqualified for various reasons at any point in
* time. May be invoked at any time from any thread and thus should generally not have any
* dependency on the other methods in this interface.
*/
boolean hasMatchingService();
public final int version;
@Nullable
public final ComponentName component;
public final boolean serviceIsMultiuser;
public final int uid;
@Nullable
public final Bundle metadata;
/**
* Invoked when the supplier should start monitoring for any changes that could result in a
* different service selection, and should invoke
* {@link ServiceChangedListener#onServiceChanged()} in that case. {@link #getServiceInfo()}
* may be invoked after this method is called.
*/
void register(ServiceChangedListener listener);
BoundService(ResolveInfo resolveInfo) {
Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
/**
* Invoked when the supplier should stop monitoring for any changes that could result in a
* different service selection, should no longer invoke
* {@link ServiceChangedListener#onServiceChanged()}. {@link #getServiceInfo()} will not be
* invoked after this method is called.
*/
void unregister();
metadata = resolveInfo.serviceInfo.metaData;
if (metadata != null) {
version = metadata.getInt(EXTRA_SERVICE_VERSION, Integer.MIN_VALUE);
serviceIsMultiuser = metadata.getBoolean(EXTRA_SERVICE_IS_MULTIUSER, false);
} else {
version = Integer.MIN_VALUE;
serviceIsMultiuser = false;
}
/**
* Must be implemented to return the current service selected by this supplier. May return
* null if no service currently meets the criteria. Only invoked while registered.
*/
@Nullable TBoundServiceInfo getServiceInfo();
}
component = resolveInfo.serviceInfo.getComponentName();
uid = resolveInfo.serviceInfo.applicationInfo.uid;
/**
* Information on the service selected as the best option for binding.
*/
class BoundServiceInfo {
protected final @Nullable String mAction;
protected final int mUid;
protected final ComponentName mComponentName;
protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
this(action, resolveInfo.serviceInfo.applicationInfo.uid,
resolveInfo.serviceInfo.getComponentName());
}
private BoundService(int version, @Nullable ComponentName component,
boolean serviceIsMultiuser, @Nullable Bundle metadata, int uid) {
Preconditions.checkArgument(component != null || version == Integer.MIN_VALUE);
this.version = version;
this.component = component;
this.serviceIsMultiuser = serviceIsMultiuser;
this.metadata = metadata;
this.uid = uid;
protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
mAction = action;
mUid = uid;
mComponentName = Objects.requireNonNull(componentName);
}
public @Nullable String getPackageName() {
return component != null ? component.getPackageName() : null;
/** Returns the action associated with this bound service. */
public @Nullable String getAction() {
return mAction;
}
/** Returns the component of this bound service. */
public ComponentName getComponentName() {
return mComponentName;
}
/** Returns the user id for this bound service. */
public @UserIdInt int getUserId() {
return UserHandle.getUserId(mUid);
}
@Override
public boolean equals(Object o) {
public final boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof BoundService)) {
if (o == null || getClass() != o.getClass()) {
return false;
}
BoundService that = (BoundService) o;
return version == that.version && uid == that.uid
&& Objects.equals(component, that.component);
BoundServiceInfo that = (BoundServiceInfo) o;
return mUid == that.mUid
&& Objects.equals(mAction, that.mAction)
&& mComponentName.equals(that.mComponentName);
}
@Override
public int hashCode() {
return Objects.hash(version, component, uid);
}
@Override
public int compareTo(BoundService that) {
// ServiceInfos with higher version numbers always win (having a version number >
// MIN_VALUE implies having a non-null component). if version numbers are equal, a
// non-null component wins over a null component. if the version numbers are equal and
// both components exist then we prefer components that work for all users vs components
// that only work for a single user at a time. otherwise everything's equal.
int ret = Integer.compare(version, that.version);
if (ret == 0) {
if (component == null && that.component != null) {
ret = -1;
} else if (component != null && that.component == null) {
ret = 1;
} else {
if (UserHandle.getUserId(uid) != UserHandle.USER_SYSTEM
&& UserHandle.getUserId(that.uid) == UserHandle.USER_SYSTEM) {
ret = -1;
} else if (UserHandle.getUserId(uid) == UserHandle.USER_SYSTEM
&& UserHandle.getUserId(that.uid) != UserHandle.USER_SYSTEM) {
ret = 1;
}
}
}
return ret;
public final int hashCode() {
return Objects.hash(mAction, mUid, mComponentName);
}
@Override
public String toString() {
if (component == null) {
if (mComponentName == null) {
return "none";
} else {
return component.toShortString() + "@" + version + "[u"
+ UserHandle.getUserId(uid) + "]";
return mUid + "/" + mComponentName.flattenToShortString();
}
}
}
private final Context mContext;
private final Handler mHandler;
private final Intent mIntent;
private final Predicate<ResolveInfo> mServiceCheckPredicate;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
return true;
}
@Override
public void onSomePackagesChanged() {
onBestServiceChanged(false);
}
};
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) {
return;
}
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
if (userId == UserHandle.USER_NULL) {
return;
}
switch (action) {
case Intent.ACTION_USER_SWITCHED:
onUserSwitched(userId);
break;
case Intent.ACTION_USER_UNLOCKED:
onUserUnlocked(userId);
break;
default:
break;
}
}
};
// read/write from handler thread only
private final Map<ComponentName, BoundService> mPendingBinds = new ArrayMap<>();
@Nullable
private final OnBindRunner mOnBind;
@Nullable
private final Runnable mOnUnbind;
// read/write from handler thread only
private boolean mRegistered;
// read/write from handler thread only
private int mCurrentUserId;
// write from handler thread only, read anywhere
private volatile BoundService mTargetService;
private volatile IBinder mBinder;
public ServiceWatcher(Context context, String action,
@Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
this(context, FgThread.getHandler(), action, onBind, onUnbind, enableOverlayResId,
nonOverlayPackageResId, DEFAULT_SERVICE_CHECK_PREDICATE);
}
public ServiceWatcher(Context context, Handler handler, String action,
@Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId) {
this(context, handler, action, onBind, onUnbind, enableOverlayResId, nonOverlayPackageResId,
DEFAULT_SERVICE_CHECK_PREDICATE);
}
public ServiceWatcher(Context context, Handler handler, String action,
@Nullable OnBindRunner onBind, @Nullable Runnable onUnbind,
@BoolRes int enableOverlayResId, @StringRes int nonOverlayPackageResId,
@NonNull Predicate<ResolveInfo> serviceCheckPredicate) {
mContext = context;
mHandler = handler;
mIntent = new Intent(Objects.requireNonNull(action));
mServiceCheckPredicate = Objects.requireNonNull(serviceCheckPredicate);
Resources resources = context.getResources();
boolean enableOverlay = resources.getBoolean(enableOverlayResId);
if (!enableOverlay) {
mIntent.setPackage(resources.getString(nonOverlayPackageResId));
}
mOnBind = onBind;
mOnUnbind = onUnbind;
mCurrentUserId = UserHandle.USER_NULL;
mTargetService = BoundService.NONE;
mBinder = null;
}
/**
* Returns true if there is at least one component that could satisfy the ServiceWatcher's
* constraints.
*/
public boolean checkServiceResolves() {
List<ResolveInfo> resolveInfos = mContext.getPackageManager()
.queryIntentServicesAsUser(mIntent,
MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_SYSTEM_ONLY,
UserHandle.USER_SYSTEM);
for (ResolveInfo resolveInfo : resolveInfos) {
if (mServiceCheckPredicate.test(resolveInfo)) {
return true;
}
}
return false;
}
/**
* Starts the process of determining the best matching service and maintaining a binding to it.
*/
public void register() {
mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::registerInternal,
ServiceWatcher.this));
}
private void registerInternal() {
Preconditions.checkState(!mRegistered);
mPackageMonitor.register(mContext, UserHandle.ALL, true, mHandler);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, intentFilter, null,
mHandler);
// TODO: This makes the behavior of the class unpredictable as the caller needs
// to know the internal impl detail that calling register would pick the current user.
mCurrentUserId = ActivityManager.getCurrentUser();
mRegistered = true;
mHandler.post(() -> onBestServiceChanged(false));
}
/**
* Stops the process of determining the best matching service and releases any binding.
*/
public void unregister() {
mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal,
ServiceWatcher.this));
}
private void unregisterInternal() {
Preconditions.checkState(mRegistered);
mRegistered = false;
mPackageMonitor.unregister();
mContext.unregisterReceiver(mBroadcastReceiver);
mHandler.post(() -> onBestServiceChanged(false));
}
private void onBestServiceChanged(boolean forceRebind) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
BoundService bestServiceInfo = BoundService.NONE;
if (mRegistered) {
List<ResolveInfo> resolveInfos = mContext.getPackageManager().queryIntentServicesAsUser(
mIntent,
GET_META_DATA | MATCH_DIRECT_BOOT_AUTO | MATCH_SYSTEM_ONLY,
mCurrentUserId);
for (ResolveInfo resolveInfo : resolveInfos) {
if (!mServiceCheckPredicate.test(resolveInfo)) {
continue;
}
BoundService serviceInfo = new BoundService(resolveInfo);
if (serviceInfo.compareTo(bestServiceInfo) > 0) {
bestServiceInfo = serviceInfo;
}
}
}
if (forceRebind || !bestServiceInfo.equals(mTargetService)) {
rebind(bestServiceInfo);
}
}
private void rebind(BoundService newServiceInfo) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (!mTargetService.equals(BoundService.NONE)) {
if (D) {
Log.d(TAG, "[" + mIntent.getAction() + "] unbinding from " + mTargetService);
}
mContext.unbindService(this);
onServiceDisconnected(mTargetService.component);
mPendingBinds.remove(mTargetService.component);
mTargetService = BoundService.NONE;
}
mTargetService = newServiceInfo;
if (mTargetService.equals(BoundService.NONE)) {
return;
}
Preconditions.checkState(mTargetService.component != null);
Log.i(TAG, getLogPrefix() + " binding to " + mTargetService);
Intent bindIntent = new Intent(mIntent).setComponent(mTargetService.component);
if (!mContext.bindServiceAsUser(bindIntent, this,
BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
mHandler, UserHandle.of(UserHandle.getUserId(mTargetService.uid)))) {
mTargetService = BoundService.NONE;
Log.e(TAG, getLogPrefix() + " unexpected bind failure - retrying later");
mHandler.postDelayed(() -> onBestServiceChanged(false), RETRY_DELAY_MS);
} else {
mPendingBinds.put(mTargetService.component, mTargetService);
}
}
@Override
public final void onServiceConnected(ComponentName component, IBinder binder) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
Preconditions.checkState(mBinder == null);
if (D) {
Log.d(TAG, getLogPrefix() + " connected to " + component.toShortString());
}
final BoundService boundService = mPendingBinds.remove(component);
if (boundService == null) {
return;
}
mBinder = binder;
if (mOnBind != null) {
try {
mOnBind.run(binder, boundService);
} catch (RuntimeException | RemoteException e) {
// binders may propagate some specific non-RemoteExceptions from the other side
// through the binder as well - we cannot allow those to crash the system server
Log.e(TAG, getLogPrefix() + " exception running on " + component, e);
}
}
}
@Override
public final void onServiceDisconnected(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (mBinder == null) {
return;
}
if (D) {
Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
}
mBinder = null;
if (mOnUnbind != null) {
mOnUnbind.run();
}
}
@Override
public final void onBindingDied(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
Log.i(TAG, getLogPrefix() + " " + component.toShortString() + " died");
onBestServiceChanged(true);
}
@Override
public final void onNullBinding(ComponentName component) {
Log.e(TAG, getLogPrefix() + " " + component.toShortString() + " has null binding");
}
void onUserSwitched(@UserIdInt int userId) {
mCurrentUserId = userId;
onBestServiceChanged(false);
}
void onUserUnlocked(@UserIdInt int userId) {
if (userId == mCurrentUserId) {
onBestServiceChanged(false);
}
}
/**
* Runs the given function asynchronously if and only if currently connected. Suppresses any
* RemoteException thrown during execution.
* Creates a new ServiceWatcher instance.
*/
public final void runOnBinder(BinderRunner runner) {
mHandler.post(() -> {
if (mBinder == null) {
runner.onError();
return;
}
try {
runner.run(mBinder);
} catch (RuntimeException | RemoteException e) {
// binders may propagate some specific non-RemoteExceptions from the other side
// through the binder as well - we cannot allow those to crash the system server
Log.e(TAG, getLogPrefix() + " exception running on " + mTargetService, e);
runner.onError();
}
});
}
private String getLogPrefix() {
return "[" + mIntent.getAction() + "]";
}
@Override
public String toString() {
return mTargetService.toString();
static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
Context context,
String tag,
ServiceSupplier<TBoundServiceInfo> serviceSupplier,
@Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
return create(context, FgThread.getHandler(), tag, serviceSupplier, serviceListener);
}
/**
* Dump for debugging.
* Creates a new ServiceWatcher instance that runs on the given handler.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.println("target service=" + mTargetService);
pw.println("connected=" + (mBinder != null));
static <TBoundServiceInfo extends BoundServiceInfo> ServiceWatcher create(
Context context,
Handler handler,
String tag,
ServiceSupplier<TBoundServiceInfo> serviceSupplier,
@Nullable ServiceListener<? super TBoundServiceInfo> serviceListener) {
return new ServiceWatcherImpl<>(context, handler, tag, serviceSupplier, serviceListener);
}
}
/**
* Returns true if there is at least one service that the ServiceWatcher could hypothetically
* bind to, as selected by the {@link ServiceSupplier}.
*/
boolean checkServiceResolves();
/**
* Registers the ServiceWatcher, so that it will begin maintaining an active binding to the
* service selected by {@link ServiceSupplier}, until {@link #unregister()} is called.
*/
void register();
/**
* Unregisters the ServiceWatcher, so that it will release any active bindings. If the
* ServiceWatcher is currently bound, this will result in one final
* {@link ServiceListener#onUnbind()} invocation, which may happen after this method completes
* (but which is guaranteed to occur before any further
* {@link ServiceListener#onBind(IBinder, BoundServiceInfo)} invocation in response to a later
* call to {@link #register()}).
*/
void unregister();
/**
* Runs the given binder operation on the currently bound service (if available). The operation
* will always fail if the ServiceWatcher is not currently registered.
*/
void runOnBinder(BinderOperation operation);
/**
* Dumps ServiceWatcher information.
*/
void dump(PrintWriter pw);
}

View File

@@ -0,0 +1,297 @@
/*
* Copyright (C) 2021 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.servicewatcher;
import static android.content.Context.BIND_AUTO_CREATE;
import static android.content.Context.BIND_NOT_FOREGROUND;
import static android.content.Context.BIND_NOT_VISIBLE;
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageMonitor;
import com.android.internal.util.Preconditions;
import com.android.server.servicewatcher.ServiceWatcher.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher.ServiceChangedListener;
import java.io.PrintWriter;
import java.util.Objects;
/**
* Implementation of ServiceWatcher. Keeping the implementation separate from the interface allows
* us to store the generic relationship between the service supplier and the service listener, while
* hiding the generics from clients, simplifying the API.
*/
class ServiceWatcherImpl<TBoundServiceInfo extends BoundServiceInfo> implements ServiceWatcher,
ServiceChangedListener {
static final String TAG = "ServiceWatcher";
static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
static final long RETRY_DELAY_MS = 15 * 1000;
final Context mContext;
final Handler mHandler;
final String mTag;
final ServiceSupplier<TBoundServiceInfo> mServiceSupplier;
final @Nullable ServiceListener<? super TBoundServiceInfo> mServiceListener;
private final PackageMonitor mPackageMonitor = new PackageMonitor() {
@Override
public boolean onPackageChanged(String packageName, int uid, String[] components) {
return true;
}
@Override
public void onSomePackagesChanged() {
onServiceChanged(false);
}
};
@GuardedBy("this")
private boolean mRegistered = false;
@GuardedBy("this")
private MyServiceConnection mServiceConnection = new MyServiceConnection(null);
ServiceWatcherImpl(Context context, Handler handler, String tag,
ServiceSupplier<TBoundServiceInfo> serviceSupplier,
ServiceListener<? super TBoundServiceInfo> serviceListener) {
mContext = context;
mHandler = handler;
mTag = tag;
mServiceSupplier = serviceSupplier;
mServiceListener = serviceListener;
}
@Override
public boolean checkServiceResolves() {
return mServiceSupplier.hasMatchingService();
}
@Override
public synchronized void register() {
Preconditions.checkState(!mRegistered);
mRegistered = true;
mPackageMonitor.register(mContext, UserHandle.ALL, /*externalStorage=*/ true, mHandler);
mServiceSupplier.register(this);
onServiceChanged(false);
}
@Override
public synchronized void unregister() {
Preconditions.checkState(mRegistered);
mServiceSupplier.unregister();
mPackageMonitor.unregister();
mRegistered = false;
onServiceChanged(false);
}
@Override
public synchronized void onServiceChanged() {
onServiceChanged(false);
}
@Override
public synchronized void runOnBinder(BinderOperation operation) {
MyServiceConnection serviceConnection = mServiceConnection;
mHandler.post(() -> serviceConnection.runOnBinder(operation));
}
synchronized void onServiceChanged(boolean forceRebind) {
TBoundServiceInfo newBoundServiceInfo;
if (mRegistered) {
newBoundServiceInfo = mServiceSupplier.getServiceInfo();
} else {
newBoundServiceInfo = null;
}
if (forceRebind || !Objects.equals(mServiceConnection.getBoundServiceInfo(),
newBoundServiceInfo)) {
MyServiceConnection oldServiceConnection = mServiceConnection;
MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
mServiceConnection = newServiceConnection;
mHandler.post(() -> {
oldServiceConnection.unbind();
newServiceConnection.bind();
});
}
}
@Override
public String toString() {
return mServiceConnection.getBoundServiceInfo().toString();
}
@Override
public void dump(PrintWriter pw) {
pw.println("target service=" + mServiceConnection.getBoundServiceInfo());
pw.println("connected=" + mServiceConnection.isConnected());
}
// runs on the handler thread, and expects most of it's methods to be called from that thread
private class MyServiceConnection implements ServiceConnection {
private final @Nullable TBoundServiceInfo mBoundServiceInfo;
// volatile so that isConnected can be called from any thread easily
private volatile @Nullable IBinder mBinder;
private @Nullable Runnable mRebinder;
MyServiceConnection(@Nullable TBoundServiceInfo boundServiceInfo) {
mBoundServiceInfo = boundServiceInfo;
}
// may be called from any thread
@Nullable TBoundServiceInfo getBoundServiceInfo() {
return mBoundServiceInfo;
}
// may be called from any thread
boolean isConnected() {
return mBinder != null;
}
void bind() {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (mBoundServiceInfo == null) {
return;
}
Log.i(TAG, "[" + mTag + "] binding to " + mBoundServiceInfo);
Intent bindIntent = new Intent(mBoundServiceInfo.getAction()).setComponent(
mBoundServiceInfo.getComponentName());
if (!mContext.bindServiceAsUser(bindIntent, this,
BIND_AUTO_CREATE | BIND_NOT_FOREGROUND | BIND_NOT_VISIBLE,
mHandler, UserHandle.of(mBoundServiceInfo.getUserId()))) {
Log.e(TAG, "[" + mTag + "] unexpected bind failure - retrying later");
mRebinder = this::bind;
mHandler.postDelayed(mRebinder, RETRY_DELAY_MS);
} else {
mRebinder = null;
}
}
void unbind() {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (mBoundServiceInfo == null) {
return;
}
if (D) {
Log.d(TAG, "[" + mTag + "] unbinding from " + mBoundServiceInfo);
}
if (mRebinder != null) {
mHandler.removeCallbacks(mRebinder);
mRebinder = null;
} else {
mContext.unbindService(this);
}
onServiceDisconnected(mBoundServiceInfo.getComponentName());
}
void runOnBinder(BinderOperation operation) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (mBinder == null) {
operation.onError();
return;
}
try {
operation.run(mBinder);
} catch (RuntimeException | RemoteException e) {
// binders may propagate some specific non-RemoteExceptions from the other side
// through the binder as well - we cannot allow those to crash the system server
Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
operation.onError();
}
}
@Override
public final void onServiceConnected(ComponentName component, IBinder binder) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
Preconditions.checkState(mBinder == null);
if (D) {
Log.d(TAG, "[" + mTag + "] connected to " + component.toShortString());
}
mBinder = binder;
if (mServiceListener != null) {
try {
mServiceListener.onBind(binder, mBoundServiceInfo);
} catch (RuntimeException | RemoteException e) {
// binders may propagate some specific non-RemoteExceptions from the other side
// through the binder as well - we cannot allow those to crash the system server
Log.e(TAG, "[" + mTag + "] error running operation on " + mBoundServiceInfo, e);
}
}
}
@Override
public final void onServiceDisconnected(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
if (mBinder == null) {
return;
}
if (D) {
Log.d(TAG, "[" + mTag + "] disconnected from " + mBoundServiceInfo);
}
mBinder = null;
if (mServiceListener != null) {
mServiceListener.onUnbind();
}
}
@Override
public final void onBindingDied(ComponentName component) {
Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
Log.i(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
onServiceChanged(true);
}
@Override
public final void onNullBinding(ComponentName component) {
Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
}
}
}

View File

@@ -16,19 +16,14 @@
package com.android.server.timezonedetector.location;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
import static android.Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_ERROR_KEY;
import static android.service.timezone.TimeZoneProviderService.TEST_COMMAND_RESULT_SUCCESS_KEY;
import static com.android.server.timezonedetector.location.LocationTimeZoneManagerService.warnLog;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -39,11 +34,12 @@ import android.service.timezone.TimeZoneProviderSuggestion;
import android.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.CurrentUserServiceSupplier.BoundServiceInfo;
import com.android.server.servicewatcher.ServiceWatcher;
import com.android.server.servicewatcher.ServiceWatcher.BoundService;
import com.android.server.servicewatcher.ServiceWatcher.ServiceListener;
import java.util.Objects;
import java.util.function.Predicate;
/**
* System server-side proxy for ITimeZoneProvider implementations, i.e. this provides the
@@ -52,7 +48,8 @@ import java.util.function.Predicate;
* different process. As "remote" providers are bound / unbound this proxy will rebind to the "best"
* available remote process.
*/
class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy implements
ServiceListener<BoundServiceInfo> {
@NonNull private final ServiceWatcher mServiceWatcher;
@@ -69,38 +66,13 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
super(context, threadingDomain);
mManagerProxy = null;
mRequest = TimeZoneProviderRequest.createStopUpdatesRequest();
// A predicate that is used to confirm that an intent service can be used as a
// location-based TimeZoneProvider. The service must:
// 1) Declare android:permission="android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE" - this
// ensures that the provider will only communicate with the system server.
// 2) Be in an application that has been granted the
// android.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE permission. This
// ensures only trusted time zone providers will be discovered.
final String requiredClientPermission = Manifest.permission.BIND_TIME_ZONE_PROVIDER_SERVICE;
final String requiredPermission =
Manifest.permission.INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE;
Predicate<ResolveInfo> intentServiceCheckPredicate = resolveInfo -> {
ServiceInfo serviceInfo = resolveInfo.serviceInfo;
boolean hasClientPermissionRequirement =
requiredClientPermission.equals(serviceInfo.permission);
String packageName = serviceInfo.packageName;
PackageManager packageManager = context.getPackageManager();
int checkResult = packageManager.checkPermission(requiredPermission, packageName);
boolean hasRequiredPermission = checkResult == PERMISSION_GRANTED;
boolean result = hasClientPermissionRequirement && hasRequiredPermission;
if (!result) {
warnLog("resolveInfo=" + resolveInfo + " does not meet requirements:"
+ " hasClientPermissionRequirement=" + hasClientPermissionRequirement
+ ", hasRequiredPermission=" + hasRequiredPermission);
}
return result;
};
mServiceWatcher = new ServiceWatcher(context, handler, action, this::onBind, this::onUnbind,
enableOverlayResId, nonOverlayPackageResId, intentServiceCheckPredicate);
mServiceWatcher = ServiceWatcher.create(context,
handler,
"RealLocationTimeZoneProviderProxy",
new CurrentUserServiceSupplier(context, action, enableOverlayResId,
nonOverlayPackageResId, BIND_TIME_ZONE_PROVIDER_SERVICE,
INSTALL_LOCATION_TIME_ZONE_PROVIDER_SERVICE),
this);
}
@Override
@@ -123,7 +95,8 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
return resolves;
}
private void onBind(IBinder binder, BoundService boundService) {
@Override
public void onBind(IBinder binder, BoundServiceInfo boundService) {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -138,7 +111,8 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
}
}
private void onUnbind() {
@Override
public void onUnbind() {
mThreadingDomain.assertCurrentThread();
synchronized (mSharedLock) {
@@ -199,7 +173,7 @@ class RealLocationTimeZoneProviderProxy extends LocationTimeZoneProviderProxy {
synchronized (mSharedLock) {
ipw.println("{RealLocationTimeZoneProviderProxy}");
ipw.println("mRequest=" + mRequest);
mServiceWatcher.dump(null, ipw, args);
mServiceWatcher.dump(ipw);
}
}