1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.companion; 18 19 import android.companion.AssociationInfo; 20 import android.companion.ContextSyncMessage; 21 import android.companion.Telecom; 22 import android.companion.datatransfer.PermissionSyncRequest; 23 import android.net.MacAddress; 24 import android.os.Binder; 25 import android.os.ShellCommand; 26 import android.util.proto.ProtoOutputStream; 27 28 import com.android.server.companion.datatransfer.SystemDataTransferProcessor; 29 import com.android.server.companion.datatransfer.contextsync.BitmapUtils; 30 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; 31 import com.android.server.companion.presence.CompanionDevicePresenceMonitor; 32 import com.android.server.companion.transport.CompanionTransportManager; 33 import com.android.server.companion.transport.Transport; 34 35 import java.io.PrintWriter; 36 import java.util.List; 37 38 class CompanionDeviceShellCommand extends ShellCommand { 39 private static final String TAG = "CDM_CompanionDeviceShellCommand"; 40 41 private final CompanionDeviceManagerService mService; 42 private final AssociationStoreImpl mAssociationStore; 43 private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; 44 private final CompanionTransportManager mTransportManager; 45 46 private final SystemDataTransferProcessor mSystemDataTransferProcessor; 47 private final AssociationRequestsProcessor mAssociationRequestsProcessor; 48 CompanionDeviceShellCommand(CompanionDeviceManagerService service, AssociationStoreImpl associationStore, CompanionDevicePresenceMonitor devicePresenceMonitor, CompanionTransportManager transportManager, SystemDataTransferProcessor systemDataTransferProcessor, AssociationRequestsProcessor associationRequestsProcessor)49 CompanionDeviceShellCommand(CompanionDeviceManagerService service, 50 AssociationStoreImpl associationStore, 51 CompanionDevicePresenceMonitor devicePresenceMonitor, 52 CompanionTransportManager transportManager, 53 SystemDataTransferProcessor systemDataTransferProcessor, 54 AssociationRequestsProcessor associationRequestsProcessor) { 55 mService = service; 56 mAssociationStore = associationStore; 57 mDevicePresenceMonitor = devicePresenceMonitor; 58 mTransportManager = transportManager; 59 mSystemDataTransferProcessor = systemDataTransferProcessor; 60 mAssociationRequestsProcessor = associationRequestsProcessor; 61 } 62 63 @Override onCommand(String cmd)64 public int onCommand(String cmd) { 65 final PrintWriter out = getOutPrintWriter(); 66 final int associationId; 67 try { 68 switch (cmd) { 69 case "list": { 70 final int userId = getNextIntArgRequired(); 71 final List<AssociationInfo> associationsForUser = 72 mAssociationStore.getAssociationsForUser(userId); 73 for (AssociationInfo association : associationsForUser) { 74 // TODO(b/212535524): use AssociationInfo.toShortString(), once it's not 75 // longer referenced in tests. 76 out.println(association.getPackageName() + " " 77 + association.getDeviceMacAddress() + " " + association.getId()); 78 } 79 } 80 break; 81 82 case "associate": { 83 int userId = getNextIntArgRequired(); 84 String packageName = getNextArgRequired(); 85 String address = getNextArgRequired(); 86 final MacAddress macAddress = MacAddress.fromString(address); 87 mService.createNewAssociation(userId, packageName, macAddress, 88 null, null, false); 89 } 90 break; 91 92 case "disassociate": { 93 final int userId = getNextIntArgRequired(); 94 final String packageName = getNextArgRequired(); 95 final String address = getNextArgRequired(); 96 final AssociationInfo association = 97 mService.getAssociationWithCallerChecks(userId, packageName, address); 98 if (association != null) { 99 mService.disassociateInternal(association.getId()); 100 } 101 } 102 break; 103 104 case "clear-association-memory-cache": 105 mService.persistState(); 106 mService.loadAssociationsFromDisk(); 107 break; 108 109 case "simulate-device-appeared": 110 associationId = getNextIntArgRequired(); 111 mDevicePresenceMonitor.simulateDeviceAppeared(associationId); 112 break; 113 114 case "simulate-device-disappeared": 115 associationId = getNextIntArgRequired(); 116 mDevicePresenceMonitor.simulateDeviceDisappeared(associationId); 117 break; 118 119 case "remove-inactive-associations": { 120 // This command should trigger the same "clean-up" job as performed by the 121 // InactiveAssociationsRemovalService JobService. However, since the 122 // InactiveAssociationsRemovalService run as system, we want to run this 123 // as system (not as shell/root) as well. 124 Binder.withCleanCallingIdentity( 125 mService::removeInactiveSelfManagedAssociations); 126 } 127 break; 128 129 case "create-emulated-transport": 130 // This command creates a RawTransport in order to test Transport listeners 131 associationId = getNextIntArgRequired(); 132 mTransportManager.createEmulatedTransport(associationId); 133 break; 134 135 case "send-context-sync-empty-message": { 136 associationId = getNextIntArgRequired(); 137 mTransportManager.createEmulatedTransport(associationId) 138 .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC, 139 /* sequence= */ 0, 140 CrossDeviceSyncController.createEmptyMessage()); 141 break; 142 } 143 144 case "send-context-sync-call-create-message": { 145 associationId = getNextIntArgRequired(); 146 String callId = getNextArgRequired(); 147 String address = getNextArgRequired(); 148 String facilitator = getNextArgRequired(); 149 mTransportManager.createEmulatedTransport(associationId) 150 .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC, 151 /* sequence= */ 0, 152 CrossDeviceSyncController.createCallCreateMessage(callId, 153 address, facilitator)); 154 break; 155 } 156 157 case "send-context-sync-call-control-message": { 158 associationId = getNextIntArgRequired(); 159 String callId = getNextArgRequired(); 160 int control = getNextIntArgRequired(); 161 mTransportManager.createEmulatedTransport(associationId) 162 .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC, 163 /* sequence= */ 0, 164 CrossDeviceSyncController.createCallControlMessage(callId, 165 control)); 166 break; 167 } 168 169 case "send-context-sync-call-facilitators-message": { 170 associationId = getNextIntArgRequired(); 171 int numberOfFacilitators = getNextIntArgRequired(); 172 String facilitatorName = getNextArgRequired(); 173 String facilitatorId = getNextArgRequired(); 174 final ProtoOutputStream pos = new ProtoOutputStream(); 175 pos.write(ContextSyncMessage.VERSION, 1); 176 final long telecomToken = pos.start(ContextSyncMessage.TELECOM); 177 for (int i = 0; i < numberOfFacilitators; i++) { 178 final long facilitatorsToken = pos.start(Telecom.FACILITATORS); 179 pos.write(Telecom.CallFacilitator.NAME, 180 numberOfFacilitators == 1 ? facilitatorName : facilitatorName + i); 181 pos.write(Telecom.CallFacilitator.IDENTIFIER, 182 numberOfFacilitators == 1 ? facilitatorId : facilitatorId + i); 183 pos.end(facilitatorsToken); 184 } 185 pos.end(telecomToken); 186 mTransportManager.createEmulatedTransport(associationId) 187 .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC, 188 /* sequence= */ 0, pos.getBytes()); 189 break; 190 } 191 192 case "send-context-sync-call-message": { 193 associationId = getNextIntArgRequired(); 194 String callId = getNextArgRequired(); 195 String facilitatorId = getNextArgRequired(); 196 int status = getNextIntArgRequired(); 197 boolean acceptControl = getNextBooleanArgRequired(); 198 boolean rejectControl = getNextBooleanArgRequired(); 199 boolean silenceControl = getNextBooleanArgRequired(); 200 boolean muteControl = getNextBooleanArgRequired(); 201 boolean unmuteControl = getNextBooleanArgRequired(); 202 boolean endControl = getNextBooleanArgRequired(); 203 boolean holdControl = getNextBooleanArgRequired(); 204 boolean unholdControl = getNextBooleanArgRequired(); 205 final ProtoOutputStream pos = new ProtoOutputStream(); 206 pos.write(ContextSyncMessage.VERSION, 1); 207 final long telecomToken = pos.start(ContextSyncMessage.TELECOM); 208 final long callsToken = pos.start(Telecom.CALLS); 209 pos.write(Telecom.Call.ID, callId); 210 final long originToken = pos.start(Telecom.Call.ORIGIN); 211 pos.write(Telecom.Call.Origin.CALLER_ID, "Caller Name"); 212 pos.write(Telecom.Call.Origin.APP_ICON, BitmapUtils.renderDrawableToByteArray( 213 mService.getContext().getPackageManager().getApplicationIcon( 214 facilitatorId))); 215 final long facilitatorToken = pos.start( 216 Telecom.Request.CreateAction.FACILITATOR); 217 pos.write(Telecom.CallFacilitator.NAME, "Test App Name"); 218 pos.write(Telecom.CallFacilitator.IDENTIFIER, facilitatorId); 219 pos.end(facilitatorToken); 220 pos.end(originToken); 221 pos.write(Telecom.Call.STATUS, status); 222 if (acceptControl) { 223 pos.write(Telecom.Call.CONTROLS, Telecom.ACCEPT); 224 } 225 if (rejectControl) { 226 pos.write(Telecom.Call.CONTROLS, Telecom.REJECT); 227 } 228 if (silenceControl) { 229 pos.write(Telecom.Call.CONTROLS, Telecom.SILENCE); 230 } 231 if (muteControl) { 232 pos.write(Telecom.Call.CONTROLS, Telecom.MUTE); 233 } 234 if (unmuteControl) { 235 pos.write(Telecom.Call.CONTROLS, Telecom.UNMUTE); 236 } 237 if (endControl) { 238 pos.write(Telecom.Call.CONTROLS, Telecom.END); 239 } 240 if (holdControl) { 241 pos.write(Telecom.Call.CONTROLS, Telecom.PUT_ON_HOLD); 242 } 243 if (unholdControl) { 244 pos.write(Telecom.Call.CONTROLS, Telecom.TAKE_OFF_HOLD); 245 } 246 pos.end(callsToken); 247 pos.end(telecomToken); 248 mTransportManager.createEmulatedTransport(associationId) 249 .processMessage(Transport.MESSAGE_REQUEST_CONTEXT_SYNC, 250 /* sequence= */ 0, pos.getBytes()); 251 break; 252 } 253 254 case "disable-context-sync": { 255 associationId = getNextIntArgRequired(); 256 int flag = getNextIntArgRequired(); 257 mAssociationRequestsProcessor.disableSystemDataSync(associationId, flag); 258 break; 259 } 260 261 case "enable-context-sync": { 262 associationId = getNextIntArgRequired(); 263 int flag = getNextIntArgRequired(); 264 mAssociationRequestsProcessor.enableSystemDataSync(associationId, flag); 265 break; 266 } 267 268 case "get-perm-sync-state": { 269 associationId = getNextIntArgRequired(); 270 PermissionSyncRequest request = 271 mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 272 out.println((request == null ? "null" : request.isUserConsented())); 273 break; 274 } 275 276 case "remove-perm-sync-state": { 277 associationId = getNextIntArgRequired(); 278 PermissionSyncRequest request = 279 mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 280 out.print((request == null ? "null" : request.isUserConsented())); 281 mSystemDataTransferProcessor.removePermissionSyncRequest(associationId); 282 request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 283 // should print " -> null" 284 out.println(" -> " + (request == null ? "null" : request.isUserConsented())); 285 break; 286 } 287 288 case "enable-perm-sync": { 289 associationId = getNextIntArgRequired(); 290 PermissionSyncRequest request = 291 mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 292 out.print((request == null ? "null" : request.isUserConsented())); 293 mSystemDataTransferProcessor.enablePermissionsSync(associationId); 294 request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 295 out.println(" -> " + request.isUserConsented()); // should print " -> true" 296 break; 297 } 298 299 case "disable-perm-sync": { 300 associationId = getNextIntArgRequired(); 301 PermissionSyncRequest request = 302 mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 303 out.print((request == null ? "null" : request.isUserConsented())); 304 mSystemDataTransferProcessor.disablePermissionsSync(associationId); 305 request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId); 306 out.println(" -> " + request.isUserConsented()); // should print " -> false" 307 break; 308 } 309 310 default: 311 return handleDefaultCommands(cmd); 312 } 313 } catch (Throwable e) { 314 final PrintWriter errOut = getErrPrintWriter(); 315 errOut.println(); 316 errOut.println("Exception occurred while executing '" + cmd + "':"); 317 e.printStackTrace(errOut); 318 return 1; 319 } 320 return 0; 321 } 322 getNextIntArgRequired()323 private int getNextIntArgRequired() { 324 return Integer.parseInt(getNextArgRequired()); 325 } 326 getNextBooleanArgRequired()327 private boolean getNextBooleanArgRequired() { 328 String arg = getNextArgRequired(); 329 if ("true".equalsIgnoreCase(arg) || "false".equalsIgnoreCase(arg)) { 330 return Boolean.parseBoolean(arg); 331 } else { 332 throw new IllegalArgumentException("Expected a boolean argument but was: " + arg); 333 } 334 } 335 336 @Override onHelp()337 public void onHelp() { 338 PrintWriter pw = getOutPrintWriter(); 339 pw.println("Companion Device Manager (companiondevice) commands:"); 340 pw.println(" help"); 341 pw.println(" Print this help text."); 342 pw.println(" list USER_ID"); 343 pw.println(" List all Associations for a user."); 344 pw.println(" associate USER_ID PACKAGE MAC_ADDRESS"); 345 pw.println(" Create a new Association."); 346 pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); 347 pw.println(" Remove an existing Association."); 348 pw.println(" clear-association-memory-cache"); 349 pw.println(" Clear the in-memory association cache and reload all association "); 350 pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); 351 pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); 352 353 pw.println(" simulate-device-appeared ASSOCIATION_ID"); 354 pw.println(" Make CDM act as if the given companion device has appeared."); 355 pw.println(" I.e. bind the associated companion application's"); 356 pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback."); 357 pw.println(" The CDM will consider the devices as present for 60 seconds and then"); 358 pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'"); 359 pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out" 360 + "."); 361 pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); 362 363 pw.println(" simulate-device-disappeared ASSOCIATION_ID"); 364 pw.println(" Make CDM act as if the given companion device has disappeared."); 365 pw.println(" I.e. unbind the associated companion application's"); 366 pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback."); 367 pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was"); 368 pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than"); 369 pw.println(" 60 seconds ago."); 370 pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); 371 372 pw.println(" remove-inactive-associations"); 373 pw.println(" Remove self-managed associations that have not been active "); 374 pw.println(" for a long time (90 days or as configured via "); 375 pw.println(" \"debug.cdm.cdmservice.cleanup_time_window\" system property). "); 376 pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); 377 378 pw.println(" create-emulated-transport <ASSOCIATION_ID>"); 379 pw.println(" Create an EmulatedTransport for testing purposes only"); 380 381 pw.println(" enable-perm-sync <ASSOCIATION_ID>"); 382 pw.println(" Enable perm sync for the association."); 383 pw.println(" disable-perm-sync <ASSOCIATION_ID>"); 384 pw.println(" Disable perm sync for the association."); 385 pw.println(" get-perm-sync-state <ASSOCIATION_ID>"); 386 pw.println(" Get perm sync state for the association."); 387 pw.println(" remove-perm-sync-state <ASSOCIATION_ID>"); 388 pw.println(" Remove perm sync state for the association."); 389 } 390 } 391