try to detect if a container contains video or audio

Daniel Gultsch created

fixes #4321

Change summary

src/main/java/eu/siacs/conversations/persistance/FileBackend.java        |  31 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   6 
src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java | 153 
src/main/java/eu/siacs/conversations/utils/MimeUtils.java                |  18 
src/main/java/eu/siacs/conversations/utils/UIHelper.java                 |   4 
src/main/res/values/strings.xml                                          |   1 
6 files changed, 159 insertions(+), 54 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -1496,6 +1496,7 @@ public class FileBackend {
         DownloadableFile file = getFile(message);
         final String mime = file.getMimeType();
         final boolean privateMessage = message.isPrivateMessage();
+        final boolean ambiguous = MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime);
         final boolean image =
                 message.getType() == Message.TYPE_IMAGE
                         || (mime != null && mime.startsWith("image/"));
@@ -1507,7 +1508,21 @@ public class FileBackend {
             body.append(url);
         }
         body.append('|').append(file.getSize());
-        if (image || video || pdf) {
+        if (ambiguous) {
+            try {
+                final Dimensions dimensions = getVideoDimensions(file);
+                if (dimensions.valid()) {
+                    Log.d(Config.LOGTAG,"ambiguous file "+mime+" is video");
+                    body.append('|').append(dimensions.width).append('|').append(dimensions.height);
+                } else {
+                    Log.d(Config.LOGTAG,"ambiguous file "+mime+" is audio");
+                    body.append("|0|0|").append(getMediaRuntime(file));
+                }
+            } catch (final NotAVideoFile e) {
+                Log.d(Config.LOGTAG,"ambiguous file "+mime+" is audio");
+                body.append("|0|0|").append(getMediaRuntime(file));
+            }
+        } else if (image || video || pdf) {
             try {
                 final Dimensions dimensions;
                 if (video) {
@@ -1537,14 +1552,16 @@ public class FileBackend {
                         : (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
     }
 
-    private int getMediaRuntime(File file) {
+    private int getMediaRuntime(final File file) {
         try {
-            MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+            final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
             mediaMetadataRetriever.setDataSource(file.toString());
-            return Integer.parseInt(
-                    mediaMetadataRetriever.extractMetadata(
-                            MediaMetadataRetriever.METADATA_KEY_DURATION));
-        } catch (RuntimeException e) {
+            final String value = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION);
+            if (Strings.isNullOrEmpty(value)) {
+                return 0;
+            }
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
             return 0;
         }
     }

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -1384,6 +1384,7 @@ public class XmppConnectionService extends Service {
         final Intent intent = new Intent(this, EventReceiver.class);
         intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE);
         try {
+            //TODO add immutable flag
             final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0);
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                 alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, pendingIntent);
@@ -1430,7 +1431,8 @@ public class XmppConnectionService extends Service {
         final Intent intent = new Intent(this, EventReceiver.class);
         intent.setAction(ACTION_IDLE_PING);
         try {
-            PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+            //TODO add immutable flag
+            final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
             alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingIntent);
         } catch (RuntimeException e) {
             Log.d(Config.LOGTAG, "unable to schedule alarm for idle ping", e);
@@ -1443,7 +1445,7 @@ public class XmppConnectionService extends Service {
         connection.setOnStatusChangedListener(this.statusListener);
         connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
         connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
-        connection.setOnJinglePacketReceivedListener(((a, jp) -> mJingleConnectionManager.deliverPacket(a, jp)));
+        connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket));
         connection.setOnBindListener(this.mOnBindListener);
         connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
         connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);

src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java 🔗

@@ -11,6 +11,7 @@ import androidx.databinding.DataBindingUtil;
 import androidx.recyclerview.widget.RecyclerView;
 
 import com.google.common.base.Optional;
+import com.google.common.base.Strings;
 
 import java.util.List;
 
@@ -24,11 +25,13 @@ import eu.siacs.conversations.ui.XmppActivity;
 import eu.siacs.conversations.ui.util.AvatarWorkerTask;
 import eu.siacs.conversations.ui.util.StyledAttributes;
 import eu.siacs.conversations.utils.IrregularUnicodeDetector;
+import eu.siacs.conversations.utils.MimeUtils;
 import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xmpp.Jid;
 import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
 
-public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
+public class ConversationAdapter
+        extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
 
     private final XmppActivity activity;
     private final List<Conversation> conversations;
@@ -39,11 +42,15 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
         this.conversations = conversations;
     }
 
-
     @NonNull
     @Override
     public ConversationViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
-        return new ConversationViewHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.conversation_list_row, parent, false));
+        return new ConversationViewHolder(
+                DataBindingUtil.inflate(
+                        LayoutInflater.from(parent.getContext()),
+                        R.layout.conversation_list_row,
+                        parent,
+                        false));
     }
 
     @Override
@@ -54,15 +61,18 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
         }
         CharSequence name = conversation.getName();
         if (name instanceof Jid) {
-            viewHolder.binding.conversationName.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
+            viewHolder.binding.conversationName.setText(
+                    IrregularUnicodeDetector.style(activity, (Jid) name));
         } else {
             viewHolder.binding.conversationName.setText(name);
         }
 
         if (conversation == ConversationFragment.getConversation(activity)) {
-            viewHolder.binding.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_tertiary));
+            viewHolder.binding.frame.setBackgroundColor(
+                    StyledAttributes.getColor(activity, R.attr.color_background_tertiary));
         } else {
-            viewHolder.binding.frame.setBackgroundColor(StyledAttributes.getColor(activity, R.attr.color_background_primary));
+            viewHolder.binding.frame.setBackgroundColor(
+                    StyledAttributes.getColor(activity, R.attr.color_background_primary));
         }
 
         Message message = conversation.getLatestMessage();
@@ -92,31 +102,70 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
         } else {
             final boolean fileAvailable = !message.isDeleted();
             final boolean showPreviewText;
-            if (fileAvailable && (message.isFileOrImage() || message.treatAsDownloadable() || message.isGeoUri())) {
+            if (fileAvailable
+                    && (message.isFileOrImage()
+                            || message.treatAsDownloadable()
+                            || message.isGeoUri())) {
                 final int imageResource;
                 if (message.isGeoUri()) {
-                    imageResource = activity.getThemeResource(R.attr.ic_attach_location, R.drawable.ic_attach_location);
+                    imageResource =
+                            activity.getThemeResource(
+                                    R.attr.ic_attach_location, R.drawable.ic_attach_location);
                     showPreviewText = false;
                 } else {
-                    //TODO move this into static MediaPreview method and use same icons as in MediaAdapter
+                    // TODO move this into static MediaPreview method and use same icons as in
+                    // MediaAdapter
                     final String mime = message.getMimeType();
-                    switch (mime == null ? "" : mime.split("/")[0]) {
-                        case "image":
-                            imageResource = activity.getThemeResource(R.attr.ic_attach_photo, R.drawable.ic_attach_photo);
-                            showPreviewText = false;
-                            break;
-                        case "video":
-                            imageResource = activity.getThemeResource(R.attr.ic_attach_videocam, R.drawable.ic_attach_videocam);
+                    if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) {
+                        final Message.FileParams fileParams = message.getFileParams();
+                        if (fileParams.width > 0 && fileParams.height > 0) {
+                            imageResource =
+                                    activity.getThemeResource(
+                                            R.attr.ic_attach_videocam,
+                                            R.drawable.ic_attach_videocam);
                             showPreviewText = false;
-                            break;
-                        case "audio":
-                            imageResource = activity.getThemeResource(R.attr.ic_attach_record, R.drawable.ic_attach_record);
+                        } else if (fileParams.runtime > 0) {
+                            imageResource =
+                                    activity.getThemeResource(
+                                            R.attr.ic_attach_record, R.drawable.ic_attach_record);
                             showPreviewText = false;
-                            break;
-                        default:
-                            imageResource = activity.getThemeResource(R.attr.ic_attach_document, R.drawable.ic_attach_document);
+                        } else {
+                            imageResource =
+                                    activity.getThemeResource(
+                                            R.attr.ic_attach_document,
+                                            R.drawable.ic_attach_document);
                             showPreviewText = true;
-                            break;
+                        }
+                    } else {
+                        switch (Strings.nullToEmpty(mime).split("/")[0]) {
+                            case "image":
+                                imageResource =
+                                        activity.getThemeResource(
+                                                R.attr.ic_attach_photo, R.drawable.ic_attach_photo);
+                                showPreviewText = false;
+                                break;
+                            case "video":
+                                imageResource =
+                                        activity.getThemeResource(
+                                                R.attr.ic_attach_videocam,
+                                                R.drawable.ic_attach_videocam);
+                                showPreviewText = false;
+                                break;
+                            case "audio":
+                                imageResource =
+                                        activity.getThemeResource(
+                                                R.attr.ic_attach_record,
+                                                R.drawable.ic_attach_record);
+                                showPreviewText = false;
+                                break;
+                            default:
+                                imageResource =
+                                        activity.getThemeResource(
+                                                R.attr.ic_attach_document,
+                                                R.drawable.ic_attach_document);
+                                showPreviewText = true;
+                                break;
+                        }
                     }
                 }
                 viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
@@ -125,13 +174,18 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
                 viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
                 showPreviewText = true;
             }
-            final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
+            final Pair<CharSequence, Boolean> preview =
+                    UIHelper.getMessagePreview(
+                            activity,
+                            message,
+                            viewHolder.binding.conversationLastmsg.getCurrentTextColor());
             if (showPreviewText) {
                 viewHolder.binding.conversationLastmsg.setText(UIHelper.shorten(preview.first));
             } else {
                 viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
             }
-            viewHolder.binding.conversationLastmsg.setVisibility(showPreviewText ? View.VISIBLE : View.GONE);
+            viewHolder.binding.conversationLastmsg.setVisibility(
+                    showPreviewText ? View.VISIBLE : View.GONE);
             if (preview.second) {
                 if (isRead) {
                     viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.ITALIC);
@@ -152,7 +206,8 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
             if (message.getStatus() == Message.STATUS_RECEIVED) {
                 if (conversation.getMode() == Conversation.MODE_MULTI) {
                     viewHolder.binding.senderName.setVisibility(View.VISIBLE);
-                    viewHolder.binding.senderName.setText(UIHelper.getMessageDisplayName(message).split("\\s+")[0] + ':');
+                    viewHolder.binding.senderName.setText(
+                            UIHelper.getMessageDisplayName(message).split("\\s+")[0] + ':');
                 } else {
                     viewHolder.binding.senderName.setVisibility(View.GONE);
                 }
@@ -164,33 +219,47 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
             }
         }
 
-
         final Optional<OngoingRtpSession> ongoingCall;
         if (conversation.getMode() == Conversational.MODE_MULTI) {
             ongoingCall = Optional.absent();
         } else {
-            ongoingCall = activity.xmppConnectionService.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
+            ongoingCall =
+                    activity.xmppConnectionService
+                            .getJingleConnectionManager()
+                            .getOngoingRtpConnection(conversation.getContact());
         }
 
         if (ongoingCall.isPresent()) {
             viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
-                final int ic_ongoing_call = activity.getThemeResource(R.attr.ic_ongoing_call_hint, R.drawable.ic_phone_in_talk_black_18dp);
-                viewHolder.binding.notificationStatus.setImageResource(ic_ongoing_call);
+            final int ic_ongoing_call =
+                    activity.getThemeResource(
+                            R.attr.ic_ongoing_call_hint, R.drawable.ic_phone_in_talk_black_18dp);
+            viewHolder.binding.notificationStatus.setImageResource(ic_ongoing_call);
         } else {
-            final long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
+            final long muted_till =
+                    conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
             if (muted_till == Long.MAX_VALUE) {
                 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
-                int ic_notifications_off = activity.getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
+                int ic_notifications_off =
+                        activity.getThemeResource(
+                                R.attr.icon_notifications_off,
+                                R.drawable.ic_notifications_off_black_24dp);
                 viewHolder.binding.notificationStatus.setImageResource(ic_notifications_off);
             } else if (muted_till >= System.currentTimeMillis()) {
                 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
-                int ic_notifications_paused = activity.getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
+                int ic_notifications_paused =
+                        activity.getThemeResource(
+                                R.attr.icon_notifications_paused,
+                                R.drawable.ic_notifications_paused_black_24dp);
                 viewHolder.binding.notificationStatus.setImageResource(ic_notifications_paused);
             } else if (conversation.alwaysNotify()) {
                 viewHolder.binding.notificationStatus.setVisibility(View.GONE);
             } else {
                 viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
-                int ic_notifications_none = activity.getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
+                int ic_notifications_none =
+                        activity.getThemeResource(
+                                R.attr.icon_notifications_none,
+                                R.drawable.ic_notifications_none_black_24dp);
                 viewHolder.binding.notificationStatus.setImageResource(ic_notifications_none);
             }
         }
@@ -201,9 +270,16 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
         } else {
             timestamp = conversation.getLatestMessage().getTimeSent();
         }
-        viewHolder.binding.pinnedOnTop.setVisibility(conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP,false) ? View.VISIBLE : View.GONE);
-        viewHolder.binding.conversationLastupdate.setText(UIHelper.readableTimeDifference(activity, timestamp));
-        AvatarWorkerTask.loadAvatar(conversation, viewHolder.binding.conversationImage, R.dimen.avatar_on_conversation_overview);
+        viewHolder.binding.pinnedOnTop.setVisibility(
+                conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false)
+                        ? View.VISIBLE
+                        : View.GONE);
+        viewHolder.binding.conversationLastupdate.setText(
+                UIHelper.readableTimeDifference(activity, timestamp));
+        AvatarWorkerTask.loadAvatar(
+                conversation,
+                viewHolder.binding.conversationImage,
+                R.dimen.avatar_on_conversation_overview);
         viewHolder.itemView.setOnClickListener(v -> listener.onConversationClick(v, conversation));
     }
 
@@ -216,7 +292,6 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
         this.listener = listener;
     }
 
-
     public void insert(Conversation c, int position) {
         conversations.add(position, c);
         notifyDataSetChanged();
@@ -238,7 +313,5 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
             super(binding.getRoot());
             this.binding = binding;
         }
-
     }
-
 }

src/main/java/eu/siacs/conversations/utils/MimeUtils.java 🔗

@@ -22,12 +22,14 @@ import android.provider.OpenableColumns;
 import android.util.Log;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 
@@ -40,6 +42,13 @@ import eu.siacs.conversations.services.ExportBackupService;
  * Used to implement java.net.URLConnection and android.webkit.MimeTypeMap.
  */
 public final class MimeUtils {
+
+    public static final List<String> AMBIGUOUS_CONTAINER_FORMATS = ImmutableList.of(
+            "application/ogg",
+            "video/3gpp", // .3gp files can contain audio, video or both
+            "video/3gpp2"
+    );
+
     private static final Map<String, String> mimeTypeToExtensionMap = new HashMap<>();
     private static final Map<String, String> extensionToMimeTypeMap = new HashMap<>();
 
@@ -225,7 +234,12 @@ public final class MimeUtils {
         add("application/x-xcf", "xcf");
         add("application/x-xfig", "fig");
         add("application/xhtml+xml", "xhtml");
+        add("video/3gpp", "3gpp");
+        add("video/3gpp", "3gp");
+        add("video/3gpp2", "3gpp2");
+        add("video/3gpp2", "3g2");
         add("audio/3gpp", "3gpp");
+        add("audio/3gpp", "3gp");
         add("audio/aac", "aac");
         add("audio/aac-adts", "aac");
         add("audio/amr", "amr");
@@ -365,10 +379,6 @@ public final class MimeUtils {
         add("text/x-tex", "cls");
         add("text/x-vcalendar", "vcs");
         add("text/x-vcard", "vcf");
-        add("video/3gpp", "3gpp");
-        add("video/3gpp", "3gp");
-        add("video/3gpp2", "3gpp2");
-        add("video/3gpp2", "3g2");
         add("video/avi", "avi");
         add("video/dl", "dl");
         add("video/dv", "dif");

src/main/java/eu/siacs/conversations/utils/UIHelper.java 🔗

@@ -477,8 +477,10 @@ public class UIHelper {
 
     public static String getFileDescriptionString(final Context context, final Message message) {
         final String mime = message.getMimeType();
-        if (mime == null) {
+        if (Strings.isNullOrEmpty(mime)) {
             return context.getString(R.string.file);
+        } else if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) {
+            return context.getString(R.string.multimedia_file);
         } else if (mime.startsWith("audio/")) {
             return context.getString(R.string.audio);
         } else if (mime.startsWith("video/")) {

src/main/res/values/strings.xml 🔗

@@ -415,6 +415,7 @@
     <string name="video">video</string>
     <string name="image">image</string>
     <string name="vector_graphic">vector graphic</string>
+    <string name="multimedia_file">multimedia file</string>
     <string name="pdf_document">PDF document</string>
     <string name="apk">Android App</string>
     <string name="vcard">Contact</string>