Merge "Refactor ServiceWatcher" into sc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
76f3adb44f
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user