Merge pull request #2011 from yash1ts/#1835-wifi-direct-manager-to-kotlin

WifiDirectManager to Kotlin
This commit is contained in:
Seán Mac Gillicuddy 2020-04-10 14:15:04 +01:00 committed by GitHub
commit 20b5d187c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 391 additions and 482 deletions

View File

@ -34,6 +34,7 @@
<ID>PackageNaming:ShareFiles.kt$package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects</ID>
<ID>PackageNaming:SimplePageChangeListener.kt$package org.kiwix.kiwixmobile.zim_manager</ID>
<ID>PackageNaming:StartMultiSelection.kt$package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects</ID>
<ID>PackageNaming:WifiDirectManager.kt$package org.kiwix.kiwixmobile.local_file_transfer</ID>
<ID>PackageNaming:WifiP2pDelegate.kt$package org.kiwix.kiwixmobile.local_file_transfer.adapter</ID>
<ID>PackageNaming:WifiP2pViewHolder.kt$package org.kiwix.kiwixmobile.local_file_transfer.adapter</ID>
<ID>PackageNaming:WifiPeerListAdapter.kt$package org.kiwix.kiwixmobile.local_file_transfer.adapter</ID>
@ -50,6 +51,7 @@
<ID>TooManyFunctions:KiwixActivityComponent.kt$KiwixActivityComponent$KiwixActivityComponent</ID>
<ID>TooManyFunctions:KiwixMainActivity.kt$KiwixMainActivity$KiwixMainActivity</ID>
<ID>TooManyFunctions:LibraryFragment.kt$LibraryFragment$LibraryFragment</ID>
<ID>TooManyFunctions:WifiDirectManager.kt$WifiDirectManager$WifiDirectManager</ID>
<ID>TooManyFunctions:ZimFileSelectFragment.kt$ZimFileSelectFragment$ZimFileSelectFragment</ID>
<ID>TooManyFunctions:ZimHostActivity.kt$ZimHostActivity$ZimHostActivity</ID>
<ID>TooManyFunctions:ZimManageViewModel.kt$ZimManageViewModel$ZimManageViewModel</ID>

View File

@ -1,482 +0,0 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.local_file_transfer;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.IntentFilter;
import android.net.Uri;
import android.net.wifi.WpsInfo;
import android.net.wifi.p2p.WifiP2pConfig;
import android.net.wifi.p2p.WifiP2pDevice;
import android.net.wifi.p2p.WifiP2pDeviceList;
import android.net.wifi.p2p.WifiP2pInfo;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.AsyncTask;
import android.os.Build;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.util.ArrayList;
import javax.inject.Inject;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import org.kiwix.kiwixmobile.core.BuildConfig;
import org.kiwix.kiwixmobile.core.R;
import org.kiwix.kiwixmobile.core.utils.AlertDialogShower;
import org.kiwix.kiwixmobile.core.utils.KiwixDialog;
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil;
import static android.os.Looper.getMainLooper;
import static org.kiwix.kiwixmobile.local_file_transfer.FileItem.FileStatus.ERROR;
import static org.kiwix.kiwixmobile.local_file_transfer.LocalFileTransferActivity.showToast;
/**
* Manager for the Wifi-P2p API, used in the local file transfer module
*/
@SuppressWarnings("MissingPermission")
public class WifiDirectManager
implements WifiP2pManager.ChannelListener, WifiP2pManager.PeerListListener,
WifiP2pManager.ConnectionInfoListener,
KiwixWifiP2pBroadcastReceiver.P2pEventListener {
private static final String TAG = "WifiDirectManager";
public static int FILE_TRANSFER_PORT = 8008;
private @NonNull Activity activity;
private @NonNull Callbacks callbacks;
private SharedPreferenceUtil sharedPreferenceUtil;
private AlertDialogShower alertDialogShower;
/* Variables related to the WiFi P2P API */
private boolean isWifiP2pEnabled = false; // Whether WiFi has been enabled or not
private boolean shouldRetry = true; // Whether channel has retried connecting previously
private WifiP2pManager manager; // Overall manager of Wifi p2p connections for the module
private WifiP2pManager.Channel channel; // Interface to the device's underlying wifi-p2p framework
private BroadcastReceiver receiver = null; // For receiving the broadcasts given by above filter
private WifiP2pInfo groupInfo; // Corresponds to P2P group formed between the two devices
private WifiP2pDevice senderSelectedPeerDevice = null;
private PeerGroupHandshakeAsyncTask peerGroupHandshakeAsyncTask;
private SenderDeviceAsyncTask senderDeviceAsyncTask;
private ReceiverDeviceAsyncTask receiverDeviceAsyncTask;
private InetAddress selectedPeerDeviceInetAddress;
private InetAddress fileReceiverDeviceAddress; // IP address of the file receiving device
private ArrayList<FileItem> filesForTransfer;
private boolean isFileSender = false; // Whether the device is the file sender or not
private boolean hasSenderStartedConnection = false;
@Inject
public WifiDirectManager(@NonNull Activity activity,
@NonNull SharedPreferenceUtil sharedPreferenceUtil,
@NonNull AlertDialogShower alertDialogShower) {
this.activity = activity;
this.callbacks = (Callbacks) activity;
this.sharedPreferenceUtil = sharedPreferenceUtil;
this.alertDialogShower = alertDialogShower;
}
/* Initialisations for using the WiFi P2P API */
public void startWifiDirectManager(@Nullable ArrayList<FileItem> filesForTransfer) {
this.filesForTransfer = filesForTransfer;
this.isFileSender = (filesForTransfer != null && filesForTransfer.size() > 0);
manager = (WifiP2pManager) activity.getSystemService(Context.WIFI_P2P_SERVICE);
channel = manager.initialize(activity, getMainLooper(), null);
registerWifiDirectBroadcastReceiver();
}
public void registerWifiDirectBroadcastReceiver() {
receiver = new KiwixWifiP2pBroadcastReceiver(this);
// For specifying broadcasts (of the P2P API) that the module needs to respond to
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
activity.registerReceiver(receiver, intentFilter);
}
public void unregisterWifiDirectBroadcastReceiver() {
activity.unregisterReceiver(receiver);
}
public void discoverPeerDevices() {
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
displayToast(R.string.discovery_initiated, Toast.LENGTH_SHORT);
}
@Override
public void onFailure(int reason) {
String errorMessage = getErrorMessage(reason);
Log.d(TAG, activity.getString(R.string.discovery_failed) + ": " + errorMessage);
displayToast(R.string.discovery_failed, Toast.LENGTH_SHORT);
}
});
}
/* From KiwixWifiP2pBroadcastReceiver.P2pEventListener callback-interface*/
@Override
public void onWifiP2pStateChanged(boolean isEnabled) {
this.isWifiP2pEnabled = isEnabled;
if (!isWifiP2pEnabled) {
displayToast(R.string.discovery_needs_wifi, Toast.LENGTH_SHORT);
callbacks.onConnectionToPeersLost();
}
Log.d(TAG, "WiFi P2P state changed - " + isWifiP2pEnabled);
}
@Override
public void onPeersChanged() {
/* List of available peers has changed, so request & use the new list through
* PeerListListener.requestPeers() callback */
manager.requestPeers(channel, this);
Log.d(TAG, "P2P peers changed");
}
@Override
public void onConnectionChanged(boolean isConnected) {
if (isConnected) {
// Request connection info about the wifi p2p group formed upon connection
manager.requestConnectionInfo(channel, this);
} else {
// Not connected after connection change -> Disconnected
callbacks.onConnectionToPeersLost();
}
}
@Override
public void onDeviceChanged(@Nullable WifiP2pDevice userDevice) {
// Update UI with wifi-direct details about the user device
callbacks.onUserDeviceDetailsAvailable(userDevice);
}
/* From WifiP2pManager.ChannelListener interface */
@Override
public void onChannelDisconnected() {
// Upon disconnection, retry one more time
if (shouldRetry) {
Log.d(TAG, "Channel lost, trying again");
callbacks.onConnectionToPeersLost();
shouldRetry = false;
manager.initialize(activity, getMainLooper(), this);
} else {
displayToast(R.string.severe_loss_error, Toast.LENGTH_LONG);
}
}
/* From WifiP2pManager.PeerListListener callback-interface */
@Override
public void onPeersAvailable(@NonNull WifiP2pDeviceList peers) {
callbacks.updateListOfAvailablePeers(peers);
}
/* From WifiP2pManager.ConnectionInfoListener callback-interface */
@Override
public void onConnectionInfoAvailable(@NonNull WifiP2pInfo groupInfo) {
/* Devices have successfully connected, and 'info' holds information about the wifi p2p group formed */
this.groupInfo = groupInfo;
performHandshakeWithSelectedPeerDevice();
}
/* Helper methods */
public boolean isWifiP2pEnabled() {
return isWifiP2pEnabled;
}
public boolean isGroupFormed() {
return groupInfo.groupFormed;
}
public boolean isGroupOwner() {
return groupInfo.isGroupOwner;
}
public @NonNull InetAddress getGroupOwnerAddress() {
return groupInfo.groupOwnerAddress;
}
public void sendToDevice(@NonNull WifiP2pDevice senderSelectedPeerDevice) {
/* Connection can only be initiated by user of the sender device, & only when transfer has not been started */
if (!isFileSender || hasSenderStartedConnection) {
return;
}
this.senderSelectedPeerDevice = senderSelectedPeerDevice;
alertDialogShower.show(
new KiwixDialog.FileTransferConfirmation(senderSelectedPeerDevice.deviceName),
new Function0<Unit>() {
@Override public Unit invoke() {
hasSenderStartedConnection = true;
connect();
displayToast(R.string.performing_handshake, Toast.LENGTH_LONG);
return Unit.INSTANCE;
}
});
}
public void connect() {
if (senderSelectedPeerDevice == null) {
Log.d(TAG, "No device set as selected");
}
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = senderSelectedPeerDevice.deviceAddress;
config.wps.setup = WpsInfo.PBC;
manager.connect(channel, config, new WifiP2pManager.ActionListener() {
@Override
public void onSuccess() {
// UI updated from broadcast receiver
}
@Override
public void onFailure(int reason) {
String errorMessage = getErrorMessage(reason);
Log.d(TAG, activity.getString(R.string.connection_failed) + ": " + errorMessage);
displayToast(R.string.connection_failed, Toast.LENGTH_LONG);
}
});
}
public void performHandshakeWithSelectedPeerDevice() {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Starting handshake");
}
peerGroupHandshakeAsyncTask = new PeerGroupHandshakeAsyncTask(this);
peerGroupHandshakeAsyncTask.execute();
}
public boolean isFileSender() {
return isFileSender;
}
public int getTotalFilesForTransfer() {
return filesForTransfer.size();
}
public @NonNull ArrayList<FileItem> getFilesForTransfer() {
return filesForTransfer;
}
public void setFilesForTransfer(@NonNull ArrayList<FileItem> fileItems) {
this.filesForTransfer = fileItems;
}
public @NonNull String getZimStorageRootPath() {
return (sharedPreferenceUtil.getPrefStorage() + "/Kiwix/");
}
public @NonNull InetAddress getFileReceiverDeviceAddress() {
return fileReceiverDeviceAddress;
}
public static void copyToOutputStream(@NonNull InputStream inputStream,
@NonNull OutputStream outputStream) throws IOException {
byte[] bufferForBytes = new byte[1024];
int bytesRead;
Log.d(TAG, "Copying to OutputStream...");
while ((bytesRead = inputStream.read(bufferForBytes)) != -1) {
outputStream.write(bufferForBytes, 0, bytesRead);
}
outputStream.close();
inputStream.close();
Log.d(LocalFileTransferActivity.TAG, "Both streams closed");
}
public void setClientAddress(@Nullable InetAddress clientAddress) {
if (clientAddress == null) {
// null is returned only in case of a failed handshake
displayToast(R.string.device_not_cooperating, Toast.LENGTH_LONG);
callbacks.onFileTransferComplete();
return;
}
// If control reaches here, means handshake was successful
selectedPeerDeviceInetAddress = clientAddress;
startFileTransfer();
}
private void startFileTransfer() {
if (isGroupFormed()) {
if (isFileSender) {
Log.d(LocalFileTransferActivity.TAG, "Starting file transfer");
fileReceiverDeviceAddress =
(isGroupOwner()) ? selectedPeerDeviceInetAddress : getGroupOwnerAddress();
displayToast(R.string.preparing_files, Toast.LENGTH_LONG);
senderDeviceAsyncTask = new SenderDeviceAsyncTask(this, activity);
senderDeviceAsyncTask.execute(filesForTransfer.toArray(new FileItem[0]));
} else {
callbacks.onFilesForTransferAvailable(filesForTransfer);
receiverDeviceAsyncTask = new ReceiverDeviceAsyncTask(this);
receiverDeviceAsyncTask.execute();
}
}
}
public void changeStatus(int itemIndex, @FileItem.FileStatus int status) {
filesForTransfer.get(itemIndex).setFileStatus(status);
callbacks.onFileStatusChanged(itemIndex);
if (status == ERROR) {
displayToast(R.string.error_transferring, filesForTransfer.get(itemIndex).getFileName(),
Toast.LENGTH_SHORT);
}
}
private void cancelAsyncTasks(AsyncTask... tasks) {
for (AsyncTask asyncTask : tasks) {
if (asyncTask != null) {
asyncTask.cancel(true);
}
}
}
public void stopWifiDirectManager() {
cancelAsyncTasks(peerGroupHandshakeAsyncTask, senderDeviceAsyncTask, receiverDeviceAsyncTask);
if (isFileSender) {
closeChannel();
} else {
disconnect();
}
unregisterWifiDirectBroadcastReceiver();
}
public void disconnect() {
manager.removeGroup(channel, new WifiP2pManager.ActionListener() {
@Override
public void onFailure(int reasonCode) {
Log.d(TAG, "Disconnect failed. Reason: " + reasonCode);
closeChannel();
}
@Override
public void onSuccess() {
Log.d(TAG, "Disconnect successful");
closeChannel();
}
});
}
private void closeChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
channel.close();
}
}
public @NonNull String getErrorMessage(int reason) {
switch (reason) {
case WifiP2pManager.ERROR:
return "Internal error";
case WifiP2pManager.BUSY:
return "Framework busy, unable to service request";
case WifiP2pManager.P2P_UNSUPPORTED:
return "P2P unsupported on this device";
default:
return ("Unknown error code - " + reason);
}
}
public static @NonNull String getDeviceStatus(int status) {
if (BuildConfig.DEBUG) Log.d(TAG, "Peer Status: " + status);
switch (status) {
case WifiP2pDevice.AVAILABLE:
return "Available";
case WifiP2pDevice.INVITED:
return "Invited";
case WifiP2pDevice.CONNECTED:
return "Connected";
case WifiP2pDevice.FAILED:
return "Failed";
case WifiP2pDevice.UNAVAILABLE:
return "Unavailable";
default:
return "Unknown";
}
}
public static @NonNull String getFileName(@NonNull Uri fileUri) {
String fileUriString = fileUri.toString();
// Returns text after location of last slash in the file path
return fileUriString.substring(fileUriString.lastIndexOf('/') + 1);
}
public void displayToast(int stringResourceId, @NonNull String templateValue, int duration) {
showToast(activity, activity.getString(stringResourceId, templateValue), duration);
}
public void displayToast(int stringResourceId, int duration) {
showToast(activity, stringResourceId, duration);
}
public void onFileTransferAsyncTaskComplete(boolean wereAllFilesTransferred) {
if (wereAllFilesTransferred) {
displayToast(R.string.file_transfer_complete, Toast.LENGTH_LONG);
} else {
displayToast(R.string.error_during_transfer, Toast.LENGTH_LONG);
}
callbacks.onFileTransferComplete();
}
public interface Callbacks {
void onUserDeviceDetailsAvailable(@Nullable WifiP2pDevice userDevice);
void onConnectionToPeersLost();
void updateListOfAvailablePeers(@NonNull WifiP2pDeviceList peers);
void onFilesForTransferAvailable(@NonNull ArrayList<FileItem> filesForTransfer);
void onFileStatusChanged(int itemIndex);
void onFileTransferComplete();
}
}

View File

@ -0,0 +1,389 @@
/*
* Kiwix Android
* Copyright (c) 2019 Kiwix <android.kiwix.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.kiwix.kiwixmobile.local_file_transfer
import android.app.Activity
import android.content.BroadcastReceiver
import android.content.Context
import android.content.IntentFilter
import android.net.Uri
import android.net.wifi.WpsInfo
import android.net.wifi.p2p.WifiP2pConfig
import android.net.wifi.p2p.WifiP2pDevice
import android.net.wifi.p2p.WifiP2pDeviceList
import android.net.wifi.p2p.WifiP2pInfo
import android.net.wifi.p2p.WifiP2pManager
import android.net.wifi.p2p.WifiP2pManager.ActionListener
import android.net.wifi.p2p.WifiP2pManager.Channel
import android.net.wifi.p2p.WifiP2pManager.ChannelListener
import android.net.wifi.p2p.WifiP2pManager.ConnectionInfoListener
import android.net.wifi.p2p.WifiP2pManager.PeerListListener
import android.os.AsyncTask
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES
import android.os.Looper
import android.util.Log
import android.widget.Toast
import org.kiwix.kiwixmobile.R
import org.kiwix.kiwixmobile.core.BuildConfig
import org.kiwix.kiwixmobile.core.extensions.toast
import org.kiwix.kiwixmobile.core.utils.AlertDialogShower
import org.kiwix.kiwixmobile.core.utils.KiwixDialog.FileTransferConfirmation
import org.kiwix.kiwixmobile.core.utils.SharedPreferenceUtil
import org.kiwix.kiwixmobile.local_file_transfer.FileItem.FileStatus
import org.kiwix.kiwixmobile.local_file_transfer.KiwixWifiP2pBroadcastReceiver.P2pEventListener
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.net.InetAddress
import java.util.ArrayList
import javax.inject.Inject
/**
* Manager for the Wifi-P2p API, used in the local file transfer module
*/
@SuppressWarnings("MissingPermission")
class WifiDirectManager @Inject constructor(
private val activity: Activity,
private val sharedPreferenceUtil: SharedPreferenceUtil,
private val alertDialogShower: AlertDialogShower
) : ChannelListener, PeerListListener, ConnectionInfoListener, P2pEventListener {
private val callbacks = activity as Callbacks
/* Helper methods */
/* Variables related to the WiFi P2P API */
// Whether WiFi has been enabled or not
var isWifiP2pEnabled = false
private set
// Whether channel has retried connecting previously
private var shouldRetry = true
// Overall manager of Wifi p2p connections for the module
private lateinit var manager: WifiP2pManager
// Interface to the device's underlying wifi-p2p framework
private lateinit var channel: Channel
// For receiving the broadcasts given by above filter
private lateinit var receiver: BroadcastReceiver
// Corresponds to P2P group formed between the two devices
private lateinit var groupInfo: WifiP2pInfo
private lateinit var senderSelectedPeerDevice: WifiP2pDevice
private var peerGroupHandshakeAsyncTask: PeerGroupHandshakeAsyncTask? = null
private var senderDeviceAsyncTask: SenderDeviceAsyncTask? = null
private var receiverDeviceAsyncTask: ReceiverDeviceAsyncTask? = null
private lateinit var selectedPeerDeviceInetAddress: InetAddress
// IP address of the file receiving device
private lateinit var fileReceiverDeviceAddress: InetAddress
private lateinit var filesForTransfer: ArrayList<FileItem>
// Whether the device is the file sender or not
var isFileSender = false
private set
private var hasSenderStartedConnection = false
/* Initialisations for using the WiFi P2P API */
fun startWifiDirectManager(filesForTransfer: ArrayList<FileItem>) {
this.filesForTransfer = filesForTransfer
isFileSender = filesForTransfer.isNotEmpty()
manager = activity.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
channel = manager.initialize(activity, Looper.getMainLooper(), null)
registerWifiDirectBroadcastReceiver()
}
private fun registerWifiDirectBroadcastReceiver() {
receiver = KiwixWifiP2pBroadcastReceiver(this)
// For specifying broadcasts (of the P2P API) that the module needs to respond to
val intentFilter = IntentFilter()
intentFilter.apply {
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)
}
activity.registerReceiver(receiver, intentFilter)
}
private fun unregisterWifiDirectBroadcastReceiver() = activity.unregisterReceiver(receiver)
fun discoverPeerDevices() {
manager.discoverPeers(channel, object : ActionListener {
override fun onSuccess() {
activity.toast(R.string.discovery_initiated, Toast.LENGTH_SHORT)
}
override fun onFailure(reason: Int) {
Log.d(
TAG, "${activity.getString(R.string.discovery_failed)}: " +
getErrorMessage(reason)
)
activity.toast(R.string.discovery_failed, Toast.LENGTH_SHORT)
}
})
}
/* From KiwixWifiP2pBroadcastReceiver.P2pEventListener callback-interface*/
override fun onWifiP2pStateChanged(isEnabled: Boolean) {
isWifiP2pEnabled = isEnabled
if (!isWifiP2pEnabled) {
activity.toast(R.string.discovery_needs_wifi, Toast.LENGTH_SHORT)
callbacks.onConnectionToPeersLost()
}
Log.d(TAG, "WiFi P2P state changed - $isWifiP2pEnabled")
}
override fun onPeersChanged() {
/* List of available peers has changed, so request & use the new list through
* PeerListListener.requestPeers() callback */
manager.requestPeers(channel, this)
Log.d(TAG, "P2P peers changed")
}
override fun onConnectionChanged(isConnected: Boolean) {
if (isConnected) {
// Request connection info about the wifi p2p group formed upon connection
manager.requestConnectionInfo(channel, this)
} else {
// Not connected after connection change -> Disconnected
callbacks.onConnectionToPeersLost()
}
}
// Update UI with wifi-direct details about the user device
override fun onDeviceChanged(userDevice: WifiP2pDevice?) =
callbacks.onUserDeviceDetailsAvailable(userDevice)
/* From WifiP2pManager.ChannelListener interface */
override fun onChannelDisconnected() {
// Upon disconnection, retry one more time
if (shouldRetry) {
Log.d(TAG, "Channel lost, trying again")
callbacks.onConnectionToPeersLost()
shouldRetry = false
manager.initialize(activity, Looper.getMainLooper(), this)
} else {
activity.toast(R.string.severe_loss_error, Toast.LENGTH_LONG)
}
}
/* From WifiP2pManager.PeerListListener callback-interface */
override fun onPeersAvailable(peers: WifiP2pDeviceList) =
callbacks.updateListOfAvailablePeers(peers)
/* From WifiP2pManager.ConnectionInfoListener callback-interface */
override fun onConnectionInfoAvailable(groupInfo: WifiP2pInfo) {
/* Devices have successfully connected, and 'info' holds information about the wifi p2p group formed */
this.groupInfo = groupInfo
performHandshakeWithSelectedPeerDevice()
}
val isGroupFormed: Boolean
get() = groupInfo.groupFormed
val isGroupOwner: Boolean
get() = groupInfo.isGroupOwner
val groupOwnerAddress: InetAddress
get() = groupInfo.groupOwnerAddress
fun sendToDevice(senderSelectedPeerDevice: WifiP2pDevice) {
/* Connection can only be initiated by user of the sender device, & only when transfer has not been started */
if (isFileSender && !hasSenderStartedConnection) {
this.senderSelectedPeerDevice = senderSelectedPeerDevice
alertDialogShower.show(
FileTransferConfirmation(senderSelectedPeerDevice.deviceName), {
hasSenderStartedConnection = true
connect()
activity.toast(R.string.performing_handshake, Toast.LENGTH_LONG)
})
}
}
private fun connect() {
val config = WifiP2pConfig().apply {
deviceAddress = senderSelectedPeerDevice.deviceAddress
wps.setup = WpsInfo.PBC
}
manager.connect(channel, config, object : ActionListener {
override fun onSuccess() {
// UI updated from broadcast receiver
}
override fun onFailure(reason: Int) {
val errorMessage = getErrorMessage(reason)
Log.d(TAG, activity.getString(R.string.connection_failed) + ": " + errorMessage)
activity.toast(R.string.connection_failed, Toast.LENGTH_LONG)
}
})
}
private fun performHandshakeWithSelectedPeerDevice() {
if (BuildConfig.DEBUG) {
Log.d(TAG, "Starting handshake")
}
peerGroupHandshakeAsyncTask = PeerGroupHandshakeAsyncTask(this)
.also { it.execute() }
}
val totalFilesForTransfer: Int
get() = filesForTransfer.size
fun getFilesForTransfer() = filesForTransfer
fun setFilesForTransfer(fileItems: ArrayList<FileItem>) {
filesForTransfer = fileItems
}
val zimStorageRootPath
get() = sharedPreferenceUtil.prefStorage + "/Kiwix/"
fun getFileReceiverDeviceAddress() = fileReceiverDeviceAddress
fun setClientAddress(clientAddress: InetAddress) {
// If control reaches here, means handshake was successful
selectedPeerDeviceInetAddress = clientAddress
startFileTransfer()
}
private fun startFileTransfer() {
if (isGroupFormed) {
if (isFileSender) {
Log.d(LocalFileTransferActivity.TAG, "Starting file transfer")
fileReceiverDeviceAddress =
if (isGroupOwner) selectedPeerDeviceInetAddress else groupOwnerAddress
activity.toast(R.string.preparing_files, Toast.LENGTH_LONG)
senderDeviceAsyncTask = SenderDeviceAsyncTask(this, activity).also {
it.execute()
}
} else {
callbacks.onFilesForTransferAvailable(filesForTransfer)
receiverDeviceAsyncTask = ReceiverDeviceAsyncTask(this).also {
it.execute()
}
}
}
}
fun changeStatus(itemIndex: Int, @FileStatus status: Int) {
filesForTransfer[itemIndex].fileStatus = status
callbacks.onFileStatusChanged(itemIndex)
if (status == FileStatus.ERROR) {
displayToast(
R.string.error_transferring, filesForTransfer[itemIndex].fileName,
Toast.LENGTH_SHORT
)
}
}
private fun cancelAsyncTasks(vararg tasks: AsyncTask<*, *, *>?) =
tasks.forEach {
it?.cancel(true)
}
fun stopWifiDirectManager() {
cancelAsyncTasks(peerGroupHandshakeAsyncTask, senderDeviceAsyncTask, receiverDeviceAsyncTask)
if (isFileSender) {
closeChannel()
} else {
disconnect()
}
unregisterWifiDirectBroadcastReceiver()
}
private fun disconnect() {
manager.removeGroup(channel, object : ActionListener {
override fun onFailure(reasonCode: Int) {
Log.d(TAG, "Disconnect failed. Reason: $reasonCode")
closeChannel()
}
override fun onSuccess() {
Log.d(TAG, "Disconnect successful")
closeChannel()
}
})
}
private fun closeChannel() {
if (VERSION.SDK_INT >= VERSION_CODES.O_MR1) {
channel.close()
}
}
fun getErrorMessage(reason: Int): String {
return when (reason) {
WifiP2pManager.ERROR -> "Internal error"
WifiP2pManager.BUSY -> "Framework busy, unable to service request"
WifiP2pManager.P2P_UNSUPPORTED -> "P2P unsupported on this device"
else -> "Unknown error code - $reason"
}
}
fun displayToast(stringResourceId: Int, templateValue: String, duration: Int) =
activity.toast(activity.getString(stringResourceId, templateValue), duration)
fun onFileTransferAsyncTaskComplete(wereAllFilesTransferred: Boolean) {
if (wereAllFilesTransferred) {
activity.toast(R.string.file_transfer_complete, Toast.LENGTH_LONG)
} else {
activity.toast(R.string.error_during_transfer, Toast.LENGTH_LONG)
}
callbacks.onFileTransferComplete()
}
interface Callbacks {
fun onUserDeviceDetailsAvailable(userDevice: WifiP2pDevice?)
fun onConnectionToPeersLost()
fun updateListOfAvailablePeers(peers: WifiP2pDeviceList)
fun onFilesForTransferAvailable(filesForTransfer: ArrayList<FileItem>)
fun onFileStatusChanged(itemIndex: Int)
fun onFileTransferComplete()
}
companion object {
private const val TAG = "WifiDirectManager"
@JvmField var FILE_TRANSFER_PORT = 8008
@JvmStatic @Throws(IOException::class) fun copyToOutputStream(
inputStream: InputStream,
outputStream: OutputStream
) {
inputStream.use { input -> outputStream.use { output -> input.copyTo(output) } }
Log.d(LocalFileTransferActivity.TAG, "Both streams closed")
}
@JvmStatic fun getDeviceStatus(status: Int): String {
if (BuildConfig.DEBUG) Log.d(TAG, "Peer Status: $status")
return when (status) {
WifiP2pDevice.AVAILABLE -> "Available"
WifiP2pDevice.INVITED -> "Invited"
WifiP2pDevice.CONNECTED -> "Connected"
WifiP2pDevice.FAILED -> "Failed"
WifiP2pDevice.UNAVAILABLE -> "Unavailable"
else -> "Unknown"
}
}
// Returns text after location of last slash in the file path
@JvmStatic fun getFileName(fileUri: Uri) = "$fileUri".substringAfterLast('/')
}
}