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![hop-multi-device-collaboration](figures/hop-multi-device-collaboration.png)
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