• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.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