mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 21:15:48 +00:00
Add Device to Device Transfer UI.
This commit is contained in:
committed by
Greyson Parrelli
parent
6f8be3260c
commit
75aab4c031
@@ -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();
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) { }
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
11
device-transfer/lib/src/main/res/values/strings.xml
Normal file
11
device-transfer/lib/src/main/res/values/strings.xml
Normal 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>
|
||||
Reference in New Issue
Block a user