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)
This commit is contained in:
Aditya-Sood 2019-06-22 01:00:57 +05:30
parent 25e0ef4dd7
commit e357d47d49
15 changed files with 1626 additions and 5 deletions

View File

@ -14,6 +14,10 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:name=".KiwixApplication"
@ -223,6 +227,7 @@
-->
</intent-filter>
</activity>
<service android:enabled="true" android:name=".zim_manager.local_file_transfer.FileTransferService" />
</application>
</manifest>

View File

@ -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);
}

View File

@ -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<WifiP2pDevice> peerDevices = new ArrayList<WifiP2pDevice>();
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<WifiP2pDevice> {
private List<WifiP2pDevice> listItems;
public WifiPeerListAdapter(@NonNull Context context, int resource, List<WifiP2pDevice> 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<Void, Void, String> {
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<Void, Void, InetAddress> {
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);
}
}

View File

@ -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();
}
}
}
}
}
}
}

View File

@ -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<Uri> 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");
}
});
}
}

View File

@ -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));
}
}
}

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M12,2C6.47,2 2,6.47 2,12s4.47,10 10,10 10,-4.47 10,-10S17.53,2 12,2zM17,15.59L15.59,17 12,13.41 8.41,17 7,15.59 10.59,12 7,8.41 8.41,7 12,10.59 15.59,7 17,8.41 13.41,12 17,15.59z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FF000000"
android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M17,1.01L7,1c-1.1,0 -1.99,0.9 -1.99,2v18c0,1.1 0.89,2 1.99,2h10c1.1,0 2,-0.9 2,-2L19,3c0,-1.1 -0.9,-1.99 -2,-1.99zM17,19L7,19L7,5h10v14zM12.8,13.22v1.75l3.2,-2.99L12.8,9v1.7c-3.11,0.43 -4.35,2.56 -4.8,4.7 1.11,-1.5 2.58,-2.18 4.8,-2.18z"/>
</vector>

View File

@ -1,3 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".zim_manager.local_file_transfer.LocalFileTransferActivity">>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar_local_file_transfer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/container_local_file_transfer">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar_local_file_transfer"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:id="@+id/container_local_file_transfer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/app_bar_local_file_transfer"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:orientation="vertical">
<fragment
class="org.kiwix.kiwixmobile.zim_manager.local_file_transfer.DeviceListFragment"
android:id="@+id/fragment_device_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"/>
<!--<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@android:color/darker_gray"/>
<fragment
android:id="@+id/fragment_device_detail"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="5"/>-->
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<!--
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
@ -18,3 +79,4 @@
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
-->

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="10dp"
android:paddingLeft="10dp"
android:paddingRight="10dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Change Device Name"
android:textStyle="bold"
android:textSize="17sp"/>
<EditText
android:id="@+id/edit_text_change_device_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter name"
android:textSize="15sp"/>
</LinearLayout>

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
android:id="@+id/text_view_your_device"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dp"
android:paddingLeft="5dp"
android:paddingTop="5dp"
android:paddingRight="5dp"
android:paddingBottom="1dp"
android:text="Your Device:"
android:textSize="13sp"
android:textStyle="italic"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn_edit_device_name"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/text_view_device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintTop_toBottomOf="@+id/text_view_your_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn_edit_device_name"
android:text="Device Name"
android:textStyle="bold"
android:textSize="17sp"
android:layout_marginLeft="15dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"/>
<TextView
android:id="@+id/text_view_device_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintTop_toBottomOf="@+id/text_view_device_name"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@id/btn_edit_device_name"
android:text="Status"
android:textSize="15sp"
android:layout_marginLeft="15dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingBottom="5dp"/>
<View
android:id="@+id/view_device_list_boundary"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="#0000f0"
app:layout_constraintTop_toBottomOf="@+id/text_view_device_status"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"/>
<ImageButton
android:id="@+id/btn_edit_device_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/ic_baseline_edit_24px"
android:tooltipText="Edit Name"
android:background="@android:color/transparent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/view_device_list_boundary"
app:layout_constraintRight_toRightOf="parent"
android:paddingLeft="15dp"
android:paddingStart="15dp"
android:paddingRight="5dp"
android:paddingEnd="5dp"
android:layout_marginRight="20dp"
android:layout_marginEnd="20dp"/>
<TextView
android:id="@+id/text_view_available_device"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="NEARBY DEVICES"
android:gravity="center"
android:textSize="16sp"
android:fontFamily="monospace"
app:layout_constraintTop_toBottomOf="@+id/view_device_list_boundary"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:paddingTop="5dp"/>
<FrameLayout
android:id="@+id/frame_layout_peer_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/text_view_available_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="invisible">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="No devices detected\nTap on search button to try again"
android:gravity="center"
android:layout_margin="50dp"
android:textColor="@android:color/black" />
<!--android:visibility="gone"-->
</FrameLayout>
<ProgressBar
android:id="@+id/progress_bar_searching_peers"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
android:layout_margin="50dp"
app:layout_constraintTop_toBottomOf="@+id/text_view_available_device"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<TextView
android:id="@+id/row_device_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:gravity="center_vertical"
android:text="Device Name"
android:tooltipText="Device Name"
android:textStyle="bold"
android:textSize="17sp"
android:paddingTop="1dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"/>
<TextView
android:id="@+id/row_device_status"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_gravity="center_vertical"
android:text="Status"
android:tooltipText="Device Status"
android:textSize="17sp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:paddingBottom="1dp"/>
</LinearLayout>

View File

@ -10,6 +10,13 @@
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:iconifiedByDefault="true"
app:showAsAction="always|collapseActionView"/>
<item
android:id="@+id/get_zim_nearby_device"
android:icon="@drawable/ic_baseline_mobile_screen_share_24px"
android:title="Get content from nearby device"
app:showAsAction="always"/>
<item
android:id="@+id/select_language"
android:icon="@drawable/ic_language_white_24dp"

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".zim_manager.local_file_transfer.LocalFileTransferActivity">
<item android:id="@+id/menu_item_disconnect"
android:title="Disconnect"
app:showAsAction="always"
android:icon="@drawable/ic_baseline_cancel_24px" />
<item android:id="@+id/menu_item_search_devices"
android:title="Search For Peers"
app:showAsAction="always"
android:icon="@drawable/action_search"/>
</menu>