Add simulate-device-(dis)appeared CDM shell commands

Add 'companiondevice simulate-device-appeared' and
'companiondevice simulate-device-disappeared' Shell commands to be used
in CompanionDeviceManager tests.

Bug: 218615198
Test: atest CtsCompanionDeviceManagerCoreTestCases
Test: atest
  CtsCompanionDeviceManagerCoreTestCases:ObservingDevicePresenceTest
Change-Id: I5dad0b1859f5292fc0db68ca6ce2114eaa889d4e
Merged-In: I5dad0b1859f5292fc0db68ca6ce2114eaa889d4e
This commit is contained in:
Sergey Nikolaienkov
2022-04-22 11:20:06 +02:00
committed by Raphael Kim
parent eb08799ad3
commit 0197bcfdc3
3 changed files with 139 additions and 14 deletions

View File

@@ -732,9 +732,12 @@ public class CompanionDeviceManagerService extends SystemService {
String[] args, ShellCallback callback, ResultReceiver resultReceiver)
throws RemoteException {
enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand");
new CompanionDeviceShellCommand(
CompanionDeviceManagerService.this, mAssociationStore)
.exec(this, in, out, err, args, callback, resultReceiver);
final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand(
CompanionDeviceManagerService.this,
mAssociationStore,
mDevicePresenceMonitor);
cmd.exec(this, in, out, err, args, callback, resultReceiver);
}
@Override

View File

@@ -21,6 +21,8 @@ import android.os.ShellCommand;
import android.util.Log;
import android.util.Slog;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
import java.io.PrintWriter;
import java.util.List;
@@ -29,20 +31,24 @@ class CompanionDeviceShellCommand extends ShellCommand {
private final CompanionDeviceManagerService mService;
private final AssociationStore mAssociationStore;
private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
CompanionDeviceShellCommand(CompanionDeviceManagerService service,
AssociationStore associationStore) {
AssociationStore associationStore,
CompanionDevicePresenceMonitor devicePresenceMonitor) {
mService = service;
mAssociationStore = associationStore;
mDevicePresenceMonitor = devicePresenceMonitor;
}
@Override
public int onCommand(String cmd) {
final PrintWriter out = getOutPrintWriter();
final int associationId;
try {
switch (cmd) {
case "list": {
final int userId = getNextArgInt();
final int userId = getNextIntArgRequired();
final List<AssociationInfo> associationsForUser =
mAssociationStore.getAssociationsForUser(userId);
for (AssociationInfo association : associationsForUser) {
@@ -55,7 +61,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
break;
case "associate": {
int userId = getNextArgInt();
int userId = getNextIntArgRequired();
String packageName = getNextArgRequired();
String address = getNextArgRequired();
mService.legacyCreateAssociation(userId, address, packageName, null);
@@ -63,7 +69,7 @@ class CompanionDeviceShellCommand extends ShellCommand {
break;
case "disassociate": {
final int userId = getNextArgInt();
final int userId = getNextIntArgRequired();
final String packageName = getNextArgRequired();
final String address = getNextArgRequired();
final AssociationInfo association =
@@ -80,6 +86,16 @@ class CompanionDeviceShellCommand extends ShellCommand {
}
break;
case "simulate-device-appeared":
associationId = getNextIntArgRequired();
mDevicePresenceMonitor.simulateDeviceAppeared(associationId);
break;
case "simulate-device-disappeared":
associationId = getNextIntArgRequired();
mDevicePresenceMonitor.simulateDeviceDisappeared(associationId);
break;
default:
return handleDefaultCommands(cmd);
}
@@ -91,10 +107,6 @@ class CompanionDeviceShellCommand extends ShellCommand {
}
}
private int getNextArgInt() {
return Integer.parseInt(getNextArgRequired());
}
@Override
public void onHelp() {
PrintWriter pw = getOutPrintWriter();
@@ -108,7 +120,31 @@ class CompanionDeviceShellCommand extends ShellCommand {
pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS");
pw.println(" Remove an existing Association.");
pw.println(" clear-association-memory-cache");
pw.println(" Clear the in-memory association cache and reload all association "
+ "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
pw.println(" Clear the in-memory association cache and reload all association ");
pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY.");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
pw.println(" simulate-device-appeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has appeared.");
pw.println(" I.e. bind the associated companion application's");
pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback.");
pw.println(" The CDM will consider the devices as present for 60 seconds and then");
pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'");
pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out"
+ ".");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
pw.println(" simulate-device-disappeared ASSOCIATION_ID");
pw.println(" Make CDM act as if the given companion device has disappeared.");
pw.println(" I.e. unbind the associated companion application's");
pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback.");
pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was");
pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than");
pw.println(" 60 seconds ago.");
pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY.");
}
private int getNextIntArgRequired() {
return Integer.parseInt(getNextArgRequired());
}
}

View File

@@ -16,11 +16,19 @@
package com.android.server.companion.presence;
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
import android.annotation.TestApi;
import android.bluetooth.BluetoothAdapter;
import android.companion.AssociationInfo;
import android.content.Context;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import com.android.server.companion.AssociationStore;
@@ -72,6 +80,11 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>();
private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>();
// Tracking "simulated" presence. Used for debugging and testing only.
private final @NonNull Set<Integer> mSimulated = new HashSet<>();
private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper =
new SimulatedDevicePresenceSchedulerHelper();
public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore,
@NonNull Callback callback) {
mAssociationStore = associationStore;
@@ -106,7 +119,8 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
public boolean isDevicePresent(int associationId) {
return mReportedSelfManagedDevices.contains(associationId)
|| mConnectedBtDevices.contains(associationId)
|| mNearbyBleDevices.contains(associationId);
|| mNearbyBleDevices.contains(associationId)
|| mSimulated.contains(associationId);
}
/**
@@ -155,6 +169,45 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble");
}
/** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
@TestApi
public void simulateDeviceAppeared(int associationId) {
// IMPORTANT: this API should only be invoked via the
// 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
// make this call are SHELL and ROOT.
// No other caller (including SYSTEM!) should be allowed.
enforceCallerShellOrRoot();
// Make sure the association exists.
enforceAssociationExists(associationId);
onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
}
/** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */
@TestApi
public void simulateDeviceDisappeared(int associationId) {
// IMPORTANT: this API should only be invoked via the
// 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to
// make this call are SHELL and ROOT.
// No other caller (including SYSTEM!) should be allowed.
enforceCallerShellOrRoot();
// Make sure the association exists.
enforceAssociationExists(associationId);
mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId);
onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
}
private void enforceAssociationExists(int associationId) {
if (mAssociationStore.getAssociationById(associationId) == null) {
throw new IllegalArgumentException(
"Association with id " + associationId + " does not exist.");
}
}
private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource,
int newDeviceAssociationId, @NonNull String sourceLoggingTag) {
if (DEBUG) {
@@ -212,6 +265,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
mConnectedBtDevices.remove(associationId);
mNearbyBleDevices.remove(associationId);
mReportedSelfManagedDevices.remove(associationId);
mSimulated.remove(associationId);
}
/**
@@ -232,4 +286,36 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange
// CompanionDeviceManagerService will know that the association is removed, and will do
// what's needed.
}
private static void enforceCallerShellOrRoot() {
final int callingUid = Binder.getCallingUid();
if (callingUid == SHELL_UID || callingUid == ROOT_UID) return;
throw new SecurityException("Caller is neither Shell nor Root");
}
private class SimulatedDevicePresenceSchedulerHelper extends Handler {
SimulatedDevicePresenceSchedulerHelper() {
super(Looper.getMainLooper());
}
void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
// First, unschedule if it was scheduled previously.
if (hasMessages(/* what */ associationId)) {
removeMessages(/* what */ associationId);
}
sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */);
}
void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) {
removeMessages(/* what */ associationId);
}
@Override
public void handleMessage(@NonNull Message msg) {
final int associationId = msg.what;
onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated");
}
}
}