Loading services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +6 −3 Original line number Diff line number Diff line Loading @@ -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 Loading services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +46 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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); Loading @@ -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 = Loading @@ -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); } Loading @@ -91,10 +107,6 @@ class CompanionDeviceShellCommand extends ShellCommand { } } private int getNextArgInt() { return Integer.parseInt(getNextArgRequired()); } @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); Loading @@ -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()); } } services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +87 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } /** Loading Loading @@ -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) { Loading Loading @@ -212,6 +265,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange mConnectedBtDevices.remove(associationId); mNearbyBleDevices.remove(associationId); mReportedSelfManagedDevices.remove(associationId); mSimulated.remove(associationId); } /** Loading @@ -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"); } } } Loading
services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +6 −3 Original line number Diff line number Diff line Loading @@ -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 Loading
services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +46 −10 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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) { Loading @@ -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); Loading @@ -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 = Loading @@ -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); } Loading @@ -91,10 +107,6 @@ class CompanionDeviceShellCommand extends ShellCommand { } } private int getNextArgInt() { return Integer.parseInt(getNextArgRequired()); } @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); Loading @@ -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()); } }
services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +87 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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); } /** Loading Loading @@ -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) { Loading Loading @@ -212,6 +265,7 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange mConnectedBtDevices.remove(associationId); mNearbyBleDevices.remove(associationId); mReportedSelfManagedDevices.remove(associationId); mSimulated.remove(associationId); } /** Loading @@ -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"); } } }