Merge "[CDM perm sync] Send and receive messages integration"
This commit is contained in:
committed by
Android (Google) Code Review
commit
c4ecc52fe0
@@ -103,7 +103,10 @@ public class CompanionApplicationController {
|
||||
mCompanionServicesRegister.invalidate(userId);
|
||||
}
|
||||
|
||||
void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
|
||||
/**
|
||||
* CDM binds to the companion app.
|
||||
*/
|
||||
public void bindCompanionApplication(@UserIdInt int userId, @NonNull String packageName,
|
||||
boolean bindImportant) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "bind() u" + userId + "/" + packageName
|
||||
@@ -143,7 +146,10 @@ public class CompanionApplicationController {
|
||||
}
|
||||
}
|
||||
|
||||
void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
|
||||
/**
|
||||
* CDM unbinds the companion app.
|
||||
*/
|
||||
public void unbindCompanionApplication(@UserIdInt int userId, @NonNull String packageName) {
|
||||
if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);
|
||||
|
||||
final List<CompanionDeviceServiceConnector> serviceConnectors;
|
||||
@@ -237,9 +243,9 @@ public class CompanionApplicationController {
|
||||
primaryServiceConnector.postOnDeviceDisappeared(association);
|
||||
}
|
||||
|
||||
/** Pass an encryped secure message to the companion application for transporting. */
|
||||
/** Pass an encrypted secure message to the companion application for transporting. */
|
||||
public void dispatchMessage(@UserIdInt int userId, @NonNull String packageName,
|
||||
int associationId, @NonNull byte[] message) {
|
||||
int associationId, int messageId, @NonNull byte[] message) {
|
||||
if (DEBUG) {
|
||||
Log.i(TAG, "dispatchMessage() u" + userId + "/" + packageName
|
||||
+ " associationId=" + associationId);
|
||||
@@ -256,7 +262,8 @@ public class CompanionApplicationController {
|
||||
return;
|
||||
}
|
||||
|
||||
primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, message);
|
||||
primaryServiceConnector.postOnMessageDispatchedFromSystem(associationId, messageId,
|
||||
message);
|
||||
}
|
||||
|
||||
private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {
|
||||
|
||||
@@ -615,7 +615,15 @@ public class CompanionDeviceManagerService extends SystemService {
|
||||
+ " message(Base64)=" + Base64.encodeToString(message, 0));
|
||||
}
|
||||
|
||||
mSecureCommsManager.receiveSecureMessage(associationId, message);
|
||||
AssociationInfo association = getAssociationWithCallerChecks(associationId);
|
||||
if (association == null) {
|
||||
throw new IllegalArgumentException("Association with ID " + associationId + " "
|
||||
+ "does not exist "
|
||||
+ "or belongs to a different package "
|
||||
+ "or belongs to a different user");
|
||||
}
|
||||
|
||||
mSecureCommsManager.receiveSecureMessage(messageId, associationId, message);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -98,15 +98,10 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
|
||||
post(companionService -> companionService.onDeviceDisappeared(associationInfo));
|
||||
}
|
||||
|
||||
void postOnMessageDispatchedFromSystem(int associationId, @NonNull byte[] message) {
|
||||
// We always use messageId 0 (at least for now).
|
||||
// Unlike the message itself, the messageId is not encoded, which means that the CDM on the
|
||||
// other (receiving) end CAN NOT and MUST NOT trust this messageId.
|
||||
// If CDM needs to pass messageId around to the other side - it should embed it in the
|
||||
// message body.
|
||||
void postOnMessageDispatchedFromSystem(int associationId, int messageId,
|
||||
@NonNull byte[] message) {
|
||||
post(companionService ->
|
||||
companionService.onMessageDispatchedFromSystem(
|
||||
/* messageId*/ 0, associationId, message));
|
||||
companionService.onMessageDispatchedFromSystem(messageId, associationId, message));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -113,7 +113,8 @@ class CompanionDeviceShellCommand extends ShellCommand {
|
||||
message = sb.toString().getBytes(UTF_8);
|
||||
}
|
||||
|
||||
mSecureCommsManager.sendSecureMessage(associationId, message);
|
||||
mSecureCommsManager.sendSecureMessage(associationId, /* messageId */ 0,
|
||||
message);
|
||||
break;
|
||||
|
||||
case "simulate-device-appeared":
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.companion.datatransfer;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.util.Slog;
|
||||
import android.util.proto.ProtoInputStream;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
@@ -39,6 +41,12 @@ public class CompanionMessageProcessor {
|
||||
|
||||
private static final String LOG_TAG = CompanionMessageProcessor.class.getSimpleName();
|
||||
|
||||
/** Listener for incoming complete messages. */
|
||||
interface Listener {
|
||||
/** When a complete message is received from the companion app. */
|
||||
void onCompleteMessageReceived(@NonNull CompanionMessageInfo message);
|
||||
}
|
||||
|
||||
// Rough size for each CompanionMessage, each message can exceed 50K for a little, but not
|
||||
// too much. Hard limit is 100K, WCS data processing limit. Closer to 100K, less stable at
|
||||
// the WCS data processing layer. Refer to
|
||||
@@ -48,6 +56,9 @@ public class CompanionMessageProcessor {
|
||||
|
||||
private final CompanionSecureCommunicationsManager mSecureCommsManager;
|
||||
|
||||
@Nullable
|
||||
private Listener mListener;
|
||||
|
||||
// Association id -> (parent id -> received messages)
|
||||
private final Map<Integer, Map<Integer, List<CompanionMessageInfo>>> mAssociationsMessagesMap =
|
||||
new HashMap<>();
|
||||
@@ -56,6 +67,11 @@ public class CompanionMessageProcessor {
|
||||
|
||||
public CompanionMessageProcessor(CompanionSecureCommunicationsManager secureCommsManager) {
|
||||
mSecureCommsManager = secureCommsManager;
|
||||
mSecureCommsManager.setListener(this::onDecryptedMessageReceived);
|
||||
}
|
||||
|
||||
public void setListener(@NonNull Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +88,8 @@ public class CompanionMessageProcessor {
|
||||
|
||||
for (int i = 0; i < totalMessageCount; i++) {
|
||||
ProtoOutputStream proto = new ProtoOutputStream();
|
||||
proto.write(CompanionMessage.ID, parentMessageId + i + 1);
|
||||
int messageId = parentMessageId + i + 1;
|
||||
proto.write(CompanionMessage.ID, messageId);
|
||||
|
||||
long paginationInfoToken = proto.start(CompanionMessage.PAGINATION_INFO);
|
||||
proto.write(CompanionMessage.PaginationInfo.PARENT_ID, parentMessageId);
|
||||
@@ -87,29 +104,68 @@ public class CompanionMessageProcessor {
|
||||
|
||||
Slog.i(LOG_TAG, "Sending " + currentData.length + " bytes to " + packageName);
|
||||
|
||||
mSecureCommsManager.sendSecureMessage(associationId, proto.getBytes());
|
||||
mSecureCommsManager.sendSecureMessage(associationId, messageId, proto.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process message and store it. If all the messages with the same parent id have been received,
|
||||
* return the message with combined message data. Otherwise, return null if there's still data
|
||||
* parts missing.
|
||||
* Process the message and store it. If all the messages with the same parent id have been
|
||||
* received, return the message with combined message data. Otherwise, return null if there's
|
||||
* still data parts missing.
|
||||
*/
|
||||
public CompanionMessageInfo processMessage(int messageId, int associationId, byte[] message) {
|
||||
public CompanionMessageInfo onDecryptedMessageReceived(int messageId, int associationId,
|
||||
byte[] message) {
|
||||
ProtoInputStream proto = new ProtoInputStream(message);
|
||||
try {
|
||||
int id = proto.readInt(CompanionMessage.ID);
|
||||
if (id == messageId) {
|
||||
// Read proto data
|
||||
long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
|
||||
int parentId = proto.readInt(CompanionMessage.PaginationInfo.PARENT_ID);
|
||||
int page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
|
||||
int total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
|
||||
proto.end(paginationToken);
|
||||
int type = proto.readInt(CompanionMessage.TYPE);
|
||||
byte[] data = proto.readBytes(CompanionMessage.DATA);
|
||||
int id = 0;
|
||||
int parentId = 0;
|
||||
int page = 0;
|
||||
int total = 0;
|
||||
int type = CompanionMessage.UNKNOWN;
|
||||
byte[] data = null;
|
||||
|
||||
// Read proto data
|
||||
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (proto.getFieldNumber()) {
|
||||
case (int) CompanionMessage.ID:
|
||||
id = proto.readInt(CompanionMessage.ID);
|
||||
break;
|
||||
case (int) CompanionMessage.PAGINATION_INFO:
|
||||
long paginationToken = proto.start(CompanionMessage.PAGINATION_INFO);
|
||||
while (proto.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
|
||||
switch (proto.getFieldNumber()) {
|
||||
case (int) CompanionMessage.PaginationInfo.PARENT_ID:
|
||||
parentId = proto.readInt(
|
||||
CompanionMessage.PaginationInfo.PARENT_ID);
|
||||
break;
|
||||
case (int) CompanionMessage.PaginationInfo.PAGE:
|
||||
page = proto.readInt(CompanionMessage.PaginationInfo.PAGE);
|
||||
break;
|
||||
case (int) CompanionMessage.PaginationInfo.TOTAL:
|
||||
total = proto.readInt(CompanionMessage.PaginationInfo.TOTAL);
|
||||
break;
|
||||
default:
|
||||
Slog.e(LOG_TAG, "Unexpected field id "
|
||||
+ proto.getFieldNumber() + " for PaginationInfo.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
proto.end(paginationToken);
|
||||
break;
|
||||
case (int) CompanionMessage.TYPE:
|
||||
type = proto.readInt(CompanionMessage.TYPE);
|
||||
break;
|
||||
case (int) CompanionMessage.DATA:
|
||||
data = proto.readBytes(CompanionMessage.DATA);
|
||||
break;
|
||||
default:
|
||||
Slog.e(LOG_TAG, "Unexpected field id " + proto.getFieldNumber()
|
||||
+ " for CompanionMessage.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id == messageId) {
|
||||
CompanionMessageInfo messageInfo = new CompanionMessageInfo(id, page, total, type,
|
||||
data);
|
||||
// Add the message into mAssociationsMessagesMap
|
||||
@@ -122,28 +178,35 @@ public class CompanionMessageProcessor {
|
||||
mAssociationsMessagesMap.put(associationId, associationMessages);
|
||||
// Check if all the messages with the same parentId are received.
|
||||
if (childMessages.size() == total) {
|
||||
Slog.i(LOG_TAG, "All [" + total + "] messages are received. Processing.");
|
||||
|
||||
childMessages.sort(Comparator.comparing(CompanionMessageInfo::getPage));
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
for (int i = 0; i < childMessages.size(); i++) {
|
||||
stream.write(childMessages.get(i).getData());
|
||||
}
|
||||
mAssociationsMessagesMap.remove(parentId);
|
||||
return new CompanionMessageInfo(parentId, 0, total, type, stream.toByteArray());
|
||||
mListener.onCompleteMessageReceived(
|
||||
new CompanionMessageInfo(parentId, 0, total, type,
|
||||
stream.toByteArray()));
|
||||
} else {
|
||||
Slog.i(LOG_TAG, "[" + childMessages.size() + "/" + total
|
||||
+ "] messages are received for parentId [" + parentId + "]");
|
||||
}
|
||||
} else {
|
||||
Slog.e(LOG_TAG, "Message id mismatch.");
|
||||
return null;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "Can't read proto message id: " + messageId + ", message: "
|
||||
+ new String(message) + ".");
|
||||
Slog.e(LOG_TAG, "Can't read proto from the message.");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the next parent id. The parent and child ids are incremental.
|
||||
* Find the next parent id from [1, Integer.MAX_VALUE].
|
||||
* The parent and child ids are incremental.
|
||||
*/
|
||||
private int findNextParentId(int associationId, int totalMessageCount) {
|
||||
int nextParentId = mNextParentId.getOrDefault(associationId, 1);
|
||||
|
||||
@@ -26,6 +26,7 @@ import static com.android.server.companion.Utils.prepareForIpc;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.PendingIntent;
|
||||
import android.companion.AssociationInfo;
|
||||
@@ -91,6 +92,7 @@ public class SystemDataTransferProcessor {
|
||||
mAssociationStore = associationStore;
|
||||
mSystemDataTransferRequestStore = systemDataTransferRequestStore;
|
||||
mCompanionMessageProcessor = companionMessageProcessor;
|
||||
mCompanionMessageProcessor.setListener(this::onCompleteMessageReceived);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -201,44 +203,36 @@ public class SystemDataTransferProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Process message reported by the companion app.
|
||||
* Process a complete decrypted message reported by the companion app.
|
||||
*/
|
||||
public void processMessage(String packageName, int userId, int associationId,
|
||||
int messageId, byte[] message) {
|
||||
Slog.i(LOG_TAG, "Start processing message [" + messageId + "] from package ["
|
||||
+ packageName + "] userId [" + userId + "] associationId [" + associationId + "]");
|
||||
|
||||
AssociationInfo association = mAssociationStore.getAssociationById(associationId);
|
||||
association = PermissionsUtils.sanitizeWithCallerChecks(mContext, association);
|
||||
if (association == null) {
|
||||
throw new DeviceNotAssociatedException("Association "
|
||||
+ associationId + " is not associated with the app " + packageName
|
||||
+ " for user " + userId);
|
||||
public void onCompleteMessageReceived(@NonNull CompanionMessageInfo completeMessage) {
|
||||
switch (completeMessage.getType()) {
|
||||
case CompanionMessage.PERMISSION_SYNC:
|
||||
processPermissionSyncMessage(completeMessage);
|
||||
break;
|
||||
default:
|
||||
Slog.e(LOG_TAG, "Unknown message type [" + completeMessage.getType()
|
||||
+ "]. Unable to process.");
|
||||
}
|
||||
}
|
||||
|
||||
PermissionsUtils.enforceCallerIsSystemOr(userId, packageName);
|
||||
private void processPermissionSyncMessage(CompanionMessageInfo messageInfo) {
|
||||
Slog.i(LOG_TAG, "Applying permissions.");
|
||||
// Start applying permissions
|
||||
BackupHelper backupHelper = new BackupHelper(mContext, mContext.getUser());
|
||||
try {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(
|
||||
messageInfo.getData());
|
||||
parser.setInput(stream, UTF_8.name());
|
||||
|
||||
CompanionMessageInfo completeMessage = mCompanionMessageProcessor.processMessage(messageId,
|
||||
associationId, message);
|
||||
if (completeMessage != null) {
|
||||
if (completeMessage.getType() == CompanionMessage.PERMISSION_SYNC) {
|
||||
// Start applying permissions
|
||||
BackupHelper backupHelper = new BackupHelper(mContext, UserHandle.of(userId));
|
||||
try {
|
||||
XmlPullParser parser = Xml.newPullParser();
|
||||
ByteArrayInputStream stream = new ByteArrayInputStream(
|
||||
completeMessage.getData());
|
||||
parser.setInput(stream, UTF_8.name());
|
||||
|
||||
backupHelper.restoreState(parser);
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "IOException reading message: "
|
||||
+ new String(completeMessage.getData()));
|
||||
} catch (XmlPullParserException e) {
|
||||
Slog.e(LOG_TAG, "Error parsing message: "
|
||||
+ new String(completeMessage.getData()));
|
||||
}
|
||||
}
|
||||
backupHelper.restoreState(parser);
|
||||
} catch (IOException e) {
|
||||
Slog.e(LOG_TAG, "IOException reading message: "
|
||||
+ new String(messageInfo.getData()));
|
||||
} catch (XmlPullParserException e) {
|
||||
Slog.e(LOG_TAG, "Error parsing message: "
|
||||
+ new String(messageInfo.getData()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package com.android.server.companion.securechannel;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.companion.AssociationInfo;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.companion.AssociationStore;
|
||||
import com.android.server.companion.CompanionApplicationController;
|
||||
@@ -31,9 +33,18 @@ public class CompanionSecureCommunicationsManager {
|
||||
static final String TAG = "CompanionDevice_SecureComms";
|
||||
static final boolean DEBUG = false;
|
||||
|
||||
/** Listener for incoming decrypted messages. */
|
||||
public interface Listener {
|
||||
/** When an incoming message is decrypted. */
|
||||
void onDecryptedMessageReceived(int messageId, int associationId, byte[] message);
|
||||
}
|
||||
|
||||
private final AssociationStore mAssociationStore;
|
||||
private final CompanionApplicationController mCompanionAppController;
|
||||
|
||||
@Nullable
|
||||
private Listener mListener;
|
||||
|
||||
/** Constructor */
|
||||
public CompanionSecureCommunicationsManager(AssociationStore associationStore,
|
||||
CompanionApplicationController companionApplicationController) {
|
||||
@@ -41,13 +52,18 @@ public class CompanionSecureCommunicationsManager {
|
||||
mCompanionAppController = companionApplicationController;
|
||||
}
|
||||
|
||||
public void setListener(@NonNull Listener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a data to the associated companion device via secure channel (establishing one if
|
||||
* needed).
|
||||
* @param associationId associationId of the "recipient" companion device.
|
||||
* @param messageId id of the message
|
||||
* @param message data to be sent securely.
|
||||
*/
|
||||
public void sendSecureMessage(int associationId, @NonNull byte[] message) {
|
||||
public void sendSecureMessage(int associationId, int messageId, @NonNull byte[] message) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
|
||||
+ " message (Base64)=\"" + Base64.encodeToString(message, 0) + "\"");
|
||||
@@ -62,13 +78,28 @@ public class CompanionSecureCommunicationsManager {
|
||||
|
||||
final int userId = association.getUserId();
|
||||
final String packageName = association.getPackageName();
|
||||
|
||||
// Bind to the app if it hasn't been bound.
|
||||
if (!mCompanionAppController.isCompanionApplicationBound(userId, packageName)) {
|
||||
throw new IllegalStateException("u" + userId + "\\" + packageName + " is NOT bound");
|
||||
Slog.d(TAG, "userId [" + userId + "] packageName [" + packageName
|
||||
+ "] is not bound. Binding it now to send a secure message.");
|
||||
mCompanionAppController.bindCompanionApplication(userId, packageName,
|
||||
association.isSelfManaged());
|
||||
|
||||
// TODO(b/202926196): implement: encrypt and pass on the companion application for
|
||||
// transporting
|
||||
mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
|
||||
message);
|
||||
|
||||
Slog.d(TAG, "Unbinding userId [" + userId + "] packageName [" + packageName
|
||||
+ "]");
|
||||
mCompanionAppController.unbindCompanionApplication(userId, packageName);
|
||||
}
|
||||
|
||||
// TODO(b/202926196): implement: encrypt and pass on the companion application for
|
||||
// transporting
|
||||
mCompanionAppController.dispatchMessage(userId, packageName, associationId, message);
|
||||
mCompanionAppController.dispatchMessage(userId, packageName, associationId, messageId,
|
||||
message);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,12 +107,15 @@ public class CompanionSecureCommunicationsManager {
|
||||
* @param associationId associationId of the "sender" companion device.
|
||||
* @param encryptedMessage data.
|
||||
*/
|
||||
public void receiveSecureMessage(int associationId, @NonNull byte[] encryptedMessage) {
|
||||
public void receiveSecureMessage(int messageId, int associationId,
|
||||
@NonNull byte[] encryptedMessage) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "sendSecureMessage() associationId=" + associationId + "\n"
|
||||
+ " message (Base64)=\"" + Base64.encodeToString(encryptedMessage, 0) + "\"");
|
||||
}
|
||||
|
||||
// TODO(b/202926196): implement: decrypt and dispatch.
|
||||
// TODO(b/202926196): implement: decrypt and dispatch
|
||||
|
||||
mListener.onDecryptedMessageReceived(messageId, associationId, encryptedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user