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