Add Device to Device Transfer UI.

This commit is contained in:
Cody Henthorne
2021-03-11 13:27:25 -05:00
committed by Greyson Parrelli
parent 6f8be3260c
commit 75aab4c031
75 changed files with 3494 additions and 200 deletions

View File

@@ -19,6 +19,7 @@ import org.greenrobot.eventbus.ThreadMode;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.logging.Log;
import java.io.Serializable;
import java.util.Objects;
/**
@@ -195,11 +196,39 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
private @NonNull Notification createNotification(@NonNull TransferStatus transferStatus, @NonNull TransferNotificationData notificationData) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, notificationData.channelId);
//TODO [cody] build notification to spec
String contentText = "";
switch (transferStatus.getTransferMode()) {
case READY:
contentText = getString(R.string.DeviceToDeviceTransferService_status_ready);
break;
case STARTING_UP:
contentText = getString(R.string.DeviceToDeviceTransferService_status_starting_up);
break;
case DISCOVERY:
contentText = getString(R.string.DeviceToDeviceTransferService_status_discovery);
break;
case NETWORK_CONNECTED:
contentText = getString(R.string.DeviceToDeviceTransferService_status_network_connected);
break;
case VERIFICATION_REQUIRED:
contentText = getString(R.string.DeviceToDeviceTransferService_status_verification_required);
break;
case SERVICE_CONNECTED:
contentText = getString(R.string.DeviceToDeviceTransferService_status_service_connected);
break;
case UNAVAILABLE:
case FAILED:
case SERVICE_DISCONNECTED:
Log.d(TAG, "Intentionally no notification text for: " + transferStatus.getTransferMode());
break;
default:
throw new AssertionError("No notification text for: " + transferStatus.getTransferMode());
}
builder.setSmallIcon(notificationData.icon)
.setOngoing(true)
.setContentTitle("Device Transfer")
.setContentText("Status: " + transferStatus.getTransferMode().name())
.setContentTitle(getString(R.string.DeviceToDeviceTransferService_content_title))
.setContentText(contentText)
.setContentIntent(pendingIntent);
return builder.build();

View File

@@ -1,6 +1,7 @@
package org.signal.devicetransfer;
import android.content.Context;
import android.net.NetworkInfo;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pInfo;
import android.os.Handler;
@@ -8,6 +9,7 @@ import android.os.HandlerThread;
import android.os.Message;
import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -16,6 +18,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
import org.signal.core.util.logging.Log;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Encapsulates the logic to find and establish a WiFi Direct connection with another
@@ -39,15 +42,16 @@ final class DeviceTransferClient implements Handler.Callback {
private static final String TAG = Log.tag(DeviceTransferClient.class);
private static final int START_CLIENT = 0;
private static final int STOP_CLIENT = 1;
private static final int START_NETWORK_CLIENT = 2;
private static final int NETWORK_DISCONNECTED = 3;
private static final int CONNECT_TO_SERVICE = 4;
private static final int RESTART_CLIENT = 5;
private static final int START_IP_EXCHANGE = 6;
private static final int IP_EXCHANGE_SUCCESS = 7;
private static final int SET_VERIFIED = 8;
private static final int START_CLIENT = 0;
private static final int STOP_CLIENT = 1;
private static final int START_NETWORK_CLIENT = 2;
private static final int NETWORK_DISCONNECTED = 3;
private static final int CONNECT_TO_SERVICE = 4;
private static final int RESTART_CLIENT = 5;
private static final int START_IP_EXCHANGE = 6;
private static final int IP_EXCHANGE_SUCCESS = 7;
private static final int SET_VERIFIED = 8;
private static final int NETWORK_CONNECTION_CHANGED = 9;
private final Context context;
private int remotePort;
@@ -60,6 +64,9 @@ final class DeviceTransferClient implements Handler.Callback {
private final Runnable autoRestart;
private IpExchange.IpExchangeThread ipExchangeThread;
private final AtomicBoolean started = new AtomicBoolean(false);
private final AtomicBoolean stopped = new AtomicBoolean(false);
private static void update(@NonNull TransferStatus transferStatus) {
Log.d(TAG, "transferStatus: " + transferStatus.getTransferMode().name());
EventBus.getDefault().postSticky(transferStatus);
@@ -81,31 +88,31 @@ final class DeviceTransferClient implements Handler.Callback {
};
}
@AnyThread
public synchronized void start() {
if (commandAndControlThread != null) {
@MainThread
public void start() {
if (started.compareAndSet(false, true)) {
update(TransferStatus.ready());
handler.sendEmptyMessage(START_CLIENT);
}
}
@AnyThread
public synchronized void stop() {
if (commandAndControlThread != null) {
@MainThread
public void stop() {
if (stopped.compareAndSet(false, true)) {
handler.sendEmptyMessage(STOP_CLIENT);
}
}
@AnyThread
public synchronized void setVerified(boolean isVerified) {
if (commandAndControlThread != null) {
@MainThread
public void setVerified(boolean isVerified) {
if (!stopped.get()) {
handler.sendMessage(handler.obtainMessage(SET_VERIFIED, isVerified));
}
}
private synchronized void shutdown() {
private void shutdown() {
stopIpExchange();
stopClient();
stopNetworkClient();
stopWifiDirect();
if (commandAndControlThread != null) {
@@ -115,7 +122,7 @@ final class DeviceTransferClient implements Handler.Callback {
commandAndControlThread = null;
}
update(TransferStatus.shutdown());
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
}
private void internalShutdown() {
@@ -136,17 +143,17 @@ final class DeviceTransferClient implements Handler.Callback {
shutdown();
break;
case START_NETWORK_CLIENT:
startClient((String) message.obj);
startNetworkClient((String) message.obj);
break;
case NETWORK_DISCONNECTED:
stopClient();
stopNetworkClient();
break;
case CONNECT_TO_SERVICE:
stopServiceDiscovery();
connectToService((String) message.obj, message.arg1);
break;
case RESTART_CLIENT:
stopClient();
stopNetworkClient();
stopWifiDirect();
startWifiDirect();
break;
@@ -161,6 +168,9 @@ final class DeviceTransferClient implements Handler.Callback {
clientThread.setVerified((Boolean) message.obj);
}
break;
case NETWORK_CONNECTION_CHANGED:
requestNetworkInfo((Boolean) message.obj);
break;
case NetworkClientThread.NETWORK_CLIENT_SSL_ESTABLISHED:
update(TransferStatus.verificationRequired((Integer) message.obj));
break;
@@ -231,7 +241,7 @@ final class DeviceTransferClient implements Handler.Callback {
}
}
private void startClient(@NonNull String serverHostAddress) {
private void startNetworkClient(@NonNull String serverHostAddress) {
if (clientThread != null) {
Log.i(TAG, "Client already running");
return;
@@ -246,14 +256,14 @@ final class DeviceTransferClient implements Handler.Callback {
clientThread.start();
}
private void stopClient() {
private void stopNetworkClient() {
if (clientThread != null) {
Log.i(TAG, "Shutting down ClientThread");
clientThread.shutdown();
try {
clientThread.join(TimeUnit.SECONDS.toMillis(1));
} catch (InterruptedException e) {
Log.i(TAG, "Server thread took too long to shutdown", e);
Log.i(TAG, "Client thread took too long to shutdown", e);
}
clientThread = null;
}
@@ -265,6 +275,11 @@ final class DeviceTransferClient implements Handler.Callback {
return;
}
if (clientThread != null) {
Log.i(TAG, "Client is running we shouldn't be connecting again");
return;
}
handler.removeCallbacks(autoRestart);
int tries = 5;
@@ -288,6 +303,26 @@ final class DeviceTransferClient implements Handler.Callback {
handler.sendMessage(handler.obtainMessage(RESTART_CLIENT));
}
private void requestNetworkInfo(boolean isNetworkConnected) {
if (wifiDirect == null) {
return;
}
if (isNetworkConnected) {
Log.i(TAG, "Network connected, requesting network info");
try {
wifiDirect.requestNetworkInfo();
} catch (WifiDirectUnavailableException e) {
Log.e(TAG, e);
internalShutdown();
update(TransferStatus.failed());
}
} else {
Log.i(TAG, "Network disconnected");
handler.sendEmptyMessage(NETWORK_DISCONNECTED);
}
}
private void startIpExchange(@NonNull String groupOwnerHostAddress) {
ipExchangeThread = IpExchange.getIp(groupOwnerHostAddress, remotePort, handler, IP_EXCHANGE_SUCCESS);
}
@@ -309,7 +344,7 @@ final class DeviceTransferClient implements Handler.Callback {
handler.sendMessage(handler.obtainMessage(START_NETWORK_CLIENT, host));
}
public class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
final class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
@Override
public void onServiceDiscovered(@NonNull WifiP2pDevice serviceDevice, @NonNull String extraInfo) {
@@ -325,17 +360,15 @@ final class DeviceTransferClient implements Handler.Callback {
}
}
@Override
public void onNetworkDisconnected() {
handler.sendEmptyMessage(NETWORK_DISCONNECTED);
}
@Override
public void onNetworkFailure() {
handler.sendEmptyMessage(NETWORK_DISCONNECTED);
}
@Override
public void onLocalDeviceChanged(@NonNull WifiP2pDevice localDevice) { }
public void onConnectionChanged(@NonNull NetworkInfo networkInfo) {
handler.sendMessage(handler.obtainMessage(NETWORK_CONNECTION_CHANGED, networkInfo.isConnected()));
}
}
}

View File

@@ -1,6 +1,7 @@
package org.signal.devicetransfer;
import android.content.Context;
import android.net.NetworkInfo;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pInfo;
import android.os.Handler;
@@ -8,6 +9,7 @@ import android.os.HandlerThread;
import android.os.Message;
import androidx.annotation.AnyThread;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -17,6 +19,7 @@ import org.signal.core.util.logging.Log;
import org.signal.devicetransfer.SelfSignedIdentity.SelfSignedKeys;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Encapsulates the logic to advertise the availability of a transfer service over a WiFi Direct
@@ -34,12 +37,13 @@ final class DeviceTransferServer implements Handler.Callback {
private static final String TAG = Log.tag(DeviceTransferServer.class);
private static final int START_SERVER = 0;
private static final int STOP_SERVER = 1;
private static final int START_IP_EXCHANGE = 2;
private static final int IP_EXCHANGE_SUCCESS = 3;
private static final int NETWORK_FAILURE = 4;
private static final int SET_VERIFIED = 5;
private static final int START_SERVER = 0;
private static final int STOP_SERVER = 1;
private static final int START_IP_EXCHANGE = 2;
private static final int IP_EXCHANGE_SUCCESS = 3;
private static final int NETWORK_FAILURE = 4;
private static final int SET_VERIFIED = 5;
private static final int NETWORK_CONNECTION_CHANGED = 6;
private NetworkServerThread serverThread;
private HandlerThread commandAndControlThread;
@@ -50,6 +54,9 @@ final class DeviceTransferServer implements Handler.Callback {
private final ShutdownCallback shutdownCallback;
private IpExchange.IpExchangeThread ipExchangeThread;
private final AtomicBoolean started = new AtomicBoolean(false);
private final AtomicBoolean stopped = new AtomicBoolean(false);
private static void update(@NonNull TransferStatus transferStatus) {
Log.d(TAG, "transferStatus: " + transferStatus.getTransferMode().name());
EventBus.getDefault().postSticky(transferStatus);
@@ -67,29 +74,29 @@ final class DeviceTransferServer implements Handler.Callback {
this.handler = new Handler(commandAndControlThread.getLooper(), this);
}
@AnyThread
public synchronized void start() {
if (commandAndControlThread != null) {
@MainThread
public void start() {
if (started.compareAndSet(false, true)) {
update(TransferStatus.ready());
handler.sendEmptyMessage(START_SERVER);
}
}
@AnyThread
public synchronized void stop() {
if (commandAndControlThread != null) {
@MainThread
public void stop() {
if (stopped.compareAndSet(false, true)) {
handler.sendEmptyMessage(STOP_SERVER);
}
}
@AnyThread
public synchronized void setVerified(boolean isVerified) {
if (commandAndControlThread != null) {
@MainThread
public void setVerified(boolean isVerified) {
if (!stopped.get()) {
handler.sendMessage(handler.obtainMessage(SET_VERIFIED, isVerified));
}
}
private synchronized void shutdown() {
private void shutdown() {
stopIpExchange();
stopServer();
stopWifiDirect();
@@ -101,7 +108,7 @@ final class DeviceTransferServer implements Handler.Callback {
commandAndControlThread = null;
}
update(TransferStatus.shutdown());
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
}
private void internalShutdown() {
@@ -132,6 +139,9 @@ final class DeviceTransferServer implements Handler.Callback {
serverThread.setVerified((Boolean) message.obj);
}
break;
case NETWORK_CONNECTION_CHANGED:
requestNetworkInfo((Boolean) message.obj);
break;
case NetworkServerThread.NETWORK_SERVER_STARTED:
startWifiDirect(message.arg1);
break;
@@ -201,6 +211,22 @@ final class DeviceTransferServer implements Handler.Callback {
}
}
private void requestNetworkInfo(boolean isNetworkConnected) {
if (wifiDirect == null) {
return;
}
if (isNetworkConnected) {
try {
wifiDirect.requestNetworkInfo();
} catch (WifiDirectUnavailableException e) {
Log.e(TAG, e);
internalShutdown();
update(TransferStatus.failed());
}
}
}
private void startNetworkServer() {
if (serverThread != null) {
Log.i(TAG, "Server already running");
@@ -253,7 +279,7 @@ final class DeviceTransferServer implements Handler.Callback {
stopIpExchange();
}
public class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
final class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
@Override
public void onNetworkConnected(@NonNull WifiP2pInfo info) {
@@ -262,16 +288,15 @@ final class DeviceTransferServer implements Handler.Callback {
}
}
@Override
public void onNetworkDisconnected() { }
@Override
public void onNetworkFailure() {
handler.sendEmptyMessage(NETWORK_FAILURE);
}
@Override
public void onLocalDeviceChanged(@NonNull WifiP2pDevice localDevice) { }
public void onConnectionChanged(@NonNull NetworkInfo networkInfo) {
handler.sendMessage(handler.obtainMessage(NETWORK_CONNECTION_CHANGED, networkInfo.isConnected()));
}
@Override
public void onServiceDiscovered(@NonNull WifiP2pDevice serviceDevice, @NonNull String extraInfo) { }

View File

@@ -15,14 +15,11 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import static org.signal.devicetransfer.DeviceTransferAuthentication.DIGEST_LENGTH;
/**
* Performs the networking setup/tear down for the client. This includes
* connecting to the server, performing the TLS/SAS verification, running an
@@ -101,11 +98,14 @@ final class NetworkClientThread extends Thread {
handler.sendEmptyMessage(NETWORK_CLIENT_CONNECTED);
clientTask.run(context, outputStream);
outputStream.flush();
client.shutdownOutput();
Log.d(TAG, "Waiting for server to tell us they got everything");
//noinspection ResultOfMethodCallIgnored
inputStream.read();
try {
//noinspection ResultOfMethodCallIgnored
inputStream.read();
} catch (IOException e) {
Log.w(TAG, "Something happened confirming with server, mostly like bad SSL shutdown state, assuming success", e);
}
success = true;
isRunning = false;
} catch (IOException e) {
@@ -117,6 +117,9 @@ final class NetworkClientThread extends Thread {
Log.w(TAG, e);
isRunning = false;
} finally {
if (success) {
clientTask.success();
}
StreamUtil.close(client);
handler.sendEmptyMessage(NETWORK_CLIENT_DISCONNECTED);
}
@@ -127,9 +130,6 @@ final class NetworkClientThread extends Thread {
}
Log.i(TAG, "Client exiting");
if (success) {
clientTask.success();
}
handler.sendEmptyMessage(NETWORK_CLIENT_STOPPED);
}

View File

@@ -78,6 +78,7 @@ final class NetworkServerThread extends Thread {
outputStream.write(0x43);
outputStream.flush();
serverTask.run(context, inputStream);
outputStream.write(0x53);
outputStream.flush();
} catch (IOException e) {

View File

@@ -63,10 +63,6 @@ public class TransferStatus {
return new TransferStatus(TransferMode.FAILED);
}
public static @NonNull TransferStatus shutdown() {
return new TransferStatus(TransferMode.SHUTDOWN);
}
public enum TransferMode {
UNAVAILABLE,
FAILED,
@@ -76,7 +72,6 @@ public class TransferStatus {
NETWORK_CONNECTED,
VERIFICATION_REQUIRED,
SERVICE_CONNECTED,
SERVICE_DISCONNECTED,
SHUTDOWN,
SERVICE_DISCONNECTED
}
}

View File

@@ -46,10 +46,7 @@ public final class WifiDirect {
private static final String TAG = Log.tag(WifiDirect.class);
private static final IntentFilter intentFilter = new IntentFilter() {{
addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
}};
private static final String EXTRA_INFO_PLACEHOLDER = "%%EXTRA_INFO%%";
@@ -57,6 +54,9 @@ public final class WifiDirect {
private static final Pattern SERVICE_INSTANCE_PATTERN = Pattern.compile("_devicetransfer(\\._(.+))?\\._signal\\.org");
private static final String SERVICE_REG_TYPE = "_presence._tcp";
private static final long SAFE_FOR_LONG_AWAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private static final long NOT_SAFE_FOR_LONG_AWAIT_TIMEOUT = 50;
private final Context context;
private WifiDirectConnectionListener connectionListener;
private WifiDirectCallbacks wifiDirectCallbacks;
@@ -115,7 +115,7 @@ public final class WifiDirect {
throw new WifiDirectUnavailableException(Reason.WIFI_P2P_MANAGER);
}
wifiDirectCallbacks = new WifiDirectCallbacks();
wifiDirectCallbacks = new WifiDirectCallbacks(connectionListener);
channel = manager.initialize(context, wifiDirectCallbacksHandler.getLooper(), wifiDirectCallbacks);
if (channel == null) {
Log.i(TAG, "Unable to initialize channel");
@@ -139,18 +139,23 @@ public final class WifiDirect {
connectionListener = null;
if (manager != null) {
retry(manager::clearServiceRequests, "clear service requests");
retry(manager::stopPeerDiscovery, "stop peer discovery");
retry(manager::clearLocalServices, "clear local services");
retrySync(manager::clearServiceRequests, "clear service requests", SAFE_FOR_LONG_AWAIT_TIMEOUT);
retrySync(manager::stopPeerDiscovery, "stop peer discovery", SAFE_FOR_LONG_AWAIT_TIMEOUT);
retrySync(manager::clearLocalServices, "clear local services", SAFE_FOR_LONG_AWAIT_TIMEOUT);
if (Build.VERSION.SDK_INT < 27) {
retrySync(manager::removeGroup, "remove group", SAFE_FOR_LONG_AWAIT_TIMEOUT);
channel = null;
}
manager = null;
}
if (channel != null) {
if (channel != null && Build.VERSION.SDK_INT >= 27) {
channel.close();
channel = null;
}
if (wifiDirectCallbacks != null) {
wifiDirectCallbacks.clearConnectionListener();
context.unregisterReceiver(wifiDirectCallbacks);
wifiDirectCallbacks = null;
}
@@ -173,10 +178,10 @@ public final class WifiDirect {
WifiP2pDnsSdServiceInfo serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(buildServiceInstanceName(extraInfo), SERVICE_REG_TYPE, Collections.emptyMap());
SyncActionListener addLocalServiceListener = new SyncActionListener("add local service");
SyncActionListener addLocalServiceListener = new SyncActionListener("add local service", SAFE_FOR_LONG_AWAIT_TIMEOUT);
manager.addLocalService(channel, serviceInfo, addLocalServiceListener);
SyncActionListener discoverPeersListener = new SyncActionListener("discover peers");
SyncActionListener discoverPeersListener = new SyncActionListener("discover peers", SAFE_FOR_LONG_AWAIT_TIMEOUT);
manager.discoverPeers(channel, discoverPeersListener);
if (!addLocalServiceListener.successful() || !discoverPeersListener.successful()) {
@@ -190,8 +195,8 @@ public final class WifiDirect {
synchronized void stopDiscoveryService() throws WifiDirectUnavailableException {
ensureInitialized();
retry(manager::stopPeerDiscovery, "stop peer discovery");
retry(manager::clearLocalServices, "clear local services");
retryAsync(manager::stopPeerDiscovery, "stop peer discovery");
retryAsync(manager::clearLocalServices, "clear local services");
}
/**
@@ -214,8 +219,9 @@ public final class WifiDirect {
String extraInfo = isInstanceNameMatching(instanceName);
if (extraInfo != null) {
Log.d(TAG, "Service found!");
if (connectionListener != null) {
connectionListener.onServiceDiscovered(sourceDevice, extraInfo);
WifiDirectConnectionListener listener = connectionListener;
if (listener != null) {
listener.onServiceDiscovered(sourceDevice, extraInfo);
}
} else {
Log.d(TAG, "Found unusable service, ignoring.");
@@ -226,10 +232,10 @@ public final class WifiDirect {
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
SyncActionListener addServiceListener = new SyncActionListener("add service request");
SyncActionListener addServiceListener = new SyncActionListener("add service request", SAFE_FOR_LONG_AWAIT_TIMEOUT);
manager.addServiceRequest(channel, serviceRequest, addServiceListener);
SyncActionListener startDiscovery = new SyncActionListener("discover services");
SyncActionListener startDiscovery = new SyncActionListener("discover services", SAFE_FOR_LONG_AWAIT_TIMEOUT);
manager.discoverServices(channel, startDiscovery);
if (!addServiceListener.successful() || !startDiscovery.successful()) {
@@ -245,7 +251,7 @@ public final class WifiDirect {
synchronized void stopServiceDiscovery() throws WifiDirectUnavailableException {
ensureInitialized();
retry(manager::clearServiceRequests, "clear service requests");
retryAsync(manager::clearServiceRequests, "clear service requests");
}
/**
@@ -268,7 +274,7 @@ public final class WifiDirect {
serviceRequest = null;
}
SyncActionListener listener = new SyncActionListener("service connect");
SyncActionListener listener = new SyncActionListener("service connect", SAFE_FOR_LONG_AWAIT_TIMEOUT);
manager.connect(channel, config, listener);
if (listener.successful()) {
@@ -278,19 +284,39 @@ public final class WifiDirect {
}
}
private synchronized void retry(@NonNull ManagerRetry retryFunction, @NonNull String message) {
public synchronized void requestNetworkInfo() throws WifiDirectUnavailableException {
ensureInitialized();
manager.requestConnectionInfo(channel, info -> {
Log.i(TAG, "Connection information available. group_formed: " + info.groupFormed + " group_owner: " + info.isGroupOwner);
WifiDirectConnectionListener listener = connectionListener;
if (listener != null) {
listener.onNetworkConnected(info);
}
});
}
private synchronized void retrySync(@NonNull ManagerRetry retryFunction, @NonNull String message, long awaitTimeout) {
int tries = 3;
while ((tries--) > 0) {
SyncActionListener listener = new SyncActionListener(message);
if (isNotInitialized()) {
return;
}
SyncActionListener listener = new SyncActionListener(message, awaitTimeout);
retryFunction.call(channel, listener);
if (listener.successful()) {
if (listener.successful() || listener.failureReason == SyncActionListener.FAILURE_TIMEOUT) {
return;
}
ThreadUtil.sleep(TimeUnit.SECONDS.toMillis(1));
}
}
private void retryAsync(@NonNull ManagerRetry retryFunction, @NonNull String message) {
SignalExecutors.BOUNDED.execute(() -> retrySync(retryFunction, message, WifiDirect.NOT_SAFE_FOR_LONG_AWAIT_TIMEOUT));
}
private synchronized boolean isInitialized() {
return manager != null && channel != null;
}
@@ -328,57 +354,41 @@ public final class WifiDirect {
void call(@NonNull WifiP2pManager.Channel a, @NonNull WifiP2pManager.ActionListener b);
}
private class WifiDirectCallbacks extends BroadcastReceiver implements WifiP2pManager.ChannelListener, WifiP2pManager.ConnectionInfoListener {
private static class WifiDirectCallbacks extends BroadcastReceiver implements WifiP2pManager.ChannelListener {
private WifiDirectConnectionListener connectionListener;
public WifiDirectCallbacks(@NonNull WifiDirectConnectionListener connectionListener) {
this.connectionListener = connectionListener;
}
public void clearConnectionListener() {
connectionListener = null;
}
@Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
String action = intent.getAction();
if (action != null) {
switch (action) {
case WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:
WifiP2pDevice localDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
if (localDevice != null && connectionListener != null) {
connectionListener.onLocalDeviceChanged(localDevice);
}
break;
case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:
if (isNotInitialized()) {
Log.w(TAG, "WiFi P2P broadcast connection changed action without being initialized.");
return;
}
if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
WifiDirectConnectionListener listener = connectionListener;
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo == null) {
Log.w(TAG, "WiFi P2P broadcast connection changed action with null network info.");
return;
}
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo == null) {
Log.w(TAG, "WiFi P2P broadcast connection changed action with null network info.");
return;
}
if (networkInfo.isConnected()) {
Log.i(TAG, "Connected to P2P network, requesting connection information.");
manager.requestConnectionInfo(channel, this);
} else {
Log.i(TAG, "Disconnected from P2P network");
if (connectionListener != null) {
connectionListener.onNetworkDisconnected();
}
}
break;
if (listener != null) {
listener.onConnectionChanged(networkInfo);
}
}
}
}
@Override
public void onConnectionInfoAvailable(@NonNull WifiP2pInfo info) {
Log.i(TAG, "Connection information available. group_formed: " + info.groupFormed + " group_owner: " + info.isGroupOwner);
if (connectionListener != null) {
connectionListener.onNetworkConnected(info);
}
}
@Override
public void onChannelDisconnected() {
if (connectionListener != null) {
connectionListener.onNetworkFailure();
WifiDirectConnectionListener listener = connectionListener;
if (listener != null) {
listener.onNetworkFailure();
}
}
}
@@ -388,13 +398,16 @@ public final class WifiDirect {
*/
private static class SyncActionListener extends LoggingActionListener {
private final CountDownLatch sync;
private static final int FAILURE_TIMEOUT = -2;
private volatile int failureReason = -1;
private final CountDownLatch sync;
private final long awaitTimeout;
private volatile int failureReason = -1;
public SyncActionListener(@NonNull String message) {
public SyncActionListener(@NonNull String message, long awaitTimeout) {
super(message);
this.sync = new CountDownLatch(1);
this.awaitTimeout = awaitTimeout;
this.sync = new CountDownLatch(1);
}
@Override
@@ -412,9 +425,14 @@ public final class WifiDirect {
public boolean successful() {
try {
sync.await();
boolean completed = sync.await(awaitTimeout, TimeUnit.MILLISECONDS);
if (!completed) {
Log.i(TAG, "SyncListener [" + message + "] timed out after " + awaitTimeout + "ms");
failureReason = FAILURE_TIMEOUT;
return false;
}
} catch (InterruptedException ie) {
throw new AssertionError(ie);
Log.i(TAG, "SyncListener [" + message + "] interrupted");
}
return failureReason < 0;
}
@@ -422,7 +440,7 @@ public final class WifiDirect {
private static class LoggingActionListener implements WifiP2pManager.ActionListener {
private final String message;
protected final String message;
public static @NonNull LoggingActionListener message(@Nullable String message) {
return new LoggingActionListener(message);
@@ -452,14 +470,13 @@ public final class WifiDirect {
}
public interface WifiDirectConnectionListener {
void onLocalDeviceChanged(@NonNull WifiP2pDevice localDevice);
void onServiceDiscovered(@NonNull WifiP2pDevice serviceDevice, @NonNull String extraInfo);
void onNetworkConnected(@NonNull WifiP2pInfo info);
void onNetworkDisconnected();
void onNetworkFailure();
void onConnectionChanged(@NonNull NetworkInfo networkInfo);
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item name="DeviceToDeviceTransferService_content_title" type="string" />
<item name="DeviceToDeviceTransferService_status_ready" type="string" />
<item name="DeviceToDeviceTransferService_status_starting_up" type="string" />
<item name="DeviceToDeviceTransferService_status_discovery" type="string" />
<item name="DeviceToDeviceTransferService_status_network_connected" type="string" />
<item name="DeviceToDeviceTransferService_status_verification_required" type="string" />
<item name="DeviceToDeviceTransferService_status_service_connected" type="string" />
</resources>