support 'Save to downloads' action for attachments
This commit is contained in:
parent
f9027fa085
commit
9467fc1789
|
@ -664,6 +664,56 @@ public class FileBackend {
|
|||
}
|
||||
}
|
||||
|
||||
public void copyAttachmentToDownloadsFolder(Message m) throws FileCopyException {
|
||||
File input = mXmppConnectionService.getFileBackend().getFile(m);
|
||||
|
||||
File parentDirectory =
|
||||
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||
File output = new File(parentDirectory, input.getName());
|
||||
int counter = 1;
|
||||
while (output.exists()) {
|
||||
output = new File(parentDirectory, "(" + counter + ") " + input.getName());
|
||||
counter++;
|
||||
}
|
||||
|
||||
try {
|
||||
output.createNewFile();
|
||||
} catch (IOException e) {
|
||||
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
||||
}
|
||||
try (final OutputStream os = new FileOutputStream(output);
|
||||
final InputStream is =
|
||||
mXmppConnectionService.getContentResolver().openInputStream(Uri.fromFile(input))) {
|
||||
if (is == null) {
|
||||
throw new FileCopyException(R.string.error_file_not_found);
|
||||
}
|
||||
try {
|
||||
ByteStreams.copy(is, os);
|
||||
} catch (IOException e) {
|
||||
throw new FileWriterException(output);
|
||||
}
|
||||
try {
|
||||
os.flush();
|
||||
} catch (IOException e) {
|
||||
throw new FileWriterException(output);
|
||||
}
|
||||
|
||||
updateMediaScanner(output);
|
||||
} catch (final FileNotFoundException e) {
|
||||
cleanup(output);
|
||||
throw new FileCopyException(R.string.error_file_not_found);
|
||||
} catch (final FileWriterException e) {
|
||||
cleanup(output);
|
||||
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
||||
} catch (final SecurityException | IllegalStateException e) {
|
||||
cleanup(output);
|
||||
throw new FileCopyException(R.string.error_security_exception);
|
||||
} catch (final IOException e) {
|
||||
cleanup(output);
|
||||
throw new FileCopyException(R.string.error_io_exception);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
|
|
|
@ -212,7 +212,7 @@ public class XmppConnectionService extends Service {
|
|||
public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1);
|
||||
private final static Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
private final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
|
||||
private final static Executor COPY_TO_DOWNLOAD_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
private final ScheduledExecutorService internalPingExecutor = Executors.newSingleThreadScheduledExecutor();
|
||||
private final static SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR = new SerialSingleThreadExecutor("VideoCompression");
|
||||
private final SerialSingleThreadExecutor mDatabaseWriterExecutor = new SerialSingleThreadExecutor("DatabaseWriter");
|
||||
|
@ -540,6 +540,17 @@ public class XmppConnectionService extends Service {
|
|||
return this.restoredFromDatabaseLatch.getCount() == 0;
|
||||
}
|
||||
|
||||
public void copyAttachmentToDownloadsFolder(Message m, final UiCallback<Integer> callback) {
|
||||
COPY_TO_DOWNLOAD_EXECUTOR.execute(() -> {
|
||||
try {
|
||||
fileBackend.copyAttachmentToDownloadsFolder(m);
|
||||
callback.success(-1);
|
||||
} catch (FileBackend.FileCopyException e) {
|
||||
callback.error(-1, e.getResId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PgpEngine getPgpEngine() {
|
||||
if (!Config.supportOpenPgp()) {
|
||||
return null;
|
||||
|
|
|
@ -18,6 +18,8 @@ import android.app.FragmentManager;
|
|||
import android.app.PendingIntent;
|
||||
import android.app.ProgressDialog;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
|
@ -28,6 +30,7 @@ import android.graphics.drawable.Drawable;
|
|||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.os.VibrationEffect;
|
||||
|
@ -40,7 +43,6 @@ import android.text.TextUtils;
|
|||
import android.text.format.DateUtils;
|
||||
import android.util.Log;
|
||||
import android.util.TypedValue;
|
||||
import android.util.Range;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
|
@ -53,8 +55,6 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.CycleInterpolator;
|
||||
import android.view.ViewParent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AbsListView;
|
||||
|
@ -64,7 +64,6 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
|
|||
import android.widget.CheckBox;
|
||||
import android.widget.ListView;
|
||||
import android.widget.PopupMenu;
|
||||
import android.widget.TextView;
|
||||
import android.widget.TextView.OnEditorActionListener;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -77,8 +76,6 @@ import androidx.appcompat.content.res.AppCompatResources;
|
|||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
|
@ -86,19 +83,18 @@ import com.google.common.collect.ImmutableList;
|
|||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -150,7 +146,6 @@ import eu.siacs.conversations.ui.widget.HighlighterView;
|
|||
import eu.siacs.conversations.ui.widget.TabLayout;
|
||||
import eu.siacs.conversations.utils.AccountUtils;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
import eu.siacs.conversations.utils.Emoticons;
|
||||
import eu.siacs.conversations.utils.GeoHelper;
|
||||
import eu.siacs.conversations.utils.MessageUtils;
|
||||
import eu.siacs.conversations.utils.NickValidityChecker;
|
||||
|
@ -172,19 +167,6 @@ import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
|||
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
public class ConversationFragment extends XmppFragment
|
||||
implements EditMessage.KeyboardListener,
|
||||
MessageAdapter.OnContactPictureLongClicked,
|
||||
|
@ -1729,6 +1711,7 @@ public class ConversationFragment extends XmppFragment
|
|||
MenuItem shareWith = menu.findItem(R.id.share_with);
|
||||
MenuItem sendAgain = menu.findItem(R.id.send_again);
|
||||
MenuItem copyUrl = menu.findItem(R.id.copy_url);
|
||||
MenuItem saveToDownloads = menu.findItem(R.id.save_to_downloads);
|
||||
MenuItem downloadFile = menu.findItem(R.id.download_file);
|
||||
MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
|
||||
MenuItem deleteFile = menu.findItem(R.id.delete_file);
|
||||
|
@ -1792,6 +1775,7 @@ public class ConversationFragment extends XmppFragment
|
|||
|| t instanceof HttpDownloadConnection) {
|
||||
copyUrl.setVisible(true);
|
||||
}
|
||||
|
||||
if (m.isFileOrImage() && deleted && m.hasFileOnRemoteHost()) {
|
||||
downloadFile.setVisible(true);
|
||||
downloadFile.setTitle(
|
||||
|
@ -1814,10 +1798,14 @@ public class ConversationFragment extends XmppFragment
|
|||
|| !path.startsWith("/")
|
||||
|| FileBackend.inConversationsDirectory(requireActivity(), path)) {
|
||||
deleteFile.setVisible(true);
|
||||
|
||||
String fileDescriptorString = UIHelper.getFileDescriptionString(activity, m);
|
||||
deleteFile.setTitle(
|
||||
activity.getString(
|
||||
R.string.delete_x_file,
|
||||
UIHelper.getFileDescriptionString(activity, m)));
|
||||
fileDescriptorString));
|
||||
|
||||
saveToDownloads.setVisible(true);
|
||||
}
|
||||
}
|
||||
if (showError) {
|
||||
|
@ -1869,6 +1857,9 @@ public class ConversationFragment extends XmppFragment
|
|||
case R.id.delete_file:
|
||||
deleteFile(selectedMessage);
|
||||
return true;
|
||||
case R.id.save_to_downloads:
|
||||
saveToDownloads(selectedMessage);
|
||||
return true;
|
||||
case R.id.show_error_message:
|
||||
showErrorMessage(selectedMessage);
|
||||
return true;
|
||||
|
@ -2635,6 +2626,24 @@ public class ConversationFragment extends XmppFragment
|
|||
builder.create().show();
|
||||
}
|
||||
|
||||
private void saveToDownloads(final Message message) {
|
||||
activity.xmppConnectionService.copyAttachmentToDownloadsFolder(message, new UiCallback<>() {
|
||||
@Override
|
||||
public void success(Integer object) {
|
||||
runOnUiThread(() -> Toast.makeText(activity, R.string.save_to_downloads_success, Toast.LENGTH_LONG).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int errorCode, Integer object) {
|
||||
runOnUiThread(() -> Toast.makeText(activity, object, Toast.LENGTH_LONG).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userInputRequired(PendingIntent pi, Integer object) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void resendMessage(final Message message) {
|
||||
if (message.isFileOrImage()) {
|
||||
if (!(message.getConversation() instanceof Conversation)) {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
package eu.siacs.conversations.ui;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
|
@ -41,6 +42,7 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
|
||||
|
@ -56,6 +58,7 @@ import eu.siacs.conversations.entities.Contact;
|
|||
import eu.siacs.conversations.entities.Conversation;
|
||||
import eu.siacs.conversations.entities.Conversational;
|
||||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.services.MessageSearchTask;
|
||||
import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
||||
import eu.siacs.conversations.ui.interfaces.OnSearchResultsAvailable;
|
||||
|
@ -67,6 +70,7 @@ import eu.siacs.conversations.ui.util.ShareUtil;
|
|||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||
import eu.siacs.conversations.utils.FtsUtils;
|
||||
import eu.siacs.conversations.utils.MessageUtils;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
|
||||
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
|
||||
import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.showKeyboard;
|
||||
|
@ -140,6 +144,26 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
|
|||
MenuItem copy = menu.findItem(R.id.copy_message);
|
||||
MenuItem quote = menu.findItem(R.id.quote_message);
|
||||
MenuItem copyUrl = menu.findItem(R.id.copy_url);
|
||||
MenuItem saveToDownloads = menu.findItem(R.id.save_to_downloads);
|
||||
|
||||
|
||||
final boolean deleted = message.isDeleted();
|
||||
final boolean waitingOfferedSending =
|
||||
message.getStatus() == Message.STATUS_WAITING
|
||||
|| message.getStatus() == Message.STATUS_UNSEND
|
||||
|| message.getStatus() == Message.STATUS_OFFERED;
|
||||
final boolean cancelable =
|
||||
(message.getTransferable() != null && !deleted) || waitingOfferedSending && message.needsUploading();
|
||||
|
||||
if (message.isFileOrImage() && !deleted && !cancelable) {
|
||||
final String path = message.getRelativeFilePath();
|
||||
if (path == null
|
||||
|| !path.startsWith("/")
|
||||
|| FileBackend.inConversationsDirectory(this, path)) {
|
||||
saveToDownloads.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (message.isGeoUri()) {
|
||||
copy.setVisible(false);
|
||||
quote.setVisible(false);
|
||||
|
@ -171,6 +195,23 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
|
|||
case R.id.copy_message:
|
||||
ShareUtil.copyToClipboard(this, message);
|
||||
break;
|
||||
case R.id.save_to_downloads:
|
||||
xmppConnectionService.copyAttachmentToDownloadsFolder(message, new UiCallback<>() {
|
||||
@Override
|
||||
public void success(Integer object) {
|
||||
runOnUiThread(() -> Toast.makeText(SearchActivity.this, R.string.save_to_downloads_success, Toast.LENGTH_LONG).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int errorCode, Integer object) {
|
||||
runOnUiThread(() -> Toast.makeText(SearchActivity.this, object, Toast.LENGTH_LONG).show());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userInputRequired(PendingIntent pi, Integer object) {
|
||||
}
|
||||
});
|
||||
break;
|
||||
case R.id.copy_url:
|
||||
ShareUtil.copyUrlToClipboard(this, message);
|
||||
break;
|
||||
|
|
|
@ -42,6 +42,12 @@
|
|||
android:id="@+id/copy_url"
|
||||
android:title="@string/copy_original_url"
|
||||
android:visible="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/save_to_downloads"
|
||||
android:title="@string/save_to_downloads"
|
||||
android:visible="false" />
|
||||
|
||||
<item
|
||||
android:id="@+id/show_error_message"
|
||||
android:title="@string/show_error_message"
|
||||
|
|
|
@ -46,4 +46,9 @@
|
|||
<item
|
||||
android:id="@+id/copy_url"
|
||||
android:title="@string/copy_original_url"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/save_to_downloads"
|
||||
android:title="@string/save_to_downloads"
|
||||
android:visible="false" />
|
||||
</menu>
|
|
@ -315,6 +315,8 @@
|
|||
<string name="quote">Quote</string>
|
||||
<string name="paste_as_quote">Paste as quote</string>
|
||||
<string name="copy_original_url">Copy original URL</string>
|
||||
<string name="save_to_downloads">Save to Downloads</string>
|
||||
<string name="save_to_downloads_success">File successfully saved to Downloads folder</string>
|
||||
<string name="send_again">Send again</string>
|
||||
<string name="file_url">File URL</string>
|
||||
<string name="url_copied_to_clipboard">Copied URL to clipboard</string>
|
||||
|
|
Loading…
Reference in a new issue