[CDM ContextSync] ContextSync support APIs

Allow ContextSync to listen to transport changes, send & receive
messages

Bug: 253307662

Test: m build
Change-Id: I424ca4ae8f1578328e093733a0f06b621aca7a07
This commit is contained in:
Guojing Yuan
2023-03-02 22:29:28 +00:00
parent ca6f905929
commit 1e5e9969db
8 changed files with 424 additions and 40 deletions

View File

@@ -800,6 +800,119 @@ public final class CompanionDeviceManager {
}
}
/**
* Listener for any changes to {@link com.android.server.companion.transport.Transport}.
*
* @hide
*/
public interface OnTransportsChangedListener {
/**
* Invoked when a change occurs to any of the transports
*
* @param associations all the associations which have connected transports
*/
void onTransportsChanged(@NonNull List<AssociationInfo> associations);
}
/**
* Register a listener for any changes to
* {@link com.android.server.companion.transport.Transport}. Your app will receive a callback to
* {@link OnTransportsChangedListener} immediately with all the existing transports.
*
* @hide
*/
public void addOnTransportsChangedListener(
@NonNull Executor executor, @NonNull OnTransportsChangedListener listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
executor, listener);
try {
mService.addOnTransportsChangedListener(proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Unregister a listener to stop receiving any changes to
* {@link com.android.server.companion.transport.Transport}.
*
* @hide
*/
public void removeOnTransportsChangedListener(
@NonNull OnTransportsChangedListener listener) {
final OnTransportsChangedListenerProxy proxy = new OnTransportsChangedListenerProxy(
null, listener);
try {
mService.removeOnTransportsChangedListener(proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Send a message to remote devices
*
* @hide
*/
public void sendMessage(int messageType, byte[] data, int[] associationIds) {
try {
mService.sendMessage(messageType, data, associationIds);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Listener when a message is received for the registered message type
*
* @see #addOnMessageReceivedListener(Executor, int, OnMessageReceivedListener)
*
* @hide
*/
public interface OnMessageReceivedListener {
/**
* Called when a message is received
*/
void onMessageReceived(int associationId, byte[] data);
}
/**
* Register a listener to receive callbacks when a message is received by the given type
*
* @see com.android.server.companion.transport.Transport for supported message types
*
* @hide
*/
public void addOnMessageReceivedListener(@NonNull Executor executor, int messageType,
OnMessageReceivedListener listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
executor, listener);
try {
mService.addOnMessageReceivedListener(messageType, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Unregister a listener to stop receiving callbacks when a message is received by the given
* type
*
* @see com.android.server.companion.transport.Transport for supported message types
*
* @hide
*/
public void removeOnMessageReceivedListener(int messageType,
OnMessageReceivedListener listener) {
final OnMessageReceivedListenerProxy proxy = new OnMessageReceivedListenerProxy(
null, listener);
try {
mService.removeOnMessageReceivedListener(messageType, proxy);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Checks whether the bluetooth device represented by the mac address was recently associated
* with the companion app. This allows these devices to skip the Bluetooth pairing dialog if
@@ -1277,6 +1390,40 @@ public final class CompanionDeviceManager {
}
}
private static class OnTransportsChangedListenerProxy
extends IOnTransportsChangedListener.Stub {
private final Executor mExecutor;
private final OnTransportsChangedListener mListener;
private OnTransportsChangedListenerProxy(Executor executor,
OnTransportsChangedListener listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onTransportsChanged(@NonNull List<AssociationInfo> associations) {
mExecutor.execute(() -> mListener.onTransportsChanged(associations));
}
}
private static class OnMessageReceivedListenerProxy
extends IOnMessageReceivedListener.Stub {
private final Executor mExecutor;
private final OnMessageReceivedListener mListener;
private OnMessageReceivedListenerProxy(Executor executor,
OnMessageReceivedListener listener) {
mExecutor = executor;
mListener = listener;
}
@Override
public void onMessageReceived(int associationId, byte[] data) {
mExecutor.execute(() -> mListener.onMessageReceived(associationId, data));
}
}
private static class SystemDataTransferCallbackProxy extends ISystemDataTransferCallback.Stub {
private final Executor mExecutor;
private final OutcomeReceiver<Void, CompanionException> mCallback;

View File

@@ -19,6 +19,8 @@ package android.companion;
import android.app.PendingIntent;
import android.companion.IAssociationRequestCallback;
import android.companion.IOnAssociationsChangedListener;
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.AssociationInfo;
import android.companion.AssociationRequest;
@@ -67,6 +69,16 @@ interface ICompanionDeviceManager {
void removeOnAssociationsChangedListener(IOnAssociationsChangedListener listener, int userId);
void addOnTransportsChangedListener(IOnTransportsChangedListener listener);
void removeOnTransportsChangedListener(IOnTransportsChangedListener listener);
void sendMessage(int messageType, in byte[] data, in int[] associationIds);
void addOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
void removeOnMessageReceivedListener(int messageType, IOnMessageReceivedListener listener);
void notifyDeviceAppeared(int associationId);
void notifyDeviceDisappeared(int associationId);

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2023 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 per missions and
* limitations under the License.
*/
package android.companion;
/** @hide */
interface IOnMessageReceivedListener {
oneway void onMessageReceived(int associationId, in byte[] data);
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2023 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 per missions and
* limitations under the License.
*/
package android.companion;
import android.companion.AssociationInfo;
/** @hide */
interface IOnTransportsChangedListener {
oneway void onTransportsChanged(in List<AssociationInfo> associations);
}

View File

@@ -59,6 +59,8 @@ import android.companion.DeviceNotAssociatedException;
import android.companion.IAssociationRequestCallback;
import android.companion.ICompanionDeviceManager;
import android.companion.IOnAssociationsChangedListener;
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.companion.ISystemDataTransferCallback;
import android.content.ComponentName;
import android.content.Context;
@@ -232,7 +234,7 @@ public class CompanionDeviceManagerService extends SystemService {
/* cdmService */this, mAssociationStore);
mCompanionAppController = new CompanionApplicationController(
context, mAssociationStore, mDevicePresenceMonitor);
mTransportManager = new CompanionTransportManager(context);
mTransportManager = new CompanionTransportManager(context, mAssociationStore);
mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
mSystemDataTransferRequestStore, mTransportManager);
@@ -600,6 +602,37 @@ public class CompanionDeviceManagerService extends SystemService {
mListeners.unregister(listener);
}
@Override
@GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
public void addOnTransportsChangedListener(IOnTransportsChangedListener listener) {
mTransportManager.addListener(listener);
}
@Override
@GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
public void removeOnTransportsChangedListener(IOnTransportsChangedListener listener) {
mTransportManager.removeListener(listener);
}
@Override
@GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
public void sendMessage(int messageType, byte[] data, int[] associationIds) {
mTransportManager.sendMessage(messageType, data, associationIds);
}
@Override
@GuardedBy("CompanionDeviceManagerService.this.mTransportManager.mTransports")
public void addOnMessageReceivedListener(int messageType,
IOnMessageReceivedListener listener) {
mTransportManager.addListener(messageType, listener);
}
@Override
public void removeOnMessageReceivedListener(int messageType,
IOnMessageReceivedListener listener) {
mTransportManager.removeListener(messageType, listener);
}
@Override
public void legacyDisassociate(String deviceMacAddress, String packageName, int userId) {
Log.i(TAG, "legacyDisassociate() pkg=u" + userId + "/" + packageName

View File

@@ -31,6 +31,7 @@ import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.companion.AssociationInfo;
import android.companion.DeviceNotAssociatedException;
import android.companion.IOnMessageReceivedListener;
import android.companion.ISystemDataTransferCallback;
import android.companion.datatransfer.PermissionSyncRequest;
import android.companion.datatransfer.SystemDataTransferRequest;
@@ -40,6 +41,7 @@ import android.content.Intent;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.UserHandle;
@@ -92,8 +94,18 @@ public class SystemDataTransferProcessor {
mAssociationStore = associationStore;
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
mTransportManager = transportManager;
mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE,
this::onReceivePermissionRestore);
IOnMessageReceivedListener messageListener = new IOnMessageReceivedListener() {
@Override
public void onMessageReceived(int associationId, byte[] data) throws RemoteException {
onReceivePermissionRestore(data);
}
@Override
public IBinder asBinder() {
return null;
}
};
mTransportManager.addListener(MESSAGE_REQUEST_PERMISSION_RESTORE, messageListener);
mPermissionControllerManager = mContext.getSystemService(PermissionControllerManager.class);
mExecutor = Executors.newSingleThreadExecutor();
}

View File

@@ -22,25 +22,32 @@ import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_P
import static com.android.server.companion.transport.Transport.MESSAGE_REQUEST_PLATFORM_INFO;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.ActivityManagerInternal;
import android.companion.AssociationInfo;
import android.companion.IOnMessageReceivedListener;
import android.companion.IOnTransportsChangedListener;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.companion.transport.Transport.Listener;
import com.android.server.companion.AssociationStore;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
@@ -63,30 +70,99 @@ public class CompanionTransportManager {
}
private final Context mContext;
private final AssociationStore mAssociationStore;
/** Association id -> Transport */
@GuardedBy("mTransports")
private final SparseArray<Transport> mTransports = new SparseArray<>();
@NonNull
private final Map<Integer, Listener> mListeners = new HashMap<>();
private final RemoteCallbackList<IOnTransportsChangedListener> mTransportsListeners =
new RemoteCallbackList<>();
/** Message type -> IOnMessageReceivedListener */
@NonNull
private final SparseArray<IOnMessageReceivedListener> mMessageListeners = new SparseArray<>();
@Nullable
private Transport mTempTransport;
public CompanionTransportManager(Context context) {
public CompanionTransportManager(Context context, AssociationStore associationStore) {
mContext = context;
mAssociationStore = associationStore;
}
/**
* Add a message listener when a message is received for the message type
* Add a listener to receive callbacks when a message is received for the message type
*/
@GuardedBy("mTransports")
public void addListener(int message, @NonNull Listener listener) {
mListeners.put(message, listener);
public void addListener(int message, @NonNull IOnMessageReceivedListener listener) {
mMessageListeners.put(message, listener);
for (int i = 0; i < mTransports.size(); i++) {
mTransports.valueAt(i).addListener(message, listener);
}
}
/**
* Add a listener to receive callbacks when any of the transports is changed
*/
@GuardedBy("mTransports")
public void addListener(IOnTransportsChangedListener listener) {
Slog.i(TAG, "Registering OnTransportsChangedListener");
mTransportsListeners.register(listener);
List<AssociationInfo> associations = new ArrayList<>();
for (int i = 0; i < mTransports.size(); i++) {
AssociationInfo association = mAssociationStore.getAssociationById(
mTransports.keyAt(i));
if (association != null) {
associations.add(association);
}
}
mTransportsListeners.broadcast(listener1 -> {
// callback to the current listener with all the associations of the transports
// immediately
if (listener1 == listener) {
try {
listener.onTransportsChanged(associations);
} catch (RemoteException ignored) {
}
}
});
}
/**
* Remove the listener for receiving callbacks when any of the transports is changed
*/
public void removeListener(IOnTransportsChangedListener listener) {
mTransportsListeners.unregister(listener);
}
/**
* Remove the listener to stop receiving calbacks when a message is received for the given type
*/
public void removeListener(int messageType, IOnMessageReceivedListener listener) {
mMessageListeners.remove(messageType);
}
/**
* Send a message to remote devices through the transports
*/
@GuardedBy("mTransports")
public void sendMessage(int message, byte[] data, int[] associationIds) {
Slog.i(TAG, "Sending message 0x" + Integer.toHexString(message)
+ " data length " + data.length);
for (int i = 0; i < associationIds.length; i++) {
if (mTransports.contains(associationIds[i])) {
try {
mTransports.get(associationIds[i]).sendMessage(message, data);
} catch (IOException e) {
Slog.e(TAG, "Failed to send message 0x" + Integer.toHexString(message)
+ " data length " + data.length + " to association "
+ associationIds[i]);
}
}
}
}
/**
* For the moment, we only offer transporting of system data to built-in
* companion apps; future work will improve the security model to support
@@ -119,6 +195,8 @@ public class CompanionTransportManager {
}
initializeTransport(associationId, fd);
notifyOnTransportsChanged();
}
}
@@ -130,16 +208,35 @@ public class CompanionTransportManager {
mTransports.delete(associationId);
transport.stop();
}
notifyOnTransportsChanged();
}
}
@GuardedBy("mTransports")
private void notifyOnTransportsChanged() {
List<AssociationInfo> associations = new ArrayList<>();
for (int i = 0; i < mTransports.size(); i++) {
AssociationInfo association = mAssociationStore.getAssociationById(
mTransports.keyAt(i));
if (association != null) {
associations.add(association);
}
}
mTransportsListeners.broadcast(listener -> {
try {
listener.onTransportsChanged(associations);
} catch (RemoteException ignored) {
}
});
}
@GuardedBy("mTransports")
private void initializeTransport(int associationId, ParcelFileDescriptor fd) {
Slog.i(TAG, "Initializing transport");
if (!isSecureTransportEnabled()) {
Transport transport = new RawTransport(associationId, fd, mContext);
for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
transport.addListener(entry.getKey(), entry.getValue());
}
addMessageListenersToTransport(transport);
transport.start();
mTransports.put(associationId, transport);
Slog.i(TAG, "RawTransport is created");
@@ -148,10 +245,21 @@ public class CompanionTransportManager {
// Exchange platform info to decide which transport should be created
mTempTransport = new RawTransport(associationId, fd, mContext);
for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
mTempTransport.addListener(entry.getKey(), entry.getValue());
}
mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, this::onPlatformInfoReceived);
addMessageListenersToTransport(mTempTransport);
IOnMessageReceivedListener listener = new IOnMessageReceivedListener() {
@Override
public void onMessageReceived(int associationId, byte[] data) throws RemoteException {
synchronized (mTransports) {
onPlatformInfoReceived(associationId, data);
}
}
@Override
public IBinder asBinder() {
return null;
}
};
mTempTransport.addListener(MESSAGE_REQUEST_PLATFORM_INFO, listener);
mTempTransport.start();
int sdk = Build.VERSION.SDK_INT;
@@ -163,14 +271,21 @@ public class CompanionTransportManager {
.put(release.getBytes());
// TODO: it should check if preSharedKey is given
mTempTransport.requestForResponse(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
try {
mTempTransport.sendMessage(MESSAGE_REQUEST_PLATFORM_INFO, data.array());
} catch (IOException e) {
Slog.e(TAG, "Failed to exchange platform info");
}
}
/**
* Depending on the remote platform info to decide which transport should be created
*/
@GuardedBy("mTransports")
private void onPlatformInfoReceived(byte[] data) {
@GuardedBy("CompanionTransportManager.this.mTransports")
private void onPlatformInfoReceived(int associationId, byte[] data) {
if (mTempTransport.getAssociationId() != associationId) {
return;
}
// TODO: it should check if preSharedKey is given
ByteBuffer buffer = ByteBuffer.wrap(data);
@@ -198,12 +313,11 @@ public class CompanionTransportManager {
Slog.i(TAG, "Creating a secure channel");
transport = new SecureTransport(transport.getAssociationId(), transport.getFd(),
mContext);
for (Map.Entry<Integer, Listener> entry : mListeners.entrySet()) {
transport.addListener(entry.getKey(), entry.getValue());
}
addMessageListenersToTransport(transport);
transport.start();
}
mTransports.put(transport.getAssociationId(), transport);
// Doesn't need to notifyTransportsChanged here, it'll be done in attachSystemDataTransport
}
public Future<?> requestPermissionRestore(int associationId, byte[] data) {
@@ -228,4 +342,10 @@ public class CompanionTransportManager {
return enabled;
}
private void addMessageListenersToTransport(Transport transport) {
for (int i = 0; i < mMessageListeners.size(); i++) {
transport.addListener(mMessageListeners.keyAt(i), mMessageListeners.valueAt(i));
}
}
}

View File

@@ -17,10 +17,12 @@
package com.android.server.companion.transport;
import android.annotation.NonNull;
import android.companion.IOnMessageReceivedListener;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
@@ -45,7 +47,8 @@ public abstract class Transport {
protected static final boolean DEBUG = Build.IS_DEBUGGABLE;
static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807086; // ?PFV
public static final int MESSAGE_REQUEST_PLATFORM_INFO = 0x63807073; // ?PFI
public static final int MESSAGE_REQUEST_CONTEXT_SYNC = 0x63678883; // ?CXS
public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
@@ -59,19 +62,14 @@ public abstract class Transport {
protected final OutputStream mRemoteOut;
protected final Context mContext;
/** Message type -> Listener */
private final Map<Integer, Listener> mListeners;
/**
* Message listener
* Message type -> Listener
*
* For now, the transport only supports 1 listener for each message type. If there's a need in
* the future to allow multiple listeners to receive callbacks for the same message type, the
* value of the map can be a list.
*/
public interface Listener {
/**
* Called when a message is received
* @param data data content in the message
*/
void onDataReceived(byte[] data);
}
private final Map<Integer, IOnMessageReceivedListener> mListeners;
private static boolean isRequest(int message) {
return (message & 0xFF000000) == 0x63000000;
@@ -100,7 +98,7 @@ public abstract class Transport {
* @param message Message type
* @param listener Execute when a message with the type is received
*/
public void addListener(int message, Listener listener) {
public void addListener(int message, IOnMessageReceivedListener listener) {
mListeners.put(message, listener);
}
@@ -117,6 +115,13 @@ public abstract class Transport {
protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
throws IOException;
/**
* Send a message
*/
public void sendMessage(int message, @NonNull byte[] data) throws IOException {
sendMessage(message, mNextSequence.incrementAndGet(), data);
}
public Future<byte[]> requestForResponse(int message, byte[] data) {
if (DEBUG) Slog.d(TAG, "Requesting for response");
final int sequence = mNextSequence.incrementAndGet();
@@ -165,7 +170,8 @@ public abstract class Transport {
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, data);
break;
}
case MESSAGE_REQUEST_PLATFORM_INFO: {
case MESSAGE_REQUEST_PLATFORM_INFO:
case MESSAGE_REQUEST_CONTEXT_SYNC: {
callback(message, data);
sendMessage(MESSAGE_RESPONSE_SUCCESS, sequence, EmptyArray.BYTE);
break;
@@ -196,7 +202,13 @@ public abstract class Transport {
private void callback(int message, byte[] data) {
if (mListeners.containsKey(message)) {
mListeners.get(message).onDataReceived(data);
try {
mListeners.get(message).onMessageReceived(getAssociationId(), data);
Slog.i(TAG, "Message 0x" + Integer.toHexString(message)
+ " is received from associationId " + mAssociationId
+ ", sending data length " + data.length + " to the listener.");
} catch (RemoteException ignored) {
}
}
}