diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 7d0f9cf41..55b3528f0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,6 +14,10 @@
+
+
+
+
+
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java
index 3a4376944..91799d701 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/ZimManageActivity.java
@@ -43,6 +43,7 @@ import org.kiwix.kiwixmobile.base.BaseActivity;
import org.kiwix.kiwixmobile.language.LanguageActivity;
import org.kiwix.kiwixmobile.main.MainActivity;
import org.kiwix.kiwixmobile.models.Language;
+import org.kiwix.kiwixmobile.zim_manager.local_file_transfer.LocalFileTransferActivity;
import static org.kiwix.kiwixmobile.utils.Constants.TAG_KIWIX;
@@ -234,6 +235,13 @@ public class ZimManageActivity extends BaseActivity implements ZimManageViewCall
showLanguageSelect();
}
}
+ return super.onOptionsItemSelected(item);
+
+ case R.id.get_zim_nearby_device:
+ Intent localFileTransferIntent = new Intent(this, LocalFileTransferActivity.class);
+ startActivity(localFileTransferIntent);
+ return true;
+
default:
return super.onOptionsItemSelected(item);
}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/DeviceListFragment.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/DeviceListFragment.java
new file mode 100644
index 000000000..c8ffb6229
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/DeviceListFragment.java
@@ -0,0 +1,588 @@
+package org.kiwix.kiwixmobile.zim_manager.local_file_transfer;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+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.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.FileProvider;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import androidx.fragment.app.ListFragment;
+
+import org.kiwix.kiwixmobile.BuildConfig;
+import org.kiwix.kiwixmobile.R;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.kiwix.kiwixmobile.zim_manager.local_file_transfer.DeviceListFragment.TAG;
+import static org.kiwix.kiwixmobile.zim_manager.local_file_transfer.LocalFileTransferActivity.filePath;
+
+public class DeviceListFragment extends ListFragment implements WifiP2pManager.PeerListListener, WifiP2pManager.ConnectionInfoListener, PeerGroupHandshakeAsyncTask.ClientAddressReady {
+
+ public static String TAG = "DeviceListFragment";
+
+ private View fragRootView = null;
+ private List peerDevices = new ArrayList();
+ private WifiP2pDevice userDevice;
+ private WifiP2pDevice selectedPeerDevice = null;
+ private InetAddress selectedPeerDeviceInetAddress;
+ private WifiP2pInfo groupInfo;
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ this.setListAdapter(new WifiPeerListAdapter(getActivity(), R.layout.row_peer_device, peerDevices));
+
+ ImageButton editUserDeviceName = getActivity().findViewById(R.id.btn_edit_device_name);
+ editUserDeviceName.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if(userDevice != null) {
+ //TODO: Dialog to take input & call this method:
+ //((DeviceActionListener) getActivity()).changeDeviceName("Sood");
+
+ if(((LocalFileTransferActivity) getActivity()).isWifiP2pEnabled()) {
+ FragmentTransaction fragmentTransaction = getActivity().getSupportFragmentManager().beginTransaction();
+ Fragment prev = getActivity().getSupportFragmentManager().findFragmentByTag("EditDeviceNameDialog");
+ if (prev != null) {
+ fragmentTransaction.remove(prev); // To prevent multiple instances of the DialogFragment
+ }
+ fragmentTransaction.addToBackStack(null);
+
+ EditDeviceNameDialog dialogFragment = new EditDeviceNameDialog();
+ // For DialogFragments, show() handles the fragment commit and display
+ dialogFragment.show(fragmentTransaction, "EditDeviceNameDialog");
+ } else {
+ Toast.makeText(getActivity(), "Enable WiFi P2P to change device name", Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ fragRootView = inflater.inflate(R.layout.fragment_device_list, null);
+ return fragRootView;
+ }
+
+ public WifiP2pDevice getUserDevice() { return userDevice; }
+
+ private static String getDeviceStatus(int status) {
+
+ Log.d(LocalFileTransferActivity.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";
+ }
+ }
+
+ @Override
+ public void onListItemClick(ListView l, View v, int position, long id) {
+ selectedPeerDevice = (WifiP2pDevice) getListAdapter().getItem(position);
+ Toast.makeText(getActivity(), selectedPeerDevice.deviceName, Toast.LENGTH_SHORT).show();
+
+ // TODO: Set sender depending upon receiving file URI in activity opening intent
+ //((LocalFileTransferActivity) getActivity()).setFileSender();
+ ((DeviceActionListener) getActivity()).connect(selectedPeerDevice);
+ }
+
+ public void updateUserDevice(WifiP2pDevice device) {
+ this.userDevice = device;
+
+ if(userDevice != null) {
+ TextView deviceName = fragRootView.findViewById(R.id.text_view_device_name);
+ TextView deviceStatus = fragRootView.findViewById(R.id.text_view_device_status);
+
+ if(deviceName != null) deviceName.setText(userDevice.deviceName);
+ if(deviceStatus != null) deviceStatus.setText(getDeviceStatus(userDevice.status));
+ }
+ }
+
+ @Override
+ public void onPeersAvailable(WifiP2pDeviceList peers) {
+
+ ProgressBar searchingPeersProgressBar = fragRootView.findViewById(R.id.progress_bar_searching_peers);
+ searchingPeersProgressBar.setVisibility(View.GONE);
+ FrameLayout frameLayoutPeerDevices = fragRootView.findViewById(R.id.frame_layout_peer_devices);
+ frameLayoutPeerDevices.setVisibility(View.VISIBLE);
+
+ peerDevices.clear();
+ peerDevices.addAll(peers.getDeviceList());
+ ((WifiPeerListAdapter) getListAdapter()).notifyDataSetChanged();
+
+ if(peerDevices.size() == 0) {
+ Log.d(LocalFileTransferActivity.TAG, "No devices found");
+ }
+ }
+
+ public void clearPeers() {
+ peerDevices.clear();
+ ((WifiPeerListAdapter) getListAdapter()).notifyDataSetChanged();
+ }
+
+ public void onInitiateDiscovery() {
+ ProgressBar searchingPeersProgressBar = fragRootView.findViewById(R.id.progress_bar_searching_peers);
+ searchingPeersProgressBar.setVisibility(View.VISIBLE);
+ FrameLayout frameLayoutPeerDevices = fragRootView.findViewById(R.id.frame_layout_peer_devices);
+ frameLayoutPeerDevices.setVisibility(View.INVISIBLE);
+ }
+
+ @Override
+ public void onConnectionInfoAvailable(WifiP2pInfo info) {
+ groupInfo = info;
+
+ //TODO: Show progress of file transfer process
+
+ new PeerGroupHandshakeAsyncTask(this, groupInfo).execute();
+
+ /*if(groupInfo.groupFormed && groupInfo.isGroupOwner) {
+
+ try {
+ ServerSocket serverSocket = new ServerSocket(8008);
+ serverSocket.setReuseAddress(true);
+ Socket client = serverSocket.accept();
+ ObjectInputStream objectInputStream = new ObjectInputStream(client.getInputStream());
+ Object object = objectInputStream.readObject();
+ if (object.getClass().equals(String.class) && ((String) object).equals("Request Kiwix File Sharing")) {
+ Log.d(TAG, "Client IP address: "+ client.getInetAddress());
+ selectedPeerDeviceInetAddress = client.getInetAddress();
+ }
+ } catch (Exception e) {
+ //Log.d(TAG, e.getMessage());
+ e.printStackTrace();
+ }
+
+ } else if(groupInfo.groupFormed && !groupInfo.isGroupOwner) {
+
+ try {
+ Socket socket = new Socket();
+ socket.setReuseAddress(true);
+ socket.connect((new InetSocketAddress(groupInfo.groupOwnerAddress.getHostAddress(), 8008)), 15000);
+ OutputStream os = socket.getOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(os);
+ oos.writeObject(new String("Request Kiwix File Sharing"));
+ oos.close();
+ os.close();
+ socket.close();
+ } catch (Exception e) {
+ //Log.d(TAG, e.getMessage());
+ e.printStackTrace();
+ }
+
+ *//*Intent serviceIntent = new Intent(getActivity(), FileTransferService.class);
+ serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);
+ serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH, Environment.getExternalStorageDirectory() + "/MainPage.txt");
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS, groupInfo.groupOwnerAddress.getHostAddress());
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT, 8008);
+ getActivity().startService(serviceIntent);*//*
+ }
+
+ if(groupInfo.groupFormed && ((MainActivity) getActivity()).isFileSender()) {
+ Toast.makeText(getActivity(), "Sending file to "+selectedPeerDevice.deviceAddress+"\nSelf: "+userDevice.deviceAddress, Toast.LENGTH_SHORT).show();
+ Log.d(MainActivity.TAG, "Starting file transfer");
+
+ new AlertDialog.Builder(getActivity())
+ .setMessage("Transfer file?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent serviceIntent = new Intent(getActivity(), FileTransferService.class);
+ serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);
+ serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH, FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".fileprovider", new File(filePath)).toString());
+
+ InetAddress fileReceiverAddress;
+ if(groupInfo.isGroupOwner) fileReceiverAddress = selectedPeerDeviceInetAddress;
+ else fileReceiverAddress = groupInfo.groupOwnerAddress;
+
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS, fileReceiverAddress.getHostAddress());//getIPFromMac(selectedPeerDevice.deviceAddress));
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT, 8008);
+ getActivity().startService(serviceIntent);
+ }
+ })
+ .setNegativeButton("No", null)
+ .show();
+
+
+ } else {
+ Toast.makeText(getActivity(), "File receiving device", Toast.LENGTH_SHORT).show();
+ new FileServerAsyncTask(getActivity()).execute();
+ }*/
+
+ // TODO: Disable onclick listener (of list) for connecting to devices
+ }
+
+ @Override
+ public void setClientAddress(InetAddress clientAddress) {
+ if(clientAddress != null) selectedPeerDeviceInetAddress = clientAddress;
+
+ startFileTransfer();
+ }
+
+ private void startFileTransfer() {
+ if(groupInfo.groupFormed && !((LocalFileTransferActivity) getActivity()).isFileSender()) {
+ Toast.makeText(getActivity(), "File receiving device", Toast.LENGTH_SHORT).show();
+ new FileServerAsyncTask(getActivity()).execute();
+
+ } else if(groupInfo.groupFormed) {
+ {
+ Toast.makeText(getActivity(), "Sending file to "+selectedPeerDevice.deviceAddress+"\nSelf: "+userDevice.deviceAddress, Toast.LENGTH_SHORT).show();
+ Log.d(LocalFileTransferActivity.TAG, "Starting file transfer");
+
+ new AlertDialog.Builder(getActivity())
+ .setMessage("Transfer file?")
+ .setPositiveButton("Yes", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent serviceIntent = new Intent(getActivity(), FileTransferService.class);
+ serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);
+ serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_URI, filePath.toString());//FileProvider.getUriForFile(getActivity(), BuildConfig.APPLICATION_ID+".fileprovider", new File(filePath)).toString());
+
+ InetAddress fileReceiverAddress;
+ if(groupInfo.isGroupOwner) fileReceiverAddress = selectedPeerDeviceInetAddress;
+ else fileReceiverAddress = groupInfo.groupOwnerAddress;
+
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS, fileReceiverAddress.getHostAddress());//getIPFromMac(selectedPeerDevice.deviceAddress));
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT, 8008);
+ getActivity().startService(serviceIntent);
+ }
+ })
+ .setNegativeButton("No", null)
+ .show();
+ }
+ }
+ }
+
+ private class WifiPeerListAdapter extends ArrayAdapter {
+
+ private List listItems;
+
+ public WifiPeerListAdapter(@NonNull Context context, int resource, List objects) {
+ super(context, resource, objects);
+ this.listItems = objects;
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+
+ View rowView = convertView;
+ if(rowView == null) {
+ LayoutInflater layoutInflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ rowView = layoutInflater.inflate(R.layout.row_peer_device, parent, false);
+ }
+
+ WifiP2pDevice device = listItems.get(position);
+
+ if(device != null) {
+ TextView deviceName = rowView.findViewById(R.id.row_device_name);
+ TextView deviceStatus = rowView.findViewById(R.id.row_device_status);
+
+ if(deviceName != null) deviceName.setText(device.deviceName);
+ if(deviceStatus != null) deviceStatus.setText(getDeviceStatus(device.status));
+ }
+
+ return rowView;
+ }
+ }
+
+ public interface DeviceActionListener {
+
+ void changeDeviceName(String deviceNewName);
+
+ /*void showDetails(WifiP2pDevice device);*/
+
+ void cancelDisconnect();
+
+ void connect(WifiP2pDevice peerDevice);
+
+ void disconnect();
+ }
+
+ //private class EditNameDialog e
+ /*private void requestEnableLocationServices() {
+
+ *//*Toast.makeText(MainActivity.this, "Enable location to allow detection of peers", Toast.LENGTH_LONG);
+ startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 1);*//*
+ *//*new AlertDialog.Builder(this)
+ .setMessage("Enable location to allow detection of peers")
+ .setPositiveButton("Open Location Settings", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface paramDialogInterface, int paramInt) {
+ paramDialogInterface.cancel();
+ startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 1);
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ }
+ })
+ .show();*//*
+
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag("LocationDialog");
+ if(prev != null) {
+ fragmentTransaction.remove(prev); // To prevent multiple instances of the DialogFragment
+ }
+ fragmentTransaction.addToBackStack(null);
+
+ RequestEnableLocationServicesDialog dialogFragment = new RequestEnableLocationServicesDialog();
+ // For DialogFragments, show() handles the fragment commit and display
+ dialogFragment.show(fragmentTransaction, "LocationDialog");
+
+ }
+
+ public static class RequestEnableLocationServicesDialog extends DialogFragment {
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage("Enable location to allow detection of peers")
+ .setPositiveButton("Open Location Settings", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface paramDialogInterface, int paramInt) {
+ paramDialogInterface.cancel();
+ startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 1);
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //TODO: Close activity
+
+ }
+ });
+
+ return builder.create();
+ }
+ }*/
+
+ public static class EditDeviceNameDialog extends DialogFragment {
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+ builder.setView(R.layout.dialog_edit_device_name)
+ .setPositiveButton("Update", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ EditText changeDeviceName = EditDeviceNameDialog.this.getDialog().findViewById(R.id.edit_text_change_device_name);
+ String deviceNewName = changeDeviceName.getText().toString();
+ //Toast.makeText(getActivity(), "Changing name to: " + deviceNewName.getText().toString(), Toast.LENGTH_SHORT).show();
+
+ if(deviceNewName != null && !deviceNewName.equals("")) {
+ ((DeviceActionListener) getActivity()).changeDeviceName(deviceNewName);
+ } else {
+ Toast.makeText(getActivity(), "Error: Empty name field", Toast.LENGTH_SHORT).show();
+ }
+
+ //dialog.cancel();
+ }
+ })
+ .setNeutralButton("Dismiss", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ public static class FileServerAsyncTask extends AsyncTask {
+
+ private Context context;
+ //private View statusView
+
+ public FileServerAsyncTask(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ protected String doInBackground(Void... voids) {
+ try {
+ ServerSocket serverSocket = new ServerSocket(8008);
+ Log.d(LocalFileTransferActivity.TAG, "Server: Socket opened at 8008");
+ Socket client = serverSocket.accept();
+ Log.d(LocalFileTransferActivity.TAG, "Server: Client connected");
+
+ // File selector, file not exists,
+ //TODO: Change to appropriate file-path
+ final File clientNoteFileLocation = new File(Environment.getExternalStorageDirectory() + "/KiwixWifi/temp.zim");
+ File dirs = new File(clientNoteFileLocation.getParent());
+ if(!dirs.exists()) {
+ Log.d(LocalFileTransferActivity.TAG, "Parent creation result: "+dirs.mkdirs());
+ } else {
+ Log.d(LocalFileTransferActivity.TAG, "Parent directories exist");
+ }
+
+ Log.d(LocalFileTransferActivity.TAG, "File creation: "+clientNoteFileLocation.createNewFile());
+
+ Log.d(LocalFileTransferActivity.TAG, "Copying files");
+ InputStream inputStream = client.getInputStream();
+ copyFile(inputStream, new FileOutputStream(clientNoteFileLocation));
+ serverSocket.close();
+ return clientNoteFileLocation.getAbsolutePath();
+
+ } catch (IOException e) {
+ Log.e(LocalFileTransferActivity.TAG, e.getMessage());
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(String s) {
+ super.onPostExecute(s);
+ Toast.makeText(context, "File transfer complete", Toast.LENGTH_LONG).show();
+ Log.d(LocalFileTransferActivity.TAG, "File transfer complete");
+
+ /*File recvFile = new File(filePath);
+ Uri fileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID+".fileprovider",recvFile);
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setDataAndType(fileUri, "text/plain");
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ context.startActivity(intent);*/
+ }
+ }
+
+ public static boolean copyFile(InputStream inputStream, OutputStream out) {
+ byte buf[] = new byte[1024];
+ int len;
+ try {
+ while ((len = inputStream.read(buf)) != -1) {
+ out.write(buf, 0, len);
+
+ }
+ out.close();
+ inputStream.close();
+ } catch (IOException e) {
+ Log.d(LocalFileTransferActivity.TAG, e.toString());
+ return false;
+ }
+ return true;
+ }
+}
+
+class PeerGroupHandshakeAsyncTask extends AsyncTask {
+
+ private DeviceListFragment deviceListFragment;
+ private WifiP2pInfo groupInfo;
+
+ public PeerGroupHandshakeAsyncTask(DeviceListFragment deviceListFragment, WifiP2pInfo groupInfo) {
+ this.deviceListFragment = deviceListFragment;
+ this.groupInfo = groupInfo;
+ }
+
+ @Override
+ protected InetAddress doInBackground(Void... voids) {
+ if(groupInfo.groupFormed && groupInfo.isGroupOwner) {
+
+ try {
+ ServerSocket serverSocket = new ServerSocket(8009);
+ serverSocket.setReuseAddress(true);
+ Socket client = serverSocket.accept();
+ ObjectInputStream objectInputStream = new ObjectInputStream(client.getInputStream());
+ Object object = objectInputStream.readObject();
+ if (object.getClass().equals(String.class) && ((String) object).equals("Request Kiwix File Sharing")) {
+ Log.d(TAG, "Client IP address: "+ client.getInetAddress());
+ //selectedPeerDeviceInetAddress = client.getInetAddress();
+ }
+ return client.getInetAddress();
+ } catch (Exception e) {
+ //Log.d(TAG, e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+
+ } else if(groupInfo.groupFormed && !groupInfo.isGroupOwner) {
+
+ try {
+ Socket socket = new Socket();
+ socket.setReuseAddress(true);
+ for(int i = 0; i < 100000000; i++);
+ socket.connect((new InetSocketAddress(groupInfo.groupOwnerAddress.getHostAddress(), 8009)), 15000);
+ OutputStream os = socket.getOutputStream();
+ ObjectOutputStream oos = new ObjectOutputStream(os);
+ oos.writeObject(new String("Request Kiwix File Sharing"));
+ oos.close();
+ os.close();
+ socket.close();
+
+ return null;
+ } catch (Exception e) {
+ //Log.d(TAG, e.getMessage());
+ e.printStackTrace();
+ return null;
+ }
+
+ /*Intent serviceIntent = new Intent(getActivity(), FileTransferService.class);
+ serviceIntent.setAction(FileTransferService.ACTION_SEND_FILE);
+ serviceIntent.putExtra(FileTransferService.EXTRAS_FILE_PATH, Environment.getExternalStorageDirectory() + "/MainPage.txt");
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_ADDRESS, groupInfo.groupOwnerAddress.getHostAddress());
+ serviceIntent.putExtra(FileTransferService.EXTRAS_GROUP_OWNER_PORT, 8008);
+ getActivity().startService(serviceIntent);*/
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(InetAddress inetAddress) {
+ (deviceListFragment).setClientAddress(inetAddress);
+ }
+
+ public interface ClientAddressReady {
+ void setClientAddress(InetAddress clientAddress);
+ }
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/FileTransferService.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/FileTransferService.java
new file mode 100644
index 000000000..7cc8359da
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/FileTransferService.java
@@ -0,0 +1,92 @@
+
+package org.kiwix.kiwixmobile.zim_manager.local_file_transfer;
+
+import android.app.IntentService;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+/**
+ * A service that process each file transfer request i.e Intent by opening a
+ * socket connection with the WiFi Direct Group Owner and writing the file
+ */
+public class FileTransferService extends IntentService {
+
+ private static final int SOCKET_TIMEOUT = 15000;
+ public static final String ACTION_SEND_FILE = "dev.sood.hermes.SEND_FILE";
+ public static final String EXTRAS_FILE_URI = "file_url";
+ public static final String EXTRAS_GROUP_OWNER_ADDRESS = "go_host";
+ public static final String EXTRAS_GROUP_OWNER_PORT = "go_port";
+
+ public FileTransferService(String name) {
+ super(name);
+ Log.d(LocalFileTransferActivity.TAG, "In FileTransferService constructor");
+ }
+
+ public FileTransferService() {
+ super("FileTransferService");
+ Log.d(LocalFileTransferActivity.TAG, "In FileTransferService constructor");
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see android.app.IntentService#onHandleIntent(android.content.Intent)
+ */
+ @Override
+ protected void onHandleIntent(Intent intent) {
+
+ Log.d(LocalFileTransferActivity.TAG, "In onHandleIntent");
+
+ Context context = getApplicationContext();
+ if (intent.getAction().equals(ACTION_SEND_FILE)) {
+
+ Log.d(LocalFileTransferActivity.TAG, "In main if-else");
+
+ String fileUriString = intent.getExtras().getString(EXTRAS_FILE_URI);
+ String host = intent.getExtras().getString(EXTRAS_GROUP_OWNER_ADDRESS);
+ Socket socket = new Socket();
+ int port = intent.getExtras().getInt(EXTRAS_GROUP_OWNER_PORT);
+
+ try {
+ Log.d(LocalFileTransferActivity.TAG, "Opening client socket - ");
+ socket.bind(null);
+ socket.connect((new InetSocketAddress(host, port)), SOCKET_TIMEOUT);
+
+ Log.d(LocalFileTransferActivity.TAG, "Client socket - " + socket.isConnected());
+ OutputStream stream = socket.getOutputStream();
+ ContentResolver cr = context.getContentResolver();
+ InputStream is = null;
+ try {
+ is = cr.openInputStream(Uri.parse(fileUriString));
+ } catch (FileNotFoundException e) {
+ Log.d(LocalFileTransferActivity.TAG, e.toString());
+ }
+ DeviceListFragment.copyFile(is, stream);
+ Log.d(LocalFileTransferActivity.TAG, "Client: Data written");
+ } catch (IOException e) {
+ Log.e(LocalFileTransferActivity.TAG, e.getMessage());
+ } finally {
+ if (socket != null) {
+ if (socket.isConnected()) {
+ try {
+ socket.close();
+ } catch (IOException e) {
+ // Give up
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ }
+ }
+}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/LocalFileTransferActivity.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/LocalFileTransferActivity.java
index fa2eac857..dd89fd4a3 100644
--- a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/LocalFileTransferActivity.java
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/LocalFileTransferActivity.java
@@ -1,23 +1,70 @@
package org.kiwix.kiwixmobile.zim_manager.local_file_transfer;
-import androidx.appcompat.app.AppCompatActivity;
-
+import android.Manifest;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
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.WifiP2pManager;
+import android.os.Build;
import android.os.Bundle;
-import android.widget.TextView;
+import android.os.Environment;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.widget.Toolbar;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
import org.kiwix.kiwixmobile.R;
+import java.lang.reflect.Method;
import java.util.ArrayList;
-public class LocalFileTransferActivity extends AppCompatActivity {
+public class LocalFileTransferActivity extends AppCompatActivity implements WifiP2pManager.ChannelListener, DeviceListFragment.DeviceActionListener {
+
+ /*TODO
+ * - Fix activity closure upon file transfer (successful or otherwise)
+ * - Handle multiple selected files
+ * */
+
+ public static final String TAG = "LocalFileTransferActvty";
+ public static Uri filePath = null; // = Environment.getExternalStorageDirectory() + "/Kiwix/temp.txt";///psiram_en_all_2018-09.zim";//Notes/Granblue Fantasy Wiki/Main Page.txt";
+ private final int PERMISSION_REQUEST_CODE_COARSE_LOCATION = 1;
+
+ private boolean isWifiP2pEnabled = false;
+ private boolean retryChannel = false;
+
+ private WifiP2pManager manager;
+ private final IntentFilter intentFilter = new IntentFilter();
+ private WifiP2pManager.Channel channel;
+ private BroadcastReceiver receiver = null;
+ private Boolean fileSendingDevice = false; // True if intent has file uri
+ // TODO: Set to true if activity opening intent has the file URI
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_local_file_transfer);
+ /*setContentView(R.layout.activity_local_file_transfer);
+
TextView fileUriListView = findViewById(R.id.text_view_file_uris);
Intent filesIntent = getIntent();
@@ -30,7 +77,509 @@ public class LocalFileTransferActivity extends AppCompatActivity {
}
}
- fileUriListView.setText(uriList);
+ fileUriListView.setText(uriList);*/
+
+ Intent filesIntent = getIntent();
+ ArrayList fileURIArrayList = filesIntent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+
+ if(fileURIArrayList != null && fileURIArrayList.size() > 0) {
+ filePath = fileURIArrayList.get(0);
+ setFileSender();
+ }
+
+ Toolbar actionBar = findViewById(R.id.toolbar_local_file_transfer);
+ setSupportActionBar(actionBar);
+
+ // Intents that the broadcast receiver will be responding to
+ 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);
+
+ //TODO: Start WiFi
+ manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
+ channel = manager.initialize(this, getMainLooper(), null);
+
+ // TODO: Add manager.removeGroup(channel, null); to remove previous groups
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+
+ if(shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_COARSE_LOCATION)) {
+ showNeutralDialog("Location permission is required to locate peer devices\n\nUser location is not being tracked by the app");
+ //TODO: Close activity
+ }
+
+ requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, PERMISSION_REQUEST_CODE_COARSE_LOCATION);
+ }
+
+ requestExternalStorageWritePermission();
+ }
+
+ private boolean requestExternalStorageWritePermission() {
+ if(Build.VERSION.SDK_INT >= 23) { // For Marshmallow & higher API levels
+
+ if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
+ return true;
+
+ } else {
+ if(shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ /* shouldShowRequestPermissionRationale() returns false when:
+ * 1) User has previously checked on "Don't ask me again", and/or
+ * 2) Permission has been disabled on device
+ */
+ Toast.makeText(getApplicationContext(), "Required for file access", Toast.LENGTH_LONG).show();
+ }
+
+ requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
+ }
+
+ } else { // For Android versions below Marshmallow 6.0 (API 23)
+ return true; // As already requested at install time
+ }
+
+ return false;
+ }
+
+ public boolean isWifiP2pEnabled() {
+ return isWifiP2pEnabled;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.wifi_file_share_items, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if(item.getItemId() == R.id.menu_item_search_devices) {
+ // Initiate discovery
+ //TODO
+ if(!isWifiP2pEnabled) {
+ requestEnableWifiP2pServices();
+ return true;
+ }
+
+ if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isLocationServicesEnabled()) {
+ requestEnableLocationServices();
+ return true;
+ }
+
+ final DeviceListFragment deviceListFragment = (DeviceListFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_device_list);
+ deviceListFragment.onInitiateDiscovery();
+ manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ Toast.makeText(LocalFileTransferActivity.this, "Discovery Initiated", Toast.LENGTH_SHORT).show();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Toast.makeText(LocalFileTransferActivity.this, "Discovery Failed: " + getErrorMessage(reason), Toast.LENGTH_SHORT).show();
+ }
+ });
+
+ return true;
+ } else if(item.getItemId() == R.id.menu_item_disconnect) {
+ if(manager != null) {
+ // TODO: 'cancelDisconnect', for removing the indefinite progress bar
+ //removeGroupDetails();
+ disconnect();
+ }
+
+
+ return true;
+ }
+ else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ private void removeGroupDetails() {
+ try {
+ Method deletePersistentGroup = manager.getClass().getMethod("deletePersistentGroup", WifiP2pManager.Channel.class, int.class, WifiP2pManager.ActionListener.class);
+ for(int netId = 0; netId < 32; netId++) {
+ deletePersistentGroup.invoke(manager, channel, netId, new WifiP2pManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ Log.d(TAG, "WiFi Direct Group successfully deleted");
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Log.d(TAG, "Group deletion failed: "+getErrorMessage(reason));
+ }
+ });
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.d(TAG, "Error removing group details: " + e.getMessage());
+ }
+ }
+
+ private boolean isLocationServicesEnabled() {
+ LocationManager locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
+ boolean gps_enabled = false;
+ boolean network_enabled = false;
+
+ try {
+ gps_enabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+ } catch(Exception ex) {ex.printStackTrace();}
+
+ try {
+ network_enabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
+ } catch(Exception ex) {ex.printStackTrace();}
+
+ /*if(!gps_enabled && !network_enabled) {
+ // notify user
+
+ }*/
+
+ return (gps_enabled || network_enabled);
+ }
+
+ private void requestEnableLocationServices() {
+
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag("LocationDialog");
+ if(prev != null) {
+ fragmentTransaction.remove(prev); // To prevent multiple instances of the DialogFragment
+ }
+ fragmentTransaction.addToBackStack(null);
+
+ RequestEnableLocationServicesDialog dialogFragment = new RequestEnableLocationServicesDialog();
+ // For DialogFragments, show() handles the fragment commit and display
+ dialogFragment.show(fragmentTransaction, "LocationDialog");
}
+
+ public static class RequestEnableLocationServicesDialog extends DialogFragment {
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage("Enable location to allow detection of peers")
+ .setPositiveButton("Open Location Settings", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface paramDialogInterface, int paramInt) {
+ paramDialogInterface.cancel();
+ startActivityForResult(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 1);
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //TODO: Close activity
+
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ private void requestEnableWifiP2pServices() {
+
+ /*new AlertDialog.Builder(this)
+ .setMessage("Enable WiFi P2P from system settings")
+ .setPositiveButton("Open WiFi Settings", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface paramDialogInterface, int paramInt) {
+ paramDialogInterface.cancel();
+ startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+
+ }
+ })
+ .show();*/
+
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag("WifiP2pDialog");
+ if(prev != null) {
+ fragmentTransaction.remove(prev); // To prevent multiple instances of the DialogFragment
+ }
+ fragmentTransaction.addToBackStack(null);
+
+ RequestEnableWifiP2pServicesDialog dialogFragment = new RequestEnableWifiP2pServicesDialog();
+ // For DialogFragments, show() handles the fragment commit and display
+ dialogFragment.show(fragmentTransaction, "WifiP2pDialog");
+ }
+
+ public static class RequestEnableWifiP2pServicesDialog extends DialogFragment {
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage("Enable WiFi P2P from system settings")
+ .setPositiveButton("Open WiFi Settings", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface paramDialogInterface, int paramInt) {
+ paramDialogInterface.cancel();
+ startActivity(new Intent(Settings.ACTION_WIRELESS_SETTINGS));
+ }
+ })
+ .setNegativeButton("No", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //TODO: Close activity
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ switch (requestCode) {
+ case 1: {
+ LocationManager locationManager = (LocationManager) getApplicationContext().getSystemService(Context.LOCATION_SERVICE);
+
+ if(locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
+ return;
+ } else {
+ /*new AlertDialog.Builder(this)
+ .setMessage("Cannot discover peers without location services")
+ .setPositiveButton("OK", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ //((MainActivity) getApplicationContext()).finish();
+ }
+ })
+ .show();*/
+ showNeutralDialog("Cannot discover peers without location services");
+ //TODO: Close activity
+ }
+
+ }
+ }
+ }
+
+ private String getErrorMessage(int reason) {
+ String error = "";
+ switch (reason) {
+ case WifiP2pManager.ERROR: error = "Internal error"; break;
+ case WifiP2pManager.BUSY: error = "Framework busy, unable to service request"; break;
+ case WifiP2pManager.P2P_UNSUPPORTED: error = "P2P unsupported on this device"; break;
+
+ default: error = "Unknown error code - "+reason; break;
+ }
+
+ return error;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ /*if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !isLocationServicesEnabled()) {
+ requestEnableLocationServices();
+ }*/
+ //TODO
+ receiver = new WifiDirectBroadcastReceiver(manager, channel, this);
+ registerReceiver(receiver, intentFilter);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ //TODO
+ unregisterReceiver(receiver);
+ }
+
+ public void setIsWifiP2pEnabled(boolean isWifiP2pEnabled) {
+ this.isWifiP2pEnabled = isWifiP2pEnabled;
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+ @NonNull int[] grantResults) {
+ switch (requestCode) {
+ case PERMISSION_REQUEST_CODE_COARSE_LOCATION : {
+ if(grantResults[0] != PackageManager.PERMISSION_GRANTED) {
+ Log.e(TAG, "Location permission not granted");
+
+ showNeutralDialog("Cannot locate peer devices without location permissions");
+ //TODO: Close activity
+
+ break;
+ }
+ }
+
+ case 0: break;
+ }
+ }
+
+ public void resetPeers() {
+ DeviceListFragment deviceListFragment = (DeviceListFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_device_list);
+ if(deviceListFragment != null) {
+ deviceListFragment.clearPeers();
+ }
+ }
+
+ @Override
+ public void onChannelDisconnected() {
+ //TODO
+ if(manager != null && !retryChannel) {
+ Toast.makeText(this, "Channel lost, trying again", Toast.LENGTH_LONG).show();
+ resetData();
+ retryChannel = true;
+ manager.initialize(this, getMainLooper(), this);
+
+ } else {
+ Toast.makeText(this, "Severe! Try Disable/Re-enable WiFi P2P", Toast.LENGTH_LONG).show();
+ }
+ }
+
+ public void resetData() {
+ DeviceListFragment deviceListFragment = (DeviceListFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_device_list);
+ if(deviceListFragment != null) {
+ deviceListFragment.clearPeers();
+ }
+ }
+
+ private void showNeutralDialog(String dialogMessage) {
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ Fragment prev = getSupportFragmentManager().findFragmentByTag("NeutralDialog");
+ if(prev != null) {
+ fragmentTransaction.remove(prev); // To prevent multiple instances of the DialogFragment
+ }
+ fragmentTransaction.addToBackStack(null);
+
+ NeutralDialog dialogFragment = new NeutralDialog(dialogMessage);
+ // For DialogFragments, show() handles the fragment commit and display
+ dialogFragment.show(fragmentTransaction, "NeutralDialog");
+ }
+
+ public static class NeutralDialog extends DialogFragment {
+
+ private String dialogMessage = "";
+
+ public NeutralDialog() {
+ super();
+ }
+
+ public NeutralDialog(String message) {
+ super();
+ this.dialogMessage = message;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setMessage(dialogMessage)
+ .setNeutralButton("Dismiss", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ }
+ });
+
+ return builder.create();
+ }
+ }
+
+ @Override
+ public void changeDeviceName(String deviceNewName) {
+ try {
+ Method method = manager.getClass().getMethod("setDeviceName", WifiP2pManager.Channel.class, String.class, WifiP2pManager.ActionListener.class);
+ method.invoke(manager, channel, deviceNewName, new WifiP2pManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ Toast.makeText(LocalFileTransferActivity.this, "Name successfully changed", Toast.LENGTH_LONG).show();
+ resetPeers();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Toast.makeText(LocalFileTransferActivity.this, "Request failed: " + reason, Toast.LENGTH_SHORT).show();
+ Log.d(TAG, "Name change failed: " + getErrorMessage(reason));
+ }
+ });
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean isFileSender() {
+ return fileSendingDevice;
+ }
+
+ public void setFileSender() {
+ fileSendingDevice = true;
+ }
+
+ /*@Override
+ public void showDetails(WifiP2pDevice device) {
+
+ }*/
+
+ @Override
+ public void cancelDisconnect() {
+ //TODO
+ }
+
+ @Override
+ public void connect(final WifiP2pDevice peerDevice) {
+ WifiP2pConfig config = new WifiP2pConfig();
+ config.deviceAddress = peerDevice.deviceAddress;
+ config.wps.setup = WpsInfo.PBC;
+
+ // If self sender, then receiver will be group owner
+ if(isFileSender())
+ config.groupOwnerIntent = 0; // Sets inclination for own device. This way other device has got to be the owner.
+ // Maybe reset the previous wifi direct group data, which is causing a fixed group owner
+
+ /*else
+ config.groupOwnerIntent = 15;*/
+
+ //TODO: Show a progress bar between starting & completion of connection
+
+ manager.connect(channel, config, new WifiP2pManager.ActionListener() {
+ @Override
+ public void onSuccess() {
+ //Toast.makeText(MainActivity.this, "Connected to " + peerDevice.deviceName, Toast.LENGTH_SHORT).show();
+ // UI updated from broadcast receiver
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Toast.makeText(LocalFileTransferActivity.this, "Connection failed: " + getErrorMessage(reason), Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ @Override
+ public void disconnect() {
+ fileSendingDevice = false;
+
+ //TODO
+ manager.removeGroup(channel, new WifiP2pManager.ActionListener() {
+
+ @Override
+ public void onFailure(int reasonCode) {
+ Log.d(TAG, "Disconnect failed. Reason :" + reasonCode);
+
+ }
+
+ @Override
+ public void onSuccess() {
+ Log.d(TAG, "Disconnect successful");
+ }
+
+ });
+ }
}
diff --git a/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/WifiDirectBroadcastReceiver.java b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/WifiDirectBroadcastReceiver.java
new file mode 100644
index 000000000..920dfbee6
--- /dev/null
+++ b/app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/WifiDirectBroadcastReceiver.java
@@ -0,0 +1,75 @@
+package org.kiwix.kiwixmobile.zim_manager.local_file_transfer;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.NetworkInfo;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import org.kiwix.kiwixmobile.R;
+
+public class WifiDirectBroadcastReceiver extends BroadcastReceiver {
+
+ private WifiP2pManager manager;
+ private WifiP2pManager.Channel channel;
+ private LocalFileTransferActivity wifiActivity;
+
+ public WifiDirectBroadcastReceiver(WifiP2pManager manager, WifiP2pManager.Channel channel, LocalFileTransferActivity activity) {
+ super();
+ this.manager = manager;
+ this.channel = channel;
+ this.wifiActivity = activity;
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ if(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
+
+ int wifiP2pState = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
+ if(wifiP2pState == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
+ wifiActivity.setIsWifiP2pEnabled(true);
+ } else {
+ wifiActivity.setIsWifiP2pEnabled(false);
+ Toast.makeText(wifiActivity, "Cannot discover peers without WiFi", Toast.LENGTH_SHORT).show();
+ //TODO
+ wifiActivity.resetPeers();
+ }
+ Log.d(wifiActivity.TAG, "WiFi P2P state changed - " + wifiP2pState);
+
+ } else if(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
+
+ if(manager != null) {
+ //TODO
+ manager.requestPeers(channel, (WifiP2pManager.PeerListListener) wifiActivity.getSupportFragmentManager().findFragmentById(R.id.fragment_device_list));
+ }
+ Log.d(wifiActivity.TAG, "P2P peers changed");
+
+ } else if(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
+
+ if(manager == null) {
+ return;
+ }
+
+ NetworkInfo networkInfo = (NetworkInfo) intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
+
+ if(networkInfo.isConnected()) {
+ //TODO
+ //
+
+ manager.requestConnectionInfo(channel, (DeviceListFragment) wifiActivity.getSupportFragmentManager().findFragmentById(R.id.fragment_device_list));
+ } else {
+ wifiActivity.resetData();
+ }
+
+ } else if(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
+ //TODO
+ DeviceListFragment deviceListFragment = (DeviceListFragment) wifiActivity.getSupportFragmentManager().findFragmentById(R.id.fragment_device_list);
+ deviceListFragment.updateUserDevice((WifiP2pDevice) intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));
+ }
+ }
+}
diff --git a/app/src/main/res/drawable/ic_baseline_cancel_24px.xml b/app/src/main/res/drawable/ic_baseline_cancel_24px.xml
new file mode 100644
index 000000000..d3962b646
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_cancel_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_edit_24px.xml b/app/src/main/res/drawable/ic_baseline_edit_24px.xml
new file mode 100644
index 000000000..c11d0729c
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_edit_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_mobile_screen_share_24px.xml b/app/src/main/res/drawable/ic_baseline_mobile_screen_share_24px.xml
new file mode 100644
index 000000000..f78eeb086
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_mobile_screen_share_24px.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_local_file_transfer.xml b/app/src/main/res/layout/activity_local_file_transfer.xml
index 1b5fea025..d06dbb829 100644
--- a/app/src/main/res/layout/activity_local_file_transfer.xml
+++ b/app/src/main/res/layout/activity_local_file_transfer.xml
@@ -1,3 +1,64 @@
+
+>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/dialog_edit_device_name.xml b/app/src/main/res/layout/dialog_edit_device_name.xml
new file mode 100644
index 000000000..4bc0bd754
--- /dev/null
+++ b/app/src/main/res/layout/dialog_edit_device_name.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_device_list.xml b/app/src/main/res/layout/fragment_device_list.xml
new file mode 100644
index 000000000..e527639d5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_device_list.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/row_peer_device.xml b/app/src/main/res/layout/row_peer_device.xml
new file mode 100644
index 000000000..cc1dd06b3
--- /dev/null
+++ b/app/src/main/res/layout/row_peer_device.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_zim_manager.xml b/app/src/main/res/menu/menu_zim_manager.xml
index 3382596d4..1a6656d2b 100644
--- a/app/src/main/res/menu/menu_zim_manager.xml
+++ b/app/src/main/res/menu/menu_zim_manager.xml
@@ -10,6 +10,13 @@
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:iconifiedByDefault="true"
app:showAsAction="always|collapseActionView"/>
+
+
+
-
+