Merge "Several changes to VDM Demo apps" into main

This commit is contained in:
Treehugger Robot
2023-12-08 22:32:32 +00:00
committed by Android (Google) Code Review
4 changed files with 158 additions and 103 deletions

View File

@@ -18,6 +18,7 @@
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity
android:name=".MainActivity"
android:screenOrientation="portrait"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

View File

@@ -39,6 +39,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import dagger.hilt.android.qualifiers.ApplicationContext;
@@ -48,7 +49,6 @@ import java.net.Inet6Address;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
@@ -68,6 +68,9 @@ public class ConnectionManager {
@ApplicationContext private final Context mContext;
private final ConnectivityManager mConnectivityManager;
private final Handler mBackgroundHandler;
private final Object mSessionLock = new Object();
@GuardedBy("mSessionLock")
private DiscoverySession mDiscoverySession;
/** Simple data structure to allow clients to query the current status. */
@@ -76,6 +79,7 @@ public class ConnectionManager {
public boolean connected = false;
}
@GuardedBy("mSessionLock")
private final ConnectionStatus mConnectionStatus = new ConnectionStatus();
/** Simple callback to notify connection and disconnection events. */
@@ -93,8 +97,8 @@ public class ConnectionManager {
default void onError(String message) {}
}
private final List<ConnectionCallback> mConnectionCallbacks =
Collections.synchronizedList(new ArrayList<>());
@GuardedBy("mConnectionCallbacks")
private final List<ConnectionCallback> mConnectionCallbacks = new ArrayList<>();
private final RemoteIo.StreamClosedCallback mStreamClosedCallback = this::disconnect;
@@ -115,22 +119,28 @@ public class ConnectionManager {
/** Registers a listener for connection events. */
public void addConnectionCallback(ConnectionCallback callback) {
mConnectionCallbacks.add(callback);
synchronized (mConnectionCallbacks) {
mConnectionCallbacks.add(callback);
}
}
/** Registers a listener for connection events. */
public void removeConnectionCallback(ConnectionCallback callback) {
mConnectionCallbacks.remove(callback);
synchronized (mConnectionCallbacks) {
mConnectionCallbacks.remove(callback);
}
}
/** Returns the current connection status. */
public ConnectionStatus getConnectionStatus() {
return mConnectionStatus;
synchronized (mSessionLock) {
return mConnectionStatus;
}
}
/** Publish a local service so remote devices can discover this device. */
public void startHostSession() {
if (mConnectionStatus.connected) {
if (isConnected()) {
return;
}
var unused = createSession().thenAccept(wifiAwareSession -> wifiAwareSession.publish(
@@ -141,7 +151,7 @@ public class ConnectionManager {
/** Looks for published services from remote devices and subscribes to them. */
public void startClientSession() {
if (mConnectionStatus.connected) {
if (isConnected()) {
return;
}
var unused = createSession().thenAccept(wifiAwareSession -> wifiAwareSession.subscribe(
@@ -150,6 +160,12 @@ public class ConnectionManager {
mBackgroundHandler));
}
private boolean isConnected() {
synchronized (mSessionLock) {
return mConnectionStatus.connected;
}
}
private CompletableFuture<WifiAwareSession> createSession() {
CompletableFuture<WifiAwareSession> wifiAwareSessionFuture = new CompletableFuture<>();
WifiAwareManager wifiAwareManager = mContext.getSystemService(WifiAwareManager.class);
@@ -184,53 +200,73 @@ public class ConnectionManager {
/** Explicitly terminate any existing connection. */
public void disconnect() {
if (mDiscoverySession != null) {
mDiscoverySession.close();
mDiscoverySession = null;
}
mConnectionStatus.remoteDeviceName = null;
mConnectionStatus.connected = false;
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onDisconnected();
synchronized (mSessionLock) {
if (mDiscoverySession != null) {
mDiscoverySession.close();
mDiscoverySession = null;
}
mConnectionStatus.remoteDeviceName = null;
mConnectionStatus.connected = false;
synchronized (mConnectionCallbacks) {
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onDisconnected();
}
}
}
}
private void onSocketAvailable(Socket socket) throws IOException {
mRemoteIo.initialize(socket.getInputStream(), mStreamClosedCallback);
mRemoteIo.initialize(socket.getOutputStream(), mStreamClosedCallback);
mConnectionStatus.connected = true;
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onConnected(mConnectionStatus.remoteDeviceName);
synchronized (mSessionLock) {
mConnectionStatus.connected = true;
synchronized (mConnectionCallbacks) {
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onConnected(mConnectionStatus.remoteDeviceName);
}
}
}
}
private void onError(String message) {
Log.e(TAG, "Error: " + message);
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onError(message);
synchronized (mConnectionCallbacks) {
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onError(message);
}
}
}
private class VdmDiscoverySessionCallback extends DiscoverySessionCallback {
@GuardedBy("mSessionLock")
private NetworkCallback mNetworkCallback;
@Override
public void onSessionTerminated() {
disconnect();
if (mNetworkCallback != null) {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
synchronized (mSessionLock) {
if (mNetworkCallback != null) {
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
}
}
}
void sendLocalEndpointId(PeerHandle peerHandle) {
mDiscoverySession.sendMessage(peerHandle, 0, getLocalEndpointId().getBytes());
synchronized (mSessionLock) {
mDiscoverySession.sendMessage(peerHandle, 0, getLocalEndpointId().getBytes());
}
}
void onConnecting(byte[] remoteDeviceName) {
mConnectionStatus.remoteDeviceName = new String(remoteDeviceName);
Log.e(TAG, "Connecting to " + mConnectionStatus.remoteDeviceName);
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onConnecting(mConnectionStatus.remoteDeviceName);
synchronized (mSessionLock) {
mConnectionStatus.remoteDeviceName = new String(remoteDeviceName);
Log.e(TAG, "Connecting to " + mConnectionStatus.remoteDeviceName);
synchronized (mConnectionCallbacks) {
for (ConnectionCallback callback : mConnectionCallbacks) {
callback.onConnecting(mConnectionStatus.remoteDeviceName);
}
}
}
}
@@ -246,8 +282,10 @@ public class ConnectionManager {
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI_AWARE)
.setNetworkSpecifier(networkSpecifierBuilder.build())
.build();
mNetworkCallback = networkCallback;
mConnectivityManager.requestNetwork(networkRequest, networkCallback);
synchronized (mSessionLock) {
mNetworkCallback = networkCallback;
mConnectivityManager.requestNetwork(networkRequest, mNetworkCallback);
}
}
}
@@ -255,12 +293,14 @@ public class ConnectionManager {
@Override
public void onPublishStarted(@NonNull PublishDiscoverySession session) {
mDiscoverySession = session;
synchronized (mSessionLock) {
mDiscoverySession = session;
}
}
@Override
public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
if (mConnectionStatus.connected) {
if (isConnected()) {
return;
}
@@ -284,7 +324,9 @@ public class ConnectionManager {
@Override
public void onSubscribeStarted(@NonNull SubscribeDiscoverySession session) {
mDiscoverySession = session;
synchronized (mSessionLock) {
mDiscoverySession = session;
}
}
@Override
@@ -295,7 +337,7 @@ public class ConnectionManager {
@Override
public void onMessageReceived(PeerHandle peerHandle, byte[] message) {
if (mConnectionStatus.connected) {
if (isConnected()) {
return;
}
onConnecting(message);
@@ -316,7 +358,7 @@ public class ConnectionManager {
@Override
public void onCapabilitiesChanged(@NonNull Network network,
@NonNull NetworkCapabilities networkCapabilities) {
if (mConnectionStatus.connected) {
if (isConnected()) {
return;
}

View File

@@ -21,12 +21,13 @@ import android.os.HandlerThread;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.GuardedBy;
import com.example.android.vdmdemo.common.RemoteEventProto.RemoteEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -44,12 +45,16 @@ public class RemoteIo {
void onStreamClosed();
}
private final Object mLock = new Object();
@GuardedBy("mLock")
private OutputStream mOutputStream = null;
private StreamClosedCallback mOutputStreamClosedCallback = null;
private final Handler mSendMessageHandler;
private final Map<Object, MessageConsumer> mMessageConsumers =
Collections.synchronizedMap(new ArrayMap<>());
@GuardedBy("mMessageConsumers")
private final Map<Object, MessageConsumer> mMessageConsumers = new ArrayMap<>();
@Inject
RemoteIo() {
@@ -60,50 +65,45 @@ public class RemoteIo {
@SuppressWarnings("ThreadPriorityCheck")
void initialize(InputStream inputStream, StreamClosedCallback inputStreamClosedCallback) {
Thread t = new Thread(() -> {
try {
while (true) {
RemoteEvent event = RemoteEvent.parseDelimitedFrom(inputStream);
if (event == null) {
break;
}
mMessageConsumers.values().forEach(consumer -> {
if (consumer != null) {
consumer.accept(event);
}
});
}
} catch (IOException e) {
Log.e(TAG, "Failed to obtain event: " + e);
}
inputStreamClosedCallback.onStreamClosed();
});
Thread t = new Thread(new ReceiverRunnable(inputStream, inputStreamClosedCallback));
t.setPriority(Thread.MAX_PRIORITY);
t.start();
}
synchronized void initialize(
void initialize(
OutputStream outputStream, StreamClosedCallback outputStreamClosedCallback) {
mOutputStream = outputStream;
mOutputStreamClosedCallback = outputStreamClosedCallback;
synchronized (mLock) {
mOutputStream = outputStream;
mOutputStreamClosedCallback = outputStreamClosedCallback;
}
}
/** Registers a consumer for processing events coming from the remote device. */
public void addMessageConsumer(Consumer<RemoteEvent> consumer) {
mMessageConsumers.put(consumer, new MessageConsumer(consumer));
synchronized (mMessageConsumers) {
mMessageConsumers.put(consumer, new MessageConsumer(consumer));
}
}
/** Unregisters a previously registered message consumer. */
public void removeMessageConsumer(Consumer<RemoteEvent> consumer) {
if (mMessageConsumers.remove(consumer) == null) {
Log.w(TAG, "Failed to remove message consumer.");
synchronized (mMessageConsumers) {
if (mMessageConsumers.remove(consumer) == null) {
Log.w(TAG, "Failed to remove message consumer.");
}
}
}
/** Sends an event to the remote device. */
public synchronized void sendMessage(RemoteEvent event) {
if (mOutputStream != null) {
mSendMessageHandler.post(() -> {
public void sendMessage(RemoteEvent event) {
synchronized (mLock) {
if (mOutputStream == null) {
Log.e(TAG, "Failed to send event, RemoteIO not initialized.");
return;
}
}
mSendMessageHandler.post(() -> {
synchronized (mLock) {
try {
event.writeDelimitedTo(mOutputStream);
mOutputStream.flush();
@@ -111,9 +111,36 @@ public class RemoteIo {
mOutputStream = null;
mOutputStreamClosedCallback.onStreamClosed();
}
});
} else {
Log.e(TAG, "Failed to send event, RemoteIO not initialized.");
}
});
}
private class ReceiverRunnable implements Runnable {
private final InputStream mInputStream;
private final StreamClosedCallback mInputStreamClosedCallback;
ReceiverRunnable(InputStream inputStream, StreamClosedCallback inputStreamClosedCallback) {
mInputStream = inputStream;
mInputStreamClosedCallback = inputStreamClosedCallback;
}
@Override
public void run() {
try {
while (true) {
RemoteEvent event = RemoteEvent.parseDelimitedFrom(mInputStream);
if (event == null) {
break;
}
synchronized (mMessageConsumers) {
mMessageConsumers.values().forEach(consumer -> consumer.accept(event));
}
}
} catch (IOException e) {
Log.e(TAG, "Failed to obtain event: " + e);
}
mInputStreamClosedCallback.onStreamClosed();
}
}

View File

@@ -20,7 +20,7 @@ function die() {
}
function run_cmd_or_die() {
"${@}" > /dev/null 2>&1 || die "Command failed: ${*}"
"${@}" > /dev/null || die "Command failed: ${*}"
}
function select_device() {
@@ -30,6 +30,13 @@ function select_device() {
done
}
function install_app() {
if ! adb -s "${1}" install -r -d -g "${2}" > /dev/null 2>&1; then
adb -s "${1}" uninstall "com.example.android.vdmdemo.${3}" > /dev/null 2>&1
run_cmd_or_die adb -s "${1}" install -r -d -g "${2}"
fi
}
[[ -f build/make/envsetup.sh ]] || die "Run this script from the root of the tree."
DEVICE_COUNT=$(adb devices -l | tail -n +2 | head -n -1 | wc -l)
@@ -41,36 +48,16 @@ HOST_SERIAL=""
CLIENT_SERIAL=""
echo
if ((DEVICE_COUNT > 1)); then
echo "Multiple devices found:"
for i in "${!DEVICE_SERIALS[@]}"; do
echo -e "${i}: ${DEVICE_SERIALS[${i}]}\t${DEVICE_NAMES[${i}]}"
done
echo "${DEVICE_COUNT}: Do not install this app"
echo
select_device "VDM Host"
HOST_INDEX=$?
select_device "VDM Client"
CLIENT_INDEX=$?
else
DEVICE_SERIAL=${DEVICE_SERIALS[0]}
DEVICE_NAME="${DEVICE_SERIAL} ${DEVICE_NAMES[0]}"
cat << EOF
0: VDM Host app
1: VDM Client app
2: All
3: None
EOF
while :; do
read -r -p "Select apps to install to ${DEVICE_NAME} (0-3): " INDEX
( [[ "${INDEX}" =~ ^[0-9]+$ ]] && ((INDEX >= 0 && INDEX <= 3)) ) || continue;
((INDEX == 3)) && exit 0
((INDEX != 0 && INDEX != 2)) && HOST_INDEX=DEVICE_COUNT
((INDEX != 1 && INDEX != 2)) && CLIENT_INDEX=DEVICE_COUNT
break
done
fi
echo "Available devices:"
for i in "${!DEVICE_SERIALS[@]}"; do
echo -e "${i}: ${DEVICE_SERIALS[${i}]}\t${DEVICE_NAMES[${i}]}"
done
echo "${DEVICE_COUNT}: Do not install this app"
echo
select_device "VDM Host"
HOST_INDEX=$?
select_device "VDM Client"
CLIENT_INDEX=$?
echo
if ((HOST_INDEX == DEVICE_COUNT)); then
@@ -103,15 +90,13 @@ UNBUNDLED_BUILD_SDKS_FROM_SOURCE=true m -j "${APKS_TO_BUILD}" || die "Build fail
if [[ -n "${CLIENT_SERIAL}" ]]; then
echo
echo "Installing VdmClient.apk to ${CLIENT_NAME}..."
adb -s "${CLIENT_SERIAL}" uninstall com.example.android.vdmdemo.client > /dev/null 2>&1
run_cmd_or_die adb -s "${CLIENT_SERIAL}" install -r -d -g "${OUT}/system/app/VdmClient/VdmClient.apk"
install_app "${CLIENT_SERIAL}" "${OUT}/system/app/VdmClient/VdmClient.apk" client
fi
if [[ -n "${HOST_SERIAL}" ]]; then
echo
echo "Installing VdmDemos.apk to ${HOST_NAME}..."
adb -s "${HOST_SERIAL}" uninstall com.example.android.vdmdemo.demos > /dev/null 2>&1
run_cmd_or_die adb -s "${HOST_SERIAL}" install -r -d -g "${OUT}/system/app/VdmDemos/VdmDemos.apk"
install_app "${CLIENT_SERIAL}" "${OUT}/system/app/VdmDemos/VdmDemos.apk" demos
echo
readonly PERM_BASENAME=com.example.android.vdmdemo.host.xml