From e357d47d49e666b4c34e699a0abdd540e2d2d0a8 Mon Sep 17 00:00:00 2001 From: Aditya-Sood Date: Sat, 22 Jun 2019 01:00:57 +0530 Subject: [PATCH] Increment: LocalFileTransferActivity now has core functionality The activity can now be used to send one selected ZIM file to a nearby device (Cleanup and refactoring yet to be done) --- app/src/main/AndroidManifest.xml | 5 + .../zim_manager/ZimManageActivity.java | 8 + .../DeviceListFragment.java | 588 ++++++++++++++++++ .../FileTransferService.java | 92 +++ .../LocalFileTransferActivity.java | 559 ++++++++++++++++- .../WifiDirectBroadcastReceiver.java | 75 +++ .../res/drawable/ic_baseline_cancel_24px.xml | 9 + .../res/drawable/ic_baseline_edit_24px.xml | 9 + .../ic_baseline_mobile_screen_share_24px.xml | 9 + .../layout/activity_local_file_transfer.xml | 62 ++ .../res/layout/dialog_edit_device_name.xml | 22 + .../main/res/layout/fragment_device_list.xml | 136 ++++ app/src/main/res/layout/row_peer_device.xml | 34 + app/src/main/res/menu/menu_zim_manager.xml | 7 + .../main/res/menu/wifi_file_share_items.xml | 16 + 15 files changed, 1626 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/DeviceListFragment.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/FileTransferService.java create mode 100644 app/src/main/java/org/kiwix/kiwixmobile/zim_manager/local_file_transfer/WifiDirectBroadcastReceiver.java create mode 100644 app/src/main/res/drawable/ic_baseline_cancel_24px.xml create mode 100644 app/src/main/res/drawable/ic_baseline_edit_24px.xml create mode 100644 app/src/main/res/drawable/ic_baseline_mobile_screen_share_24px.xml create mode 100644 app/src/main/res/layout/dialog_edit_device_name.xml create mode 100644 app/src/main/res/layout/fragment_device_list.xml create mode 100644 app/src/main/res/layout/row_peer_device.xml create mode 100644 app/src/main/res/menu/wifi_file_share_items.xml 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"/> + + + + + + + + +