• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Multi-device Collaboration (for System Applications Only)
2
3
4## When to Use
5
6Multi-device coordination involves the following scenarios:
7
8- [Starting UIAbility and ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-and-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![hop-multi-device-collaboration](figures/hop-multi-device-collaboration.png)
23
24
25## Constraints
26
27- Since multi-device collaboration task management is not available, you can obtain the device list by developing system applications. Access to third-party applications is not supported.
28
29- Multi-device collaboration must comply with [Inter-Device Component Startup Rules](component-startup-rules.md#inter-device-component-startup-rules).
30
31- For better user experience, you are advised to use the **want** parameter to transmit data smaller than 100 KB.
32
33
34## Starting UIAbility and ServiceExtensionAbility Across Devices (No Data Returned)
35
36On device A, touch the **Start** button provided by the initiator application to start a specified UIAbility on device B.
37
38
39### Available APIs
40
41**Table 1** Cross-device startup APIs
42
43| **API**| **Description**|
44| -------- | -------- |
45| startAbility(want: Want, callback: AsyncCallback<void>): void; | Starts UIAbility and ServiceExtensionAbility. This API uses an asynchronous callback to return the result.|
46
47
48### How to Develop
49
501. Request the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Applying for Permissions](../security/accesstoken-guidelines.md#stage-model).
51
522. Request the data synchronization permission. The sample code for displaying a dialog box to request the permission is as follows:
53
54   ```ts
55   requestPermission() {
56       let context = this.context
57       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
58       context.requestPermissionsFromUser(permissions).then((data) => {
59           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
60       }).catch((error) => {
61           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
62       })
63   }
64   ```
65
663. Obtain the device ID of the target device.
67
68   ```ts
69   import deviceManager from '@ohos.distributedHardware.deviceManager';
70
71   let dmClass;
72   function initDmClass() {
73       // createDeviceManager is a system API.
74       deviceManager.createDeviceManager('ohos.samples.demo', (err, dm) => {
75           if (err) {
76               // ...
77               return
78           }
79           dmClass = dm
80       })
81   }
82   function getRemoteDeviceId() {
83       if (typeof dmClass === 'object' && dmClass !== null) {
84           let list = dmClass.getTrustedDeviceListSync()
85           if (typeof (list) === 'undefined' || typeof (list.length) === 'undefined') {
86               console.info('EntryAbility onButtonClick getRemoteDeviceId err: list is null')
87               return;
88           }
89           return list[0].deviceId
90       } else {
91           console.info('EntryAbility onButtonClick getRemoteDeviceId err: dmClass is null')
92       }
93   }
94   ```
95
964. Set the target component parameters, and call **startAbility()** to start UIAbility or ServiceExtensionAbility.
97
98   ```ts
99   let want = {
100       deviceId: getRemoteDeviceId(),
101       bundleName: 'com.example.myapplication',
102       abilityName: 'FuncAbility',
103       moduleName: 'module1', // moduleName is optional.
104   }
105   // context is the AbilityContext of the initiator UIAbility.
106   this.context.startAbility(want).then(() => {
107       // ...
108   }).catch((err) => {
109       // ...
110   })
111   ```
112
113
114## Starting UIAbility Across Devices (Data Returned)
115
116On device A, touch the **Start** button provided by the initiator application to start a specified UIAbility on device B. When the UIAbility on device B exits, a value is sent back to the initiator application.
117
118
119### Available APIs
120
121**Table 2** APIs for starting a UIAbility across devices and returning the result data
122
123| API| Description|
124| -------- | -------- |
125| startAbilityForResult(want: Want, callback: AsyncCallback&lt;AbilityResult&gt;): void; | Starts a UIAbility. This API uses an asynchronous callback to return the result when the UIAbility is terminated.|
126| terminateSelfWithResult(parameter: AbilityResult, callback: AsyncCallback&lt;void&gt;): void;| Terminates this UIAbility. This API uses an asynchronous callback to return the ability result information. It is used together with **startAbilityForResult**.|
127| terminateSelfWithResult(parameter: AbilityResult): Promise&lt;void&gt;; | Terminates this UIAbility. This API uses a promise to return the ability result information. It is used together with **startAbilityForResult**.|
128
129
130### How to Develop
131
1321. Request the **ohos.permission.DISTRIBUTED_DATASYNC** permission. For details, see [Applying for Permissions](../security/accesstoken-guidelines.md#stage-model).
133
1342. Request the data synchronization permission. The sample code for displaying a dialog box to request the permission is as follows:
135
136   ```ts
137   requestPermission() {
138       let context = this.context
139       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
140       context.requestPermissionsFromUser(permissions).then((data) => {
141           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
142       }).catch((error) => {
143           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
144       })
145   }
146   ```
147
1483. Set the target component parameters on the initiator, and call **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 and ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-and-serviceextensionability-across-devices-no-data-returned).
149
150   ```ts
151   let want = {
152       deviceId: getRemoteDeviceId(),
153       bundleName: 'com.example.myapplication',
154       abilityName: 'FuncAbility',
155       moduleName: 'module1', // moduleName is optional.
156   }
157   // context is the AbilityContext of the initiator UIAbility.
158   this.context.startAbilityForResult(want).then((data) => {
159       // ...
160   }).catch((err) => {
161       // ...
162   })
163   ```
164
1654. After the UIAbility task at the target device is complete, call **terminateSelfWithResult()** to return the data to the initiator UIAbility.
166
167   ```ts
168   const RESULT_CODE: number = 1001;
169   let abilityResult = {
170       resultCode: RESULT_CODE,
171       want: {
172           bundleName: 'com.example.myapplication',
173           abilityName: 'FuncAbility',
174           moduleName: 'module1',
175       },
176   }
177   // context is the AbilityContext of the target UIAbility.
178   this.context.terminateSelfWithResult(abilityResult, (err) => {
179       // ...
180   });
181   ```
182
1835. The initiator UIAbility receives the information returned by the target UIAbility and processes the information.
184
185   ```ts
186   const RESULT_CODE: number = 1001;
187
188   // ...
189
190   // context is the AbilityContext of the initiator UIAbility.
191   this.context.startAbilityForResult(want).then((data) => {
192       if (data?.resultCode === RESULT_CODE) {
193           // Parse the information returned by the target UIAbility.
194           let info = data.want?.parameters?.info
195           // ...
196       }
197   }).catch((err) => {
198       // ...
199   })
200   ```
201
202
203## Connecting to ServiceExtensionAbility Across Devices
204
205A system application can connect to a service on another device by calling [connectServiceExtensionAbility()](../reference/apis/js-apis-inner-application-uiAbilityContext.md#abilitycontextconnectserviceextensionability). For example, in the distributed game scenario, a tablet is used as the remote control and a smart TV is used as the display.
206
207
208### Available APIs
209
210**Table 3** APIs for cross-device connection
211
212| API| Description|
213| -------- | -------- |
214| connectServiceExtensionAbility(want: Want, options: ConnectOptions): number; | Connects to a ServiceExtensionAbility.|
215| disconnectServiceExtensionAbility(connection: number, callback: AsyncCallback&lt;void&gt;): void; | Disconnects a connection. This API uses an asynchronous callback to return the result.|
216| disconnectServiceExtensionAbility(connection: number): Promise&lt;void&gt;; | Disconnects a connection. This API uses a promise to return the result.|
217
218
219### How to Develop
220
2211. Configure the data synchronization permission in the **module.json5** file. The sample code is as follows:
222
223   ```json
224   {
225     "module": {
226       "requestPermissions":[
227         {
228           "name" : "ohos.permission.DISTRIBUTED_DATASYNC",
229         }
230       ]
231     }
232   }
233   ```
234
2352. Request the data synchronization permission. The sample code for displaying a dialog box to request the permission is as follows:
236
237   ```ts
238   requestPermission() {
239       let context = this.context
240       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
241       context.requestPermissionsFromUser(permissions).then((data) => {
242           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
243       }).catch((error) => {
244           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
245       })
246   }
247   ```
248
2493. (Optional) [Implement a background service](serviceextensionability.md#implementing-a-background-service). Perform this operation only if no background service is available.
250
2514. Connect to the background service.
252   - Implement the **IAbilityConnection** class. **IAbilityConnection** provides the following callbacks that you should implement: **onConnect()**, **onDisconnect()**, and **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.
253   - Set the target component parameters, including the target device ID, bundle name, and ability name.
254   - Call **connectServiceExtensionAbility** to initiate a connection.
255   - Receive the service handle returned by the target device when the connection is successful.
256   - Perform cross-device invoking and obtain the result returned by the target service.
257
258      ```ts
259      import rpc from '@ohos.rpc';
260
261      const REQUEST_CODE = 99;
262      let want = {
263          "deviceId": getRemoteDeviceId(),
264          "bundleName": "com.example.myapplication",
265          "abilityName": "ServiceExtAbility"
266      };
267      let options = {
268          onConnect(elementName, remote) {
269              console.info('onConnect callback');
270              if (remote === null) {
271                  console.info(`onConnect remote is null`);
272                  return;
273              }
274              let option = new rpc.MessageOption();
275              let data = new rpc.MessageParcel();
276              let reply = new rpc.MessageParcel();
277              data.writeInt(1);
278              data.writeInt(99); // You can send data to the target application for corresponding operations.
279
280              // @param code Indicates the service request code sent by the client.
281              // @param data Indicates the {@link MessageParcel} object sent by the client.
282              // @param reply Indicates the response message object sent by the remote service.
283              // @param options Specifies whether the operation is synchronous or asynchronous.
284              //
285              // @return Returns {@code true} if the operation is successful; returns {@code false} otherwise.
286              remote.sendRequest(REQUEST_CODE, data, reply, option).then((ret) => {
287                  let msg = reply.readInt();   // Receive the information (100) returned by the target device if the connection is successful.
288                  console.info(`sendRequest ret:${ret} msg:${msg}`);
289              }).catch((error) => {
290                  console.info('sendRequest failed');
291              });
292          },
293          onDisconnect(elementName) {
294              console.info('onDisconnect callback')
295          },
296          onFailed(code) {
297              console.info('onFailed callback')
298          }
299      }
300      // The ID returned after the connection is set up must be saved. The ID will be passed for service disconnection.
301      let connectionId = this.context.connectServiceExtensionAbility(want, options);
302      ```
303
304      For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility and ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-and-serviceextensionability-across-devices-no-data-returned).
305
3065. Disconnect the connection. Use **disconnectServiceExtensionAbility()** to disconnect from the background service.
307
308   ```ts
309   let connectionId = 1 // ID returned when the service is connected through connectServiceExtensionAbility.
310   this.context.disconnectServiceExtensionAbility(connectionId).then((data) => {
311       console.info('disconnectServiceExtensionAbility success');
312   }).catch((error) => {
313       console.error('disconnectServiceExtensionAbility failed');
314   })
315   ```
316
317
318## Using Cross-Device Call
319
320The 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).
321
322The following describes how to implement multi-device collaboration through cross-device call.
323
324
325### Available APIs
326
327**Table 4** Call APIs
328
329| API| Description|
330| -------- | -------- |
331| startAbilityByCall(want: Want): Promise&lt;Caller&gt;; | Starts a UIAbility in the foreground or background and obtains the caller object for communicating with the UIAbility.|
332| on(method: string, callback: CalleeCallBack): void | Callback invoked when the CalleeAbility registers a method.|
333| off(method: string): void | Callback invoked when the CalleeAbility deregisters a method.|
334| call(method: string, data: rpc.Parcelable): Promise&lt;void&gt; | Sends agreed parcelable data to the CalleeAbility.|
335| callWithResult(method: string, data: rpc.Parcelable): Promise&lt;rpc.MessageSequence&gt; | Sends agreed parcelable data to the CalleeAbility and obtains the agreed parcelable data returned by the CalleeAbility.|
336| release(): void | Releases the caller object.|
337| on(type: "release", callback: OnReleaseCallback): void | Callback invoked when the caller object is released.|
338
339
340### How to Develop
341
3421. Configure the data synchronization permission in the **module.json5** file. The sample code is as follows:
343
344   ```json
345   {
346     "module": {
347       "requestPermissions":[
348         {
349           "name" : "ohos.permission.DISTRIBUTED_DATASYNC",
350         }
351       ]
352     }
353   }
354   ```
355
3562. Request the data synchronization permission. The sample code for displaying a dialog box to request the permission is as follows:
357
358   ```ts
359   requestPermission() {
360       let context = this.context
361       let permissions: Array<string> = ['ohos.permission.DISTRIBUTED_DATASYNC']
362       context.requestPermissionsFromUser(permissions).then((data) => {
363           console.info("Succeed to request permission from user with data: "+ JSON.stringify(data))
364       }).catch((error) => {
365           console.info("Failed to request permission from user with error: "+ JSON.stringify(error))
366       })
367   }
368   ```
369
3703. Create the CalleeAbility.
371
372   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()** to register a listener. When data does not need to be received, use **off()** to deregister the listener.
373
374   1. Configure the launch type of the UIAbility.
375
376      Set **launchType** of the CalleeAbility to **singleton** in the **module.json5** file.
377
378      | JSON Field| Description|
379      | -------- | -------- |
380      | "launchType"| UIAbility launch type. Set this parameter to **singleton**.|
381
382      An example of the UIAbility configuration is as follows:
383
384       ```json
385       "abilities":[{
386           "name": ".CalleeAbility",
387           "srcEnty": "./ets/CalleeAbility/CalleeAbility.ts",
388           "launchType": "singleton",
389           "description": "$string:CalleeAbility_desc",
390           "icon": "$media:icon",
391           "label": "$string:CalleeAbility_label",
392           "exported": true
393       }]
394       ```
395
396   2. Import the **UIAbility** module.
397
398       ```ts
399       import Ability from '@ohos.app.ability.UIAbility'
400       ```
401
402   3. Define the agreed parcelable data.
403
404       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.
405
406       ```ts
407       export default class MyParcelable {
408           num: number = 0
409           str: string = ""
410
411           constructor(num, string) {
412               this.num = num
413               this.str = string
414           }
415
416           marshalling(messageSequence) {
417               messageSequence.writeInt(this.num)
418               messageSequence.writeString(this.str)
419               return true
420           }
421
422           unmarshalling(messageSequence) {
423               this.num = messageSequence.readInt()
424               this.str = messageSequence.readString()
425               return true
426           }
427       }
428       ```
429
430   4. Implement **Callee.on** and **Callee.off**.
431
432       In the following example, the **MSG_SEND_METHOD** listener is registered in **onCreate()** of the ability and deregistered in **onDestroy()**. After receiving parcelable data, the application processes the data and returns the data result. You need to implement processing based on service requirements.
433
434       ```ts
435       const TAG: string = '[CalleeAbility]'
436       const MSG_SEND_METHOD: string = 'CallSendMsg'
437
438       function sendMsgCallback(data) {
439           console.info('CalleeSortFunc called')
440
441           // Obtain the parcelable data sent by the CallerAbility.
442           let receivedData = new MyParcelable(0, '')
443           data.readParcelable(receivedData)
444           console.info(`receiveData[${receivedData.num}, ${receivedData.str}]`)
445
446           // Process the data.
447           // Return the parcelable data result to the CallerAbility.
448           return new MyParcelable(receivedData.num + 1, `send ${receivedData.str} succeed`)
449       }
450
451       export default class CalleeAbility extends Ability {
452           onCreate(want, launchParam) {
453               try {
454                   this.callee.on(MSG_SEND_METHOD, sendMsgCallback)
455               } catch (error) {
456                   console.info(`${MSG_SEND_METHOD} register failed with error ${JSON.stringify(error)}`)
457               }
458           }
459
460           onDestroy() {
461               try {
462                   this.callee.off(MSG_SEND_METHOD)
463               } catch (error) {
464                   console.error(TAG, `${MSG_SEND_METHOD} unregister failed with error ${JSON.stringify(error)}`)
465               }
466           }
467       }
468       ```
469
4704. Obtain the caller object and access the CalleeAbility.
471   1. Import the **UIAbility** module.
472
473       ```ts
474       import Ability from '@ohos.app.ability.UIAbility'
475       ```
476
477   2. Obtain the caller object.
478
479       The **context** attribute of the UIAbility implements **startAbilityByCall** to obtain the caller object for communication. The following example uses **this.context** to obtain the **context** attribute of the UIAbility, uses **startAbilityByCall** to start the CalleeAbility, obtain the caller object, and register the **onRelease** listener of the CallerAbility. You need to implement processing based on service requirements.
480
481       ```ts
482       async onButtonGetRemoteCaller() {
483           var caller = undefined
484           var context = this.context
485
486           context.startAbilityByCall({
487               deviceId: getRemoteDeviceId(),
488               bundleName: 'com.samples.CallApplication',
489               abilityName: 'CalleeAbility'
490           }).then((data) => {
491               if (data != null) {
492                   caller = data
493                   console.info('get remote caller success')
494                   // Register the onRelease() listener of the CallerAbility.
495                   caller.onRelease((msg) => {
496                       console.info(`remote caller onRelease is called ${msg}`)
497                   })
498                   console.info('remote caller register OnRelease succeed')
499               }
500           }).catch((error) => {
501               console.error(`get remote caller failed with ${error}`)
502           })
503       }
504       ```
505
506       For details about how to implement **getRemoteDeviceId()**, see [Starting UIAbility and ServiceExtensionAbility Across Devices (No Data Returned)](#starting-uiability-and-serviceextensionability-across-devices-no-data-returned).
507
5085. Sends agreed parcelable data to the CalleeAbility.
509   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 send data to the CalleeAbility.
510
511       ```ts
512       const MSG_SEND_METHOD: string = 'CallSendMsg'
513       async onButtonCall() {
514           try {
515               let msg = new MyParcelable(1, 'origin_Msg')
516               await this.caller.call(MSG_SEND_METHOD, msg)
517           } catch (error) {
518               console.info(`caller call failed with ${error}`)
519           }
520       }
521       ```
522
523   2. In the following, **CallWithResult** is used to send data **originMsg** to the CalleeAbility and assign the data processed by the **CallSendMsg** method to **backMsg**.
524
525       ```ts
526       const MSG_SEND_METHOD: string = 'CallSendMsg'
527       originMsg: string = ''
528       backMsg: string = ''
529       async onButtonCallWithResult(originMsg, backMsg) {
530           try {
531               let msg = new MyParcelable(1, originMsg)
532               const data = await this.caller.callWithResult(MSG_SEND_METHOD, msg)
533               console.info('caller callWithResult succeed')
534
535               let result = new MyParcelable(0, '')
536               data.readParcelable(result)
537               backMsg(result.str)
538               console.info(`caller result is [${result.num}, ${result.str}]`)
539           } catch (error) {
540               console.info(`caller callWithResult failed with ${error}`)
541           }
542       }
543       ```
544
5456. Release the caller object.
546
547   When the caller object is no longer required, use **release()** to release it.
548
549   ```ts
550   releaseCall() {
551       try {
552           this.caller.release()
553           this.caller = undefined
554           console.info('caller release succeed')
555       } catch (error) {
556           console.info(`caller release failed with ${error}`)
557       }
558   }
559   ```