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"> android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:screenOrientation="portrait"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

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

View File

@@ -21,12 +21,13 @@ import android.os.HandlerThread;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Log; import android.util.Log;
import androidx.annotation.GuardedBy;
import com.example.android.vdmdemo.common.RemoteEventProto.RemoteEvent; import com.example.android.vdmdemo.common.RemoteEventProto.RemoteEvent;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@@ -44,12 +45,16 @@ public class RemoteIo {
void onStreamClosed(); void onStreamClosed();
} }
private final Object mLock = new Object();
@GuardedBy("mLock")
private OutputStream mOutputStream = null; private OutputStream mOutputStream = null;
private StreamClosedCallback mOutputStreamClosedCallback = null; private StreamClosedCallback mOutputStreamClosedCallback = null;
private final Handler mSendMessageHandler; private final Handler mSendMessageHandler;
private final Map<Object, MessageConsumer> mMessageConsumers = @GuardedBy("mMessageConsumers")
Collections.synchronizedMap(new ArrayMap<>()); private final Map<Object, MessageConsumer> mMessageConsumers = new ArrayMap<>();
@Inject @Inject
RemoteIo() { RemoteIo() {
@@ -60,50 +65,45 @@ public class RemoteIo {
@SuppressWarnings("ThreadPriorityCheck") @SuppressWarnings("ThreadPriorityCheck")
void initialize(InputStream inputStream, StreamClosedCallback inputStreamClosedCallback) { void initialize(InputStream inputStream, StreamClosedCallback inputStreamClosedCallback) {
Thread t = new Thread(() -> { Thread t = new Thread(new ReceiverRunnable(inputStream, inputStreamClosedCallback));
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();
});
t.setPriority(Thread.MAX_PRIORITY); t.setPriority(Thread.MAX_PRIORITY);
t.start(); t.start();
} }
synchronized void initialize( void initialize(
OutputStream outputStream, StreamClosedCallback outputStreamClosedCallback) { OutputStream outputStream, StreamClosedCallback outputStreamClosedCallback) {
mOutputStream = outputStream; synchronized (mLock) {
mOutputStreamClosedCallback = outputStreamClosedCallback; mOutputStream = outputStream;
mOutputStreamClosedCallback = outputStreamClosedCallback;
}
} }
/** Registers a consumer for processing events coming from the remote device. */ /** Registers a consumer for processing events coming from the remote device. */
public void addMessageConsumer(Consumer<RemoteEvent> consumer) { 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. */ /** Unregisters a previously registered message consumer. */
public void removeMessageConsumer(Consumer<RemoteEvent> consumer) { public void removeMessageConsumer(Consumer<RemoteEvent> consumer) {
if (mMessageConsumers.remove(consumer) == null) { synchronized (mMessageConsumers) {
Log.w(TAG, "Failed to remove message consumer."); if (mMessageConsumers.remove(consumer) == null) {
Log.w(TAG, "Failed to remove message consumer.");
}
} }
} }
/** Sends an event to the remote device. */ /** Sends an event to the remote device. */
public synchronized void sendMessage(RemoteEvent event) { public void sendMessage(RemoteEvent event) {
if (mOutputStream != null) { synchronized (mLock) {
mSendMessageHandler.post(() -> { if (mOutputStream == null) {
Log.e(TAG, "Failed to send event, RemoteIO not initialized.");
return;
}
}
mSendMessageHandler.post(() -> {
synchronized (mLock) {
try { try {
event.writeDelimitedTo(mOutputStream); event.writeDelimitedTo(mOutputStream);
mOutputStream.flush(); mOutputStream.flush();
@@ -111,9 +111,36 @@ public class RemoteIo {
mOutputStream = null; mOutputStream = null;
mOutputStreamClosedCallback.onStreamClosed(); 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() { function run_cmd_or_die() {
"${@}" > /dev/null 2>&1 || die "Command failed: ${*}" "${@}" > /dev/null || die "Command failed: ${*}"
} }
function select_device() { function select_device() {
@@ -30,6 +30,13 @@ function select_device() {
done 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." [[ -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) DEVICE_COUNT=$(adb devices -l | tail -n +2 | head -n -1 | wc -l)
@@ -41,36 +48,16 @@ HOST_SERIAL=""
CLIENT_SERIAL="" CLIENT_SERIAL=""
echo echo
if ((DEVICE_COUNT > 1)); then echo "Available devices:"
echo "Multiple devices found:" for i in "${!DEVICE_SERIALS[@]}"; do
for i in "${!DEVICE_SERIALS[@]}"; do echo -e "${i}: ${DEVICE_SERIALS[${i}]}\t${DEVICE_NAMES[${i}]}"
echo -e "${i}: ${DEVICE_SERIALS[${i}]}\t${DEVICE_NAMES[${i}]}" done
done echo "${DEVICE_COUNT}: Do not install this app"
echo "${DEVICE_COUNT}: Do not install this app" echo
echo select_device "VDM Host"
select_device "VDM Host" HOST_INDEX=$?
HOST_INDEX=$? select_device "VDM Client"
select_device "VDM Client" CLIENT_INDEX=$?
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 echo
if ((HOST_INDEX == DEVICE_COUNT)); then 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 if [[ -n "${CLIENT_SERIAL}" ]]; then
echo echo
echo "Installing VdmClient.apk to ${CLIENT_NAME}..." echo "Installing VdmClient.apk to ${CLIENT_NAME}..."
adb -s "${CLIENT_SERIAL}" uninstall com.example.android.vdmdemo.client > /dev/null 2>&1 install_app "${CLIENT_SERIAL}" "${OUT}/system/app/VdmClient/VdmClient.apk" client
run_cmd_or_die adb -s "${CLIENT_SERIAL}" install -r -d -g "${OUT}/system/app/VdmClient/VdmClient.apk"
fi fi
if [[ -n "${HOST_SERIAL}" ]]; then if [[ -n "${HOST_SERIAL}" ]]; then
echo echo
echo "Installing VdmDemos.apk to ${HOST_NAME}..." echo "Installing VdmDemos.apk to ${HOST_NAME}..."
adb -s "${HOST_SERIAL}" uninstall com.example.android.vdmdemo.demos > /dev/null 2>&1 install_app "${CLIENT_SERIAL}" "${OUT}/system/app/VdmDemos/VdmDemos.apk" demos
run_cmd_or_die adb -s "${HOST_SERIAL}" install -r -d -g "${OUT}/system/app/VdmDemos/VdmDemos.apk"
echo echo
readonly PERM_BASENAME=com.example.android.vdmdemo.host.xml readonly PERM_BASENAME=com.example.android.vdmdemo.host.xml