Merge "[Thread] add demoapp for Thread network APIs" into main
This commit is contained in:
commit
a39db11899
32 changed files with 1386 additions and 0 deletions
39
thread/demoapp/Android.bp
Normal file
39
thread/demoapp/Android.bp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (C) 2023 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package {
|
||||
default_applicable_licenses: ["Android-Apache-2.0"],
|
||||
}
|
||||
|
||||
android_app {
|
||||
name: "ThreadNetworkDemoApp",
|
||||
srcs: ["java/**/*.java"],
|
||||
min_sdk_version: "34",
|
||||
resource_dirs: ["res"],
|
||||
static_libs: [
|
||||
"androidx-constraintlayout_constraintlayout",
|
||||
"androidx.appcompat_appcompat",
|
||||
"androidx.navigation_navigation-common",
|
||||
"androidx.navigation_navigation-fragment",
|
||||
"androidx.navigation_navigation-ui",
|
||||
"com.google.android.material_material",
|
||||
"guava",
|
||||
],
|
||||
libs: [
|
||||
"framework-connectivity-t",
|
||||
],
|
||||
certificate: "platform",
|
||||
privileged: true,
|
||||
platform_apis: true,
|
||||
}
|
||||
40
thread/demoapp/AndroidManifest.xml
Normal file
40
thread/demoapp/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.threadnetwork.demoapp">
|
||||
|
||||
<uses-sdk android:minSdkVersion="34" android:targetSdkVersion="35"/>
|
||||
<uses-feature android:name="android.hardware.threadnetwork" android:required="true" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.THREAD_NETWORK_PRIVILEGED" />
|
||||
|
||||
<application
|
||||
android:label="ThreadNetworkDemoApp"
|
||||
android:theme="@style/Theme.ThreadNetworkDemoApp"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:testOnly="true">
|
||||
<activity android:name=".MainActivity" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.threadnetwork.demoapp;
|
||||
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.AutoCompleteTextView;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import com.android.threadnetwork.demoapp.concurrent.BackgroundExecutorProvider;
|
||||
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial;
|
||||
import com.google.android.material.textfield.TextInputEditText;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.common.util.concurrent.FluentFuture;
|
||||
import com.google.common.util.concurrent.FutureCallback;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class ConnectivityToolsFragment extends Fragment {
|
||||
private static final String TAG = "ConnectivityTools";
|
||||
|
||||
// This is a mirror of NetworkCapabilities#NET_CAPABILITY_LOCAL_NETWORK which is @hide for now
|
||||
private static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
|
||||
|
||||
private static final Duration PING_TIMEOUT = Duration.ofSeconds(10L);
|
||||
private static final Duration UDP_TIMEOUT = Duration.ofSeconds(10L);
|
||||
private final ListeningScheduledExecutorService mBackgroundExecutor =
|
||||
BackgroundExecutorProvider.getBackgroundExecutor();
|
||||
private final ArrayList<String> mServerIpCandidates = new ArrayList<>();
|
||||
private final ArrayList<String> mServerPortCandidates = new ArrayList<>();
|
||||
private Executor mMainExecutor;
|
||||
|
||||
private ListenableFuture<String> mPingFuture;
|
||||
private ListenableFuture<String> mUdpFuture;
|
||||
private ArrayAdapter<String> mPingServerIpAdapter;
|
||||
private ArrayAdapter<String> mUdpServerIpAdapter;
|
||||
private ArrayAdapter<String> mUdpServerPortAdapter;
|
||||
|
||||
private Network mThreadNetwork;
|
||||
private boolean mBindThreadNetwork = false;
|
||||
|
||||
private void subscribeToThreadNetwork() {
|
||||
ConnectivityManager cm = getActivity().getSystemService(ConnectivityManager.class);
|
||||
cm.registerNetworkCallback(
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.build(),
|
||||
new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
mThreadNetwork = network;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
mThreadNetwork = network;
|
||||
}
|
||||
},
|
||||
new Handler(Looper.myLooper()));
|
||||
}
|
||||
|
||||
private static String getPingCommand(String serverIp) {
|
||||
try {
|
||||
InetAddress serverAddress = InetAddresses.forString(serverIp);
|
||||
return (serverAddress instanceof Inet6Address)
|
||||
? "/system/bin/ping6"
|
||||
: "/system/bin/ping";
|
||||
} catch (IllegalArgumentException e) {
|
||||
// The ping command can handle the illegal argument and output error message
|
||||
return "/system/bin/ping6";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.connectivity_tools_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
mMainExecutor = ContextCompat.getMainExecutor(getActivity());
|
||||
|
||||
subscribeToThreadNetwork();
|
||||
|
||||
AutoCompleteTextView pingServerIpText = view.findViewById(R.id.ping_server_ip_address_text);
|
||||
mPingServerIpAdapter =
|
||||
new ArrayAdapter<String>(
|
||||
getActivity(), R.layout.list_server_ip_address_view, mServerIpCandidates);
|
||||
pingServerIpText.setAdapter(mPingServerIpAdapter);
|
||||
TextView pingOutputText = view.findViewById(R.id.ping_output_text);
|
||||
Button pingButton = view.findViewById(R.id.ping_button);
|
||||
|
||||
pingButton.setOnClickListener(
|
||||
v -> {
|
||||
if (mPingFuture != null) {
|
||||
mPingFuture.cancel(/* mayInterruptIfRunning= */ true);
|
||||
mPingFuture = null;
|
||||
}
|
||||
|
||||
String serverIp = pingServerIpText.getText().toString().strip();
|
||||
updateServerIpCandidates(serverIp);
|
||||
pingOutputText.setText("Sending ping message to " + serverIp + "\n");
|
||||
|
||||
mPingFuture = sendPing(serverIp);
|
||||
Futures.addCallback(
|
||||
mPingFuture,
|
||||
new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(String result) {
|
||||
pingOutputText.append(result + "\n");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
if (t instanceof CancellationException) {
|
||||
// Ignore the cancellation error
|
||||
return;
|
||||
}
|
||||
pingOutputText.append("Failed: " + t.getMessage() + "\n");
|
||||
}
|
||||
},
|
||||
mMainExecutor);
|
||||
});
|
||||
|
||||
AutoCompleteTextView udpServerIpText = view.findViewById(R.id.udp_server_ip_address_text);
|
||||
mUdpServerIpAdapter =
|
||||
new ArrayAdapter<String>(
|
||||
getActivity(), R.layout.list_server_ip_address_view, mServerIpCandidates);
|
||||
udpServerIpText.setAdapter(mUdpServerIpAdapter);
|
||||
AutoCompleteTextView udpServerPortText = view.findViewById(R.id.udp_server_port_text);
|
||||
mUdpServerPortAdapter =
|
||||
new ArrayAdapter<String>(
|
||||
getActivity(), R.layout.list_server_port_view, mServerPortCandidates);
|
||||
udpServerPortText.setAdapter(mUdpServerPortAdapter);
|
||||
TextInputEditText udpMsgText = view.findViewById(R.id.udp_message_text);
|
||||
TextView udpOutputText = view.findViewById(R.id.udp_output_text);
|
||||
|
||||
SwitchMaterial switchBindThreadNetwork = view.findViewById(R.id.switch_bind_thread_network);
|
||||
switchBindThreadNetwork.setChecked(mBindThreadNetwork);
|
||||
switchBindThreadNetwork.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
Log.i(TAG, "Binding to the Thread network");
|
||||
|
||||
if (mThreadNetwork == null) {
|
||||
Log.e(TAG, "Thread network is not available");
|
||||
Toast.makeText(
|
||||
getActivity().getApplicationContext(),
|
||||
"Thread network is not available",
|
||||
Toast.LENGTH_LONG);
|
||||
switchBindThreadNetwork.setChecked(false);
|
||||
} else {
|
||||
mBindThreadNetwork = true;
|
||||
}
|
||||
} else {
|
||||
mBindThreadNetwork = false;
|
||||
}
|
||||
});
|
||||
|
||||
Button sendUdpButton = view.findViewById(R.id.send_udp_button);
|
||||
sendUdpButton.setOnClickListener(
|
||||
v -> {
|
||||
if (mUdpFuture != null) {
|
||||
mUdpFuture.cancel(/* mayInterruptIfRunning= */ true);
|
||||
mUdpFuture = null;
|
||||
}
|
||||
|
||||
String serverIp = udpServerIpText.getText().toString().strip();
|
||||
String serverPort = udpServerPortText.getText().toString().strip();
|
||||
String udpMsg = udpMsgText.getText().toString().strip();
|
||||
updateServerIpCandidates(serverIp);
|
||||
updateServerPortCandidates(serverPort);
|
||||
udpOutputText.setText(
|
||||
String.format(
|
||||
"Sending UDP message \"%s\" to [%s]:%s",
|
||||
udpMsg, serverIp, serverPort));
|
||||
|
||||
mUdpFuture = sendUdpMessage(serverIp, serverPort, udpMsg);
|
||||
Futures.addCallback(
|
||||
mUdpFuture,
|
||||
new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(String result) {
|
||||
udpOutputText.append("\n" + result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable t) {
|
||||
if (t instanceof CancellationException) {
|
||||
// Ignore the cancellation error
|
||||
return;
|
||||
}
|
||||
udpOutputText.append("\nFailed: " + t.getMessage());
|
||||
}
|
||||
},
|
||||
mMainExecutor);
|
||||
});
|
||||
}
|
||||
|
||||
private void updateServerIpCandidates(String newServerIp) {
|
||||
if (!mServerIpCandidates.contains(newServerIp)) {
|
||||
mServerIpCandidates.add(0, newServerIp);
|
||||
mPingServerIpAdapter.notifyDataSetChanged();
|
||||
mUdpServerIpAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateServerPortCandidates(String newServerPort) {
|
||||
if (!mServerPortCandidates.contains(newServerPort)) {
|
||||
mServerPortCandidates.add(0, newServerPort);
|
||||
mUdpServerPortAdapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private ListenableFuture<String> sendPing(String serverIp) {
|
||||
return FluentFuture.from(Futures.submit(() -> doSendPing(serverIp), mBackgroundExecutor))
|
||||
.withTimeout(PING_TIMEOUT.getSeconds(), TimeUnit.SECONDS, mBackgroundExecutor);
|
||||
}
|
||||
|
||||
private String doSendPing(String serverIp) throws IOException {
|
||||
String pingCommand = getPingCommand(serverIp);
|
||||
Process process =
|
||||
new ProcessBuilder()
|
||||
.command(pingCommand, "-c 1", serverIp)
|
||||
.redirectErrorStream(true)
|
||||
.start();
|
||||
|
||||
return CharStreams.toString(new InputStreamReader(process.getInputStream()));
|
||||
}
|
||||
|
||||
private ListenableFuture<String> sendUdpMessage(
|
||||
String serverIp, String serverPort, String msg) {
|
||||
return FluentFuture.from(
|
||||
Futures.submit(
|
||||
() -> doSendUdpMessage(serverIp, serverPort, msg),
|
||||
mBackgroundExecutor))
|
||||
.withTimeout(UDP_TIMEOUT.getSeconds(), TimeUnit.SECONDS, mBackgroundExecutor);
|
||||
}
|
||||
|
||||
private String doSendUdpMessage(String serverIp, String serverPort, String msg)
|
||||
throws IOException {
|
||||
SocketAddress serverAddr = new InetSocketAddress(serverIp, Integer.parseInt(serverPort));
|
||||
|
||||
try (DatagramSocket socket = new DatagramSocket()) {
|
||||
if (mBindThreadNetwork && mThreadNetwork != null) {
|
||||
mThreadNetwork.bindSocket(socket);
|
||||
Log.i(TAG, "Successfully bind the socket to the Thread network");
|
||||
}
|
||||
|
||||
socket.connect(serverAddr);
|
||||
Log.d(TAG, "connected " + serverAddr);
|
||||
|
||||
byte[] msgBytes = msg.getBytes();
|
||||
DatagramPacket packet = new DatagramPacket(msgBytes, msgBytes.length);
|
||||
|
||||
Log.d(TAG, String.format("Sending message to server %s: %s", serverAddr, msg));
|
||||
socket.send(packet);
|
||||
Log.d(TAG, "Send done");
|
||||
|
||||
Log.d(TAG, "Waiting for server reply");
|
||||
socket.receive(packet);
|
||||
return new String(packet.getData(), packet.getOffset(), packet.getLength(), UTF_8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.threadnetwork.demoapp;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.drawerlayout.widget.DrawerLayout;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import com.google.android.material.navigation.NavigationView;
|
||||
|
||||
public final class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.main_activity);
|
||||
|
||||
NavHostFragment navHostFragment =
|
||||
(NavHostFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
|
||||
|
||||
NavController navController = navHostFragment.getNavController();
|
||||
|
||||
DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
|
||||
Toolbar topAppBar = findViewById(R.id.top_app_bar);
|
||||
AppBarConfiguration appBarConfig =
|
||||
new AppBarConfiguration.Builder(navController.getGraph())
|
||||
.setOpenableLayout(drawerLayout)
|
||||
.build();
|
||||
|
||||
NavigationUI.setupWithNavController(topAppBar, navController, appBarConfig);
|
||||
|
||||
NavigationView navView = findViewById(R.id.nav_view);
|
||||
NavigationUI.setupWithNavController(navView, navController);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.threadnetwork.demoapp;
|
||||
|
||||
import static com.google.common.io.BaseEncoding.base16;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.LinkAddress;
|
||||
import android.net.LinkProperties;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkRequest;
|
||||
import android.net.RouteInfo;
|
||||
import android.net.thread.ActiveOperationalDataset;
|
||||
import android.net.thread.OperationalDatasetTimestamp;
|
||||
import android.net.thread.PendingOperationalDataset;
|
||||
import android.net.thread.ThreadNetworkController;
|
||||
import android.net.thread.ThreadNetworkException;
|
||||
import android.net.thread.ThreadNetworkManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.OutcomeReceiver;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
public final class ThreadNetworkSettingsFragment extends Fragment {
|
||||
private static final String TAG = "ThreadNetworkSettings";
|
||||
|
||||
// This is a mirror of NetworkCapabilities#NET_CAPABILITY_LOCAL_NETWORK which is @hide for now
|
||||
private static final int NET_CAPABILITY_LOCAL_NETWORK = 36;
|
||||
|
||||
private ThreadNetworkController mThreadController;
|
||||
private TextView mTextState;
|
||||
private TextView mTextNetworkInfo;
|
||||
private TextView mMigrateNetworkState;
|
||||
private Executor mMainExecutor;
|
||||
|
||||
private int mDeviceRole;
|
||||
private long mPartitionId;
|
||||
private ActiveOperationalDataset mActiveDataset;
|
||||
|
||||
private static final byte[] DEFAULT_ACTIVE_DATASET_TLVS =
|
||||
base16().lowerCase()
|
||||
.decode(
|
||||
"0e080000000000010000000300001235060004001fffe00208dae21bccb8c321c40708fdc376ead74396bb0510c52f56cd2d38a9eb7a716954f8efd939030f4f70656e5468726561642d646231390102db190410fcb737e6fd6bb1b0fed524a4496363110c0402a0f7f8");
|
||||
private static final ActiveOperationalDataset DEFAULT_ACTIVE_DATASET =
|
||||
ActiveOperationalDataset.fromThreadTlvs(DEFAULT_ACTIVE_DATASET_TLVS);
|
||||
|
||||
private static String deviceRoleToString(int mDeviceRole) {
|
||||
switch (mDeviceRole) {
|
||||
case ThreadNetworkController.DEVICE_ROLE_STOPPED:
|
||||
return "Stopped";
|
||||
case ThreadNetworkController.DEVICE_ROLE_DETACHED:
|
||||
return "Detached";
|
||||
case ThreadNetworkController.DEVICE_ROLE_CHILD:
|
||||
return "Child";
|
||||
case ThreadNetworkController.DEVICE_ROLE_ROUTER:
|
||||
return "Router";
|
||||
case ThreadNetworkController.DEVICE_ROLE_LEADER:
|
||||
return "Leader";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(
|
||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
return inflater.inflate(R.layout.thread_network_settings_fragment, container, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
ConnectivityManager cm = getActivity().getSystemService(ConnectivityManager.class);
|
||||
cm.registerNetworkCallback(
|
||||
new NetworkRequest.Builder()
|
||||
.addTransportType(NetworkCapabilities.TRANSPORT_THREAD)
|
||||
.addCapability(NET_CAPABILITY_LOCAL_NETWORK)
|
||||
.build(),
|
||||
new ConnectivityManager.NetworkCallback() {
|
||||
@Override
|
||||
public void onAvailable(Network network) {
|
||||
Log.i(TAG, "New Thread network is available");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLinkPropertiesChanged(
|
||||
Network network, LinkProperties linkProperties) {
|
||||
updateNetworkInfo(linkProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLost(Network network) {
|
||||
Log.i(TAG, "Thread network " + network + " is lost");
|
||||
updateNetworkInfo(null /* linkProperties */);
|
||||
}
|
||||
},
|
||||
new Handler(Looper.myLooper()));
|
||||
|
||||
mMainExecutor = ContextCompat.getMainExecutor(getActivity());
|
||||
ThreadNetworkManager threadManager =
|
||||
getActivity().getSystemService(ThreadNetworkManager.class);
|
||||
if (threadManager != null) {
|
||||
mThreadController = threadManager.getAllThreadNetworkControllers().get(0);
|
||||
mThreadController.registerStateCallback(
|
||||
mMainExecutor,
|
||||
new ThreadNetworkController.StateCallback() {
|
||||
@Override
|
||||
public void onDeviceRoleChanged(int mDeviceRole) {
|
||||
ThreadNetworkSettingsFragment.this.mDeviceRole = mDeviceRole;
|
||||
updateState();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPartitionIdChanged(long mPartitionId) {
|
||||
ThreadNetworkSettingsFragment.this.mPartitionId = mPartitionId;
|
||||
updateState();
|
||||
}
|
||||
});
|
||||
mThreadController.registerOperationalDatasetCallback(
|
||||
mMainExecutor,
|
||||
newActiveDataset -> {
|
||||
this.mActiveDataset = newActiveDataset;
|
||||
updateState();
|
||||
});
|
||||
}
|
||||
|
||||
mTextState = (TextView) view.findViewById(R.id.text_state);
|
||||
mTextNetworkInfo = (TextView) view.findViewById(R.id.text_network_info);
|
||||
|
||||
if (mThreadController == null) {
|
||||
mTextState.setText("Thread not supported!");
|
||||
return;
|
||||
}
|
||||
|
||||
((Button) view.findViewById(R.id.button_join_network)).setOnClickListener(v -> doJoin());
|
||||
((Button) view.findViewById(R.id.button_leave_network)).setOnClickListener(v -> doLeave());
|
||||
|
||||
mMigrateNetworkState = view.findViewById(R.id.text_migrate_network_state);
|
||||
((Button) view.findViewById(R.id.button_migrate_network))
|
||||
.setOnClickListener(v -> doMigration());
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
private void doJoin() {
|
||||
mThreadController.join(
|
||||
DEFAULT_ACTIVE_DATASET,
|
||||
mMainExecutor,
|
||||
new OutcomeReceiver<Void, ThreadNetworkException>() {
|
||||
@Override
|
||||
public void onError(ThreadNetworkException error) {
|
||||
Log.e(TAG, "Failed to join network " + DEFAULT_ACTIVE_DATASET, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(Void v) {
|
||||
Log.i(TAG, "Successfully Joined");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doLeave() {
|
||||
mThreadController.leave(
|
||||
mMainExecutor,
|
||||
new OutcomeReceiver<>() {
|
||||
@Override
|
||||
public void onError(ThreadNetworkException error) {
|
||||
Log.e(TAG, "Failed to leave network " + DEFAULT_ACTIVE_DATASET, error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResult(Void v) {
|
||||
Log.i(TAG, "Successfully Left");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doMigration() {
|
||||
var newActiveDataset =
|
||||
new ActiveOperationalDataset.Builder(DEFAULT_ACTIVE_DATASET)
|
||||
.setNetworkName("NewThreadNet")
|
||||
.setActiveTimestamp(OperationalDatasetTimestamp.fromInstant(Instant.now()))
|
||||
.build();
|
||||
var pendingDataset =
|
||||
new PendingOperationalDataset(
|
||||
newActiveDataset,
|
||||
OperationalDatasetTimestamp.fromInstant(Instant.now()),
|
||||
Duration.ofSeconds(30));
|
||||
mThreadController.scheduleMigration(
|
||||
pendingDataset,
|
||||
mMainExecutor,
|
||||
new OutcomeReceiver<Void, ThreadNetworkException>() {
|
||||
@Override
|
||||
public void onResult(Void v) {
|
||||
mMigrateNetworkState.setText(
|
||||
"Scheduled migration to network \"NewThreadNet\" in 30s");
|
||||
// TODO: update Pending Dataset state
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ThreadNetworkException e) {
|
||||
mMigrateNetworkState.setText(
|
||||
"Failed to schedule migration: " + e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateState() {
|
||||
Log.i(
|
||||
TAG,
|
||||
String.format(
|
||||
"Updating Thread states (mDeviceRole: %s)",
|
||||
deviceRoleToString(mDeviceRole)));
|
||||
|
||||
String state =
|
||||
String.format(
|
||||
"Role %s\n"
|
||||
+ "Partition ID %d\n"
|
||||
+ "Network Name %s\n"
|
||||
+ "Extended PAN ID %s",
|
||||
deviceRoleToString(mDeviceRole),
|
||||
mPartitionId,
|
||||
mActiveDataset != null ? mActiveDataset.getNetworkName() : null,
|
||||
mActiveDataset != null
|
||||
? base16().encode(mActiveDataset.getExtendedPanId())
|
||||
: null);
|
||||
mTextState.setText(state);
|
||||
}
|
||||
|
||||
private void updateNetworkInfo(LinkProperties linProperties) {
|
||||
if (linProperties == null) {
|
||||
mTextNetworkInfo.setText("");
|
||||
return;
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder("Interface name:\n");
|
||||
sb.append(linProperties.getInterfaceName() + "\n");
|
||||
sb.append("Addresses:\n");
|
||||
for (LinkAddress la : linProperties.getLinkAddresses()) {
|
||||
sb.append(la + "\n");
|
||||
}
|
||||
sb.append("Routes:\n");
|
||||
for (RouteInfo route : linProperties.getRoutes()) {
|
||||
sb.append(route + "\n");
|
||||
}
|
||||
mTextNetworkInfo.setText(sb.toString());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.threadnetwork.demoapp.concurrent;
|
||||
|
||||
import androidx.annotation.GuardedBy;
|
||||
|
||||
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/** Provides executors for executing tasks in background. */
|
||||
public final class BackgroundExecutorProvider {
|
||||
private static final int CONCURRENCY = 4;
|
||||
|
||||
@GuardedBy("BackgroundExecutorProvider.class")
|
||||
private static ListeningScheduledExecutorService backgroundExecutor;
|
||||
|
||||
private BackgroundExecutorProvider() {}
|
||||
|
||||
public static synchronized ListeningScheduledExecutorService getBackgroundExecutor() {
|
||||
if (backgroundExecutor == null) {
|
||||
backgroundExecutor =
|
||||
MoreExecutors.listeningDecorator(
|
||||
Executors.newScheduledThreadPool(/* maxConcurrency= */ CONCURRENCY));
|
||||
}
|
||||
return backgroundExecutor;
|
||||
}
|
||||
}
|
||||
40
thread/demoapp/res/drawable/ic_launcher_foreground.xml
Normal file
40
thread/demoapp/res/drawable/ic_launcher_foreground.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group android:scaleX="0.0612"
|
||||
android:scaleY="0.0612"
|
||||
android:translateX="23.4"
|
||||
android:translateY="23.683332">
|
||||
<path
|
||||
android:pathData="M0,0h1000v1000h-1000z"
|
||||
android:fillColor="#00FFCEC7"/>
|
||||
<path
|
||||
android:pathData="m630.6,954.5l-113.5,0l0,-567.2l-170.5,0c-50.6,0 -92,41.2 -92,91.9c0,50.6 41.4,91.8 92,91.8l0,113.5c-113.3,0 -205.5,-92.1 -205.5,-205.4c0,-113.3 92.2,-205.5 205.5,-205.5l170.5,0l0,-57.5c0,-94.2 76.7,-171 171.1,-171c94.2,0 170.8,76.7 170.8,171c0,94.2 -76.6,171 -170.8,171l-57.6,0l0,567.2zM630.6,273.9l57.6,0c31.7,0 57.3,-25.8 57.3,-57.5c0,-31.7 -25.7,-57.5 -57.3,-57.5c-31.8,0 -57.6,25.8 -57.6,57.5l0,57.5z"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="0"
|
||||
android:fillColor="#000000"
|
||||
android:fillType="nonZero"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"/>
|
||||
</group>
|
||||
</vector>
|
||||
26
thread/demoapp/res/drawable/ic_menu_24dp.xml
Normal file
26
thread/demoapp/res/drawable/ic_menu_24dp.xml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24.0dp"
|
||||
android:height="24.0dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0"
|
||||
android:tint="?android:attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M3.0,18.0l18.0,0.0l0.0,-2.0L3.0,16.0l0.0,2.0zm0.0,-5.0l18.0,0.0l0.0,-2.0L3.0,11.0l0.0,2.0zm0.0,-7.0l0.0,2.0l18.0,0.0L21.0,6.0L3.0,6.0z"/>
|
||||
</vector>
|
||||
40
thread/demoapp/res/drawable/ic_thread_wordmark.xml
Normal file
40
thread/demoapp/res/drawable/ic_thread_wordmark.xml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="167dp"
|
||||
android:height="31dp"
|
||||
android:viewportWidth="167"
|
||||
android:viewportHeight="31">
|
||||
<path
|
||||
android:pathData="m32.413,7.977 l3.806,0 0,9.561 11.48,0 0,-9.561 3.837,0 0,22.957 -3.837,0 0,-9.558 -11.48,0 0,9.558 -3.806,0 0,-22.957z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m76.761,30.934 l-4.432,-7.641 -6.483,0 0,7.641 -3.807,0 0,-22.957 11.48,0c2.095,0 3.894,0.75 5.392,2.246 1.501,1.504 2.249,3.298 2.249,5.392 0,1.591 -0.453,3.034 -1.356,4.335 -0.885,1.279 -2.006,2.193 -3.376,2.747l4.732,8.236 -4.4,0zM73.519,11.812l-7.673,0 0,7.645 7.673,0c1.034,0 1.928,-0.379 2.678,-1.124 0.75,-0.752 1.126,-1.657 1.126,-2.717 0,-1.034 -0.376,-1.926 -1.126,-2.678C75.448,12.188 74.554,11.812 73.519,11.812Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m106.945,7.977 l0,3.835 -11.478,0 0,5.757 11.478,0 0,3.807 -11.478,0 0,5.722 11.478,0 0,3.836 -15.277,0 0,-22.957 15.277,0z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m132.325,27.08 l-10.586,0 -1.958,3.854 -4.283,0 11.517,-23.519 11.522,23.519 -4.283,0 -1.928,-3.854zM123.627,23.267 L130.404,23.267 127.014,16.013 123.627,23.267z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m146.606,7.977 l7.638,0c1.569,0 3.044,0.304 4.436,0.907 1.387,0.609 2.608,1.437 3.656,2.485 1.047,1.047 1.869,2.266 2.479,3.653 0.609,1.391 0.909,2.866 0.909,4.435 0,1.563 -0.299,3.041 -0.909,4.432 -0.61,1.391 -1.425,2.608 -2.464,3.654 -1.037,1.05 -2.256,1.874 -3.656,2.48 -1.401,0.607 -2.882,0.91 -4.451,0.91l-7.638,0 0,-22.956zM154.244,27.098c1.06,0 2.054,-0.199 2.978,-0.599 0.925,-0.394 1.737,-0.945 2.432,-1.654 0.696,-0.702 1.241,-1.521 1.638,-2.446 0.397,-0.925 0.597,-1.907 0.597,-2.942 0,-1.037 -0.201,-2.02 -0.597,-2.948 -0.397,-0.925 -0.946,-1.737 -1.651,-2.447 -0.709,-0.703 -1.524,-1.256 -2.45,-1.653 -0.925,-0.397 -1.907,-0.597 -2.948,-0.597l-3.834,0 0,15.286 3.834,0z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="m16.491,30.934 l-3.828,0 0,-19.128 -5.749,0c-1.705,0 -3.102,1.391 -3.102,3.1 0,1.706 1.397,3.097 3.102,3.097l0,3.83c-3.821,0 -6.931,-3.106 -6.931,-6.926 0,-3.822 3.111,-6.929 6.931,-6.929l5.749,0 0,-1.938c0,-3.179 2.587,-5.766 5.77,-5.766 3.175,0 5.76,2.588 5.76,5.766 0,3.179 -2.584,5.766 -5.76,5.766l-1.942,0 0,19.128zM16.491,7.977 L18.433,7.977c1.069,0 1.934,-0.869 1.934,-1.938 0,-1.069 -0.865,-1.938 -1.934,-1.938 -1.072,0 -1.942,0.869 -1.942,1.938l0,1.938z"
|
||||
android:fillColor="#ffffff"/>
|
||||
</vector>
|
||||
137
thread/demoapp/res/layout/connectivity_tools_fragment.xml
Normal file
137
thread/demoapp/res/layout/connectivity_tools_fragment.xml
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".ConnectivityToolsFragment" >
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/ping_server_ip_address_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="Server IP Address">
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/ping_server_ip_address_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="fdde:ad00:beef::ff:fe00:7400"
|
||||
android:textSize="14sp"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/ping_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Ping"
|
||||
android:textSize="20dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ping_output_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:scrollbars="vertical"
|
||||
android:textIsSelectable="true"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:orientation="horizontal" >
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/udp_server_ip_address_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="Server IP Address">
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/udp_server_ip_address_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="fdde:ad00:beef::ff:fe00:7400"
|
||||
android:textSize="14sp"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/udp_server_port_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:hint="Server Port">
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/udp_server_port_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:text="12345"
|
||||
android:textSize="14sp"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/udp_message_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="UDP Message">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/udp_message_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello Thread!"
|
||||
android:textSize="14sp"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/switch_bind_thread_network"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:checked="true"
|
||||
android:text="Bind to Thread network" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/send_udp_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="Send UDP Message"
|
||||
android:textSize="20dp"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/udp_output_text"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:scrollbars="vertical"
|
||||
android:textIsSelectable="true"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
25
thread/demoapp/res/layout/list_server_ip_address_view.xml
Normal file
25
thread/demoapp/res/layout/list_server_ip_address_view.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
/>
|
||||
25
thread/demoapp/res/layout/list_server_port_view.xml
Normal file
25
thread/demoapp/res/layout/list_server_port_view.xml
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="8dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
/>
|
||||
63
thread/demoapp/res/layout/main_activity.xml
Normal file
63
thread/demoapp/res/layout/main_activity.xml
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/top_app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/ic_menu_24dp" />
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true"
|
||||
app:navGraph="@navigation/nav_graph" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/nav_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
android:layout_gravity="start"
|
||||
android:fitsSystemWindows="true"
|
||||
app:headerLayout="@layout/nav_header"
|
||||
app:menu="@menu/nav_menu" />
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
30
thread/demoapp/res/layout/nav_header.xml
Normal file
30
thread/demoapp/res/layout/nav_header.xml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="bottom"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/nav_header_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/nav_header_vertical_spacing"
|
||||
android:src="@drawable/ic_thread_wordmark" />
|
||||
</LinearLayout>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="8dp"
|
||||
android:orientation="vertical"
|
||||
tools:context=".ThreadNetworkSettingsFragment" >
|
||||
|
||||
<Button android:id="@+id/button_join_network"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Join Network" />
|
||||
<Button android:id="@+id/button_leave_network"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Leave Network" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16dp"
|
||||
android:textStyle="bold"
|
||||
android:text="State" />
|
||||
<TextView
|
||||
android:id="@+id/text_state"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12dp"
|
||||
android:typeface="monospace" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:textSize="16dp"
|
||||
android:textStyle="bold"
|
||||
android:text="Network Info" />
|
||||
<TextView
|
||||
android:id="@+id/text_network_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12dp" />
|
||||
|
||||
<Button android:id="@+id/button_migrate_network"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Migrate Network" />
|
||||
<TextView
|
||||
android:id="@+id/text_migrate_network_state"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12dp" />
|
||||
</LinearLayout>
|
||||
24
thread/demoapp/res/menu/nav_menu.xml
Normal file
24
thread/demoapp/res/menu/nav_menu.xml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (C) 2023 The Android Open Source Project
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/thread_network_settings"
|
||||
android:title="Thread Network Settings" />
|
||||
<item
|
||||
android:id="@+id/connectivity_tools"
|
||||
android:title="Connectivity Tools" />
|
||||
</menu>
|
||||
6
thread/demoapp/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
6
thread/demoapp/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<background android:drawable="@color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<background android:drawable="@color/white"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
thread/demoapp/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
thread/demoapp/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
thread/demoapp/res/mipmap-hdpi/ic_launcher_round.png
Normal file
BIN
thread/demoapp/res/mipmap-hdpi/ic_launcher_round.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue