package com.nutomic.syncthingandroid.activities;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.MimeTypeMap;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import com.google.common.io.Files;
import com.nutomic.syncthingandroid.R;
import com.nutomic.syncthingandroid.model.Folder;
import com.nutomic.syncthingandroid.service.SyncthingService;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Shares incoming files to syncthing folders.
*
* {@link #getDisplayNameForUri} and {@link #getDisplayNameFromContentResolver} are taken from
* ownCloud Android {@see https://github.com/owncloud/android/blob/79664304fdb762b2e04f1ac505f50d0923ddd212/src/com/owncloud/android/utils/UriUtils.java#L193}
*/
public class ShareActivity extends SyncthingActivity
implements SyncthingActivity.OnServiceConnectedListener, SyncthingService.OnApiChangeListener {
private static final String TAG = "ShareActivity";
@Override
public void onApiChange(SyncthingService.State currentState) {
if (currentState != SyncthingService.State.ACTIVE || getApi() == null)
return;
List<Folder> folders = getApi().getFolders();
ArrayAdapter<Folder> adapter = new ArrayAdapter<>(
this, android.R.layout.simple_spinner_item, folders);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner sItems = (Spinner) findViewById(R.id.folders);
sItems.setAdapter(adapter);
}
@Override
public void onServiceConnected() {
getService().registerOnApiChangeListener(this);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
//noinspection ConstantConditions
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_share);
registerOnServiceConnectedListener(this);
Spinner mFoldersSpinner = (Spinner) findViewById(R.id.folders);
Button mShareButton = (Button) findViewById(R.id.share_button);
Button mCancelButton = (Button) findViewById(R.id.cancel_button);
EditText mShareName = (EditText) findViewById(R.id.name);
TextView mShareTitle = (TextView) findViewById(R.id.namesTitle);
// TODO: add support for EXTRA_TEXT (notes, memos sharing)
ArrayList<Uri> extrasToCopy = new ArrayList<>();
if (getIntent().getAction().equals(Intent.ACTION_SEND)) {
Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
if (uri != null)
extrasToCopy.add(uri);
} else if (getIntent().getAction().equals(Intent.ACTION_SEND_MULTIPLE)) {
ArrayList<Uri> extras = getIntent().getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (extras != null)
extrasToCopy = extras;
}
if (extrasToCopy.isEmpty()) {
Toast.makeText(this, getString(R.string.nothing_share), Toast.LENGTH_SHORT).show();
finish();
}
Map<Uri, String> files = new HashMap<>();
for (Uri sourceUri : extrasToCopy) {
String displayName = getDisplayNameForUri(sourceUri);
if (displayName == null) {
displayName = generateDisplayName();
}
files.put(sourceUri, displayName);
}
mShareName.setText(TextUtils.join("\n", files.values()));
if (files.size() > 1) {
mShareName.setFocusable(false);
mShareName.setKeyListener(null);
}
mShareTitle.setText(getResources().getQuantityString(R.plurals.file_name_title,
files.size() > 1 ? 2 : 1));
mShareButton.setOnClickListener(view -> {
if (files.size() == 1)
files.entrySet().iterator().next().setValue(mShareName.getText().toString());
Folder folder = (Folder) mFoldersSpinner.getSelectedItem();
new CopyFilesTask(files, folder).execute();
});
mCancelButton.setOnClickListener(view -> finish());
}
/**
* Generate file name for new file.
*/
private String generateDisplayName() {
Date date = new Date(System.currentTimeMillis());
DateFormat df = DateFormat.getDateTimeInstance();
return String.format(getResources().getString(R.string.file_name_template),
df.format(date));
}
/**
* Get file name from uri.
*/
private String getDisplayNameForUri(Uri uri) {
String displayName;
if (!ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
displayName = uri.getLastPathSegment();
} else {
displayName = getDisplayNameFromContentResolver(uri);
if (displayName == null) {
// last chance to have a name
displayName = uri.getLastPathSegment().replaceAll("\\s", "");
}
// Add best possible extension
int index = displayName.lastIndexOf(".");
if (index == -1 || MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(displayName.substring(index + 1)) == null) {
String mimeType = this.getContentResolver().getType(uri);
String extension = MimeTypeMap.getSingleton()
.getExtensionFromMimeType(mimeType);
if (extension != null) {
displayName += "." + extension;
}
}
}
// Replace path separator characters to avoid inconsistent paths
return displayName != null ? displayName.replaceAll("/", "-") : null;
}
/**
* Get file name from content uri (content://).
*/
private String getDisplayNameFromContentResolver(Uri uri) {
String displayName = null;
String mimeType = getContentResolver().getType(uri);
if (mimeType != null) {
String displayNameColumn;
if (mimeType.startsWith("image/")) {
displayNameColumn = MediaStore.Images.ImageColumns.DISPLAY_NAME;
} else if (mimeType.startsWith("video/")) {
displayNameColumn = MediaStore.Video.VideoColumns.DISPLAY_NAME;
} else if (mimeType.startsWith("audio/")) {
displayNameColumn = MediaStore.Audio.AudioColumns.DISPLAY_NAME;
} else {
displayNameColumn = MediaStore.Files.FileColumns.DISPLAY_NAME;
}
Cursor cursor = getContentResolver().query(
uri,
new String[]{displayNameColumn},
null,
null,
null
);
if (cursor != null) {
cursor.moveToFirst();
displayName = cursor.getString(cursor.getColumnIndex(displayNameColumn));
}
if (cursor != null) {
cursor.close();
}
}
return displayName;
}
private class CopyFilesTask extends AsyncTask<Void, Void, Boolean> {
private ProgressDialog mProgress;
private Map<Uri, String> mFiles;
private Folder mFolder;
private int mCopied = 0, mIgnored = 0;
CopyFilesTask(Map<Uri, String> files, Folder folder) {
this.mFiles = files;
this.mFolder = folder;
}
protected void onPreExecute() {
mProgress = ProgressDialog.show(ShareActivity.this, null,
getString(R.string.copy_progress), true);
}
protected Boolean doInBackground(Void... params) {
boolean isError = false;
for (Map.Entry<Uri, String> entry : mFiles.entrySet()) {
InputStream inputStream = null;
try {
File outFile = new File(mFolder.path, entry.getValue());
if (outFile.isFile()) {
mIgnored++;
continue;
}
inputStream = getContentResolver().openInputStream(entry.getKey());
Files.asByteSink(outFile).writeFrom(inputStream);
mCopied++;
} catch (FileNotFoundException e) {
Log.e(TAG, String.format("Can't find input file \"%s\" to copy",
entry.getKey()), e);
isError = true;
} catch (IOException e) {
Log.e(TAG, String.format("IO exception during file \"%s\" sharing",
entry.getKey()), e);
isError = true;
} finally {
try {
if (inputStream != null)
inputStream.close();
} catch (IOException e) {
Log.w(TAG, "Exception on input/output stream close", e);
}
}
}
return isError;
}
protected void onPostExecute(Boolean isError) {
mProgress.dismiss();
Toast.makeText(ShareActivity.this, mIgnored > 0 ?
getResources().getQuantityString(R.plurals.copy_success_partially, mCopied,
mCopied, mFolder.label, mIgnored) :
getResources().getQuantityString(R.plurals.copy_success, mCopied, mCopied,
mFolder.label),
Toast.LENGTH_LONG).show();
if (isError) {
Toast.makeText(ShareActivity.this, getString(R.string.copy_exception),
Toast.LENGTH_SHORT).show();
}
finish();
}
}
}