1# Multi-device Collaboration 2 3 4## When to Use 5 6Multi-device collaboration involves the following scenarios: 7 8- [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned) 9 10- [Starting UIAbility Across Devices (Data Returned)](#starting-uiability-across-devices-data-returned) 11 12- [Connecting to ServiceExtensionAbility Across Devices](#connecting-to-serviceextensionability-across-devices) 13 14- [Using Cross-Device Call](#using-cross-device-call) 15 16 17## Multi-Device Collaboration Process 18 19The figure below shows the multi-device collaboration process. 20 21**Figure 1** Multi-device collaboration process 22 23 24 25 26## Constraints 27 28- Since multi-device collaboration mission management is not available, you can obtain the device list by developing system applications. Third-party applications cannot access the device list. 29 30- Multi-device collaboration must comply with [Inter-Device Component Startup Rules](component-startup-rules.md#inter-device-component-startup-rules). 31 32- For better user experience, you are advised to use the [want](../reference/apis-ability-kit/js-apis-app-ability-want.md) parameter to transmit data smaller than 100 KB. 33 34 35## Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned) 36 37On device A, touch the **Start** button provided by the initiator application to start a specified [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) or [ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md) on device B. 38 39 40### Available APIs 41 42**Table 1** Cross-device startup APIs 43 44| **API**| **Description**| 45| -------- | -------- | 46| startAbility(want: Want, callback: AsyncCallback<void>): void; | Starts a UIAbility or ServiceExtensionAbility. This API uses an asynchronous callback to return the result.| 47| stopServiceExtensionAbility(want: Want, callback: AsyncCallback<void>): void; | Stops a ServiceExtensionAbility. This API uses an asynchronous callback to return the result.| 48| stopServiceExtensionAbility(want: Want): Promise<void>; | Stops a ServiceExtensionAbility. This API uses a promise to return the result.| 49 50 51### How to Develop 52 531. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 54 552. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md). 56 573. Obtain the device ID of the target device. 58 59 ```ts 60 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 61 import { hilog } from '@kit.PerformanceAnalysisKit'; 62 63 const TAG: string = '[Page_CollaborateAbility]'; 64 const DOMAIN_NUMBER: number = 0xFF00; 65 66 let dmClass: distributedDeviceManager.DeviceManager; 67 68 function initDmClass(): void { 69 // createDeviceManager is a system API. 70 try { 71 dmClass = distributedDeviceManager.createDeviceManager('com.samples.stagemodelabilitydevelop'); 72 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass) ?? ''); 73 } catch (err) { 74 hilog.error(DOMAIN_NUMBER, TAG, 'createDeviceManager err: ' + JSON.stringify(err)); 75 } 76 } 77 78 function getRemoteDeviceId(): string | undefined { 79 if (typeof dmClass === 'object' && dmClass !== null) { 80 let list = dmClass.getAvailableDeviceListSync(); 81 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 82 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 83 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 84 return; 85 } 86 if (list.length === 0) { 87 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 88 return; 89 } 90 return list[0].networkId; 91 } else { 92 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 93 return; 94 } 95 } 96 ``` 97 984. Set the target component parameters, and call [startAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartability) to start a [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) or [ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md). 99 100 ```ts 101 import { BusinessError } from '@kit.BasicServicesKit'; 102 import { hilog } from '@kit.PerformanceAnalysisKit'; 103 import { Want, common } from '@kit.AbilityKit'; 104 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 105 import { promptAction } from '@kit.ArkUI'; 106 107 const TAG: string = '[Page_CollaborateAbility]'; 108 const DOMAIN_NUMBER: number = 0xFF00; 109 let dmClass: distributedDeviceManager.DeviceManager; 110 111 function getRemoteDeviceId(): string | undefined { 112 if (typeof dmClass === 'object' && dmClass !== null) { 113 let list = dmClass.getAvailableDeviceListSync(); 114 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 115 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 116 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 117 return; 118 } 119 if (list.length === 0) { 120 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 121 return; 122 } 123 return list[0].networkId; 124 } else { 125 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 126 return; 127 } 128 }; 129 130 @Entry 131 @Component 132 struct Page_CollaborateAbility { 133 private context = getContext(this) as common.UIAbilityContext; 134 135 build() { 136 Column() { 137 //... 138 List({ initialIndex: 0 }) { 139 //... 140 ListItem() { 141 Row() { 142 //... 143 } 144 .onClick(() => { 145 let want: Want = { 146 deviceId: getRemoteDeviceId(), 147 bundleName: 'com.samples.stagemodelabilityinteraction', 148 abilityName: 'CollaborateAbility', 149 moduleName: 'entry', // moduleName is optional. 150 }; 151 // context is the AbilityContext of the initiator UIAbility. 152 this.context.startAbility(want).then(() => { 153 promptAction.showToast({ 154 message: 'SuccessfulCollaboration' 155 }); 156 }).catch((err: BusinessError) => { 157 hilog.error(DOMAIN_NUMBER, TAG, `startAbility err: ` + JSON.stringify(err)); 158 }); 159 }) 160 } 161 //... 162 } 163 //... 164 } 165 //... 166 } 167 } 168 ``` 169 1705. Call [stopServiceExtensionAbility](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext-sys.md#uiabilitycontextstopserviceextensionability-1) to stop the [ServiceExtensionAbility](../reference/apis-ability-kit/js-apis-app-ability-serviceExtensionAbility-sys.md) when it is no longer required on device B. (This API cannot be used to stop a [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md). Users must manually stop a UIAbility through mission management.) 171 172 ```ts 173 import { BusinessError } from '@kit.BasicServicesKit'; 174 import { hilog } from '@kit.PerformanceAnalysisKit'; 175 import { Want, common } from '@kit.AbilityKit'; 176 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 177 178 const TAG: string = '[Page_CollaborateAbility]'; 179 const DOMAIN_NUMBER: number = 0xFF00; 180 let dmClass: distributedDeviceManager.DeviceManager; 181 182 function getRemoteDeviceId(): string | undefined { 183 if (typeof dmClass === 'object' && dmClass !== null) { 184 let list = dmClass.getAvailableDeviceListSync(); 185 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 186 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 187 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 188 return; 189 } 190 if (list.length === 0) { 191 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 192 return; 193 } 194 return list[0].networkId; 195 } else { 196 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 197 return; 198 } 199 }; 200 201 @Entry 202 @Component 203 struct Page_CollaborateAbility { 204 private context = getContext(this) as common.UIAbilityContext; 205 206 build() { 207 // ... 208 Button('stopServiceExtensionAbility') 209 .onClick(() => { 210 let want: Want = { 211 deviceId: getRemoteDeviceId(), 212 bundleName: 'com.example.myapplication', 213 abilityName: 'FuncAbility', 214 moduleName: 'module1', // moduleName is optional. 215 } 216 // Stop the ServiceExtensionAbility started by calling startAbility(). 217 this.context.stopServiceExtensionAbility(want).then(() => { 218 hilog.info(DOMAIN_NUMBER, TAG, "stop service extension ability success") 219 }).catch((err: BusinessError) => { 220 hilog.error(DOMAIN_NUMBER, TAG, `stop service extension ability err is ` + JSON.stringify(err)); 221 }) 222 }) 223 } 224 } 225 ``` 226 227## Starting UIAbility Across Devices (Data Returned) 228 229On device A, touch the Start button provided by the initiator application to start a specified [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) on device B. When the UIAbility on device B exits, a value is returned to the initiator application. 230 231 232### Available APIs 233 234**Table 2** APIs for starting a UIAbility across devices and returning the result data 235 236| API| Description| 237| -------- | -------- | 238| startAbilityForResult(want: Want, callback: AsyncCallback<AbilityResult>): void; | Starts a UIAbility. This API uses an asynchronous callback to return the result when the UIAbility is terminated.| 239| terminateSelfWithResult(parameter: AbilityResult, callback: AsyncCallback<void>): void;| Terminates this UIAbility. This API uses an asynchronous callback to return the result. It is used together with **startAbilityForResult**.| 240| terminateSelfWithResult(parameter: AbilityResult): Promise<void>; | Terminates this UIAbility. This API uses a promise to return the result. It is used together with **startAbilityForResult**.| 241 242 243### How to Develop 244 2451. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 246 2472. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md). 248 2493. Set the target component parameters on the initiator, and call [startAbilityForResult()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartabilityforresult) to start the target UIAbility. **data** in the asynchronous callback is used to receive the information returned by the target UIAbility to the initiator UIAbility after the target UIAbility terminates itself. For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned). 250 251 ```ts 252 import { BusinessError } from '@kit.BasicServicesKit'; 253 import { hilog } from '@kit.PerformanceAnalysisKit'; 254 import { Want, common } from '@kit.AbilityKit'; 255 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 256 import { promptAction } from '@kit.ArkUI'; 257 258 const DOMAIN_NUMBER: number = 0xFF00; 259 const TAG: string = '[Page_CollaborateAbility]'; 260 let dmClass: distributedDeviceManager.DeviceManager; 261 262 function getRemoteDeviceId(): string | undefined { 263 if (typeof dmClass === 'object' && dmClass !== null) { 264 let list = dmClass.getAvailableDeviceListSync(); 265 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 266 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 267 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 268 return; 269 } 270 if (list.length === 0) { 271 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 272 return; 273 } 274 return list[0].networkId; 275 } else { 276 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 277 return; 278 } 279 }; 280 281 @Entry 282 @Component 283 struct Page_CollaborateAbility { 284 private context = getContext(this) as common.UIAbilityContext; 285 286 build() { 287 Column() { 288 //... 289 List({ initialIndex: 0 }) { 290 //... 291 ListItem() { 292 Row() { 293 //... 294 } 295 .onClick(() => { 296 let want: Want = { 297 deviceId: getRemoteDeviceId(), 298 bundleName: 'com.samples.stagemodelabilityinteraction', 299 abilityName: 'ServiceExtAbility', 300 moduleName: 'entry', // moduleName is optional. 301 }; 302 // Stop the ServiceExtensionAbility started by calling startAbility(). 303 this.context.stopServiceExtensionAbility(want).then(() => { 304 hilog.info(DOMAIN_NUMBER, TAG, 'stop service extension ability success') 305 promptAction.showToast({ 306 message: 'SuccessfullyStop' 307 }); 308 }).catch((err: BusinessError) => { 309 hilog.error(DOMAIN_NUMBER, TAG, `stop service extension ability err is ` + JSON.stringify(err)); 310 }); 311 }) 312 } 313 //... 314 } 315 //... 316 } 317 //... 318 } 319 } 320 ``` 321 3224. After the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) mission on the target device is complete, call [terminateSelfWithResult()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextterminateselfwithresult) to return the data to the initiator UIAbility. 323 324 ```ts 325 import { common } from '@kit.AbilityKit'; 326 import { hilog } from '@kit.PerformanceAnalysisKit'; 327 import { BusinessError } from '@kit.BasicServicesKit'; 328 329 const TAG: string = '[Page_CollaborateAbility]'; 330 const DOMAIN_NUMBER: number = 0xFF00; 331 332 @Entry 333 @Component 334 struct Page_CollaborateAbility { 335 private context = getContext(this) as common.UIAbilityContext; 336 337 build() { 338 Column() { 339 //... 340 List({ initialIndex: 0 }) { 341 //... 342 ListItem() { 343 Row() { 344 //... 345 } 346 .onClick(() => { 347 const RESULT_CODE: number = 1001; 348 // context is the AbilityContext of the target UIAbility. 349 this.context.terminateSelfWithResult( 350 { 351 resultCode: RESULT_CODE, 352 want: { 353 bundleName: 'ohos.samples.stagemodelabilitydevelop', 354 abilityName: 'CollaborateAbility', 355 moduleName: 'entry', 356 parameters: { 357 info: 'From Page_CollaborateAbility' 358 } 359 } 360 }, 361 (err: BusinessError) => { 362 hilog.info(DOMAIN_NUMBER, TAG, `terminateSelfWithResult err: ` + JSON.stringify(err)); 363 }); 364 }) 365 } 366 //... 367 } 368 //... 369 } 370 //... 371 } 372 } 373 ``` 374 3755. The initiator [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) receives the information returned by the target UIAbility and processes the information. 376 377 ```ts 378 import { BusinessError } from '@kit.BasicServicesKit'; 379 import { hilog } from '@kit.PerformanceAnalysisKit'; 380 import { Want, common } from '@kit.AbilityKit'; 381 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 382 import { promptAction } from '@kit.ArkUI'; 383 384 const TAG: string = '[Page_CollaborateAbility]'; 385 const DOMAIN_NUMBER: number = 0xFF00; 386 let dmClass: distributedDeviceManager.DeviceManager; 387 388 function getRemoteDeviceId(): string | undefined { 389 if (typeof dmClass === 'object' && dmClass !== null) { 390 let list = dmClass.getAvailableDeviceListSync(); 391 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 392 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 393 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 394 return; 395 } 396 if (list.length === 0) { 397 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 398 return; 399 } 400 return list[0].networkId; 401 } else { 402 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 403 return; 404 } 405 }; 406 407 @Entry 408 @Component 409 struct Page_CollaborateAbility { 410 private context = getContext(this) as common.UIAbilityContext; 411 412 build() { 413 Column() { 414 //... 415 List({ initialIndex: 0 }) { 416 //... 417 ListItem() { 418 Row() { 419 //... 420 } 421 .onClick(() => { 422 let want: Want = { 423 deviceId: getRemoteDeviceId(), 424 bundleName: 'com.samples.stagemodelabilityinteraction', 425 abilityName: 'CollaborateAbility', 426 moduleName: 'entry', // moduleName is optional. 427 }; 428 const RESULT_CODE: number = 1001; 429 // context is the UIAbilityContext of the initiator UIAbility. 430 this.context.startAbilityForResult(want).then((data) => { 431 if (data?.resultCode === RESULT_CODE) { 432 // Parse the information returned by the target UIAbility. 433 let info = data.want?.parameters?.info; 434 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(info) ?? ''); 435 if (info !== null) { 436 promptAction.showToast({ 437 message: JSON.stringify(info) 438 }); 439 } 440 } 441 }).catch((error: BusinessError) => { 442 hilog.error(DOMAIN_NUMBER, TAG, `startAbilityForResult err: ` + JSON.stringify(error)); 443 }); 444 }) 445 } 446 //... 447 } 448 //... 449 } 450 //... 451 } 452 } 453 ``` 454 455 456## Connecting to ServiceExtensionAbility Across Devices 457 458A system application can connect to a service on another device by calling [connectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextconnectserviceextensionability). For example, in the distributed game scenario, a tablet is used as the remote control and a smart TV is used as the display. 459 460 461### Available APIs 462 463**Table 3** APIs for cross-device connection 464 465| API| Description| 466| -------- | -------- | 467| connectServiceExtensionAbility(want: Want, options: ConnectOptions): number; | Connects to a ServiceExtensionAbility.| 468| disconnectServiceExtensionAbility(connection: number, callback: AsyncCallback<void>): void; | Disconnects a connection. This API uses an asynchronous callback to return the result.| 469| disconnectServiceExtensionAbility(connection: number): Promise<void>; | Disconnects a connection. This API uses a promise to return the result.| 470 471 472### How to Develop 473 4741. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 475 4762. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md). 477 4783. (Optional) [Implement a background service](serviceextensionability.md#implementing-a-background-service-for-system-applications-only). Perform this operation only if no background service is available. This operation is available only for system applications. 479 4804. Connect to the background service. 481 - Implement the **IAbilityConnection** class. **IAbilityConnection** provides the following callbacks that you should implement: [onConnect()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#onconnect), [onDisconnect()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#ondisconnect), and [onFailed()](../reference/apis-ability-kit/js-apis-inner-ability-connectOptions.md#onfailed). The **onConnect()** callback is invoked when a service is connected, **onDisconnect()** is invoked when a service is unexpectedly disconnected, and **onFailed()** is invoked when the connection to a service fails. 482 - Set the target component parameters, including the target device ID, bundle name, and ability name. 483 - Call **connectServiceExtensionAbility()** to initiate a connection. 484 - Receive the service handle returned by the target device when the connection is successful. 485 - Perform cross-device call and obtain the result returned by the target service. 486 487 ```ts 488 import { BusinessError } from '@kit.BasicServicesKit'; 489 import { hilog } from '@kit.PerformanceAnalysisKit'; 490 import { Want, common } from '@kit.AbilityKit'; 491 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 492 import { rpc } from '@kit.IPCKit'; 493 494 const TAG: string = '[Page_CollaborateAbility]'; 495 const DOMAIN_NUMBER: number = 0xFF00; 496 const REQUEST_CODE = 1; 497 let dmClass: distributedDeviceManager.DeviceManager; 498 let connectionId: number; 499 let options: common.ConnectOptions = { 500 onConnect(elementName, remote): void { 501 hilog.info(DOMAIN_NUMBER, TAG, 'onConnect callback'); 502 if (remote === null) { 503 hilog.info(DOMAIN_NUMBER, TAG, `onConnect remote is null`); 504 return; 505 } 506 let option = new rpc.MessageOption(); 507 let data = new rpc.MessageSequence(); 508 let reply = new rpc.MessageSequence(); 509 data.writeInt(99); // You can send data to the target application for corresponding operations. 510 // @param code Indicates the service request code sent by the client. 511 // @param data Indicates the {@link MessageSequence} object sent by the client. 512 // @param reply Indicates the response message object sent by the remote service. 513 // @param options Specifies whether the operation is synchronous or asynchronous. 514 // 515 // @return Returns {@code true} if the operation is successful; returns {@code false} otherwise. 516 remote.sendMessageRequest(REQUEST_CODE, data, reply, option).then((ret: rpc.RequestResult) => { 517 let errCode = reply.readInt(); // Receive the information (100) returned by the target device if the connection is successful. 518 let msg: number = 0; 519 if (errCode === 0) { 520 msg = reply.readInt(); 521 } 522 // The background service is connected. 523 hilog.info(DOMAIN_NUMBER, TAG, `sendRequest msg:${msg}`); 524 }).catch((error: BusinessError) => { 525 hilog.info(DOMAIN_NUMBER, TAG, `sendRequest failed, ${JSON.stringify(error)}`); 526 }); 527 }, 528 onDisconnect(elementName): void { 529 hilog.info(DOMAIN_NUMBER, TAG, 'onDisconnect callback'); 530 }, 531 onFailed(code): void { 532 hilog.info(DOMAIN_NUMBER, TAG, 'onFailed callback'); 533 } 534 }; 535 536 function getRemoteDeviceId(): string | undefined { 537 if (typeof dmClass === 'object' && dmClass !== null) { 538 let list = dmClass.getAvailableDeviceListSync(); 539 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 540 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 541 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 542 return; 543 } 544 if (list.length === 0) { 545 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 546 return; 547 } 548 return list[0].networkId; 549 } else { 550 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 551 return; 552 } 553 } 554 555 @Entry 556 @Component 557 struct Page_CollaborateAbility { 558 private context = getContext(this) as common.UIAbilityContext; 559 560 build() { 561 Column() { 562 //... 563 List({ initialIndex: 0 }) { 564 //... 565 ListItem() { 566 Row() { 567 //... 568 } 569 .onClick(() => { 570 let want: Want = { 571 'deviceId': getRemoteDeviceId(), 572 'bundleName': 'com.samples.stagemodelabilityinteraction', 573 'abilityName': 'ServiceExtAbility' 574 }; 575 // The ID returned after the connection is set up must be saved. The ID will be passed for service disconnection. 576 connectionId = this.context.connectServiceExtensionAbility(want, options); 577 }) 578 } 579 //... 580 } 581 //... 582 } 583 //... 584 } 585 } 586 ``` 587 588 For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned). 589 5905. Disconnect the connection. Use [disconnectServiceExtensionAbility()](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextdisconnectserviceextensionability) to disconnect from the background service. 591 592 ```ts 593 import { BusinessError } from '@kit.BasicServicesKit'; 594 import { hilog } from '@kit.PerformanceAnalysisKit'; 595 import { common } from '@kit.AbilityKit'; 596 import { promptAction } from '@kit.ArkUI'; 597 598 let connectionId: number; 599 const TAG: string = '[Page_CollaborateAbility]'; 600 const DOMAIN_NUMBER: number = 0xFF00; 601 602 @Entry 603 @Component 604 struct Page_CollaborateAbility { 605 private context = getContext(this) as common.UIAbilityContext; 606 607 build() { 608 Column() { 609 //... 610 List({ initialIndex: 0 }) { 611 //... 612 ListItem() { 613 Row() { 614 //... 615 } 616 .onClick(() => { 617 this.context.disconnectServiceExtensionAbility(connectionId).then(() => { 618 hilog.info(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility success'); 619 // The background service is disconnected. 620 promptAction.showToast({ 621 message: 'SuccessfullyDisconnectBackendService' 622 }) 623 }).catch((error: BusinessError) => { 624 hilog.error(DOMAIN_NUMBER, TAG, 'disconnectServiceExtensionAbility failed'); 625 }); 626 }) 627 } 628 //... 629 } 630 //... 631 } 632 //... 633 } 634 } 635 ``` 636 637 638## Using Cross-Device Call 639 640The basic principle of cross-device call is the same as that of intra-device call. For details, see [Using Call to Implement UIAbility Interaction (for System Applications Only)](uiability-intra-device-interaction.md#using-call-to-implement-uiability-interaction-for-system-applications-only). 641 642The following describes how to implement multi-device collaboration through cross-device call. 643 644 645### Available APIs 646 647**Table 4** Call APIs 648 649| API| Description| 650| -------- | -------- | 651| startAbilityByCall(want: Want): Promise<Caller>; | Starts a UIAbility in the foreground or background and obtains the caller object for communicating with the UIAbility.| 652| on(method: string, callback: CalleeCallBack): void | Callback invoked when the CalleeAbility registers a method.| 653| off(method: string): void | Callback invoked when the CalleeAbility deregisters a method.| 654| call(method: string, data: rpc.Parcelable): Promise<void> | Sends agreed parcelable data to the CalleeAbility.| 655| callWithResult(method: string, data: rpc.Parcelable): Promise<rpc.MessageSequence>| Sends agreed parcelable data to the CalleeAbility and obtains the agreed parcelable data returned by the CalleeAbility.| 656| release(): void | Releases the caller object.| 657| on(type: "release", callback: OnReleaseCallback): void | Callback invoked when the caller object is released.| 658 659 660### How to Develop 661 6621. Declare the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Declaring Permissions](../security/AccessToken/declare-permissions.md). 663 6642. Display a dialog box to ask for authorization from the user when the application is started for the first time. For details, see [Requesting User Authorization](../security/AccessToken/request-user-authorization.md). 665 6663. Create the CalleeAbility. 667 668 For the CalleeAbility, implement the callback to receive data and the methods to marshal and unmarshal data. When data needs to be received, use [on](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#calleeon) to register a listener. When data does not need to be received, use [off](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#calleeoff) to deregister the listener. 669 670 1. Configure the launch type of the UIAbility. 671 672 Set **launchType** of the CalleeAbility to **singleton** in the [module.json5](../quick-start/module-configuration-file.md) file. 673 674 | JSON Field| Description| 675 | -------- | -------- | 676 | "launchType"| UIAbility launch type. Set this parameter to **singleton**.| 677 678 An example of the UIAbility configuration is as follows: 679 680 ```json 681 "abilities":[{ 682 "name": ".CalleeAbility", 683 "srcEntry": "./ets/CalleeAbility/CalleeAbility.ets", 684 "launchType": "singleton", 685 "description": "$string:CalleeAbility_desc", 686 "icon": "$media:icon", 687 "label": "$string:CalleeAbility_label", 688 "exported": true 689 }] 690 ``` 691 2. Import the **UIAbility** module. 692 693 ```ts 694 import { UIAbility } from '@kit.AbilityKit'; 695 ``` 696 3. Define the agreed parcelable data. 697 698 The data formats sent and received by the CallerAbility and CalleeAbility must be consistent. In the following example, the data formats are number and string. 699 700 701 ```ts 702 import { rpc } from '@kit.IPCKit'; 703 704 class MyParcelable { 705 num: number = 0; 706 str: string = ''; 707 708 constructor(num: number, string: string) { 709 this.num = num; 710 this.str = string; 711 } 712 713 mySequenceable(num: number, string: string): void { 714 this.num = num; 715 this.str = string; 716 } 717 718 marshalling(messageSequence: rpc.MessageSequence): boolean { 719 messageSequence.writeInt(this.num); 720 messageSequence.writeString(this.str); 721 return true; 722 }; 723 724 unmarshalling(messageSequence: rpc.MessageSequence): boolean { 725 this.num = messageSequence.readInt(); 726 this.str = messageSequence.readString(); 727 return true; 728 }; 729 } 730 ``` 731 4. Implement [Callee.on](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#calleeon) and [Callee.off](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#calleeoff). 732 733 In the following example, the **MSG_SEND_METHOD** listener is registered in [onCreate](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityoncreate) of the UIAbility and deregistered in [onDestroy](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#uiabilityondestroy). After receiving parcelable data, the application processes the data and returns the data result. You need to implement processing based on service requirements. 734 735 ```ts 736 import { AbilityConstant, UIAbility, Want, Caller } from '@kit.AbilityKit'; 737 import { hilog } from '@kit.PerformanceAnalysisKit'; 738 import { rpc } from '@kit.IPCKit'; 739 740 741 const TAG: string = '[CalleeAbility]'; 742 const MSG_SEND_METHOD: string = 'CallSendMsg'; 743 const DOMAIN_NUMBER: number = 0xFF00; 744 745 class MyParcelable { 746 num: number = 0; 747 str: string = ''; 748 749 constructor(num: number, string: string) { 750 this.num = num; 751 this.str = string; 752 }; 753 754 mySequenceable(num: number, string: string): void { 755 this.num = num; 756 this.str = string; 757 }; 758 759 marshalling(messageSequence: rpc.MessageSequence): boolean { 760 messageSequence.writeInt(this.num); 761 messageSequence.writeString(this.str); 762 return true; 763 }; 764 765 unmarshalling(messageSequence: rpc.MessageSequence): boolean { 766 this.num = messageSequence.readInt(); 767 this.str = messageSequence.readString(); 768 return true; 769 }; 770 } 771 772 function sendMsgCallback(data: rpc.MessageSequence): rpc.Parcelable { 773 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'CalleeSortFunc called'); 774 775 // Obtain the parcelable data sent by the CallerAbility. 776 let receivedData: MyParcelable = new MyParcelable(0, ''); 777 data.readParcelable(receivedData); 778 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', `receiveData[${receivedData.num}, ${receivedData.str}]`); 779 let num: number = receivedData.num; 780 781 // Process the data. 782 // Return the parcelable data result to the CallerAbility. 783 return new MyParcelable(num + 1, `send ${receivedData.str} succeed`) as rpc.Parcelable; 784 }; 785 786 export default class CalleeAbility extends UIAbility { 787 caller: Caller | undefined; 788 789 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 790 try { 791 this.callee.on(MSG_SEND_METHOD, sendMsgCallback); 792 } catch (error) { 793 hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`); 794 } 795 } 796 797 //... 798 releaseCall(): void { 799 try { 800 if (this.caller) { 801 this.caller.release(); 802 this.caller = undefined; 803 } 804 hilog.info(DOMAIN_NUMBER, TAG, 'caller release succeed'); 805 } catch (error) { 806 hilog.info(DOMAIN_NUMBER, TAG, `caller release failed with ${error}`); 807 } 808 } 809 810 //... 811 onDestroy(): void { 812 try { 813 this.callee.off(MSG_SEND_METHOD); 814 hilog.info(DOMAIN_NUMBER, TAG, '%{public}s', 'Callee OnDestroy'); 815 this.releaseCall(); 816 } catch (error) { 817 hilog.error(DOMAIN_NUMBER, TAG, '%{public}s', `Failed to register. Error is ${error}`); 818 } 819 } 820 } 821 ``` 822 8234. Obtain the caller object and access the CalleeAbility. 824 1. Import the [UIAbility](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md) module. 825 826 ```ts 827 import { UIAbility } from '@kit.AbilityKit'; 828 ``` 829 2. Obtain the caller object. 830 831 The **context** attribute of the UIAbility implements [startAbilityByCall](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontextstartabilitybycall) to obtain the [Caller](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#caller) object for communication. The following example uses **this.context** to obtain the **context** attribute of the UIAbility, uses **startAbilityByCall** to start [Callee](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callee), obtain the Caller object, and register the [onRelease](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#calleronrelease) and [onRemoteStateChange](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#calleronremotestatechange10) listeners of the CallerAbility. You need to implement processing based on service requirements. 832 833 ```ts 834 import { BusinessError } from '@kit.BasicServicesKit'; 835 import { Caller, common } from '@kit.AbilityKit'; 836 import { hilog } from '@kit.PerformanceAnalysisKit'; 837 import { distributedDeviceManager } from '@kit.DistributedServiceKit'; 838 import { promptAction } from '@kit.ArkUI'; 839 840 841 const TAG: string = '[Page_CollaborateAbility]'; 842 const DOMAIN_NUMBER: number = 0xFF00; 843 let caller: Caller | undefined; 844 let dmClass: distributedDeviceManager.DeviceManager; 845 846 function getRemoteDeviceId(): string | undefined { 847 if (typeof dmClass === 'object' && dmClass !== null) { 848 let list = dmClass.getAvailableDeviceListSync(); 849 hilog.info(DOMAIN_NUMBER, TAG, JSON.stringify(dmClass), JSON.stringify(list)); 850 if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') { 851 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: list is null'); 852 return; 853 } 854 if (list.length === 0) { 855 hilog.info(DOMAIN_NUMBER, TAG, `getRemoteDeviceId err: list is empty`); 856 return; 857 } 858 return list[0].networkId; 859 } else { 860 hilog.info(DOMAIN_NUMBER, TAG, 'getRemoteDeviceId err: dmClass is null'); 861 return; 862 } 863 }; 864 865 @Entry 866 @Component 867 struct Page_CollaborateAbility { 868 private context = getContext(this) as common.UIAbilityContext; 869 870 build() { 871 Column() { 872 //... 873 List({ initialIndex: 0 }) { 874 //... 875 ListItem() { 876 Row() { 877 //... 878 } 879 .onClick(() => { 880 let caller: Caller | undefined; 881 let context = this.context; 882 883 context.startAbilityByCall({ 884 deviceId: getRemoteDeviceId(), 885 bundleName: 'com.samples.stagemodelabilityinteraction', 886 abilityName: 'CalleeAbility' 887 }).then((data) => { 888 if (data !== null) { 889 caller = data; 890 hilog.info(DOMAIN_NUMBER, TAG, 'get remote caller success'); 891 // Register the onRelease listener of the CallerAbility. 892 caller.onRelease((msg) => { 893 hilog.info(DOMAIN_NUMBER, TAG, `remote caller onRelease is called ${msg}`); 894 }); 895 hilog.info(DOMAIN_NUMBER, TAG, 'remote caller register OnRelease succeed'); 896 promptAction.showToast({ 897 message: 'CallerSuccess' 898 }); 899 // Register the onRemoteStateChange listener of the CallerAbility. 900 try { 901 caller.onRemoteStateChange((str) => { 902 hilog.info(DOMAIN_NUMBER, TAG, 'Remote state changed ' + str); 903 }); 904 } catch (error) { 905 hilog.info(DOMAIN_NUMBER, TAG, `Caller.onRemoteStateChange catch error, error.code: ${JSON.stringify(error.code)}, error.message: ${JSON.stringify(error.message)}`); 906 } 907 } 908 }).catch((error: BusinessError) => { 909 hilog.error(DOMAIN_NUMBER, TAG, `get remote caller failed with ${error}`); 910 }); 911 }) 912 } 913 //... 914 } 915 //... 916 } 917 //... 918 } 919 } 920 ``` 921 922 For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility or ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-or-serviceextensionability-across-devices-no-data-returned). 923 9245. Sends agreed parcelable data to the CalleeAbility. 925 1. The parcelable data can be sent to the CalleeAbility with or without a return value. The method and parcelable data must be consistent with those of the CalleeAbility. The following example describes how to use [Call](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callercall) to send data to [Callee](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callee). 926 927 ```ts 928 import { UIAbility, Caller } from '@kit.AbilityKit'; 929 import { rpc } from '@kit.IPCKit'; 930 import { hilog } from '@kit.PerformanceAnalysisKit'; 931 932 const TAG: string = '[CalleeAbility]'; 933 const DOMAIN_NUMBER: number = 0xFF00; 934 const MSG_SEND_METHOD: string = 'CallSendMsg'; 935 936 class MyParcelable { 937 num: number = 0; 938 str: string = ''; 939 940 constructor(num: number, string: string) { 941 this.num = num; 942 this.str = string; 943 }; 944 945 mySequenceable(num: number, string: string): void { 946 this.num = num; 947 this.str = string; 948 }; 949 950 marshalling(messageSequence: rpc.MessageSequence): boolean { 951 messageSequence.writeInt(this.num); 952 messageSequence.writeString(this.str); 953 return true; 954 }; 955 956 unmarshalling(messageSequence: rpc.MessageSequence): boolean { 957 this.num = messageSequence.readInt(); 958 this.str = messageSequence.readString(); 959 return true; 960 }; 961 } 962 963 export default class EntryAbility extends UIAbility { 964 // ... 965 caller: Caller | undefined; 966 967 async onButtonCall(): Promise<void> { 968 try { 969 let msg: MyParcelable = new MyParcelable(1, 'origin_Msg'); 970 if (this.caller) { 971 await this.caller.call(MSG_SEND_METHOD, msg); 972 } 973 } catch (error) { 974 hilog.info(DOMAIN_NUMBER, TAG, `caller call failed with ${error}`); 975 } 976 } 977 // ... 978 } 979 ``` 980 2. In the following, [CallWithResult](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callercallwithresult) is used to send data **originMsg** to the CalleeAbility and assign the data processed by the **CallSendMsg** method to **backMsg**. 981 982 ```ts 983 import { UIAbility, Caller } from '@kit.AbilityKit'; 984 import { rpc } from '@kit.IPCKit'; 985 import { hilog } from '@kit.PerformanceAnalysisKit'; 986 987 const TAG: string = '[CalleeAbility]'; 988 const DOMAIN_NUMBER: number = 0xFF00; 989 990 const MSG_SEND_METHOD: string = 'CallSendMsg'; 991 let originMsg: string = ''; 992 let backMsg: string = ''; 993 994 class MyParcelable { 995 num: number = 0; 996 str: string = ''; 997 998 constructor(num: number, string: string) { 999 this.num = num; 1000 this.str = string; 1001 }; 1002 1003 mySequenceable(num: number, string: string): void { 1004 this.num = num; 1005 this.str = string; 1006 }; 1007 1008 marshalling(messageSequence: rpc.MessageSequence): boolean { 1009 messageSequence.writeInt(this.num); 1010 messageSequence.writeString(this.str); 1011 return true; 1012 }; 1013 1014 unmarshalling(messageSequence: rpc.MessageSequence): boolean { 1015 this.num = messageSequence.readInt(); 1016 this.str = messageSequence.readString(); 1017 return true; 1018 }; 1019 } 1020 1021 export default class EntryAbility extends UIAbility { 1022 // ... 1023 caller: Caller | undefined; 1024 1025 async onButtonCallWithResult(originMsg: string, backMsg: string): Promise<void> { 1026 try { 1027 let msg: MyParcelable = new MyParcelable(1, originMsg); 1028 if (this.caller) { 1029 const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg); 1030 hilog.info(DOMAIN_NUMBER, TAG, 'caller callWithResult succeed'); 1031 let result: MyParcelable = new MyParcelable(0, ''); 1032 data.readParcelable(result); 1033 backMsg = result.str; 1034 hilog.info(DOMAIN_NUMBER, TAG, `caller result is [${result.num}, ${result.str}]`); 1035 } 1036 } catch (error) { 1037 hilog.info(DOMAIN_NUMBER, TAG, `caller callWithResult failed with ${error}`); 1038 } 1039 } 1040 // ... 1041 } 1042 ``` 1043 10446. Release the caller object. 1045 1046 When the [Caller](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#caller) object is no longer required, use [release](../reference/apis-ability-kit/js-apis-app-ability-uiAbility.md#callerrelease) to release it. 1047 1048 ```ts 1049 import { UIAbility, Caller } from '@kit.AbilityKit'; 1050 import { hilog } from '@kit.PerformanceAnalysisKit'; 1051 1052 const TAG: string = '[CalleeAbility]'; 1053 const DOMAIN_NUMBER: number = 0xFF00; 1054 1055 export default class EntryAbility extends UIAbility { 1056 caller: Caller | undefined; 1057 1058 releaseCall(): void { 1059 try { 1060 if (this.caller) { 1061 this.caller.release(); 1062 this.caller = undefined; 1063 } 1064 hilog.info(DOMAIN_NUMBER, TAG, 'caller release succeed'); 1065 } catch (error) { 1066 hilog.info(DOMAIN_NUMBER, TAG, `caller release failed with ${error}`); 1067 } 1068 } 1069 } 1070 ``` 1071<!--no_check--> 1072