I am developing an Android app to share files using Wi-Fi Direct. The app works, but I am unable to switch between being a sender and a receiver without closing the app. When I try to switch roles, the group owner is not updated and remains the same as the previous transfer. How can I update the group owner and switch roles correctly?
I do disconnect and reinitialize after every file transfer session. But it doesn't create a new group.
Here is my initialization code:
manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager?
channel = manager?.initialize(this, mainLooper, null)?.also {
receiver = P2pBroadcastReceiver(this)
}
And here is the code for connecting to a peer and switching roles:
fun connectToPeer(device: WifiP2pDevice, isSender: Boolean) {
val config = WifiP2pConfig().apply {
deviceAddress = device.deviceAddress
groupOwnerIntent = if (isSender) WifiP2pConfig.GROUP_OWNER_INTENT_MIN else WifiP2pConfig.GROUP_OWNER_INTENT_MAX
}
channel?.also { channel ->
manager?.connect(channel, config, object : WifiP2pManager.ActionListener {
override fun onSuccess() {
context.apply {
toast("${getString(R.string.connecting_to)} ${device.deviceName}")
}
}
override fun onFailure(reasonCode: Int) = onP2POperationFailed(reasonCode)
})
}
}
And here is the code for disconnecting from the group:
fun disconnect() {
manager?.apply {
cancelConnect(channel, null)
removeGroup(channel, null)
stopPeerDiscovery(channel, null)
deletePersistentGroups()
}
try {
if (::serverSocket.isInitialized) serverSocket.close()
if (::clientSocket.isInitialized) clientSocket.close()
} catch (e: IOException) {}
}
private fun deletePersistentGroups() {
try {
val methods = WifiP2pManager::class.java.methods
for (method in methods) {
if (method.name == "deletePersistentGroup") {
for (networkId in 0..31) {
method.invoke(manager, channel, networkId, null)
}
}
}
} catch (e: Exception) {}
}
Any help would be greatly appreciated. Thank you!
I would like for devices d1 and d2 to have the ability to switch roles without closing the app. Currently, d1 is set up to receive files and d2 is set up to send them. However, if d1 needs to send files without closing the app after d2 has finished sending its files, I would like for them to be able to easily reestablish the connection and initiate the transfer.
Edit 2:
How I initiate the file transferring or sending process:
private fun onConnectionInfoChanged(info: WifiP2pInfo?) {
if (isDeviceConnected) {
if (info!!.isGroupOwner) { // receiver || server || group owner
startReceiverServer()
} else { // sender || client
startSenderClient(info.groupOwnerAddress)
}
}
}
private fun startReceiverServer() {
runOnBg {
serverSocket = ServerSocket(P2pFileTransferHelper.PORT)
P2pFileTransferHelper.receiveFiles(serverSocket, context.contentResolver) { fileTransferInfoLiveData.postValue(it) }
}
}
private fun startSenderClient(serverAddress: InetAddress) {
runOnBg {
clientSocket = Socket()
P2pFileTransferHelper.sendFiles(clientSocket, serverAddress, selectedFilePaths) { fileTransferInfoLiveData.postValue(it) }
}
}
Related
Currently, we are trying to write a Flutter-Dart client (ported from a Java Client) using sockets for transferring plaintext-data and files.
The Server is written in Kotlin and works well with Kotlin clients sending a UTF Package (information about the binary package sent after), followed by a long package defining the package size and finally the binary data.
A small truncated sample code for the server is:
class DataProcessRunnable internal constructor(private val socket: Socket, private val onFileReceivedListener: OnFileReceivedListener) : Runnable {
private val dateFormat = SimpleDateFormat("HH-mm-ss")
private val dateDayFormat = SimpleDateFormat("yyyy-MM-dd")
override fun run() {
try {
DataInputStream(socket.getInputStream()).use { stream ->
print ("connected ${stream.toString()}")
val rawData = stream.readUTF();
print("Rawdata: $rawData")
val fileData = gson.fromJson(rawData, FileData::class.java)
fileData.ipAddress = socket.inetAddress.hostAddress
val fileSize = stream.readLong()
println("sender: " + socket.inetAddress)
println("Filedata:$fileData")
println("filesize:$fileSize")
var fileName = "noname"
val fileTransfer = FileTransfer()
fileTransfer.fileSize = fileSize
fileTransfer.filename = fileName
println("filename:$fileName")
try {
try {
try {
GZIPInputStream(stream).use { input ->
FileOutputStream(fileName).use { out ->
input.copyTo(out)
}
}
} catch (ex: Exception) {
ex.printStackTrace()
} finally {
println("Wrote File in GZIP")
}
fileTransfer.transferedSize = fileTransfer.fileSize
val fData = FileData()
fData.file = File(fileName)
fData.extras = fileData.extras
fData.ipAddress = socket.inetAddress.toString()
fData.type = fileData.type
onFileReceivedListener.fileReceived(fData)
} catch (ex: Exception) {
ex.printStackTrace()
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
} catch (ex: Exception) {
ex.printStackTrace()
}
}
}
When we use Flutter-Dart to establish a socket, we are trying to send the first UTF8-Package using that codesample. The connection is established but readUTF can't read any data.
await Socket.connect('dev1.local-cloud-server.local', 2555).then((socket) {
socket.writeln(gzip.encode(utf8.encode(fData.toString())));
socket.flush();
});
});
After trying around several methods to send a utf8 encoded string from flutter dart to java, the only way I was able to read the first package was using (on the serverside, code truncated):
private fun validateSocketConnection2(socket: Socket) {
Thread {
DataInputStream(socket.getInputStream()).use { stream ->
val rawData = stream.bufferedReader().forEachLine { println(it) }
print("Rawdata: $rawData")
}.start()
}
Using a bufferedreader i was able to read the utf8 encoded string properly, but that would break the whole kotlin server code for other clients. In Flutter-Dart we tried to use sockets.add(), sockets.write("...\n"), sockets.writeln(), await socket.flush() after every package sent and also socket.close() to ensure that all data were transmitted.
But we were not able to read data using readUTF as we do for any other Java/Kotlin/C# client.
What are we missing to get readUTF working?
I have developed a compose app which retrieves crypto data (a list of 1063 objects) from Socket each 3 seconds. While using app I recognized that after some time working with app it crashes with Out of memory. I profiled my app (release version) and recognized that while using app java code increase until it crashes with OOM error.
Let me show you some code for better understanding.
For socket, I created a SocketManager class which instantiates on app startup via Dagger Hilt in singleton component.
class SocketManager #Inject constructor(
socket: Socket,
private val json: Json
) {
var cryptoFlow = MutableStateFlow<List<CryptoDataSetItemDto>>(emptyList())
init {
socket.connect()
socket.on("crypto_data") {
it.forEach { data ->
try {
val coinList =
json.decodeFromString<List<CryptoDataSetItemDto>>(data.toString())
cryptoFlow.tryEmit(coinList)
} catch (e: Exception) {
Sentry.captureException(e)
}
}
}
}
}
#Module
#InstallIn(SingletonComponent::class)
object SocketModule {
#Provides
#Singleton
fun provideJson(): Json {
return Json {
encodeDefaults = true
ignoreUnknownKeys = true
}
}
#Provides
#Singleton
fun provideSocket(client: OkHttpClient): Socket {
val socket: Socket
val opts = IO.Options()
opts.path = Constants.SOCKET_PATH
opts.secure = true
socket = IO.socket(Constants.SOCKET_BASE_URL, opts)
return socket
}
#Provides
#Singleton
fun provideSocketManager(socket: Socket, json: Json): SocketManager {
return SocketManager(socket, json)
}
}
Then I inject SocketManager to viewModel to get data on the screens I need those data. I create a function which gets a coroutine context from composable and collects data in that scope.
#HiltViewModel
class WithdrawDepositViewModel #Inject constructor(
private val useCase: WalletUseCases,
private val socketManager: SocketManager,
savedStateHandle: SavedStateHandle
) : ViewModel() {
fun getCoinData(context: CoroutineContext) {
CoroutineScope(context).launch(Dispatchers.Default) {
socketManager.cryptoFlow.asStateFlow()
.shareIn(CoroutineScope(context), SharingStarted.WhileSubscribed())
.collectLatest { cryptoListDto ->
val cryptoList = cryptoListDto.map { it.toCryptoList() }.toMutableList()
val irt = CryptoDataItem(
enName = "Toman",
faName = "تومان",
symbol = "IRT"
)
cryptoList.add(0, irt)
_state.value = state.value.copy(
cryptoList = cryptoList,
isLoading = false
)
}
}
}
and in screen I call the function like this:
#Composable
fun HomeScreen(
onNavigate: (String) -> Unit,
scaffoldState: ScaffoldState,
viewModel: HomeScreenViewModel = hiltViewModel()
) {
val scope = rememberCoroutineScope()
val lifecycleOwner = LocalLifecycleOwner.current
DisposableEffect(key1 = lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_CREATE) {
viewModel.getCryptoData(scope.coroutineContext)
}
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
This is my architecture to get data. when looked at heap dump result, I recognized that CryptoDataSetItemDto object which I get it on socket, has allocated lots of memory. I can not find the problem also I know that there is better architecture which I hope to learn from you.
I'm trying to convert the following reflection into Kotlin. The following uses reflection to call an RFCOMMs function so it can take a port/channel as an input instead of UUID. I have all my program in Kotlin. Anyone know how to write this in Kotlin?
int bt_port_to_connect = 5;
BluetoothDevice device = mDevice;
BluetoothSocket deviceSocket = null;
...
// IMPORTANT: we create a reference to the 'createInsecureRfcommSocket' method
// and not(!) to the 'createInsecureRfcommSocketToServiceRecord' (which is what the
// android SDK documentation publishes
Method m = device.getClass().getMethod("createInsecureRfcommSocket", new Class[] {int.class});
deviceSocket = (BluetoothSocket) m.invoke(device,bt_port_to_connect);
Updating with recommendation:
class BluetoothClient(device: BluetoothDevice): Thread() {
// https://stackoverflow.com/questions/9703779/connecting-to-a-specific-bluetooth-port-on-a-bluetooth-device-using-android
// Need to reflection - create RFCOMM socket to a port number instead of UUID
// Invoke btdevice as 1st parameter and then the port number
var bt_port_to_connect = 5
var deviceSocket: BluetoothSocket? = null
private val socket = device.createInsecureRfcommSocketToServiceRecord(uuid)
val m = device::class.declaredFunctions.single { it.name == "createInsecureRfcommSocket" }
m.call(device, bt_port_to_connect)
override fun run() {
try {
Log.i("client", "Connecting")
this.socket.connect()
Log.i("client", "Sending")
val outputStream = this.socket.outputStream
val inputStream = this.socket.inputStream
try {
outputStream.write(message.toByteArray())
outputStream.flush()
Log.i("client", "Sent")
} catch(e: Exception) {
Log.e("client", "Cannot send", e)
} finally {
outputStream.close()
inputStream.close()
this.socket.close()
}
}
catch (e: IOException) {
println("Socket Failed")
}
}
}
You can really use exactly the same code, just convert it to Kotlin:
val m = device::class.java.getMethod("createInsecureRfcommSocket", Int::class.java)
m.invoke(device, bt_port_to_connect)
Or you can use Kotlin reflection:
val m = device::class.declaredFunctions.single { it.name == "createInsecureRfcommSocket" }
m.call(device, bt_port_to_connect)
I don't know if there is any better way to find a function with provided name. You can create an extension function to make it cleaner. You may also need to check parameters if this function has overrides.
I have an hour of experience using RxJava and I am trying to implement it in my project instead of using interfaces and listeners.
I have an async task which calls a google cloud endpoint method in a separate module and receives a List<Profile> when done.
In the onPostExecute() method of the async task, I call onNext so that any subscribers receive this data.
Here is what the AsyncTask looks like:
private BirthpayApi mApi;
private String mUserId;
private ReplaySubject<List<Profile>> notifier = ReplaySubject.create();
public GetFriends(String userId) {
mUserId = userId;
}
public Observable<List<Profile>> asObservable() {
return notifier;
}
#Override
protected List<Profile> doInBackground(Void... params) {
if (mApi == null) {
BirthpayApi.Builder builder = new BirthpayApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://10.0.2.2:8080/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
mApi = builder.build();
}
try {
return mApi.getFriends(mUserId).execute().getItems();
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
#Override
protected void onPostExecute(List<Profile> friends) {
super.onPostExecute(friends);
notifier.onNext(friends);
}
In my Fragment I then want to collect this data from the async task calling the onNext() method. I therefore use implements Action1<List<Profile>> when declaring my class which also extends Fragment.
In the onCall() method that comes from the Action1 interface I collect the data sent from the Async task:
#Override
public void call(List<Profile> profiles) {
if (profiles.size() > 0) {
updateAdapter(profiles);
} else
setUpNoFriendsViews();
}
I am following along with treehouse but they use a object to model their data which becomes the observable instead of using an async class and they use an adapter as the observer. Am I doing this wrong, either way how do I get it to work?
It doesn't look like you're subscribing to the Observable you're creating anywhere and it's not clear where you're calling execute on the AsyncTask. Also I don't think you'd want a ReplaySubject but that depends on what you're trying to achieve.
All that aside I'd suggest completely switching over to Rx rather than mixing up Rx and AsyncTasks. In this case in your model class make a method something like:
public Observable<List<Profile>> getProfiles() {
return Observable.defer(new Func0<Observable<List<Profile>>>() {
#Override
public Observable<List<Profile>> call() {
if (mApi == null) {
BirthpayApi.Builder builder = new BirthpayApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://10.0.2.2:8080/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
mApi = builder.build();
}
try {
List<Profile> profiles = mApi.getFriends(mUserId).execute().getItems();
return Observable.just(profiles);
} catch (IOException e) {
return Observable.error(e);
}
}
});
}
Then in your Fragment:
modal.getProfiles()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
new Action1<List<Profile>>() {
//...
},
new Action1<Throwable>() {
#Override
public void call(Throwable throwable) {
throwable.printStackTrace();
}
}
);
We use hazelcast in client-server mode. The hazelcast cluster contains 2 hazelcast nodes and we have about 25 clients connected to the cluster.
What I am lookin for now is a simple check that tries to figure out if the cluster is still alive. It should be a rather cheap operation because this check will occure on every client quite frequently (once every second I could imagine).
What is the best way to do so?
The simplest way would be the register a LifecycleListener to the client HazelcastInstance:
HazelcastInstance client = HazelcastClient.newHazelcastClient();
client.getLifecycleService().addLifecycleListener(new LifecycleListener() {
#Override
public void stateChanged(LifecycleEvent event) {
}
})
The client uses a periodic heartbeat to detect if the cluster is still running.
You can use the LifecycleService.isRunning() method as well:
HazelcastInstance hzInstance = HazelcastClient.newHazelcastClient();
hzInstance.getLifecycleService().isRunning()
As isRunning() may be true even if cluster is down, I'd go for the following approach (a mixture of #konstantin-zyubin's answer and this). This doesn't need an event-listener, which is an advantage in my setup:
if (!hazelcastInstance.getLifecycleService().isRunning()) {
return Health.down().build();
}
int parameterCount;
LocalTopicStats topicStats;
try {
parameterCount = hazelcastInstance.getMap("parameters").size();
topicStats = hazelcastInstance.getTopic("myTopic").getLocalTopicStats();
} catch (Exception e) {
// instance may run but cluster is down:
Health.Builder builder = Health.down();
builder.withDetail("Error", e.getMessage());
return builder.build();
}
Health.Builder builder = Health.up();
builder.withDetail("parameterCount", parameterCount);
builder.withDetail("receivedMsgs", topicStats.getReceiveOperationCount());
builder.withDetail("publishedMsgs", topicStats.getPublishOperationCount());
return builder.build();
I have found a more reliable way to check hazelcast availability, because
client.getLifecycleService().isRunning()
when you use async reconnection mode is always return true, as was mentioned.
#Slf4j
public class DistributedCacheServiceImpl implements DistributedCacheService {
private HazelcastInstance client;
#Autowired
protected ConfigLoader<ServersConfig> serversConfigLoader;
#PostConstruct
private void initHazelcastClient() {
ClientConfig config = new ClientConfig();
if (isCacheEnabled()) {
ServersConfig.Hazelсast hazelcastConfig = getWidgetCacheSettings().getHazelcast();
config.getGroupConfig().setName(hazelcastConfig.getName());
config.getGroupConfig().setPassword(hazelcastConfig.getPassword());
for (String address : hazelcastConfig.getAddresses()) {
config.getNetworkConfig().addAddress(address);
}
config.getConnectionStrategyConfig()
.setAsyncStart(true)
.setReconnectMode(ClientConnectionStrategyConfig.ReconnectMode.ASYNC);
config.getNetworkConfig()
.setConnectionAttemptLimit(0) // infinite (Integer.MAX_VALUE) attempts to reconnect
.setConnectionTimeout(5000);
client = HazelcastClient.newHazelcastClient(config);
}
}
#Override
public boolean isCacheEnabled() {
ServersConfig.WidgetCache widgetCache = getWidgetCacheSettings();
return widgetCache != null && widgetCache.getEnabled();
}
#Override
public boolean isCacheAlive() {
boolean aliveResult = false;
if (isCacheEnabled() && client != null) {
try {
IMap<Object, Object> defaultMap = client.getMap("default");
if (defaultMap != null) {
defaultMap.size(); // will throw Hazelcast exception if cluster is down
aliveResult = true;
}
} catch (Exception e) {
log.error("Connection to hazelcast cluster is lost. Reason : {}", e.getMessage());
}
}
return aliveResult;
}
}