blob: 631be380e2ebc7bacdd3aacb987da4ce3ceebf38 [file] [log] [blame]
/*
* 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)) {
Log.i(TAG, "[" + mTag + "] chose new implementation " + newBoundServiceInfo);
MyServiceConnection oldServiceConnection = mServiceConnection;
MyServiceConnection newServiceConnection = new MyServiceConnection(newBoundServiceInfo);
mServiceConnection = newServiceConnection;
mHandler.post(() -> {
oldServiceConnection.unbind();
newServiceConnection.bind();
});
}
}
@Override
public String toString() {
MyServiceConnection serviceConnection;
synchronized (this) {
serviceConnection = mServiceConnection;
}
return serviceConnection.getBoundServiceInfo().toString();
}
@Override
public void dump(PrintWriter pw) {
MyServiceConnection serviceConnection;
synchronized (this) {
serviceConnection = mServiceConnection;
}
pw.println("target service=" + serviceConnection.getBoundServiceInfo());
pw.println("connected=" + serviceConnection.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;
}
if (D) {
Log.d(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);
Log.i(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;
}
Log.i(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.w(TAG, "[" + mTag + "] " + mBoundServiceInfo + " died");
// introduce a small delay to prevent spamming binding over and over, since the likely
// cause of a binding dying is some package event that may take time to recover from
mHandler.postDelayed(() -> onServiceChanged(true), 500);
}
@Override
public final void onNullBinding(ComponentName component) {
Log.e(TAG, "[" + mTag + "] " + mBoundServiceInfo + " has null binding");
}
}
}