From 79b6d163ad1e3c661f2b9c08449aba5409ef8c80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20B=C3=A9raud?= <adrien.beraud@savoirfairelinux.com> Date: Tue, 8 Jun 2021 17:23:39 -0400 Subject: [PATCH] file transfer: update for new API Change-Id: I24add1f0bcdb5847245dc66f84585bd65d6e7042 --- build-daemon.sh | 2 +- .../cx/ring/adapters/ConversationAdapter.java | 75 +++++---- .../ring/fragments/ConversationFragment.java | 18 ++- .../java/cx/ring/service/DRingService.java | 8 +- .../services/DeviceRuntimeServiceImpl.java | 5 + .../services/NotificationServiceImpl.java | 10 +- .../conversation/TvConversationAdapter.java | 6 +- .../conversation/TvConversationFragment.java | 10 +- .../java/cx/ring/utils/AndroidFileUtils.java | 37 ++++- .../java/cx/ring/utils/ContentUriHandler.java | 28 ++-- .../src/main/res/layout/item_conv_file_me.xml | 2 +- .../main/res/layout/item_conv_file_peer.xml | 2 +- .../app/src/main/res/values/strings.xml | 5 +- .../conversation/ConversationPresenter.java | 15 +- .../jami/conversation/ConversationView.java | 4 +- .../net/jami/facades/ConversationFacade.java | 38 +++-- .../src/main/java/net/jami/model/Account.java | 9 +- .../java/net/jami/model/Conversation.java | 2 +- .../java/net/jami/model/DataTransfer.java | 49 ++++-- .../main/java/net/jami/model/Interaction.java | 3 +- .../net/jami/services/AccountService.java | 150 +++++++++++------- .../java/net/jami/services/DaemonService.java | 6 +- .../jami/services/DeviceRuntimeService.java | 21 +++ .../jami/services/NotificationService.java | 2 +- 24 files changed, 325 insertions(+), 182 deletions(-) diff --git a/build-daemon.sh b/build-daemon.sh index b2b5bd669..8f0e04957 100755 --- a/build-daemon.sh +++ b/build-daemon.sh @@ -181,8 +181,8 @@ STATIC_LIBS_ALL="-llog -lOpenSLES -landroid \ -lyaml-cpp -ljsoncpp -lhttp_parser -lfmt\ -luuid -lz -ldl \ -lvpx -lopus -lspeex -lspeexdsp -lx264 \ - -largon2 \ -lgit2 \ + -largon2 \ -liconv" LIBRING_JNI_DIR=${ANDROID_APP_DIR}/app/src/main/libs/${ANDROID_ABI} diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java index 90227b291..9c516366e 100644 --- a/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/adapters/ConversationAdapter.java @@ -442,31 +442,19 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo if (interaction.getType() == (InteractionType.CONTACT)) return false; - switch (item.getItemId()) { - case R.id.conv_action_download: { - presenter.saveFile(interaction); - break; - } - case R.id.conv_action_share: { - presenter.shareFile(interaction); - break; - } - case R.id.conv_action_open: { - presenter.openFile(interaction); - break; - } - case R.id.conv_action_delete: { - presenter.deleteConversationItem(interaction); - break; - } - case R.id.conv_action_cancel_message: { - presenter.cancelMessage(interaction); - break; - } - case R.id.conv_action_copy_text: { - addToClipboard((interaction).getBody()); - break; - } + int itemId = item.getItemId(); + if (itemId == R.id.conv_action_download) { + presenter.saveFile(interaction); + } else if (itemId == R.id.conv_action_share) { + presenter.shareFile(interaction); + } else if (itemId == R.id.conv_action_open) { + presenter.openFile(interaction); + } else if (itemId == R.id.conv_action_delete) { + presenter.deleteConversationItem(interaction); + } else if (itemId == R.id.conv_action_cancel_message) { + presenter.cancelMessage(interaction); + } else if (itemId == R.id.conv_action_copy_text) { + addToClipboard((interaction).getBody()); } return true; } @@ -487,8 +475,10 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo viewHolder.mImage.setOnClickListener(v -> { Uri contentUri = ContentUriHandler.getUriForFile(v.getContext(), ContentUriHandler.AUTHORITY_FILES, path); - Intent i = new Intent(context, MediaViewerActivity.class); - i.setAction(Intent.ACTION_VIEW).setDataAndType(contentUri, "image/*").setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Intent i = new Intent(context, MediaViewerActivity.class) + .setAction(Intent.ACTION_VIEW) + .setDataAndType(contentUri, "image/*") + .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); ActivityOptionsCompat options = ActivityOptionsCompat. makeSceneTransitionAnimation(conversationFragment.getActivity(), viewHolder.mImage, "picture"); conversationFragment.startActivityForResult(i, 3006, options.toBundle()); @@ -628,23 +618,24 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo private void configureForFileInfo(@NonNull final ConversationViewHolder viewHolder, @NonNull final Interaction interaction, int position) { DataTransfer file = (DataTransfer) interaction; - File path = presenter.getDeviceRuntimeService().getConversationPath(interaction.getConversationId() == null ? interaction.getConversation().getParticipant() : interaction.getConversationId(), file.getStoragePath()); + + File path = presenter.getDeviceRuntimeService().getConversationPath(file); if (file.isComplete()) file.setSize(path.length()); - String timeString = timestampToDetailString(viewHolder.itemView.getContext(), file.getTimestamp()); - viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(t -> { - if (file.getStatus() == InteractionStatus.TRANSFER_FINISHED) { + viewHolder.compositeDisposable.add(timestampUpdateTimer.subscribe(time -> { + InteractionStatus status = file.getStatus(); + if (status == InteractionStatus.TRANSFER_FINISHED) { viewHolder.mMsgDetailTxt.setText(String.format("%s - %s", timeString, Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getTotalSize()))); - } else if (file.getStatus() == InteractionStatus.TRANSFER_ONGOING) { + } else if (status == InteractionStatus.TRANSFER_ONGOING) { viewHolder.mMsgDetailTxt.setText(String.format("%s / %s - %s", Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getBytesProgress()), Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getTotalSize()), - ResourceMapper.getReadableFileTransferStatus(conversationFragment.getActivity(), file.getStatus()))); + ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.getContext(), status))); } else { viewHolder.mMsgDetailTxt.setText(String.format("%s - %s - %s", timeString, Formatter.formatFileSize(viewHolder.itemView.getContext(), file.getTotalSize()), - ResourceMapper.getReadableFileTransferStatus(conversationFragment.getActivity(), file.getStatus()))); + ResourceMapper.getReadableFileTransferStatus(viewHolder.itemView.getContext(), status))); } })); @@ -731,7 +722,8 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo } else if (type == TransferMsgType.AUDIO) { configureAudio(viewHolder, path); } else { - if (file.getStatus().isError()) { + InteractionStatus status = file.getStatus(); + if (status.isError()) { viewHolder.mIcon.setImageResource(R.drawable.baseline_warning_24); } else { viewHolder.mIcon.setImageResource(R.drawable.baseline_attach_file_24); @@ -739,13 +731,18 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo viewHolder.mMsgTxt.setText(file.getDisplayName()); - if (file.getStatus() == InteractionStatus.TRANSFER_AWAITING_HOST) { + if (status == InteractionStatus.TRANSFER_AWAITING_HOST) { + viewHolder.btnRefuse.setVisibility(View.VISIBLE); viewHolder.mAnswerLayout.setVisibility(View.VISIBLE); viewHolder.btnAccept.setOnClickListener(v -> presenter.acceptFile(file)); viewHolder.btnRefuse.setOnClickListener(v -> presenter.refuseFile(file)); + } else if (status == InteractionStatus.FILE_AVAILABLE) { + viewHolder.btnRefuse.setVisibility(View.GONE); + viewHolder.mAnswerLayout.setVisibility(View.VISIBLE); + viewHolder.btnAccept.setOnClickListener(v -> presenter.acceptFile(file)); } else { viewHolder.mAnswerLayout.setVisibility(View.GONE); - if (file.getStatus() == InteractionStatus.TRANSFER_ONGOING) { + if (status == InteractionStatus.TRANSFER_ONGOING) { viewHolder.progress.setMax((int) (file.getTotalSize() / 1024)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { viewHolder.progress.setProgress((int) (file.getBytesProgress() / 1024), true); @@ -1194,14 +1191,14 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationViewHo FIRST, MIDDLE, LAST, - SINGLE; + SINGLE } private enum TransferMsgType { FILE, IMAGE, AUDIO, - VIDEO; + VIDEO } public enum MessageType { INCOMING_FILE(R.layout.item_conv_file_peer), diff --git a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java index a832ac441..037ad09f0 100644 --- a/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java +++ b/ring-android/app/src/main/java/cx/ring/fragments/ConversationFragment.java @@ -759,23 +759,25 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen return; } requireActivity().startService(new Intent(DRingService.ACTION_FILE_ACCEPT, ConversationPath.toUri(accountId, conversationUri), requireContext(), DRingService.class) - .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getDaemonId())); + .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId()) + .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId())); } @Override public void refuseFile(String accountId, Uri conversationUri, DataTransfer transfer) { requireActivity().startService(new Intent(DRingService.ACTION_FILE_CANCEL, ConversationPath.toUri(accountId, conversationUri), requireContext(), DRingService.class) - .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getDaemonId())); + .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId()) + .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId())); } @Override - public void shareFile(File path) { + public void shareFile(File path, String displayName) { Context c = getContext(); if (c == null) return; android.net.Uri fileUri = null; try { - fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path); + fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName); } catch (IllegalArgumentException e) { Log.e("File Selector", "The selected file can't be shared: " + path.getName()); } @@ -783,7 +785,7 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - String type = c.getContentResolver().getType(fileUri); + String type = c.getContentResolver().getType(fileUri.buildUpon().appendPath(displayName).build()); sendIntent.setDataAndType(fileUri, type); sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri); startActivity(Intent.createChooser(sendIntent, null)); @@ -791,13 +793,13 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen } @Override - public void openFile(File path) { + public void openFile(File path, String displayName) { Context c = getContext(); if (c == null) return; android.net.Uri fileUri = null; try { - fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path); + fileUri = ContentUriHandler.getUriForFile(c, ContentUriHandler.AUTHORITY_FILES, path, displayName); } catch (IllegalArgumentException e) { Log.e(TAG, "The selected file can't be shared: " + path.getName()); } @@ -805,7 +807,7 @@ public class ConversationFragment extends BaseSupportFragment<ConversationPresen Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_VIEW); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - String type = c.getContentResolver().getType(fileUri); + String type = c.getContentResolver().getType(fileUri.buildUpon().appendPath(displayName).build()); sendIntent.setDataAndType(fileUri, type); sendIntent.putExtra(Intent.EXTRA_STREAM, fileUri); //startActivity(Intent.createChooser(sendIntent, null)); diff --git a/ring-android/app/src/main/java/cx/ring/service/DRingService.java b/ring-android/app/src/main/java/cx/ring/service/DRingService.java index 319115791..743ac70b2 100644 --- a/ring-android/app/src/main/java/cx/ring/service/DRingService.java +++ b/ring-android/app/src/main/java/cx/ring/service/DRingService.java @@ -97,6 +97,7 @@ public class DRingService extends Service { static public final String ACTION_FILE_ACCEPT = BuildConfig.APPLICATION_ID + ".action.FILE_ACCEPT"; static public final String ACTION_FILE_CANCEL = BuildConfig.APPLICATION_ID + ".action.FILE_CANCEL"; + static public final String KEY_MESSAGE_ID = "messageId"; static public final String KEY_TRANSFER_ID = "transferId"; static public final String KEY_TEXT_REPLY = "textReply"; @@ -651,13 +652,14 @@ public class DRingService extends Service { } private void handleFileAction(android.net.Uri uri, String action, Bundle extras) { - long id = extras.getLong(KEY_TRANSFER_ID); + String messageId = extras.getString(KEY_MESSAGE_ID); + String id = extras.getString(KEY_TRANSFER_ID); ConversationPath path = ConversationPath.fromUri(uri); if (action.equals(ACTION_FILE_ACCEPT)) { mNotificationService.removeTransferNotification(path.getAccountId(), path.getConversationUri(), id); - mAccountService.acceptFileTransfer(path.getAccountId(), path.getConversationUri(), id); + mAccountService.acceptFileTransfer(path.getAccountId(), path.getConversationUri(), messageId, id); } else if (action.equals(ACTION_FILE_CANCEL)) { - mConversationFacade.cancelFileTransfer(path.getAccountId(), path.getConversationUri(), id); + mConversationFacade.cancelFileTransfer(path.getAccountId(), path.getConversationUri(), messageId, id); } } diff --git a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java index 07eda44a8..bdad5c9f8 100644 --- a/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java +++ b/ring-android/app/src/main/java/cx/ring/services/DeviceRuntimeServiceImpl.java @@ -97,6 +97,11 @@ public class DeviceRuntimeServiceImpl extends DeviceRuntimeService { return AndroidFileUtils.getConversationPath(mContext, conversationId, name); } + @Override + public File getConversationPath(String accountId, String conversationId, String name) { + return AndroidFileUtils.getConversationPath(mContext, accountId, conversationId, name); + } + @Override public File getTemporaryPath(String conversationId, String name) { return AndroidFileUtils.getTempPath(mContext, conversationId, name); diff --git a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java index fb4840dcb..9861ef901 100644 --- a/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java +++ b/ring-android/app/src/main/java/cx/ring/services/NotificationServiceImpl.java @@ -471,12 +471,12 @@ public class NotificationServiceImpl implements NotificationService { if (!remove) { showFileTransferNotification(conversation, transfer); } else { - removeTransferNotification(ConversationPath.toUri(conversation), transfer.getDaemonId()); + removeTransferNotification(ConversationPath.toUri(conversation), transfer.getFileId()); } } @Override - public void removeTransferNotification(String accountId, Uri conversationUri, long transferId) { + public void removeTransferNotification(String accountId, Uri conversationUri, String transferId) { removeTransferNotification(ConversationPath.toUri(accountId, conversationUri), transferId); } @@ -485,7 +485,7 @@ public class NotificationServiceImpl implements NotificationService { * * @param transferId the transfer id which is required to generate the notification id */ - public void removeTransferNotification(android.net.Uri path, long transferId) { + public void removeTransferNotification(android.net.Uri path, String transferId) { int id = getFileTransferNotificationId(path, transferId); dataTransferNotifications.remove(id); cancelFileNotification(id, false); @@ -749,7 +749,7 @@ public class NotificationServiceImpl implements NotificationService { } android.net.Uri path = ConversationPath.toUri(conversation); Log.d(TAG, "showFileTransferNotification " + path); - long dataTransferId = info.getDaemonId(); + String dataTransferId = info.getFileId(); int notificationId = getFileTransferNotificationId(path, dataTransferId); Intent intentConversation = new Intent(DRingService.ACTION_CONV_ACCEPT, path, mContext, DRingService.class); @@ -955,7 +955,7 @@ public class NotificationServiceImpl implements NotificationService { return (NOTIF_MSG + accountId + contact.toString()).hashCode(); } - private int getFileTransferNotificationId(android.net.Uri path, long dataTransferId) { + private int getFileTransferNotificationId(android.net.Uri path, String dataTransferId) { return (NOTIF_FILE_TRANSFER + path.toString() + dataTransferId).hashCode(); } diff --git a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationAdapter.java b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationAdapter.java index be6428a5f..0eb649d75 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationAdapter.java +++ b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationAdapter.java @@ -632,13 +632,15 @@ public class TvConversationAdapter extends RecyclerView.Adapter<TvConversationVi } context.startService(new Intent(DRingService.ACTION_FILE_ACCEPT) .setClass(context.getApplicationContext(), DRingService.class) - .putExtra(DRingService.KEY_TRANSFER_ID, file.getDaemonId())); + .putExtra(DRingService.KEY_MESSAGE_ID, file.getMessageId()) + .putExtra(DRingService.KEY_TRANSFER_ID, file.getFileId())); }); viewHolder.btnRefuse.setOnClickListener(v -> { Context context = v.getContext(); context.startService(new Intent(DRingService.ACTION_FILE_CANCEL) .setClass(context.getApplicationContext(), DRingService.class) - .putExtra(DRingService.KEY_TRANSFER_ID, file.getDaemonId())); + .putExtra(DRingService.KEY_MESSAGE_ID, file.getMessageId()) + .putExtra(DRingService.KEY_TRANSFER_ID, file.getFileId())); }); } else { viewHolder.mAnswerLayout.setVisibility(View.GONE); diff --git a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java index a0abdab81..0e93ea008 100644 --- a/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java +++ b/ring-android/app/src/main/java/cx/ring/tv/conversation/TvConversationFragment.java @@ -379,7 +379,7 @@ public class TvConversationFragment extends BaseSupportFragment<ConversationPres } @Override - public void shareFile(File path) { + public void shareFile(File path, String displayName) { Context c = getContext(); if (c == null) return; @@ -400,7 +400,7 @@ public class TvConversationFragment extends BaseSupportFragment<ConversationPres } @Override - public void openFile(File path) { + public void openFile(File path, String displayName) { Context c = getContext(); if (c == null) return; @@ -807,7 +807,8 @@ public class TvConversationFragment extends BaseSupportFragment<ConversationPres requireActivity().startService(new Intent(DRingService.ACTION_FILE_ACCEPT) .setClass(requireContext(), DRingService.class) .setData(ConversationPath.toUri(accountId, conversationUri)) - .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getDaemonId())); + .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId()) + .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId())); } @Override @@ -815,7 +816,8 @@ public class TvConversationFragment extends BaseSupportFragment<ConversationPres requireActivity().startService(new Intent(DRingService.ACTION_FILE_CANCEL) .setClass(requireContext(), DRingService.class) .setData(ConversationPath.toUri(accountId, conversationUri)) - .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getDaemonId())); + .putExtra(DRingService.KEY_MESSAGE_ID, transfer.getMessageId()) + .putExtra(DRingService.KEY_TRANSFER_ID, transfer.getFileId())); } } diff --git a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java index f59739659..f57bf02cb 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java +++ b/ring-android/app/src/main/java/cx/ring/utils/AndroidFileUtils.java @@ -52,6 +52,7 @@ import java.util.Locale; import androidx.annotation.NonNull; +import net.jami.model.Conversation; import net.jami.utils.FileUtils; import io.reactivex.Completable; @@ -317,6 +318,22 @@ public class AndroidFileUtils { }).subscribeOn(Schedulers.io()); } + public static @NonNull Single<File> getFileToSend(@NonNull Context context, @NonNull Conversation conversation, @NonNull Uri uri) { + ContentResolver contentResolver = context.getContentResolver(); + File cacheDir = context.getCacheDir(); + return Single.fromCallable(() -> { + File file = new File(cacheDir, getFilename(contentResolver, uri)); + try (InputStream inputStream = contentResolver.openInputStream(uri); + FileOutputStream output = new FileOutputStream(file)) { + if (inputStream == null) + throw new FileNotFoundException(); + net.jami.utils.FileUtils.copyFile(inputStream, output); + output.flush(); + } + return file; + }).subscribeOn(Schedulers.io()); + } + public static Completable moveToUri(@NonNull ContentResolver cr, @NonNull File input, @NonNull Uri outUri) { return Completable.fromAction(() -> { try (InputStream inputStream = new FileInputStream(input); @@ -363,7 +380,6 @@ public class AndroidFileUtils { public static File getConversationDir(Context context, String conversationId) { File conversationsDir = getFilePath(context, "conversation_data"); - if (!conversationsDir.exists()) conversationsDir.mkdir(); @@ -374,9 +390,28 @@ public class AndroidFileUtils { return conversationDir; } + public static File getConversationDir(Context context, String accountId, String conversationId) { + File conversationsDir = getFilePath(context, "conversation_data"); + if (!conversationsDir.exists()) + conversationsDir.mkdir(); + + File accountDir = new File(conversationsDir, accountId); + if (!accountDir.exists()) + accountDir.mkdir(); + + File conversationDir = new File(accountDir, conversationId); + if (!conversationDir.exists()) + conversationDir.mkdir(); + + return conversationDir; + } + public static File getConversationPath(Context context, String conversationId, String name) { return new File(getConversationDir(context, conversationId), name); } + public static File getConversationPath(Context context, String accountId, String conversationId, String name) { + return new File(getConversationDir(context, accountId, conversationId), name); + } public static File getTempPath(Context context, String conversationId, String name) { File conversationsDir = getCachePath(context, "conversation_data"); diff --git a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java index 9c1d51d8b..27f733902 100644 --- a/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java +++ b/ring-android/app/src/main/java/cx/ring/utils/ContentUriHandler.java @@ -24,12 +24,13 @@ import android.content.Context; import android.content.res.Resources; import android.net.Uri; import android.os.Build; +import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.content.FileProvider; import net.jami.utils.FileUtils; -import net.jami.utils.Log; import java.io.File; @@ -75,28 +76,33 @@ public class ContentUriHandler { } public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) { - if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) { - try { - return FileProvider.getUriForFile(context, authority, file); - } catch (IllegalArgumentException e) { + return getUriForFile(context, authority, file, null); + } + public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file, @Nullable String displayName) { + try { + return displayName == null ? FileProvider.getUriForFile(context, authority, file) + : FileProvider.getUriForFile(context, authority, file, displayName); + } catch (IllegalArgumentException e) { + if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - net.jami.utils.Log.w(TAG, "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e); + Log.w(TAG, "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e); return Uri.fromFile(file); } else { - net.jami.utils.Log.w(TAG, "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e); + Log.w(TAG, "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e); // Note: Periodically clear this cache final File cacheFolder = new File(context.getCacheDir(), HUAWEI_MANUFACTURER); final File cacheLocation = new File(cacheFolder, file.getName()); if (FileUtils.copyFile(file, cacheLocation)) { Log.i(TAG, "Completed Android N+ Huawei file copy. Attempting to return the cached file"); - return FileProvider.getUriForFile(context, authority, cacheLocation); + return displayName == null ? FileProvider.getUriForFile(context, authority, cacheLocation) + : FileProvider.getUriForFile(context, authority, cacheLocation, displayName); } - net.jami.utils.Log.e(TAG, "Failed to copy the Huawei file. Re-throwing exception"); + Log.e(TAG, "Failed to copy the Huawei file. Re-throwing exception"); throw new IllegalArgumentException("Huawei devices are unsupported for Android N"); } + } else { + throw e; } - } else { - return FileProvider.getUriForFile(context, authority, file); } } } diff --git a/ring-android/app/src/main/res/layout/item_conv_file_me.xml b/ring-android/app/src/main/res/layout/item_conv_file_me.xml index 6289f837a..17469f2b0 100644 --- a/ring-android/app/src/main/res/layout/item_conv_file_me.xml +++ b/ring-android/app/src/main/res/layout/item_conv_file_me.xml @@ -141,7 +141,7 @@ android:layout_weight="0.5" android:background="?attr/selectableItemBackground" android:gravity="center" - android:text="@string/accept" + android:text="@string/file_download" android:textColor="@color/color_primary_dark" /> </LinearLayout> diff --git a/ring-android/app/src/main/res/layout/item_conv_file_peer.xml b/ring-android/app/src/main/res/layout/item_conv_file_peer.xml index b784d2fc4..251a58740 100644 --- a/ring-android/app/src/main/res/layout/item_conv_file_peer.xml +++ b/ring-android/app/src/main/res/layout/item_conv_file_peer.xml @@ -148,7 +148,7 @@ android:layout_weight="0.5" android:background="?attr/selectableItemBackground" android:gravity="center" - android:text="@string/accept" + android:text="@string/file_download" android:textColor="@color/color_primary_dark" /> </LinearLayout> diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml index d7c96cd03..1d2aa5260 100644 --- a/ring-android/app/src/main/res/values/strings.xml +++ b/ring-android/app/src/main/res/values/strings.xml @@ -241,8 +241,8 @@ along with this program; if not, write to the Free Software <string name="conversation_info_contact_you">You</string> <string name="conversation_members">Members</string> - <string name="conversation_preference_color">Change conversation color</string> - <string name="conversation_preference_emoji">Change conversation emoji</string> + <string name="conversation_preference_color">Conversation color</string> + <string name="conversation_preference_emoji">Conversation emoji</string> <string name="conversation_type_contact">Jami contact</string> <string name="conversation_type_private">Private swarm</string> <string name="conversation_type_group">Group swarm</string> @@ -337,6 +337,7 @@ along with this program; if not, write to the Free Software <string name="file_transfer_status_timed_out">timed out</string> <string name="file_saved_in">File saved in %s</string> <string name="file_saved_successfully">File saved successfully</string> + <string name="file_download">Download</string> <string name="no_space_left_on_device">No space left on device</string> <string name="title_media_viewer">Media viewer</string> <string name="menu_file_open">Open file</string> diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java index 0e793ccc5..d106a517b 100644 --- a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java +++ b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationPresenter.java @@ -39,6 +39,7 @@ import net.jami.services.DeviceRuntimeService; import net.jami.services.HardwareService; import net.jami.services.PreferencesService; import net.jami.services.VCardService; +import net.jami.utils.FileUtils; import net.jami.utils.Log; import net.jami.utils.StringUtils; import net.jami.utils.Tuple; @@ -307,28 +308,24 @@ public class ConversationPresenter extends RootPresenter<ConversationView> { public void saveFile(Interaction interaction) { DataTransfer transfer = (DataTransfer) interaction; String fileAbsolutePath = getDeviceRuntimeService(). - getConversationPath(mConversation.getUri().getRawRingId(), transfer.getStoragePath()) + getConversationPath(transfer) .getAbsolutePath(); getView().startSaveFile(transfer, fileAbsolutePath); } public void shareFile(Interaction interaction) { DataTransfer file = (DataTransfer) interaction; - File path = getDeviceRuntimeService().getConversationPath(mConversation.getUri().getRawRingId(), file.getStoragePath()); - getView().shareFile(path); + File path = getDeviceRuntimeService().getConversationPath(file); + getView().shareFile(path, file.getDisplayName()); } public void openFile(Interaction interaction) { DataTransfer file = (DataTransfer) interaction; - File path = getDeviceRuntimeService().getConversationPath(mConversation.getUri().getRawRingId(), file.getStoragePath()); - getView().openFile(path); + File path = getDeviceRuntimeService().getConversationPath(file); + getView().openFile(path, file.getDisplayName()); } public void acceptFile(DataTransfer transfer) { - if (!getDeviceRuntimeService().hasWriteExternalStoragePermission()) { - getView().askWriteExternalStoragePermission(); - return; - } getView().acceptFile(mConversation.getAccountId(), mConversationUri, transfer); } diff --git a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java index e52b98d10..9194f0f8a 100644 --- a/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java +++ b/ring-android/libringclient/src/main/java/net/jami/conversation/ConversationView.java @@ -73,8 +73,8 @@ public interface ConversationView extends BaseView { void acceptFile(String accountId, Uri conversationUri, DataTransfer transfer); void refuseFile(String accountId, Uri conversationUri, DataTransfer transfer); - void shareFile(File path); - void openFile(File path); + void shareFile(File path, String displayName); + void openFile(File path, String displayName); void addElement(Interaction e); void updateElement(Interaction e); diff --git a/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java b/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java index ec34df577..99bea15d3 100644 --- a/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java +++ b/ring-android/libringclient/src/main/java/net/jami/facades/ConversationFacade.java @@ -38,6 +38,7 @@ import net.jami.services.HistoryService; import net.jami.services.NotificationService; import net.jami.services.PreferencesService; import net.jami.smartlist.SmartListViewModel; +import net.jami.utils.FileUtils; import net.jami.utils.Log; import net.jami.utils.Tuple; @@ -267,18 +268,21 @@ public class ConversationFacade { } public Completable sendFile(Conversation conversation, Uri to, File file) { - return Single.fromCallable(() -> { - if (file == null || !file.exists() || !file.canRead()) { - Log.w(TAG, "sendFile: file not found or not readable: " + file); - return null; - } + if (file == null || !file.exists() || !file.canRead()) { + Log.w(TAG, "sendFile: file not found or not readable: " + file); + return null; + } - DataTransfer transfer = new DataTransfer(conversation, to.getRawRingId(), conversation.getAccountId(), file.getName(), true, file.length(), 0, 0L); - if (conversation.isSwarm()) { - transfer.setSwarmInfo(conversation.getUri().getRawRingId(), null, null); - } else { - mHistoryService.insertInteraction(conversation.getAccountId(), conversation, transfer).blockingAwait(); - } + if (conversation.isSwarm()) { + File destPath = mDeviceRuntimeService.getNewConversationPath(conversation.getAccountId(), conversation.getUri().getRawRingId(), file.getName()); + FileUtils.moveFile(file, destPath); + mAccountService.sendFile(conversation, destPath); + return Completable.complete(); + } + + return Single.fromCallable(() -> { + DataTransfer transfer = new DataTransfer(conversation, to.getRawRingId(), conversation.getAccountId(), file.getName(), true, file.length(), 0, null); + mHistoryService.insertInteraction(conversation.getAccountId(), conversation, transfer).blockingAwait(); transfer.destination = mDeviceRuntimeService.getConversationDir(conversation.getUri().getRawRingId()); return transfer; @@ -297,7 +301,7 @@ public class ConversationFacade { if (element.getType() == Interaction.InteractionType.DATA_TRANSFER) { DataTransfer transfer = (DataTransfer) element; if (transfer.getStatus() == Interaction.InteractionStatus.TRANSFER_ONGOING) { - mAccountService.cancelDataTransfer(conversation.getAccountId(), conversation.getUri().getRawRingId(), transfer.getDaemonId()); + mAccountService.cancelDataTransfer(conversation.getAccountId(), conversation.getUri().getRawRingId(), transfer.getMessageId(), transfer.getFileId()); } else { File file = mDeviceRuntimeService.getConversationPath(conversation.getUri().getRawRingId(), transfer.getStoragePath()); mDisposableBag.add(Completable.mergeArrayDelayError( @@ -588,7 +592,7 @@ public class ConversationFacade { Conversation conversation = mAccountService.getAccount(transfer.getAccount()).onDataTransferEvent(transfer); if (transfer.getStatus() == Interaction.InteractionStatus.TRANSFER_CREATED && !transfer.isOutgoing()) { if (transfer.canAutoAccept(mPreferencesService.getMaxFileAutoAccept(transfer.getAccount()))) { - mAccountService.acceptFileTransfer(conversation, transfer); + mAccountService.acceptFileTransfer(conversation, transfer.getFileId(), transfer); return; } } @@ -678,10 +682,10 @@ public class ConversationFacade { }); } - public void cancelFileTransfer(String accountId, Uri conversationId, long id) { - mAccountService.cancelDataTransfer(accountId, conversationId.isSwarm() ? conversationId.getRawRingId() : "", id); - mNotificationService.removeTransferNotification(accountId, conversationId, id); - DataTransfer transfer = mAccountService.getAccount(accountId).getDataTransfer(id); + public void cancelFileTransfer(String accountId, Uri conversationId, String messageId, String fileId) { + mAccountService.cancelDataTransfer(accountId, conversationId.isSwarm() ? conversationId.getRawRingId() : "", messageId, fileId); + mNotificationService.removeTransferNotification(accountId, conversationId, fileId); + DataTransfer transfer = mAccountService.getAccount(accountId).getDataTransfer(fileId); if (transfer != null) deleteConversationItem((Conversation) transfer.getConversation(), transfer); } diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Account.java b/ring-android/libringclient/src/main/java/net/jami/model/Account.java index 934d36658..1ffe46f61 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Account.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/Account.java @@ -65,7 +65,7 @@ public class Account { private final Map<String, TrustRequest> mRequests = new HashMap<>(); private final Map<String, Contact> mContactCache = new HashMap<>(); private final Map<String, Conversation> swarmConversations = new HashMap<>(); - private final HashMap<Long, DataTransfer> mDataTransfers = new HashMap<>(); + private final HashMap<String, DataTransfer> mDataTransfers = new HashMap<>(); private final Map<String, Conversation> conversations = new HashMap<>(); private final Map<String, Conversation> pending = new HashMap<>(); @@ -379,6 +379,7 @@ public class Account { } public Conversation onDataTransferEvent(DataTransfer transfer) { + Log.d(TAG, "Account onDataTransferEvent " + transfer.getMessageId()); Conversation conversation = (Conversation) transfer.getConversation(); Interaction.InteractionStatus transferEventCode = transfer.getStatus(); if (transferEventCode == Interaction.InteractionStatus.TRANSFER_CREATED) { @@ -1120,12 +1121,12 @@ public class Account { mLoadedProfile = profile; } - public DataTransfer getDataTransfer(long id) { + public DataTransfer getDataTransfer(String id) { return mDataTransfers.get(id); } - public void putDataTransfer(long transferId, DataTransfer transfer) { - mDataTransfers.put(transferId, transfer); + public void putDataTransfer(String fileId, DataTransfer transfer) { + mDataTransfers.put(fileId, transfer); } private static class ConversationComparator implements Comparator<Conversation> { diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java index 0aa215df5..db047ac28 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/Conversation.java @@ -694,7 +694,7 @@ public class Conversation extends ConversationHistory { } public void updateFileTransfer(DataTransfer transfer, Interaction.InteractionStatus eventCode) { - DataTransfer dataTransfer = (DataTransfer) findConversationElement(transfer.getId()); + DataTransfer dataTransfer = (DataTransfer) (isSwarm() ? transfer : findConversationElement(transfer.getId())); if (dataTransfer != null) { dataTransfer.setStatus(eventCode); updatedElementSubject.onNext(new Tuple<>(dataTransfer, ElementStatus.UPDATE)); diff --git a/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java b/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java index 079d8793f..31a8ceaeb 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/DataTransfer.java @@ -24,6 +24,7 @@ import net.jami.utils.HashUtils; import net.jami.utils.StringUtils; import java.io.File; +import java.io.IOException; import java.util.Set; public class DataTransfer extends Interaction { @@ -32,8 +33,9 @@ public class DataTransfer extends Interaction { private long mBytesProgress; //private final String mPeerId; private String mExtension; - //private String mTransferId; + private String mFileId; public File destination; + private File mDaemonPath; private static final Set<String> IMAGE_EXTENSIONS = HashUtils.asSet("jpg", "jpeg", "png", "gif"); private static final Set<String> AUDIO_EXTENSIONS = HashUtils.asSet("ogg", "mp3", "aac", "flac", "m4a"); @@ -41,7 +43,8 @@ public class DataTransfer extends Interaction { private static final int MAX_SIZE = 32 * 1024 * 1024; private static final int UNLIMITED_SIZE = 256 * 1024 * 1024; - public DataTransfer(ConversationHistory conversation, String peer, String account, String displayName, boolean isOutgoing, long totalSize, long bytesProgress, long daemonId) { + /* Legacy constructor */ + public DataTransfer(ConversationHistory conversation, String peer, String account, String displayName, boolean isOutgoing, long totalSize, long bytesProgress, String fileId) { mAuthor = isOutgoing ? null : peer; mAccount = account; mConversation = conversation; @@ -52,8 +55,15 @@ public class DataTransfer extends Interaction { mType = InteractionType.DATA_TRANSFER.toString(); mTimestamp = System.currentTimeMillis(); mIsRead = 1; - mDaemonId = daemonId; mIsIncoming = !isOutgoing; + if (fileId != null) { + mFileId = fileId; + try { + mDaemonId = Long.parseUnsignedLong(fileId); + } catch (Exception e) { + + } + } } public DataTransfer(Interaction interaction) { @@ -72,18 +82,15 @@ public class DataTransfer extends Interaction { mIsIncoming = interaction.mIsIncoming;//mAuthor != null; } - public DataTransfer(long transferId, String accountId, String peerUri, String displayName, boolean isOutgoing, long timestamp, long totalSize, long bytesProgress) { - mDaemonId = transferId; + public DataTransfer(String fileId, String accountId, String peerUri, String displayName, boolean isOutgoing, long timestamp, long totalSize, long bytesProgress) { mAccount = accountId; - //mTransferId = transferId; - //mPeerId = peerUri; + mFileId = fileId; mBody = displayName; mAuthor = peerUri; mIsIncoming = !isOutgoing; mTotalSize = totalSize; mBytesProgress = bytesProgress; mTimestamp = timestamp; - //mDaemonId = Long.parseUnsignedLong(transferId); mType = InteractionType.DATA_TRANSFER.toString(); } @@ -115,7 +122,7 @@ public class DataTransfer extends Interaction { public String getStoragePath() { if (StringUtils.isEmpty(mBody)) { - return getMessageId(); + return getFileId(); } else { String ext = StringUtils.getFileExtension(mBody); if (ext.length() > 8) @@ -148,7 +155,8 @@ public class DataTransfer extends Interaction { return mBytesProgress; } - public void setBytesProgress(long bytesProgress) { mBytesProgress = bytesProgress; + public void setBytesProgress(long bytesProgress) { + mBytesProgress = bytesProgress; } public boolean isError() { @@ -159,5 +167,26 @@ public class DataTransfer extends Interaction { return maxSize == UNLIMITED_SIZE || getTotalSize() <= maxSize; } + public String getFileId() { + return mFileId; + } + + public void setDaemonPath(File file) { + mDaemonPath = file; + } + public File getDaemonPath() { + return mDaemonPath; + } + + public File getPublicPath() { + if (mDaemonPath == null) { + return null; + } + try { + return mDaemonPath.getCanonicalFile(); + } catch (IOException e) { + return null; + } + } } diff --git a/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java b/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java index b4608a6e7..d7f2c02dc 100644 --- a/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java +++ b/ring-android/libringclient/src/main/java/net/jami/model/Interaction.java @@ -242,7 +242,8 @@ public class Interaction { TRANSFER_AWAITING_PEER, TRANSFER_AWAITING_HOST, TRANSFER_TIMEOUT_EXPIRED, - TRANSFER_FINISHED; + TRANSFER_FINISHED, + FILE_AVAILABLE; static InteractionStatus fromString(String str) { for (InteractionStatus s : values()) { diff --git a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java index 7568ece1c..f732512be 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/AccountService.java @@ -29,7 +29,6 @@ import net.jami.daemon.Blob; import net.jami.daemon.DataTransferInfo; import net.jami.daemon.JamiService; import net.jami.daemon.StringMap; -import net.jami.daemon.StringVect; import net.jami.daemon.UintVect; import net.jami.model.Account; import net.jami.model.AccountConfig; @@ -1494,18 +1493,16 @@ public class AccountService { } private Interaction addMessage(Account account, Conversation conversation, Map<String, String> message) { + /* for (Map.Entry<String, String> e : message.entrySet()) { + Log.w(TAG, e.getKey() + " -> " + e.getValue()); + } */ String id = message.get("id"); - //List<String> parents = Arrays.asList(message.get("parents").split(",")); - //if (parents.size() == 1 && parents.get(0).isEmpty()) - // parents = Collections.emptyList(); String type = message.get("type"); String author = message.get("author"); String parent = message.get("linearizedParent"); List<String> parents = StringUtils.isEmpty(parent) ? Collections.emptyList() : Collections.singletonList(parent); Uri authorUri = Uri.fromId(author); - //Log.w(TAG, "addMessage2 " + type + " " + author + " id:" + id + " parents:" + parents); - long timestamp = Long.parseLong(message.get("timestamp")) * 1000; Contact contact = conversation.findContact(authorUri); if (contact == null) { @@ -1522,19 +1519,24 @@ public class AccountService { break; case "application/data-transfer+json": { try { - String transferId = message.get("tid"); - long tid = Long.parseLong(transferId); String fileName = message.get("displayName"); - long fileSize = Long.parseLong(message.get("totalSize")); - interaction = account.getDataTransfer(tid); - if (interaction == null) { - interaction = new DataTransfer(tid, account.getAccountID(), author, fileName, contact.isUser(), timestamp, fileSize, 0); - File path = mDeviceRuntimeService.getConversationPath(conversation.getUri().getRawRingId(), ((DataTransfer) interaction).getStoragePath()); - boolean exists = path.exists(); - if (exists) - ((DataTransfer) interaction).setBytesProgress(path.length()); - interaction.setStatus(exists ? InteractionStatus.TRANSFER_FINISHED : InteractionStatus.TRANSFER_TIMEOUT_EXPIRED); - } + String fileId = message.get("fileId"); + //interaction = account.getDataTransfer(fileId); + //if (interaction == null) { + String[] paths = new String[1]; + long[] progressA = new long[1]; + long[] totalA = new long[1]; + JamiService.fileTransferInfo(account.getAccountID(), conversation.getUri().getRawRingId(), fileId, paths, totalA, progressA); + if (totalA[0] == 0) { + totalA[0] = Long.parseLong(message.get("totalSize")); + } + File path = new File(paths[0]); + interaction = new DataTransfer(fileId, account.getAccountID(), author, fileName, contact.isUser(), timestamp, totalA[0], progressA[0]); + ((DataTransfer)interaction).setDaemonPath(path); + boolean isComplete = path.exists() && progressA[0] == totalA[0]; + Log.w(TAG, "add DataTransfer at " + paths[0] + " with progress " + progressA[0] + "/" + totalA[0]); + interaction.setStatus(isComplete ? InteractionStatus.TRANSFER_FINISHED : InteractionStatus.FILE_AVAILABLE); + //} } catch (Exception e) { interaction = new Interaction(conversation, Interaction.InteractionType.INVALID); } @@ -1690,7 +1692,7 @@ public class AccountService { Log.i(TAG, "sendFile() id=" + dataTransfer.getId() + " accountId=" + dataTransferInfo.getAccountId() + ", peer=" + dataTransferInfo.getPeer() + ", filePath=" + dataTransferInfo.getPath()); long[] id = new long[1]; - DataTransferError err = getDataTransferError(JamiService.sendFile(dataTransferInfo, id)); + DataTransferError err = getDataTransferError(JamiService.sendFileLegacy(dataTransferInfo, id)); if (err != DataTransferError.SUCCESS) { throw new IOException(err.name()); } else { @@ -1701,6 +1703,10 @@ public class AccountService { }).subscribeOn(Schedulers.from(mExecutor)); } + public void sendFile(Conversation conversation, final File file) { + mExecutor.execute(() -> JamiService.sendFile(conversation.getAccountId(), conversation.getUri().getRawRingId(), file.getAbsolutePath(), file.getName(), "")); + } + public List<net.jami.daemon.Message> getLastMessages(String accountId, long baseTime) { try { return mExecutor.submit(() -> SwigNativeConverter.toJava(JamiService.getLastMessages(accountId, baseTime))).get(); @@ -1710,30 +1716,33 @@ public class AccountService { return new ArrayList<>(); } - public void acceptFileTransfer(final String accountId, final Uri conversationUri, long id) { + public void acceptFileTransfer(final String accountId, final Uri conversationUri, String messageId, String fileId) { Account account = getAccount(accountId); if (account != null) { Conversation conversation = account.getByUri(conversationUri); - acceptFileTransfer(conversation, account.getDataTransfer(id)); + acceptFileTransfer(conversation, fileId, conversation.isSwarm() ? (DataTransfer)conversation.getMessage(messageId) : account.getDataTransfer(fileId)); } } - public void acceptFileTransfer(Conversation conversation, DataTransfer transfer) { - if (transfer == null) - return; - File path = mDeviceRuntimeService.getTemporaryPath(conversation.getUri().getRawRingId(), transfer.getStoragePath()); - String conversationId = conversation.getUri().getRawRingId(); - acceptFileTransfer(conversation.getAccountId(), conversationId, transfer.getDaemonId(), path.getAbsolutePath(), 0); - } - - private void acceptFileTransfer(final String accountId, final String conversationId, final Long dataTransferId, final String filePath, long offset) { - Log.i(TAG, "acceptFileTransfer() id=" + dataTransferId + ", path=" + filePath + ", offset=" + offset); - mExecutor.execute(() -> JamiService.acceptFileTransfer(accountId, conversationId, dataTransferId, filePath, offset)); + public void acceptFileTransfer(Conversation conversation, String fileId, DataTransfer transfer) { + if (conversation.isSwarm()) { + String conversationId = conversation.getUri().getRawRingId(); + File newPath = mDeviceRuntimeService.getNewConversationPath(conversation.getAccountId(), conversationId, transfer.getDisplayName()); + Log.i(TAG, "downloadFile() id=" + conversation.getAccountId() + ", path=" + conversationId + " " + fileId + " to -> " + newPath.getAbsolutePath()); + JamiService.downloadFile(conversation.getAccountId(), conversationId, transfer.getMessageId(), fileId, newPath.getAbsolutePath()); + } else { + if (transfer == null) { + return; + } + File path = mDeviceRuntimeService.getTemporaryPath(conversation.getUri().getRawRingId(), transfer.getStoragePath()); + Log.i(TAG, "acceptFileTransfer() id=" + fileId + ", path=" + path.getAbsolutePath()); + JamiService.acceptFileTransfer(conversation.getAccountId(), fileId, path.getAbsolutePath()); + } } - public void cancelDataTransfer(final String accountId, final String conversationId, long dataTransferId) { - Log.i(TAG, "cancelDataTransfer() id=" + dataTransferId); - mExecutor.execute(() -> JamiService.cancelDataTransfer(accountId, conversationId, dataTransferId)); + public void cancelDataTransfer(final String accountId, final String conversationId, final String messageId, final String fileId) { + Log.i(TAG, "cancelDataTransfer() id=" + fileId); + mExecutor.execute(() -> JamiService.cancelDataTransfer(accountId, conversationId, fileId)); } private class DataTransferRefreshTask implements Runnable { @@ -1752,7 +1761,7 @@ public class AccountService { public void run() { synchronized (mToUpdate) { if (mToUpdate.getStatus() == Interaction.InteractionStatus.TRANSFER_ONGOING) { - dataTransferEvent(mAccount, mConversation, mToUpdate.getDaemonId(), 5); + dataTransferEvent(mAccount, mConversation, mToUpdate.getMessageId(), mToUpdate.getFileId(), 5); } else { scheduledTask.cancel(false); scheduledTask = null; @@ -1761,41 +1770,69 @@ public class AccountService { } } - void dataTransferEvent(String accountId, String conversationId, final long transferId, int eventCode) { + void dataTransferEvent(String accountId, String conversationId, String interactionId, final String fileId, int eventCode) { Account account = getAccount(accountId); if (account != null) { Conversation conversation = StringUtils.isEmpty(conversationId) ? null : account.getSwarm(conversationId); - if (conversation == null) - conversation = account.getByUri(conversationId); - if (conversation == null) - return; - dataTransferEvent(account, conversation, transferId, eventCode); + dataTransferEvent(account, conversation, interactionId, fileId, eventCode); } } - void dataTransferEvent(Account account, Conversation conversation, final long transferId, int eventCode) { + void dataTransferEvent(Account account, Conversation conversation, final String interactionId, final String fileId, int eventCode) { Interaction.InteractionStatus transferStatus = getDataTransferEventCode(eventCode); - Log.d(TAG, "Data Transfer " + transferStatus); - DataTransferInfo info = new DataTransferInfo(); - if (getDataTransferError(JamiService.dataTransferInfo(account.getAccountID(), conversation.getUri().getRawRingId(), transferId, info)) != DataTransferError.SUCCESS) - return; + Log.d(TAG, "Data Transfer " + interactionId + " " + fileId + " " + transferStatus); + + String from; + long total, progress; + String displayName; + DataTransfer transfer = account.getDataTransfer(fileId); + boolean outgoing = false; + if (conversation == null) { + DataTransferInfo info = new DataTransferInfo(); + DataTransferError err = getDataTransferError(JamiService.dataTransferInfo(account.getAccountID(), fileId, info)); + if (err != DataTransferError.SUCCESS) { + Log.d(TAG, "Data Transfer error getting details " + err); + return; + } + from = info.getPeer(); + total = info.getTotalSize(); + progress = info.getBytesProgress(); + conversation = account.getByUri(from); + outgoing = info.getFlags() == 0; + displayName = info.getDisplayName(); + } else { + String[] paths = new String[1]; + long[] progressA = new long[1]; + long[] totalA = new long[1]; + JamiService.fileTransferInfo(account.getAccountID(), conversation.getUri().getRawRingId(), fileId, paths, totalA, progressA); + progress = progressA[0]; + total = totalA[0]; + if (transfer == null && !StringUtils.isEmpty(interactionId)) { + transfer = (DataTransfer) conversation.getMessage(interactionId); + } + if (transfer == null) + return; + transfer.setConversation(conversation); + transfer.setDaemonPath(new File(paths[0])); + from = transfer.getAuthor(); + displayName = transfer.getDisplayName(); + } - boolean outgoing = info.getFlags() == 0; - DataTransfer transfer = account.getDataTransfer(transferId); if (transfer == null) { if (outgoing && mStartingTransfer != null) { + Log.d(TAG, "Data Transfer mStartingTransfer"); transfer = mStartingTransfer; mStartingTransfer = null; } else { - transfer = new DataTransfer(conversation, info.getPeer(), account.getAccountID(), info.getDisplayName(), - outgoing, info.getTotalSize(), - info.getBytesProgress(), transferId); + transfer = new DataTransfer(conversation, from, account.getAccountID(), displayName, + outgoing, total, + progress, fileId); if (conversation.isSwarm()) { - transfer.setSwarmInfo(conversation.getUri().getRawRingId(), null, null); + transfer.setSwarmInfo(conversation.getUri().getRawRingId(), interactionId, null); } else { mHistoryService.insertInteraction(account.getAccountID(), conversation, transfer).blockingAwait(); } } - account.putDataTransfer(transferId, transfer); + account.putDataTransfer(fileId, transfer); } else synchronized (transfer) { InteractionStatus oldState = transfer.getStatus(); if (oldState != transferStatus) { @@ -1810,7 +1847,7 @@ public class AccountService { tmpPath.delete(); } } else if (transferStatus == (Interaction.InteractionStatus.TRANSFER_FINISHED)) { - if (!transfer.isOutgoing()) { + if (!conversation.isSwarm() && !transfer.isOutgoing()) { File tmpPath = mDeviceRuntimeService.getTemporaryPath(conversation.getUri().getRawRingId(), transfer.getStoragePath()); File path = mDeviceRuntimeService.getConversationPath(conversation.getUri().getRawRingId(), transfer.getStoragePath()); FileUtils.moveFile(tmpPath, path); @@ -1818,12 +1855,13 @@ public class AccountService { } } transfer.setStatus(transferStatus); - transfer.setBytesProgress(info.getBytesProgress()); + transfer.setBytesProgress(progress); if (!conversation.isSwarm()) { mHistoryService.updateInteraction(transfer, account.getAccountID()).subscribe(); } } + Log.d(TAG, "Data Transfer dataTransferSubject.onNext"); dataTransferSubject.onNext(transfer); } diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java index cd26b52b8..b50d6cb42 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/DaemonService.java @@ -380,9 +380,9 @@ public class DaemonService { class DaemonDataTransferCallback extends DataTransferCallback { @Override - public void dataTransferEvent(String accountId, String conversationId, long transferId, int eventCode) { - Log.d(TAG, "dataTransferEvent: conversationId=" + conversationId + ", transferId=" + transferId + ", eventCode=" + eventCode); - mAccountService.dataTransferEvent(accountId, conversationId, transferId, eventCode); + public void dataTransferEvent(String accountId, String conversationId, String interactionId, String fileId, int eventCode) { + Log.d(TAG, "dataTransferEvent: conversationId=" + conversationId + ", fileId=" + fileId + ", eventCode=" + eventCode); + mAccountService.dataTransferEvent(accountId, conversationId, interactionId, fileId, eventCode); } } diff --git a/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.java b/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.java index 4f551e896..c6d5aafc5 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/DeviceRuntimeService.java @@ -19,6 +19,9 @@ */ package net.jami.services; +import net.jami.model.Conversation; +import net.jami.model.DataTransfer; + import java.io.File; public abstract class DeviceRuntimeService implements DaemonService.SystemInfoCallbacks { @@ -30,6 +33,24 @@ public abstract class DeviceRuntimeService implements DaemonService.SystemInfoCa public abstract File getFilePath(String name); public abstract File getConversationPath(String conversationId, String name); + public abstract File getConversationPath(String accountId, String conversationId, String name); + + public File getConversationPath(DataTransfer interaction) { + return interaction.getConversationId() == null + ? getConversationPath(interaction.getConversation().getParticipant(), interaction.getStoragePath()) + : interaction.getPublicPath(); + } + public File getNewConversationPath(String accountId, String conversationId, String name) { + int prefix = 0; + File destPath; + do { + String fileName = prefix == 0 ? name : prefix + '_' + name; + destPath = getConversationPath(accountId, conversationId, fileName); + prefix++; + } while (destPath.exists()); + return destPath; + } + public abstract File getTemporaryPath(String conversationId, String name); public abstract File getConversationDir(String conversationId); diff --git a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java index 76e5f4d60..1229695e3 100644 --- a/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java +++ b/ring-android/libringclient/src/main/java/net/jami/services/NotificationService.java @@ -52,7 +52,7 @@ public interface NotificationService { void showFileTransferNotification(Conversation conversation, DataTransfer info); void cancelFileNotification(int id, boolean isMigratingToService); void handleDataTransferNotification(DataTransfer transfer, Conversation contact, boolean remove); - void removeTransferNotification(String accountId, Uri conversationUri, long transferId); + void removeTransferNotification(String accountId, Uri conversationUri, String fileId); Object getDataTransferNotification(int notificationId); void updateNotification(Object notification, int notificationId); -- GitLab