Intermediate: Integration of Hop Feature in Harmony OS App

Manoj Kumar
10 min readSep 3, 2021

Overview

In this article, I will create a demo app in which I will implement the Hop Feature in Harmony OS based application.

As the all-scenario, multi-device lifestyle becomes popular, users have an increasing number of devices. Each device provides users as per they need in a certain scenario. For example, watches allow users to view information in a timely manner, and smart TVs bring them an immersive watching experience. However, each device has its limitation. For example, inputting text on a smart TV is frustrating as it is much more difficult than on a phone. If multiple devices can sense each other through a distributed OS and together form a super device, the strengths of each device can be fully exerted to provide a more natural and smoother distributed experience for users.

Harmony OS Security Introduction

Hop refers to a distributed operation involving multiple devices running HarmonyOS. The hop capability breaks boundaries of devices and enables multi-device collaboration, achieving precise control, universal coordination, and seamless hops of user applications.

For example:

A user can edit the same email, do a crossfit exercise, or play a game across devices. The hop capability provides you with broad application scenarios, innovative product perspectives, enhanced product advantages, and superior experience. Hops are implemented using the following technologies:

Cross-device migration: allows user apps to be migrated across devices. It migrates a running user app from device A to device B seamlessly without interrupting its running. Upon the migration, the user app exits from device A and continues running on device B from the state it was in when it left off device A. When the network changes.

For example:

When a user goes outdoors or when a more appropriate device is detected, the user can migrate an ongoing task to another device for better experience. Cross-device migration is used in the following typical scenarios:

  • Migrate a video call from the phone to the smart TV for better experience. When the migration is complete, the video app exits on the phone.
  • Migrate the content being read from the phone to the tablet for better experience. When the migration is complete, the reading app exits on the phone.

Multi-device collaboration: enables different FAs or PAs on multiple devices to run concurrently or successively, or same FAs or PAs on multiple devices to run concurrently to implement complete business functionalities. Multiple devices working as a whole provided a more efficient and immersive experience than a single device.

For example:

When a user takes a photo using an app on the smart TV, the app can call another app on the phone for beautification. The obtained photo is stored in the app on the smart TV. Multi-device collaboration is used in the following typical scenarios:

  • Use an app on the phone as the game controller, and display the game UI on an app on the smart TV for better experience.
  • Use an app on the tablet to answer questions, and take an online class through an app on the smart TV.

API Overview

Cross-device migration:

APIs provided by the hop task management service, such as registering a hop callback with and unregistering a hop callback from the hop task management service, showing the device list, and updating the hop status. These APIs are used for implementing cross-device migration. Cross-device migration allows you to implement various functions, such as editing documents and playing videos across devices.

void register(String bundleName, ExtraParams parameter, IContinuationDeviceCallback deviceCallback, RequestCallback requestCallback):

Registers an ability with and connects to the hop task management service, and obtains the token assigned to the ability.

Parameter description:

bundleName: (mandatory) app bundle name in string format.

params: (optional) filtering conditions for system suggested hops. This parameter is of the ExtraParams type. If a system suggested hop has no special requirements for the filtering conditions, you can use the filtering conditions for the showDeviceList method. To disable system suggested hops, pass {“isTurnOffRecommend”:true} to jsonParams in ExtraParams.

deviceCallback: (optional) called when a device in the device list is selected. This callback returns the ID of the selected device.

requestCallback: (optional) registration request callback. This callback returns the registered token.

ExtraParams description:

devType: (optional) type of the device to be connected. The value can be “00E” (mobile phone), “011” (tablet), “06D” (watch), or “09C” (smart TV). For example, “devType”:[“011”]. If this parameter is null, mobile phones, tablets, watches, and smart TVs are all supported.

targetBundleName: (optional) bundle name of the target app. If this parameter is null, the target app bundle name is the same as bundleName.

description: (optional) ability description, which is displayed on the device list page.

jsonParams: (optional) extended parameters used for filtering devices. An example value is as follows:

{"filter":{"commonFilter": {"system":{"harmonyVersion":"2.0.0"},"groupType": "1","curComType": 0x00000004, "faFilter":"{\"targetBundleName\":\"com.xxx.yyy\"}"}},"transferScene":1,"isTurnOffRecommend":false,"remoteAuthenticationDescription": "Description in the dialog box for HiVision scanning","remoteAuthenticationPicture":""}

jsonParams description:

system: (optional) HarmonyOS version of the target device. The value is a string, for example, {“harmonyVersion”:”2.0.0"}. The HarmonyOS version of the target device must be greater than or equal to the value of this parameter.

groupType: (optional) whether the current device and the target device use the same account. If this parameter is null, the two devices do not need to use the same account. The value is a string and can be 1 or 1|256. The former indicates that the two devices must use the same account, and the latter indicates the opposite. For example, “groupType”:”1".

curComType: (optional) whether the current device and the target device must be in the same LAN. The value is of the int type and can be 0x00000004 or 0x00030004. The former indicates that the two devices must be in the same LAN, and the latter indicates the opposite. If this parameter is null, the two devices do not need to be in the same LAN.

faFilter: (optional) filtering conditions in string format. If this parameter is null, version compatibility will not be checked. To check the version compatibility, you need to pass the bundle name of the target app.

transferScene: (optional) hop scene. The value is of the int type and the default value is 0. The value can be: 0 indicates collaboration with a single device. Only one target device can be selected on the device selection panel. If the hop is successful, the panel automatically disappears. If the hop fails, the panel does not disappear. The system maintains the hop status. If the panel is opened after it disappears, the hop success state is displayed on the panel; 1 indicates migration to a single device. Only one target device can be selected on the device selection panel. If the hop is successful, the panel automatically disappears. If the hop fails, the panel does not disappear. The system does not maintain the hop status. If the panel is opened after it disappears, the unhopped state is displayed on the panel; 2 indicates collaboration with multiple devices. Multiple target devices can be selected on the device selection panel. The panel does not disappear regardless of whether the hop is successful. The system maintains the hop status.

isTurnOffRecommend: (optional) whether to disable system suggested hops. The value is of the boolean type. The value true means to disable system suggested hops, and false means the opposite. The default value is false.

remoteAuthenticationDescription: (optional) description in the dialog box for HiVision scanning during authentication for a device with a different account from the current device or for a device with no account. The value is a string. This parameter is not required for the register() method, and is optional for the showDeviceList() method.

remoteAuthenticationPicture: (optional) picture displayed in the dialog box for HiVision scanning during authentication for a device with a different account from the current device or for a device with no account. The value is a string. If the picture is of the byte[] type, it needs to be converted into a string via Base64.encodeToString(mBuff,Base64.DEFAULT). This parameter is not required for the register() method, and is optional for the showDeviceList() method.

Check whether the registration is successful based on the onResult callback in RequestCallback. If the return value is less than 0, the registration fails, otherwise, the registration is successful, and the unique token for the hop task is returned.

When the user selects a device, the onConnected callback defined by deviceCallback is used to obtain the device ID, type, and name.

void unregister(int token, RequestCallback requestCallback):

Unregisters an ability from the hop task management service based on the token obtained during ability registration.

After calling this method, check whether the operation is successful based on the onResult callback in RequestCallback.

void updateConnectStatus(int token, String deviceId, int status, RequestCallback requestCallback):

Notifies the hop task management service to update the connection status and display the updated status on the UI of the hop task management service. Parameters token and deviceId can be obtained from the callbacks for the register() method. The value of status can be IDLE, CONNECTING, CONNECTED, or DIS_CONNECTING. If an error occurs, the error code needs to be reported.

After calling this method, check whether the operation is successful based on the onResult callback in RequestCallback.

Prerequisite

  1. Harmony OS phone.
  2. Java JDK.
  3. DevEco Studio.

App Development

  1. Create a New Harmony OS Project.

2. Configure Project config.json.

{
"module": {
"reqPermissions": [
{
"name": "ohos.permission.DISTRIBUTED_DATASYNC"
}
],
...
}
...
}

3. Configure Project Gradle.

// Top-level build file where you can add configuration options common to all sub-projects/modules.
apply plugin: 'com.huawei.ohos.app'
//For instructions on signature configuration, see https://developer.harmonyos.com/en/docs/documentation/doc-guides/ide_debug_device-0000001053822404#EN-US_TOPIC_0000001154985555__section1112183053510
ohos {
compileSdkVersion 5
defaultConfig {
compatibleSdkVersion 4
}
}

buildscript {
repositories {
maven {
url 'https://repo.huaweicloud.com/repository/maven/'
}
maven {
url 'https://developer.huawei.com/repo/'
}
jcenter()
}
dependencies {
classpath 'com.huawei.ohos:hap:2.4.4.2'
classpath 'com.huawei.ohos:decctest:1.2.4.0'
}
}
allprojects {
repositories {
maven {
url 'https://repo.huaweicloud.com/repository/maven/'
}
maven {
url 'https://developer.huawei.com/repo/'
}
jcenter()
}
}

4. Configure App Gradle.

apply plugin: 'com.huawei.ohos.hap'
apply plugin: 'com.huawei.ohos.decctest'
//For instructions on signature configuration, see https://developer.harmonyos.com/en/docs/documentation/doc-guides/ide_debug_device-0000001053822404#EN-US_TOPIC_0000001154985555__section1112183053510
ohos {
compileSdkVersion 5
defaultConfig {
compatibleSdkVersion 4
}
buildTypes {
release {
proguardOpt {
proguardEnabled false
rulesFiles 'proguard-rules.pro'
}
}
}

}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.har'])
testImplementation 'junit:junit:4.13'
ohosTestImplementation 'com.huawei.ohos.testkit:runner:1.0.0.100'
}
decc {
supportType = ['html','xml']
}

5.Create Ability class with XML UI.

MainAbilitySlice.java:

public class MainAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
// You can design the GUI
// and set a unified background color for buttons as you like.
// For example, you can use PositionLayout to create a simple page.
PositionLayout layout = new PositionLayout(this);
LayoutConfig config = new LayoutConfig(LayoutConfig.MATCH_PARENT, LayoutConfig.MATCH_PARENT);
layout.setLayoutConfig(config);
ShapeElement buttonBg = new ShapeElement();
buttonBg.setRgbColor(new RgbColor(0, 125, 255));
super.setUIContent(layout);
}
@Override
public void onInactive() {
super.onInactive();
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onBackground() {
super.onBackground();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
@Override
public void onStop() {
super.onStop();
}
}
public class MainAbilitySlice extends AbilitySlice implements IAbilityContinuation {
private void showMessage(String msg) {
ToastDialog toastDialog = new ToastDialog(this);
toastDialog.setText(msg);
toastDialog.show();
}
@Override
public boolean onStartContinuation() {
showMessage("ContinueAbility Start");
return true;
}
@Override
public boolean onSaveData(IntentParams saveData) {
String exampleData = String.valueOf(System.currentTimeMillis());
saveData.setParam("continueParam", exampleData);
return true;
}
@Override
public boolean onRestoreData(IntentParams restoreData) {
// Restore the FA state data transferred from the target device as required.
Object data = restoreData.getParam("continueParam");
return true;
}
@Override
public void onCompleteContinuation(int result) {
// Show a message to notify the user that the migration is complete and remind the user of stopping the FA on the source device.
showMessage("ContinueAbility Done");
if (!isReversibly) {
terminateAbility();
}
}
@Override
public void onFailedContinuation(int errorCode) {
// Notify the user of the migration failure if required.
showMessage("ContinueAbility failed");
if (!isReversibly) {
terminateAbility();
}
}
}
// You are advised to design buttons in your own style. The following sample code is for reference only:
private static final int OFFSET_X = 100;
private static final int OFFSET_Y = 100;
private static final int ADD_OFFSET_Y = 150;
private static final int BUTTON_WIDTH = 800;
private static final int BUTTON_HEIGHT = 100;
private static final int TEXT_SIZE = 50;
private int offsetY = 0;
private Button createButton(String text, ShapeElement buttonBg) {
Button button = new Button(this);
button.setContentPosition(OFFSET_X, OFFSET_Y + offsetY);
offsetY += ADD_OFFSET_Y;
button.setWidth(BUTTON_WIDTH);
button.setHeight(BUTTON_HEIGHT);
button.setTextSize(TEXT_SIZE);
button.setTextColor(Color.YELLOW);
button.setText(text);
button.setBackground(buttonBg);
return button;
}
// Example of adding buttons to PositionLayout in sequence:
private void addComponents(PositionLayout linear, ShapeElement buttonBg) {
// Create a button for displaying the registration of an FA with the hop task management service.
Button btnRegister = createButton("register", buttonBg);
btnRegister.setClickedListener(mRegisterListener);
linear.addComponent(btnRegister);
// Create a button for displaying the device list.
Button btnShowDeviceList = createButton("ShowDeviceList", buttonBg);
btnShowDeviceList.setClickedListener(mShowDeviceListListener);
linear.addComponent(btnShowDeviceList);
// Create a button for migrating an FA.
Button btnContinueRemoteFA = createButton("ContinueRemoteFA", buttonBg);
btnContinueRemoteFA.setClickedListener(mContinueAbilityListener);
linear.addComponent(btnContinueRemoteFA);
// Create a button for migrating an FA that is reversible.
Button btnContinueReversibly = createButton("ContinueReversibly", buttonBg);
btnContinueReversibly.setClickedListener(mContinueReversiblyListener);
linear.addComponent(btnContinueReversibly);
// Create a button for reversing an FA.
Button btnReverseContinue = createButton("ReverseContinuation", buttonBg);
btnReverseContinue.setClickedListener(mReverseContinueListener);
linear.addComponent(btnReverseContinue);
}
@Override
public void onStart(Intent intent) {
...
// Add the layout of function buttons.
addComponents(layout, buttonBg);
super.setUIContent(layout);
}

MainAbility.java:

public class MainAbility extends Ability implements IAbilityContinuation {
private static final int DOMAIN_ID = 0xD001100;
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, DOMAIN_ID, "MainAbility");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setMainRoute(MainAbilitySlice.class.getName());
}
// For your convenience, the hop logic is implemented in AbilitySlice rather than Ability.
@Override
public boolean onStartContinuation() {
HiLog.info(LABEL_LOG, "onStartContinuation called");
return true;
}
@Override
public boolean onSaveData(IntentParams saveData) {
HiLog.info(LABEL_LOG, "onSaveData called");
return true;
}
@Override
public boolean onRestoreData(IntentParams restoreData) {
HiLog.info(LABEL_LOG, "onRestoreData called");
return true;
}
@Override
public void onCompleteContinuation(int result) {
HiLog.info(LABEL_LOG, "onCompleteContinuation called");
}
@Override
public void onFailedContinuation(int errorCode) {
HiLog.info(LABEL_LOG, "onFailedContinuation called");
}
}

App Build Result

Tips and Tricks

  1. After an FA is registered with the hop task management service, no devices are recommended. When the showDeviceList() method is called, no devices are returned.
  2. User need to specify the deviceId of the peer device. User can call the getDeviceList method in the ohos.distributedschedule.interwork.DeviceManager class to obtain the list of anonymized devices, and then select a target device from the list.
  3. Call the getDeviceList method to obtain the device list, from which you can select the target device.

Conclusion

In this article, we have learned how to implement Hop Feature in Harmony OS application. In this application, I have explained that how user can connect remotely PA devices from FA device.

Thanks for reading this article. Be sure to like and comments to this article, if you found it helpful. It means a lot to me.

References

Harmony OS Doc: https://developer.harmonyos.com/en/docs/documentation/doc-guides/hop-overview-0000001092995092

https://developer.harmonyos.com/en/docs/design/des-guides/service-overview-0000001139795693

--

--