summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSoonil Nagarkar <sooniln@google.com>2021-03-01 16:26:21 -0800
committerSoonil Nagarkar <sooniln@google.com>2021-03-22 20:27:31 -0700
commitc257baefb5758f895c52b520d9d772225c067e14 (patch)
tree530938cb055065355888f6c3a6e44ce4b129cc6b
parent74882cf536a560d23a1cf422faee8c15e1cda763 (diff)
Refactor ServiceWatcher
Split it up into more generic and useable components that are not as location specific. Bug: 182491144 Bug: 181665909 Test: presubmits Change-Id: I2d6d5e70328657a7430e170531402cb3acbed028
-rw-r--r--location/java/android/location/provider/ILocationProviderManager.aidl2
-rw-r--r--location/java/android/location/provider/LocationProviderBase.java4
-rw-r--r--location/lib/java/com/android/location/provider/LocationProviderBase.java4
-rw-r--r--packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java18
-rw-r--r--services/core/java/com/android/server/location/GeocoderProxy.java13
-rw-r--r--services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java30
-rw-r--r--services/core/java/com/android/server/location/LocationManagerService.java2
-rw-r--r--services/core/java/com/android/server/location/geofence/GeofenceProxy.java23
-rw-r--r--services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java131
-rw-r--r--services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java302
-rw-r--r--services/core/java/com/android/server/servicewatcher/ServiceWatcher.java606
-rw-r--r--services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java297
-rw-r--r--services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java64
13 files changed, 899 insertions, 597 deletions
diff --git a/location/java/android/location/provider/ILocationProviderManager.aidl b/location/java/android/location/provider/ILocationProviderManager.aidl
index 50ed046f09cd..092ec67ff146 100644
--- a/location/java/android/location/provider/ILocationProviderManager.aidl
+++ b/location/java/android/location/provider/ILocationProviderManager.aidl
@@ -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);
diff --git a/location/java/android/location/provider/LocationProviderBase.java b/location/java/android/location/provider/LocationProviderBase.java
index ae6395d5d12d..eada22cd94dc 100644
--- a/location/java/android/location/provider/LocationProviderBase.java
+++ b/location/java/android/location/provider/LocationProviderBase.java
@@ -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) {
diff --git a/location/lib/java/com/android/location/provider/LocationProviderBase.java b/location/lib/java/com/android/location/provider/LocationProviderBase.java
index 7f1cf6dc3459..95f6c2f38ecb 100644
--- a/location/lib/java/com/android/location/provider/LocationProviderBase.java
+++ b/location/lib/java/com/android/location/provider/LocationProviderBase.java
@@ -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) {
diff --git a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
index 7cc599499ca1..c4c60ea5aaa8 100644
--- a/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
+++ b/packages/FusedLocation/test/src/com/android/location/fused/tests/FusedLocationServiceTest.java
@@ -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);
diff --git a/services/core/java/com/android/server/location/GeocoderProxy.java b/services/core/java/com/android/server/location/GeocoderProxy.java
index c93c4b1f21b7..dc3596b6c2a7 100644
--- a/services/core/java/com/android/server/location/GeocoderProxy.java
+++ b/services/core/java/com/android/server/location/GeocoderProxy.java
@@ -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);
diff --git a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
index e1c87000ea89..6ac6e77342be 100644
--- a/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
+++ b/services/core/java/com/android/server/location/HardwareActivityRecognitionProxy.java
@@ -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() {}
}
diff --git a/services/core/java/com/android/server/location/LocationManagerService.java b/services/core/java/com/android/server/location/LocationManagerService.java
index 6d1606defa04..864aa33a58d0 100644
--- a/services/core/java/com/android/server/location/LocationManagerService.java
+++ b/services/core/java/com/android/server/location/LocationManagerService.java
@@ -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);
diff --git a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
index c70714932792..90b446ecb15d 100644
--- a/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
+++ b/services/core/java/com/android/server/location/geofence/GeofenceProxy.java
@@ -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() {}
diff --git a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
index 317e61b44e46..5df78704d002 100644
--- a/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
+++ b/services/core/java/com/android/server/location/provider/proxy/ProxyLocationProvider.java
@@ -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));
}
}
diff --git a/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
new file mode 100644
index 000000000000..3ca8a5a1f554
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/CurrentUserServiceSupplier.java
@@ -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;
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
index 5d49663209b7..23b5d98de59f 100644
--- a/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcher.java
@@ -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 {
-
- 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;
+public interface ServiceWatcher {
- 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;
- }
-
/**
- * Information on the service ServiceWatcher has selected as the best option for binding.
+ * 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
*/
- @Immutable
- public static final class BoundService implements Comparable<BoundService> {
-
- public static final BoundService NONE = new BoundService(Integer.MIN_VALUE, null,
- false, null, -1);
-
- public final int version;
- @Nullable
- public final ComponentName component;
- public final boolean serviceIsMultiuser;
- public final int uid;
- @Nullable
- public final Bundle metadata;
-
- BoundService(ResolveInfo resolveInfo) {
- Preconditions.checkArgument(resolveInfo.serviceInfo.getComponentName() != null);
-
- 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;
- }
-
- component = resolveInfo.serviceInfo.getComponentName();
- uid = resolveInfo.serviceInfo.applicationInfo.uid;
- }
-
- 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;
- }
-
- public @Nullable String getPackageName() {
- return component != null ? component.getPackageName() : null;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof BoundService)) {
- return false;
- }
- BoundService that = (BoundService) o;
- return version == that.version && uid == that.uid
- && Objects.equals(component, that.component);
- }
+ interface ServiceListener<TBoundServiceInfo extends BoundServiceInfo> {
+ /** Invoked when a service is bound. Run on the ServiceWatcher thread. */
+ void onBind(IBinder binder, TBoundServiceInfo service) throws RemoteException;
- @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;
- }
-
- @Override
- public String toString() {
- if (component == null) {
- return "none";
- } else {
- return component.toShortString() + "@" + version + "[u"
- + UserHandle.getUserId(uid) + "]";
- }
- }
- }
-
- 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;
+ /** Invoked when a service is unbound. Run on the ServiceWatcher thread. */
+ void onUnbind();
}
/**
- * Returns true if there is at least one component that could satisfy the ServiceWatcher's
- * constraints.
+ * A listener for when a {@link ServiceSupplier} decides that the current service has changed.
*/
- 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;
+ interface ServiceChangedListener {
+ /**
+ * Should be invoked when the current service may have changed.
+ */
+ void onServiceChanged();
}
/**
- * Starts the process of determining the best matching service and maintaining a binding to it.
+ * 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
*/
- 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);
+ 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();
- // 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();
+ /**
+ * 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);
- mRegistered = true;
+ /**
+ * 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();
- mHandler.post(() -> onBestServiceChanged(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();
}
/**
- * Stops the process of determining the best matching service and releases any binding.
+ * Information on the service selected as the best option for binding.
*/
- public void unregister() {
- mHandler.sendMessage(PooledLambda.obtainMessage(ServiceWatcher::unregisterInternal,
- ServiceWatcher.this));
- }
-
- private void unregisterInternal() {
- Preconditions.checkState(mRegistered);
+ class BoundServiceInfo {
- mRegistered = false;
+ protected final @Nullable String mAction;
+ protected final int mUid;
+ protected final ComponentName mComponentName;
- 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;
+ protected BoundServiceInfo(String action, ResolveInfo resolveInfo) {
+ this(action, resolveInfo.serviceInfo.applicationInfo.uid,
+ resolveInfo.serviceInfo.getComponentName());
}
- mTargetService = newServiceInfo;
- if (mTargetService.equals(BoundService.NONE)) {
- return;
+ protected BoundServiceInfo(String action, int uid, ComponentName componentName) {
+ mAction = action;
+ mUid = uid;
+ mComponentName = Objects.requireNonNull(componentName);
}
- 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);
+ /** Returns the action associated with this bound service. */
+ public @Nullable String getAction() {
+ return mAction;
}
- }
-
- @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());
+ /** Returns the component of this bound service. */
+ public ComponentName getComponentName() {
+ return mComponentName;
}
- final BoundService boundService = mPendingBinds.remove(component);
- if (boundService == null) {
- return;
+ /** Returns the user id for this bound service. */
+ public @UserIdInt int getUserId() {
+ return UserHandle.getUserId(mUid);
}
- 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 boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
}
- }
- }
-
- @Override
- public final void onServiceDisconnected(ComponentName component) {
- Preconditions.checkState(Looper.myLooper() == mHandler.getLooper());
- if (mBinder == null) {
- return;
+ BoundServiceInfo that = (BoundServiceInfo) o;
+ return mUid == that.mUid
+ && Objects.equals(mAction, that.mAction)
+ && mComponentName.equals(that.mComponentName);
}
- if (D) {
- Log.d(TAG, getLogPrefix() + " disconnected from " + component.toShortString());
+ @Override
+ public final int hashCode() {
+ return Objects.hash(mAction, mUid, mComponentName);
}
- mBinder = null;
- if (mOnUnbind != null) {
- mOnUnbind.run();
+ @Override
+ public String toString() {
+ if (mComponentName == null) {
+ return "none";
+ } else {
+ return mUid + "/" + mComponentName.flattenToShortString();
+ }
}
}
- @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);
+ /**
+ * Creates a new ServiceWatcher instance.
+ */
+ 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);
}
- void onUserUnlocked(@UserIdInt int userId) {
- if (userId == mCurrentUserId) {
- onBestServiceChanged(false);
- }
+ /**
+ * Creates a new ServiceWatcher instance that runs on the given handler.
+ */
+ 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);
}
/**
- * Runs the given function asynchronously if and only if currently connected. Suppresses any
- * RemoteException thrown during execution.
+ * Returns true if there is at least one service that the ServiceWatcher could hypothetically
+ * bind to, as selected by the {@link ServiceSupplier}.
*/
- public final void runOnBinder(BinderRunner runner) {
- mHandler.post(() -> {
- if (mBinder == null) {
- runner.onError();
- return;
- }
+ boolean checkServiceResolves();
- 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();
- }
- });
- }
+ /**
+ * 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();
- private String getLogPrefix() {
- return "[" + mIntent.getAction() + "]";
- }
+ /**
+ * 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();
- @Override
- public String toString() {
- return mTargetService.toString();
- }
+ /**
+ * 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);
/**
- * Dump for debugging.
+ * Dumps ServiceWatcher information.
*/
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("target service=" + mTargetService);
- pw.println("connected=" + (mBinder != null));
- }
-}
+ void dump(PrintWriter pw);
+} \ No newline at end of file
diff --git a/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
new file mode 100644
index 000000000000..7757a7a096db
--- /dev/null
+++ b/services/core/java/com/android/server/servicewatcher/ServiceWatcherImpl.java
@@ -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");
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
index 0b51488280e0..6c3f016588f8 100644
--- a/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
+++ b/services/core/java/com/android/server/timezonedetector/location/RealLocationTimeZoneProviderProxy.java
@@ -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);
}
}