diff --git a/app/detekt_baseline.xml b/app/detekt_baseline.xml
index a2dff3391..5a94ce9eb 100644
--- a/app/detekt_baseline.xml
+++ b/app/detekt_baseline.xml
@@ -34,6 +34,7 @@
PackageNaming:ShareFiles.kt$package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
PackageNaming:SimplePageChangeListener.kt$package org.kiwix.kiwixmobile.zim_manager
PackageNaming:StartMultiSelection.kt$package org.kiwix.kiwixmobile.zim_manager.fileselect_view.effects
+ PackageNaming:WifiDirectManager.kt$package org.kiwix.kiwixmobile.local_file_transfer
PackageNaming:WifiP2pDelegate.kt$package org.kiwix.kiwixmobile.local_file_transfer.adapter
PackageNaming:WifiP2pViewHolder.kt$package org.kiwix.kiwixmobile.local_file_transfer.adapter
PackageNaming:WifiPeerListAdapter.kt$package org.kiwix.kiwixmobile.local_file_transfer.adapter
@@ -50,6 +51,7 @@
TooManyFunctions:KiwixActivityComponent.kt$KiwixActivityComponent$KiwixActivityComponent
TooManyFunctions:KiwixMainActivity.kt$KiwixMainActivity$KiwixMainActivity
TooManyFunctions:LibraryFragment.kt$LibraryFragment$LibraryFragment
+ TooManyFunctions:WifiDirectManager.kt$WifiDirectManager$WifiDirectManager
TooManyFunctions:ZimFileSelectFragment.kt$ZimFileSelectFragment$ZimFileSelectFragment
TooManyFunctions:ZimHostActivity.kt$ZimHostActivity$ZimHostActivity
TooManyFunctions:ZimManageViewModel.kt$ZimManageViewModel$ZimManageViewModel
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/local_file_transfer/WifiDirectManager.java b/app/src/main/java/org/kiwix/kiwixmobile/local_file_transfer/WifiDirectManager.java
deleted file mode 100644
index 45f840ba5..000000000
--- a/app/src/main/java/org/kiwix/kiwixmobile/local_file_transfer/WifiDirectManager.java
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Kiwix Android
- * Copyright (c) 2019 Kiwix
- * 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 .
- *
- */
-
-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 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 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() {
- @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 getFilesForTransfer() {
- return filesForTransfer;
- }
-
- public void setFilesForTransfer(@NonNull ArrayList 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 filesForTransfer);
-
- void onFileStatusChanged(int itemIndex);
-
- void onFileTransferComplete();
- }
-}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/local_file_transfer/WifiDirectManager.kt b/app/src/main/java/org/kiwix/kiwixmobile/local_file_transfer/WifiDirectManager.kt
new file mode 100644
index 000000000..7777331fc
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/local_file_transfer/WifiDirectManager.kt
@@ -0,0 +1,389 @@
+/*
+ * Kiwix Android
+ * Copyright (c) 2019 Kiwix
+ * 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 .
+ *
+ */
+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
+
+ // 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) {
+ 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) {
+ 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)
+ 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('/')
+ }
+}