blob: a9641f0f1c1b281b3f217e24f290996b82f4bcda [file] [log] [blame]
/*
* Copyright (C) 2020 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.location.provider.proxy;
import static com.android.internal.util.ConcurrentUtils.DIRECT_EXECUTOR;
import static com.android.server.location.LocationManagerService.TAG;
import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
import android.location.provider.ILocationProvider;
import android.location.provider.ILocationProviderManager;
import android.location.provider.ProviderProperties;
import android.location.provider.ProviderRequest;
import android.location.util.identity.CallerIdentity;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
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.ServiceListener;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Proxy for ILocationProvider implementations.
*/
public class ProxyLocationProvider extends AbstractLocationProvider implements
ServiceListener<BoundServiceInfo> {
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;
/**
* Creates and registers this proxy. If no suitable service is available for the proxy, returns
* null.
*/
@Nullable
public static ProxyLocationProvider create(Context context, String provider, String action,
int enableOverlayResId, int nonOverlayPackageResId) {
ProxyLocationProvider proxy = new ProxyLocationProvider(context, provider, action,
enableOverlayResId, nonOverlayPackageResId);
if (proxy.checkServiceResolves()) {
return proxy;
} else {
return null;
}
}
final Object mLock = new Object();
final Context mContext;
final ServiceWatcher mServiceWatcher;
final String mName;
@GuardedBy("mLock")
final ArrayList<Runnable> mFlushListeners = new ArrayList<>(0);
@GuardedBy("mLock")
@Nullable Runnable mResetter;
@GuardedBy("mLock")
@Nullable Proxy mProxy;
@GuardedBy("mLock")
@Nullable BoundServiceInfo mBoundServiceInfo;
private volatile ProviderRequest mRequest;
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 = ServiceWatcher.create(context, provider,
new CurrentUserServiceSupplier(context, action, enableOverlayResId,
nonOverlayPackageResId), this);
mName = provider;
mProxy = null;
mRequest = ProviderRequest.EMPTY_REQUEST;
}
private boolean checkServiceResolves() {
return mServiceWatcher.checkServiceResolves();
}
@Override
public void onBind(IBinder binder, BoundServiceInfo boundServiceInfo) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
synchronized (mLock) {
mProxy = new Proxy();
mBoundServiceInfo = boundServiceInfo;
provider.setLocationProviderManager(mProxy);
ProviderRequest request = mRequest;
if (!request.equals(ProviderRequest.EMPTY_REQUEST)) {
provider.setRequest(request);
}
}
}
@Override
public void onUnbind() {
Runnable[] flushListeners;
synchronized (mLock) {
mProxy = 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 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);
}
flushListeners = mFlushListeners.toArray(new Runnable[0]);
mFlushListeners.clear();
}
final int size = flushListeners.length;
for (int i = 0; i < size; ++i) {
flushListeners[i].run();
}
}
@Override
protected void onStart() {
mServiceWatcher.register();
}
@Override
protected void onStop() {
mServiceWatcher.unregister();
}
@Override
protected void onSetRequest(ProviderRequest request) {
mRequest = request;
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
provider.setRequest(request);
});
}
@Override
protected void onFlush(Runnable callback) {
mServiceWatcher.runOnBinder(new ServiceWatcher.BinderOperation() {
@Override
public void run(IBinder binder) throws RemoteException {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
// at first glance it would be more straightforward to pass the flush callback
// through to the provider and allow it to be invoked directly. however, in this
// case the binder calls 1) provider delivering flushed locations 2) provider
// delivering flush complete, while correctly ordered within the provider, would
// be invoked on different binder objects and thus would have no defined order
// on the system server side. thus, we ensure that both (1) and (2) are invoked
// on the same binder object (the ILocationProviderManager) and have a well
// defined ordering, so that the flush callback will always happen after
// location delivery.
synchronized (mLock) {
mFlushListeners.add(callback);
}
provider.flush();
}
@Override
public void onError() {
synchronized (mLock) {
mFlushListeners.remove(callback);
}
callback.run();
}
});
}
@Override
public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider provider = ILocationProvider.Stub.asInterface(binder);
provider.sendExtraCommand(command, extras);
});
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mServiceWatcher.dump(pw);
}
private class Proxy extends ILocationProviderManager.Stub {
Proxy() {}
// executed on binder thread
@Override
public void onInitialize(boolean allowed, ProviderProperties properties,
@Nullable String attributionTag) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
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);
Log.i(TAG, mName + " provider loaded extra attribution tags: "
+ Arrays.toString(attributionTags));
}
}
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
.withAllowed(allowed)
.withProperties(properties)
.withIdentity(identity)
.withExtraAttributionTags(extraAttributionTags));
}
}
// executed on binder thread
@Override
public void onSetProperties(ProviderProperties properties) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
setProperties(properties);
}
}
// executed on binder thread
@Override
public void onSetAllowed(boolean allowed) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
setAllowed(allowed);
}
}
// executed on binder thread
@Override
public void onReportLocation(Location location) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
reportLocation(LocationResult.wrap(location).validate());
}
}
// executed on binder thread
@Override
public void onReportLocations(List<Location> locations) {
synchronized (mLock) {
if (mProxy != this) {
return;
}
reportLocation(LocationResult.wrap(locations).validate());
}
}
// executed on binder thread
@Override
public void onFlushComplete() {
Runnable callback = null;
synchronized (mLock) {
if (mProxy != this) {
return;
}
if (!mFlushListeners.isEmpty()) {
callback = mFlushListeners.remove(0);
}
}
if (callback != null) {
callback.run();
}
}
}
}