Merge "Use ServiceConnector and @DataClass in companion service"

This commit is contained in:
TreeHugger Robot
2019-12-21 01:37:44 +00:00
committed by Android (Google) Code Review
8 changed files with 341 additions and 163 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2017 The Android Open Source Project
* Copyright (C) 2019 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.
@@ -10,16 +10,9 @@
* 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 per missions and
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.companion;
/** @hide */
interface ICompanionDeviceDiscoveryServiceCallback {
@UnsupportedAppUsage
oneway void onDeviceSelected(String packageName, int userId, String deviceAddress);
@UnsupportedAppUsage
oneway void onDeviceSelectionCancel();
}
parcelable Association;

View File

@@ -0,0 +1,179 @@
/*
* Copyright (C) 2019 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 android.companion;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
import java.util.Objects;
/**
* A record indicating that a device with a given address was confirmed by the user to be
* associated to a given companion app
*
* @hide
*/
@DataClass(genEqualsHashCode = true, genToString = true)
public class Association implements Parcelable {
public final int userId;
public final @NonNull String deviceAddress;
public final @NonNull String companionAppPackage;
// Code below generated by codegen v1.0.13.
//
// DO NOT MODIFY!
// CHECKSTYLE:OFF Generated code
//
// To regenerate run:
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/companion/Association.java
//
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
// Settings > Editor > Code Style > Formatter Control
//@formatter:off
@DataClass.Generated.Member
public Association(
int userId,
@NonNull String deviceAddress,
@NonNull String companionAppPackage) {
this.userId = userId;
this.deviceAddress = deviceAddress;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deviceAddress);
this.companionAppPackage = companionAppPackage;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, companionAppPackage);
// onConstructed(); // You can define this method to get a callback
}
@Override
@DataClass.Generated.Member
public String toString() {
// You can override field toString logic by defining methods like:
// String fieldNameToString() { ... }
return "Association { " +
"userId = " + userId + ", " +
"deviceAddress = " + deviceAddress + ", " +
"companionAppPackage = " + companionAppPackage +
" }";
}
@Override
@DataClass.Generated.Member
public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(Association other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
@SuppressWarnings("unchecked")
Association that = (Association) o;
//noinspection PointlessBooleanExpression
return true
&& userId == that.userId
&& Objects.equals(deviceAddress, that.deviceAddress)
&& Objects.equals(companionAppPackage, that.companionAppPackage);
}
@Override
@DataClass.Generated.Member
public int hashCode() {
// You can override field hashCode logic by defining methods like:
// int fieldNameHashCode() { ... }
int _hash = 1;
_hash = 31 * _hash + userId;
_hash = 31 * _hash + Objects.hashCode(deviceAddress);
_hash = 31 * _hash + Objects.hashCode(companionAppPackage);
return _hash;
}
@Override
@DataClass.Generated.Member
public void writeToParcel(@NonNull Parcel dest, int flags) {
// You can override field parcelling by defining methods like:
// void parcelFieldName(Parcel dest, int flags) { ... }
dest.writeInt(userId);
dest.writeString(deviceAddress);
dest.writeString(companionAppPackage);
}
@Override
@DataClass.Generated.Member
public int describeContents() { return 0; }
/** @hide */
@SuppressWarnings({"unchecked", "RedundantCast"})
@DataClass.Generated.Member
protected Association(@NonNull Parcel in) {
// You can override field unparcelling by defining methods like:
// static FieldType unparcelFieldName(Parcel in) { ... }
int _userId = in.readInt();
String _deviceAddress = in.readString();
String _companionAppPackage = in.readString();
this.userId = _userId;
this.deviceAddress = _deviceAddress;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, deviceAddress);
this.companionAppPackage = _companionAppPackage;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, companionAppPackage);
// onConstructed(); // You can define this method to get a callback
}
@DataClass.Generated.Member
public static final @NonNull Parcelable.Creator<Association> CREATOR
= new Parcelable.Creator<Association>() {
@Override
public Association[] newArray(int size) {
return new Association[size];
}
@Override
public Association createFromParcel(@NonNull Parcel in) {
return new Association(in);
}
};
@DataClass.Generated(
time = 1573767103332L,
codegenVersion = "1.0.13",
sourceFile = "frameworks/base/core/java/android/companion/Association.java",
inputSignatures = "public final int userId\npublic final @android.annotation.NonNull java.lang.String deviceAddress\npublic final @android.annotation.NonNull java.lang.String companionAppPackage\nclass Association extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
@Deprecated
private void __metadata() {}
//@formatter:on
// End of generated code
}

View File

@@ -16,9 +16,10 @@
package android.companion;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.IFindDeviceCallback;
import com.android.internal.infra.AndroidFuture;
/** @hide */
@@ -27,5 +28,5 @@ interface ICompanionDeviceDiscoveryService {
in AssociationRequest request,
in String callingPackage,
in IFindDeviceCallback findCallback,
in ICompanionDeviceDiscoveryServiceCallback serviceCallback);
in AndroidFuture<Association> serviceCallback);
}

View File

@@ -17,4 +17,4 @@
package com.android.internal.infra;
/** @hide */
parcelable AndroidFuture;
parcelable AndroidFuture<T>;

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2019 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.internal.infra;
import android.annotation.NonNull;
import android.util.SparseArray;
import com.android.internal.util.Preconditions;
/**
* A {@link SparseArray} customized for a common use-case of storing state per-user.
*
* Unlike a normal {@link SparseArray} this will always create a value on {@link #get} if one is
* not present instead of returning null.
*
* @param <T> user state type
*/
public abstract class PerUser<T> extends SparseArray<T> {
/**
* Initialize state for the given user
*/
protected abstract @NonNull T create(int userId);
/**
* Same as {@link #get(int)}, renamed for readability.
*
* This will never return null, deferring to {@link #create} instead
* when called for the first time.
*/
public @NonNull T forUser(int userId) {
return get(userId);
}
@Override
public @NonNull T get(int userId) {
T userState = super.get(userId);
if (userState != null) {
return userState;
} else {
userState = Preconditions.checkNotNull(create(userId));
put(userId, userState);
return userState;
}
}
}

View File

@@ -19,6 +19,7 @@ package com.android.internal.util;
import android.os.RemoteException;
import android.util.ExceptionUtils;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -51,6 +52,13 @@ public class FunctionalUtils {
return action;
}
/**
* @see #uncheckExceptions(ThrowingConsumer)
*/
public static <A, B> BiConsumer<A, B> uncheckExceptions(ThrowingBiConsumer<A, B> action) {
return action;
}
/**
* @see #uncheckExceptions(ThrowingConsumer)
*/
@@ -185,4 +193,29 @@ public class FunctionalUtils {
}
}
}
/**
* A {@link BiConsumer} that allows throwing checked exceptions from its single abstract method.
*
* Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
* that throws a checked exception into a regular {@link Function}
*
* @param <A> see {@link BiConsumer}
* @param <B> see {@link BiConsumer}
*/
@FunctionalInterface
@SuppressWarnings("FunctionalInterfaceMethodChanged")
public interface ThrowingBiConsumer<A, B> extends BiConsumer<A, B> {
/** @see ThrowingFunction */
void acceptOrThrow(A a, B b) throws Exception;
@Override
default void accept(A a, B b) {
try {
acceptOrThrow(a, b);
} catch (Exception ex) {
throw ExceptionUtils.propagate(ex);
}
}
}
}

View File

@@ -37,12 +37,12 @@ import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.BluetoothDeviceFilter;
import android.companion.BluetoothLeDeviceFilter;
import android.companion.DeviceFilter;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.IFindDeviceCallback;
import android.companion.WifiDeviceFilter;
import android.content.BroadcastReceiver;
@@ -63,6 +63,7 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
import com.android.internal.util.Preconditions;
@@ -97,7 +98,7 @@ public class DeviceDiscoveryService extends Service {
DevicesAdapter mDevicesAdapter;
IFindDeviceCallback mFindCallback;
ICompanionDeviceDiscoveryServiceCallback mServiceCallback;
AndroidFuture<Association> mServiceCallback;
boolean mIsScanning = false;
@Nullable DeviceChooserActivity mActivity = null;
@@ -107,7 +108,7 @@ public class DeviceDiscoveryService extends Service {
public void startDiscovery(AssociationRequest request,
String callingPackage,
IFindDeviceCallback findCallback,
ICompanionDeviceDiscoveryServiceCallback serviceCallback) {
AndroidFuture serviceCallback) {
if (DEBUG) {
Log.i(LOG_TAG,
"startDiscovery() called with: filter = [" + request
@@ -303,23 +304,12 @@ public class DeviceDiscoveryService extends Service {
}
void onDeviceSelected(String callingPackage, String deviceAddress) {
try {
mServiceCallback.onDeviceSelected(
//TODO is this the right userId?
callingPackage, getUserId(), deviceAddress);
} catch (RemoteException e) {
Log.e(LOG_TAG, "Failed to record association: "
+ callingPackage + " <-> " + deviceAddress);
}
mServiceCallback.complete(new Association(getUserId(), deviceAddress, callingPackage));
}
void onCancel() {
if (DEBUG) Log.i(LOG_TAG, "onCancel()");
try {
mServiceCallback.onDeviceSelectionCancel();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
mServiceCallback.cancel(true);
}
class DevicesAdapter extends ArrayAdapter<DeviceFilterPair> {

View File

@@ -18,6 +18,7 @@
package com.android.server.companion;
import static com.android.internal.util.CollectionUtils.size;
import static com.android.internal.util.FunctionalUtils.uncheckExceptions;
import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
@@ -26,16 +27,15 @@ import static com.android.internal.util.function.pooled.PooledLambda.obtainRunna
import android.annotation.CheckResult;
import android.annotation.Nullable;
import android.app.PendingIntent;
import android.companion.Association;
import android.companion.AssociationRequest;
import android.companion.CompanionDeviceManager;
import android.companion.ICompanionDeviceDiscoveryService;
import android.companion.ICompanionDeviceDiscoveryServiceCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IFindDeviceCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageItemInfo;
@@ -67,6 +67,9 @@ import android.util.Xml;
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AndroidFuture;
import com.android.internal.infra.PerUser;
import com.android.internal.infra.ServiceConnector;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.CollectionUtils;
@@ -118,12 +121,13 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
private final CompanionDeviceManagerImpl mImpl;
private final ConcurrentMap<Integer, AtomicFile> mUidToStorage = new ConcurrentHashMap<>();
private IDeviceIdleController mIdleController;
private ServiceConnection mServiceConnection;
private PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>> mServiceConnectors;
private IAppOpsService mAppOpsManager;
private IFindDeviceCallback mFindDeviceCallback;
private AssociationRequest mRequest;
private String mCallingPackage;
private AndroidFuture<Association> mOngoingDeviceDiscovery;
private final Object mLock = new Object();
@@ -134,6 +138,19 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
Intent serviceIntent = new Intent().setComponent(SERVICE_TO_BIND_TO);
mServiceConnectors = new PerUser<ServiceConnector<ICompanionDeviceDiscoveryService>>() {
@Override
protected ServiceConnector<ICompanionDeviceDiscoveryService> create(int userId) {
return new ServiceConnector.Impl<>(
getContext(),
serviceIntent, 0/* bindingFlags */, userId,
ICompanionDeviceDiscoveryService.Stub::asInterface);
}
};
registerPackageMonitor();
}
@@ -187,7 +204,10 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
private void cleanup() {
synchronized (mLock) {
mServiceConnection = unbind(mServiceConnection);
AndroidFuture<Association> ongoingDeviceDiscovery = mOngoingDeviceDiscovery;
if (ongoingDeviceDiscovery != null && !ongoingDeviceDiscovery.isDone()) {
ongoingDeviceDiscovery.cancel(true);
}
mFindDeviceCallback = unlinkToDeath(mFindDeviceCallback, this, 0);
mRequest = null;
mCallingPackage = null;
@@ -207,15 +227,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
return null;
}
@Nullable
@CheckResult
private ServiceConnection unbind(@Nullable ServiceConnection conn) {
if (conn != null) {
getContext().unbindService(conn);
}
return null;
}
class CompanionDeviceManagerImpl extends ICompanionDeviceManager.Stub {
@Override
@@ -243,13 +254,27 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
checkCallerIsSystemOr(callingPackage);
int userId = getCallingUserId();
checkUsesFeature(callingPackage, userId);
mFindDeviceCallback = callback;
mRequest = request;
mCallingPackage = callingPackage;
callback.asBinder().linkToDeath(CompanionDeviceManagerService.this /* recipient */, 0);
final long callingIdentity = Binder.clearCallingIdentity();
try {
getContext().bindServiceAsUser(
new Intent().setComponent(SERVICE_TO_BIND_TO),
createServiceConnection(request, callback, callingPackage),
Context.BIND_AUTO_CREATE,
UserHandle.of(userId));
mOngoingDeviceDiscovery = mServiceConnectors.forUser(userId).postAsync(service -> {
AndroidFuture<Association> future = new AndroidFuture<>();
service.startDiscovery(request, callingPackage, callback, future);
return future;
}).whenComplete(uncheckExceptions((association, err) -> {
if (err == null) {
addAssociation(association);
} else {
Log.e(LOG_TAG, "Failed to discover device(s)", err);
callback.onFailure("No devices found: " + err.getMessage());
}
cleanup();
}));
} finally {
Binder.restoreCallingIdentity(callingIdentity);
}
@@ -386,82 +411,14 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
return Binder.getCallingUid() == Process.SYSTEM_UID;
}
private ServiceConnection createServiceConnection(
final AssociationRequest request,
final IFindDeviceCallback findDeviceCallback,
final String callingPackage) {
mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG) {
Slog.i(LOG_TAG,
"onServiceConnected(name = " + name + ", service = "
+ service + ")");
}
mFindDeviceCallback = findDeviceCallback;
mRequest = request;
mCallingPackage = callingPackage;
try {
mFindDeviceCallback.asBinder().linkToDeath(
CompanionDeviceManagerService.this, 0);
} catch (RemoteException e) {
cleanup();
return;
}
try {
ICompanionDeviceDiscoveryService.Stub
.asInterface(service)
.startDiscovery(
request,
callingPackage,
findDeviceCallback,
getServiceCallback());
} catch (RemoteException e) {
Log.e(LOG_TAG, "Error while initiating device discovery", e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (DEBUG) Slog.i(LOG_TAG, "onServiceDisconnected(name = " + name + ")");
}
};
return mServiceConnection;
}
private ICompanionDeviceDiscoveryServiceCallback.Stub getServiceCallback() {
return new ICompanionDeviceDiscoveryServiceCallback.Stub() {
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
return super.onTransact(code, data, reply, flags);
} catch (Throwable e) {
Slog.e(LOG_TAG, "Error during IPC", e);
throw ExceptionUtils.propagate(e, RemoteException.class);
}
}
@Override
public void onDeviceSelected(String packageName, int userId, String deviceAddress) {
addAssociation(userId, packageName, deviceAddress);
cleanup();
}
@Override
public void onDeviceSelectionCancel() {
cleanup();
}
};
}
void addAssociation(int userId, String packageName, String deviceAddress) {
updateSpecialAccessPermissionForAssociatedPackage(packageName, userId);
recordAssociation(packageName, deviceAddress);
addAssociation(new Association(userId, deviceAddress, packageName));
}
void addAssociation(Association association) {
updateSpecialAccessPermissionForAssociatedPackage(
association.companionAppPackage, association.userId);
recordAssociation(association);
}
void removeAssociation(int userId, String pkg, String deviceMacAddress) {
@@ -525,14 +482,15 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}, getContext(), packageName, userId).recycleOnUse());
}
private void recordAssociation(String priviledgedPackage, String deviceAddress) {
private void recordAssociation(Association association) {
if (DEBUG) {
Log.i(LOG_TAG, "recordAssociation(priviledgedPackage = " + priviledgedPackage
+ ", deviceAddress = " + deviceAddress + ")");
Log.i(LOG_TAG, "recordAssociation(" + association + ")");
}
int userId = getCallingUserId();
updateAssociations(associations -> CollectionUtils.add(associations,
new Association(userId, deviceAddress, priviledgedPackage)));
updateAssociations(associations -> CollectionUtils.add(associations, association));
}
private void recordAssociation(String privilegedPackage, String deviceAddress) {
recordAssociation(new Association(getCallingUserId(), deviceAddress, privilegedPackage));
}
private void updateAssociations(Function<Set<Association>, Set<Association>> update) {
@@ -629,41 +587,6 @@ public class CompanionDeviceManagerService extends SystemService implements Bind
}
}
private class Association {
public final int uid;
public final String deviceAddress;
public final String companionAppPackage;
private Association(int uid, String deviceAddress, String companionAppPackage) {
this.uid = uid;
this.deviceAddress = checkNotNull(deviceAddress);
this.companionAppPackage = checkNotNull(companionAppPackage);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Association that = (Association) o;
if (uid != that.uid) return false;
if (!deviceAddress.equals(that.deviceAddress)) return false;
return companionAppPackage.equals(that.companionAppPackage);
}
@Override
public int hashCode() {
int result = uid;
result = 31 * result + deviceAddress.hashCode();
result = 31 * result + companionAppPackage.hashCode();
return result;
}
}
private class ShellCmd extends ShellCommand {
public static final String USAGE = "help\n"
+ "list USER_ID\n"