Introduction

Nearby Connection allows apps to discover and establish a direct communication channel with nearby devices through Bluetooth or Wi-Fi, without the need to connect to the Internet. Nearby Connection achieves seamless short-distance interaction in cases such as local multi-player gaming, collaborative whiteboard, multi-screen gaming, and offline file transfer. The Nearby Connection service supports the transmission of short text, streams, and files. You can select a data type to suit your development scenario. By building a demo project, you will be able to learn how to:

What You Will Create

In this codelab, you will create an app that uses Huawei's Nearby Connection service. This service can easily connect two phones for data exchange between them.

What You Will Learn

Hardware Requirements

Software Requirements

To integrate HMS Core services, you must complete the following preparations:

For details, please refer to Preparations for Integrating HMS Core.

Sign in to AppGallery Connect and select My apps. On the displayed page, click the app you have created.

Go to Develop > Overview, click and choose Manage APIs to go to the service management page. Then, enable Nearby Service.

Sign in to HUAWEI Developer and click Console.

Go to APIs and Services > Credentials, and click Edit in the Operation column of the API key for the created app.

In the Application restrictions area, select Android apps, enter the package name of the created app and the generated SHA256 certificate fingerprint, and save the settings.

Adding the AppGallery Connect Configuration File

Sign in to AppGallery Connect and select My apps. On the displayed page, click the created app. Go to Develop > Overview, click , choose Add SDK, and perform operations as instructed.

Adding SDK Dependencies

Open the project-level build.gradle file.

Add configurations to the build.gradle file header.

apply plugin: 'com.huawei.agconnect'

Add build dependencies to dependencies.

dependencies { implementation 'com.huawei.hms:nearby: {version}' }

Example:

Configuring Obfuscation Scripts

Open the obfuscation configuration file proguard-rules.pro of your Android project.

Add configurations to exclude from obfuscation.

-ignorewarnings -keepattributes *Annotation* -keepattributes Exceptions -keepattributes InnerClasses -keepattributes Signature -keepattributes SourceFile,LineNumberTable -keep class com.hianalytics.android.**{*;} -keep class com.huawei.updatesdk.**{*;} -keep class com.huawei.hms.**{*;}

If you have used AndResGuard, whitelist it in the obfuscation script file.

"R.string.hms*", "R.string.connect_server_fail_prompt_toast", "R.string.getting_message_fail_prompt_toast", "R.string.no_available_network_prompt_toast", "R.string.third_app_*", "R.string.upsdk_*", "R.layout.hms*", "R.layout.upsdk_*", "R.drawable.upsdk*", "R.color.upsdk*", "R.dimen.upsdk*", "R.style.upsdk*" "R.string.agc*"

Synchronizing the Project

Choose File > Sync Project with Gradle Files to start building your project.

The synchronization is successful if "synced successfully" is displayed, as shown in the following figure.

You have successfully integrated the Nearby Service SDK, which consists of the broadcast and scanning module, connection setup module, and data transmission module.
Now you will develop a demo project to learn how to use the APIs of the Nearby Service SDK by performing the following steps.

Download source code

Extracting the Sample Code to the src Folder

Copy all Java files from the code directory to your project.

Copy all resource files from the res folder to your project.

Modifying the AndroidManifest.xml File

Modify the permission of your app. Go to app > src > main in your project, open the AndroidManifest.xml file, and add six required permissions to the file. Android 6.0 also introduces the permission application mechanism for dangerous permissions. Therefore, the following part also describes how to dynamically apply for dangerous permissions.

The sample code is as follows:

<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

Modifying the MainActivity Class

  1. Import the following classes from the Nearby Servcie SDK:

    import com.huawei.hms.nearby.Nearby; import com.huawei.hms.nearby.StatusCode; import com.huawei.hms.nearby.discovery.BroadcastOption; import com.huawei.hms.nearby.discovery.ConnectCallback; import com.huawei.hms.nearby.discovery.ConnectInfo; import com.huawei.hms.nearby.discovery.ConnectResult; import com.huawei.hms.nearby.discovery.DiscoveryEngine; import com.huawei.hms.nearby.discovery.Policy; import com.huawei.hms.nearby.discovery.ScanEndpointCallback; import com.huawei.hms.nearby.discovery.ScanEndpointInfo; import com.huawei.hms.nearby.discovery.ScanOption; import com.huawei.hms.nearby.transfer.Data; import com.huawei.hms.nearby.transfer.DataCallback; import com.huawei.hms.nearby.transfer.TransferEngine; import com.huawei.hms.nearby.transfer.TransferStateUpdate;
  2. Import the following classes from the system library:

    import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.Toast; import com.huawei.hms.nearbyconnectiondemo.utils.ToastUtil; import com.huawei.hms.nearbyconnectiondemo.utils.permission.PermissionHelper; import com.huawei.hms.nearbyconnectiondemo.utils.permission.PermissionInterface; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List;
  3. Add the following code to MainActivity:

    private static final int TIMEOUT_MILLISECONDS = 10000; private static final String TAG = "Nearby Connection Demo"; private TransferEngine mTransferEngine = null; private DiscoveryEngine mDiscoveryEngine = null; private PermissionHelper mPermissionHelper; private EditText myNameEt; private EditText friendNameEt; private EditText msgEt; private ListView messageListView; private List<MessageBean> msgList; private ChatAdapter adapter; private Button sendBtn; private Button connectBtn; private int connectTaskResult; private String myNameStr; private String friendNameStr; private String myServiceId; private String mEndpointId; private String msgStr;
  4. Add the function for processing connection timeout.

    /** * Handle timeout function */ @SuppressLint("HandlerLeak") private Handler handler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { handler.removeMessages(0); if (connectTaskResult != StatusCode.STATUS_SUCCESS) { ToastUtil.showShortToastTop("Connection timeout, make sure your friend is ready and try again."); if (myNameStr.compareTo(friendNameStr) > 0) { mDiscoveryEngine.stopScan(); } else { mDiscoveryEngine.stopBroadcasting(); } myNameEt.setEnabled(true); friendNameEt.setEnabled(true); connectBtn.setEnabled(true); } } };
  5. Add the following code to the onCreate function

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); requestPermissions(); initView(); sendBtn.setEnabled(false); msgEt.setEnabled(false); } private void initView() { myNameEt = findViewById(R.id.et_my_name); friendNameEt = findViewById(R.id.et_friend_name); msgEt = findViewById(R.id.et_msg); connectBtn = findViewById(R.id.btn_connect); sendBtn = findViewById(R.id.btn_send); connectBtn.setOnClickListener(this); sendBtn.setOnClickListener(this); messageListView = findViewById(R.id.lv_chat); msgList = new ArrayList<>(); adapter = new ChatAdapter(this, msgList); messageListView.setAdapter(adapter); connectTaskResult = StatusCode.STATUS_ENDPOINT_UNKNOWN; }
  6. Add the function for permission application.

    private void requestPermissions() { mPermissionHelper = new PermissionHelper(this, this); mPermissionHelper.requestPermissions(); } @Override public int getPermissionsRequestCode() { return 10086; } @Override public String[] getPermissions() { return new String[]{Manifest.permission.BLUETOOTH, Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.ACCESS_WIFI_STATE, Manifest.permission.CHANGE_WIFI_STATE, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}; } @Override public void requestPermissionsSuccess() { } @Override public void requestPermissionsFail() { Toast.makeText(this, R.string.error_missing_permissions, Toast.LENGTH_LONG).show(); finish(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (mPermissionHelper.requestPermissionsResult(requestCode, permissions, grantResults)) { return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); }
  7. Add the scanning and broadcast function.

    public void doStartBroadcast(View view) throws RemoteException { BroadcastOption.Builder advBuilder = new BroadcastOption.Builder(); advBuilder.setPolicy(Policy.POLICY_STAR); mDiscoveryEngine.startBroadcasting(myNameStr, myServiceId, mConnCb, advBuilder.build()); } public void doStartScan(View view) throws RemoteException { ScanOption.Builder discBuilder = new ScanOption.Builder(); discBuilder.setPolicy(Policy.POLICY_STAR); mDiscoveryEngine.startScan(myServiceId, mDiscCb, discBuilder.build()); }
  8. Add the function for sending and receiving data.

    /** * Send message function */ private void sendMessage() { msgStr = msgEt.getText().toString(); Data data = Data.fromBytes(msgStr.getBytes(Charset.defaultCharset())); Log.d(TAG, "myEndpointId " + mEndpointId); mTransferEngine.sendData(mEndpointId, data); MessageBean item = new MessageBean(); item.setMyName(myNameStr); item.setFriendName(friendNameStr); item.setMsg(msgStr); item.setSend(true); msgList.add(item); adapter.notifyDataSetChanged(); msgEt.setText(""); messageListView.setSelection(messageListView.getBottom()); } /** * Receive message function */ private void receiveMessage(Data data) { msgStr = new String(data.asBytes()); MessageBean item = new MessageBean(); item.setMyName(myNameStr); item.setFriendName(friendNameStr); item.setMsg(msgStr); item.setSend(false); msgList.add(item); adapter.notifyDataSetChanged(); messageListView.setSelection(messageListView.getBottom()); }
  9. Add the scanning callback function.

    private ScanEndpointCallback mDiscCb = new ScanEndpointCallback() { @Override public void onFound(String endpointId, ScanEndpointInfo discoveryEndpointInfo) { mEndpointId = endpointId; mDiscoveryEngine.requestConnect(myNameStr, mEndpointId, mConnCb); } @Override public void onLost(String endpointId) { Log.d(TAG, "Nearby Connection Demo app: Lost endpoint: " + endpointId); } };
  10. Add the connection callback function.

    private ConnectCallback mConnCb = new ConnectCallback() { @Override public void onEstablish(String endpointId, ConnectInfo connectionInfo) { mTransferEngine = Nearby.getTransferEngine(getApplicationContext()); mEndpointId = endpointId; mDiscoveryEngine.acceptConnect(endpointId, mDataCb); ToastUtil.showShortToastTop("Let's chat!"); sendBtn.setEnabled(true); msgEt.setEnabled(true); connectBtn.setEnabled(false); connectTaskResult = StatusCode.STATUS_SUCCESS; if (myNameStr.compareTo(friendNameStr) > 0) { mDiscoveryEngine.stopScan(); } else { mDiscoveryEngine.stopBroadcasting(); } } @Override public void onResult(String endpointId, ConnectResult resolution) { mEndpointId = endpointId; } @Override public void onDisconnected(String endpointId) { ToastUtil.showShortToastTop("Disconnect."); connectTaskResult = StatusCode.STATUS_NOT_CONNECTED; sendBtn.setEnabled(false); connectBtn.setEnabled(true); msgEt.setEnabled(false); myNameEt.setEnabled(true); friendNameEt.setEnabled(true); } };
  11. Add the data callback function.

    private DataCallback mDataCb = new DataCallback() { @Override public void onReceived(String string, Data data) { receiveMessage(data); } @Override public void onTransferUpdate(String string, TransferStateUpdate update) { } };
  12. Add the button tapping function.

    @Override public void onClick(View view) { switch (view.getId()) { case R.id.btn_connect: if (checkName()) { return; } connect(view); handler.sendEmptyMessageDelayed(0, TIMEOUT_MILLISECONDS); break; case R.id.btn_send: if (checkMessage()) { return; } sendMessage(); break; default: break; } } private void connect(View view) { ToastUtil.showShortToastTop("Connecting to your friend."); connectBtn.setEnabled(false); myNameEt.setEnabled(false); friendNameEt.setEnabled(false); Context context = getApplicationContext(); mDiscoveryEngine = Nearby.getDiscoveryEngine(context); try { if (myNameStr.compareTo(friendNameStr) > 0) { doStartScan(view); } else { doStartBroadcast(view); } } catch (RemoteException e) { Log.e(TAG, "remote exception." + e.getMessage()); } }
  13. Add the input check function.

    /** * Check input message */ private boolean checkMessage() { if (TextUtils.isEmpty(msgEt.getText())) { ToastUtil.showShortToastTop("Please input data you want to send."); return true; } return false; } /** * Check input name */ private boolean checkName() { if (TextUtils.isEmpty(myNameEt.getText())) { ToastUtil.showShortToastTop("Please input your name."); return true; } if (TextUtils.isEmpty(friendNameEt.getText())) { ToastUtil.showShortToastTop("Please input your friend's name."); return true; } if (TextUtils.equals(myNameEt.getText().toString(), friendNameEt.getText().toString())) { ToastUtil.showShortToastTop("Please input two different names."); return true; } friendNameStr = friendNameEt.getText().toString(); myNameStr = myNameEt.getText().toString(); getServiceId(); return false; }
  14. Add the function for generating ServiceId.

    private void getServiceId() { if (myNameStr.compareTo(friendNameStr) > 0) { myServiceId = myNameStr + friendNameStr; } else { myServiceId = friendNameStr + myNameStr; } }
  15. Build, load, and debug the app.

    After completing the above service code, you can build the code.

After the build is complete, generate the APK. Install it on the two phones, and run it.

Now you've finished the preceding steps. Run the app on both phones.

UI Introduction

The above are the screenshots of the sample app running on a phone.
The app provides the following two buttons:
BEGIN TO CHAT!: Used to enable scanning on one phone and discovery on the other and automatically set up a connection.
SEND: Sends data to the remote phone.
In addition, there are three text boxes on the UI, which are used to enter the following data from top to bottom, respectively:
My name: your name to be displayed during chatting.
Friend's name: friend you want to chat with.
Data: chat messages you want to send.

Running Procedure

The procedures on the two phones are the same. Follow the instructions in the figure.

  1. Enter your name.
  2. Enter your friend's name.
  3. Tap BEGIN TO CHAT!.
  4. Edit your message and tap SEND to send it to your friend.

Chat Screen

After the correct chat names are entered on two phones and a connection is set up, the two phones can send chat data to each other.

Well done. You have successfully completed this codelab and learned:

Now, realize your ideas using the HMS Core Nearby Service SDK.

For more information, please click the following links:
Related documentation

Download source code

Code copied