diff --git a/CHANGELOG-upstream.md b/CHANGELOG-upstream.md index 523440fd8..784c5bbb3 100644 --- a/CHANGELOG-upstream.md +++ b/CHANGELOG-upstream.md @@ -6,6 +6,7 @@ * Unified date display in call bubbles * Explain at "Settings / Chats / Outgoing Media Quality" how to send original quality * Add a basic sticker picker +* Leave groups and channels before deletion * Fix: keep original sent timestamp for resent messages * Fix: make clicking on broadcast member-added messages work always * Fix: remove notification when a message is deleted by sender diff --git a/src/androidTest/java/com/b44t/messenger/uitests/offline/SharingTest.java b/src/androidTest/java/com/b44t/messenger/uitests/offline/SharingTest.java index f2ae75395..11416cd1c 100644 --- a/src/androidTest/java/com/b44t/messenger/uitests/offline/SharingTest.java +++ b/src/androidTest/java/com/b44t/messenger/uitests/offline/SharingTest.java @@ -253,7 +253,7 @@ public class SharingTest { *

If you save your contacts's emails in the contacts app of the phone, there are buttons to * call them and also to write an email to them. * - *

If you click the email button, ArcaneChat opened but instead of opening a chat with that + *

If you click the email button, Delta Chat opened but instead of opening a chat with that * contact, the chat list was show and "share with" was displayed at the top */ @Test diff --git a/src/main/java/com/b44t/messenger/DcChat.java b/src/main/java/com/b44t/messenger/DcChat.java index c8d7cf9ee..8f32f9725 100644 --- a/src/main/java/com/b44t/messenger/DcChat.java +++ b/src/main/java/com/b44t/messenger/DcChat.java @@ -1,5 +1,7 @@ package com.b44t.messenger; +import org.thoughtcrime.securesms.util.Util; + public class DcChat { public static final int DC_CHAT_TYPE_UNDEFINED = 0; @@ -73,6 +75,16 @@ public class DcChat { return type != DC_CHAT_TYPE_SINGLE; } + public boolean shallLeaveBeforeDelete(DcContext dcContext) { + if (isInBroadcast()) { + final int[] members = dcContext.getChatContacts(getId()); + return Util.contains(members, DcContact.DC_CONTACT_ID_SELF); + } else if (isMultiUser() && isEncrypted() && canSend() && !isOutBroadcast()) { + return true; + } + return false; + } + public boolean isMailingList() { return getType() == DC_CHAT_TYPE_MAILINGLIST; } diff --git a/src/main/java/com/b44t/messenger/DcContext.java b/src/main/java/com/b44t/messenger/DcContext.java index 53cfab3a5..722b76c62 100644 --- a/src/main/java/com/b44t/messenger/DcContext.java +++ b/src/main/java/com/b44t/messenger/DcContext.java @@ -359,7 +359,7 @@ public class DcContext { public String getName() { String displayname = getConfig("displayname"); if (displayname.isEmpty()) { - displayname = getContact(DcContact.DC_CONTACT_ID_SELF).getAddr(); + displayname = getConfig("addr"); } return displayname; } diff --git a/src/main/java/org/thoughtcrime/securesms/BaseConversationListFragment.java b/src/main/java/org/thoughtcrime/securesms/BaseConversationListFragment.java index 817e6015c..a7887863f 100644 --- a/src/main/java/org/thoughtcrime/securesms/BaseConversationListFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/BaseConversationListFragment.java @@ -27,6 +27,7 @@ import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.graphics.drawable.IconCompat; import androidx.fragment.app.Fragment; import com.b44t.messenger.DcChat; +import com.b44t.messenger.DcContact; import com.b44t.messenger.DcContext; import com.google.android.material.snackbar.Snackbar; import java.util.ArrayList; @@ -297,12 +298,20 @@ public abstract class BaseConversationListFragment extends Fragment implements A .getQuantityString(R.plurals.ask_delete_chat, chatsCount, chatsCount); } + String alertButton = getString(R.string.delete_for_me); + for (long chatId : selectedChats) { + if (dcContext.getChat((int) chatId).shallLeaveBeforeDelete(dcContext)) { + alertButton = getString(R.string.menu_leave_and_delete); + break; + } + } + AlertDialog.Builder alert = new AlertDialog.Builder(activity); alert.setMessage(alertText); alert.setCancelable(true); alert.setPositiveButton( - R.string.delete, + alertButton, (dialog, which) -> { if (!selectedChats.isEmpty()) { new AsyncTask() { @@ -325,6 +334,9 @@ public abstract class BaseConversationListFragment extends Fragment implements A for (long chatId : selectedChats) { DcHelper.getNotificationCenter(requireContext()) .removeNotifications(accountId, (int) chatId); + if (dcContext.getChat((int) chatId).shallLeaveBeforeDelete(dcContext)) { + dcContext.removeContactFromChat((int) chatId, DcContact.DC_CONTACT_ID_SELF); + } dcContext.deleteChat((int) chatId); DirectShareUtil.clearShortcut(requireContext(), (int) chatId); } diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index 608ee4053..9f148bd40 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -531,15 +531,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity menu.findItem(R.id.menu_ephemeral_messages).setVisible(false); } - if (isMultiUser()) { - if (dcChat.isInBroadcast() && !dcChat.isContactRequest()) { - menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel).setVisible(true); - } else if (dcChat.isEncrypted() - && dcChat.canSend() - && !dcChat.isOutBroadcast() - && !dcChat.isMailingList()) { - menu.findItem(R.id.menu_leave).setVisible(true); + if (dcChat.shallLeaveBeforeDelete(DcHelper.getContext(context))) { + if (dcChat.isInBroadcast()) { + menu.findItem(R.id.menu_leave).setTitle(R.string.menu_leave_channel); } + menu.findItem(R.id.menu_leave).setVisible(true); + menu.findItem(R.id.menu_delete_chat).setVisible(false); } if (isArchived()) { @@ -732,9 +729,16 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF); Toast.makeText(this, getString(R.string.done), Toast.LENGTH_SHORT).show(); }) - .setNegativeButton(R.string.cancel, null) + .setNegativeButton(R.string.menu_leave_and_delete, (d, which) -> { + DcHelper.getContext(context).removeContactFromChat(chatId, DcContact.DC_CONTACT_ID_SELF); + DcHelper.getContext(context).deleteChat(chatId); + DirectShareUtil.clearShortcut(this, chatId); + finish(); + }) + .setNeutralButton(R.string.cancel, null) .show(); Util.redPositiveButton(dialog); + Util.redButton(dialog, AlertDialog.BUTTON_NEGATIVE); } private void handleArchiveChat() { @@ -753,7 +757,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleDeleteChat() { AlertDialog dialog = new AlertDialog.Builder(this) .setMessage(getResources().getString(R.string.ask_delete_named_chat, dcChat.getName())) - .setPositiveButton(R.string.delete, (d, which) -> { + .setPositiveButton(R.string.delete_for_me, (d, which) -> { DcHelper.getContext(context).deleteChat(chatId); DirectShareUtil.clearShortcut(this, chatId); finish(); diff --git a/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java b/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java index 4e95ddbfb..5f9acbe6e 100644 --- a/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ProfileActivity.java @@ -12,6 +12,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.EditText; +import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.ActionBar; @@ -415,10 +416,15 @@ public class ProfileActivity extends PassphraseRequiredActionBarActivity isContactProfile() ? dcContext.getContactEncrInfo(contactId) : dcContext.getChatEncrInfo(chatId); - new AlertDialog.Builder(this) - .setMessage(infoStr) - .setPositiveButton(android.R.string.ok, null) - .show(); + AlertDialog dialog = + new AlertDialog.Builder(this) + .setMessage(infoStr) + .setPositiveButton(android.R.string.ok, null) + .show(); + TextView messageView = dialog.findViewById(android.R.id.message); + if (messageView != null) { + messageView.setTextIsSelectable(true); + } } private void onBlockContact() { diff --git a/src/main/java/org/thoughtcrime/securesms/ProfileAdapter.java b/src/main/java/org/thoughtcrime/securesms/ProfileAdapter.java index 6a59a4810..94ee9fb97 100644 --- a/src/main/java/org/thoughtcrime/securesms/ProfileAdapter.java +++ b/src/main/java/org/thoughtcrime/securesms/ProfileAdapter.java @@ -26,6 +26,7 @@ import java.util.Set; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.contacts.ContactSelectionListItem; import org.thoughtcrime.securesms.mms.GlideRequests; +import org.thoughtcrime.securesms.util.DateUtils; import org.thoughtcrime.securesms.util.Util; public class ProfileAdapter extends RecyclerView.Adapter { @@ -360,9 +361,11 @@ public class ProfileAdapter extends RecyclerView.Adapter { String lastSeenTxt; if (lastSeenTimestamp == 0) { lastSeenTxt = context.getString(R.string.last_seen_unknown); - } - else { - lastSeenTxt = context.getString(R.string.last_seen_at, DateUtils.getExtendedTimeSpanString(context, lastSeenTimestamp)); + } else { + lastSeenTxt = + context.getString( + R.string.last_seen_at, + DateUtils.getExtendedTimeSpanString(context, lastSeenTimestamp)); } itemData.add(new ItemData(ITEM_LAST_SEEN, lastSeenTxt, 0)); } diff --git a/src/main/java/org/thoughtcrime/securesms/components/CallItemView.java b/src/main/java/org/thoughtcrime/securesms/components/CallItemView.java index aa9dbf9e9..4b41c166b 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/CallItemView.java +++ b/src/main/java/org/thoughtcrime/securesms/components/CallItemView.java @@ -8,14 +8,11 @@ import android.view.View; import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.util.DateUtils; - import chat.delta.rpc.types.CallInfo; import chat.delta.rpc.types.CallState; +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.util.DateUtils; public class CallItemView extends FrameLayout { private static final String TAG = CallItemView.class.getSimpleName(); @@ -43,11 +40,12 @@ public class CallItemView extends FrameLayout { this.title = findViewById(R.id.title); this.duration = findViewById(R.id.duration); - setOnClickListener(v -> { - if (viewListener != null && callInfo != null) { - viewListener.onClick(v, callInfo); - } - }); + setOnClickListener( + v -> { + if (viewListener != null && callInfo != null) { + viewListener.onClick(v, callInfo); + } + }); } public void setCallClickListener(CallClickListener listener) { @@ -58,7 +56,9 @@ public class CallItemView extends FrameLayout { this.callInfo = callInfo; if (callInfo.state instanceof CallState.Completed) { - duration.setText(DateUtils.getFormattedCallDuration(getContext(), ((CallState.Completed) callInfo.state).duration)); + duration.setText( + DateUtils.getFormattedCallDuration( + getContext(), ((CallState.Completed) callInfo.state).duration)); duration.setVisibility(VISIBLE); } else { duration.setVisibility(GONE); @@ -71,22 +71,25 @@ public class CallItemView extends FrameLayout { } else if (callInfo.state instanceof CallState.Declined) { title.setText(R.string.declined_call); } else if (callInfo.hasVideo) { - title.setText(isOutgoing? R.string.outgoing_video_call : R.string.incoming_video_call); + title.setText(R.string.video_call); } else { - title.setText(isOutgoing? R.string.outgoing_audio_call : R.string.incoming_audio_call); + title.setText(R.string.audio_call); } - icon.setImageResource(callInfo.hasVideo? R.drawable.ic_videocam_white_24dp : R.drawable.baseline_call_24); + icon.setImageResource( + callInfo.hasVideo ? R.drawable.ic_videocam_white_24dp : R.drawable.baseline_call_24); int[] attrs; if (isOutgoing) { - attrs = new int[]{ - R.attr.conversation_item_outgoing_text_primary_color, - }; + attrs = + new int[] { + R.attr.conversation_item_outgoing_text_primary_color, + }; } else { - attrs = new int[]{ - R.attr.conversation_item_incoming_text_primary_color, - }; + attrs = + new int[] { + R.attr.conversation_item_incoming_text_primary_color, + }; } try (TypedArray ta = getContext().obtainStyledAttributes(attrs)) { icon.setColorFilter(ta.getColor(0, Color.BLACK)); @@ -94,7 +97,8 @@ public class CallItemView extends FrameLayout { } public String getDescription() { - return title.getText() + (duration.getVisibility()==VISIBLE ? ("\n" + duration.getText()) : ""); + return title.getText() + + (duration.getVisibility() == VISIBLE ? ("\n" + duration.getText()) : ""); } public interface CallClickListener { diff --git a/src/main/java/org/thoughtcrime/securesms/components/MediaView.java b/src/main/java/org/thoughtcrime/securesms/components/MediaView.java index bd335218f..8aed3cc18 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/MediaView.java +++ b/src/main/java/org/thoughtcrime/securesms/components/MediaView.java @@ -1,26 +1,23 @@ package org.thoughtcrime.securesms.components; - import android.content.Context; import android.net.Uri; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import android.util.AttributeSet; import android.view.View; import android.view.Window; import android.widget.FrameLayout; - +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.io.IOException; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.util.views.Stub; import org.thoughtcrime.securesms.video.VideoPlayer; -import java.io.IOException; - public class MediaView extends FrameLayout { - private ZoomingImageView imageView; + private ZoomingImageView imageView; private Stub videoView; public MediaView(@NonNull Context context) { @@ -38,7 +35,8 @@ public class MediaView extends FrameLayout { initialize(); } - public MediaView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + public MediaView( + @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); initialize(); } @@ -50,15 +48,15 @@ public class MediaView extends FrameLayout { this.videoView = new Stub<>(findViewById(R.id.video_player_stub)); } - public void set(@NonNull GlideRequests glideRequests, - @NonNull Window window, - @NonNull Uri source, - @Nullable String fileName, - @NonNull String mediaType, - long size, - boolean autoplay) - throws IOException - { + public void set( + @NonNull GlideRequests glideRequests, + @NonNull Window window, + @NonNull Uri source, + @Nullable String fileName, + @NonNull String mediaType, + long size, + boolean autoplay) + throws IOException { if (mediaType.startsWith("image/")) { imageView.setVisibility(View.VISIBLE); if (videoView.resolved()) videoView.get().setVisibility(View.GONE); @@ -67,14 +65,16 @@ public class MediaView extends FrameLayout { imageView.setVisibility(View.GONE); videoView.get().setVisibility(View.VISIBLE); videoView.get().setWindow(window); - videoView.get().setVideoSource(new VideoSlide(getContext(), source, fileName, size), autoplay); + videoView + .get() + .setVideoSource(new VideoSlide(getContext(), source, fileName, size), autoplay); } else { throw new IOException("Unsupported media type: " + mediaType); } } public void pause() { - if (this.videoView.resolved()){ + if (this.videoView.resolved()) { this.videoView.get().pause(); } } diff --git a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java index c9082e2d2..281cf41a1 100644 --- a/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java +++ b/src/main/java/org/thoughtcrime/securesms/connect/DcHelper.java @@ -220,10 +220,10 @@ public class DcHelper { dcContext.setStockTranslation(220, context.getString(R.string.proxy_enabled)); dcContext.setStockTranslation(221, context.getString(R.string.proxy_enabled_hint)); dcContext.setStockTranslation(230, context.getString(R.string.chat_unencrypted_explanation)); - dcContext.setStockTranslation(232, context.getString(R.string.outgoing_audio_call)); - dcContext.setStockTranslation(233, context.getString(R.string.outgoing_video_call)); - dcContext.setStockTranslation(234, context.getString(R.string.incoming_audio_call)); - dcContext.setStockTranslation(235, context.getString(R.string.incoming_video_call)); + dcContext.setStockTranslation(232, context.getString(R.string.audio_call)); + dcContext.setStockTranslation(233, context.getString(R.string.video_call)); + dcContext.setStockTranslation(234, context.getString(R.string.audio_call)); + dcContext.setStockTranslation(235, context.getString(R.string.video_call)); dcContext.setStockTranslation(240, context.getString(R.string.chat_description_changed_by_you)); dcContext.setStockTranslation(241, context.getString(R.string.chat_description_changed_by_other)); } diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/InChatSounds.java b/src/main/java/org/thoughtcrime/securesms/notifications/InChatSounds.java index 373d7bc09..549e014b9 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/InChatSounds.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/InChatSounds.java @@ -4,55 +4,55 @@ import android.content.Context; import android.media.AudioAttributes; import android.media.SoundPool; import android.util.Log; - import org.thoughtcrime.securesms.R; public class InChatSounds { - private static final String TAG = InChatSounds.class.getSimpleName(); - private static volatile InChatSounds instance; + private static final String TAG = InChatSounds.class.getSimpleName(); + private static volatile InChatSounds instance; - private SoundPool soundPool = null; - private int soundIn = 0; - private int soundOut = 0; + private SoundPool soundPool = null; + private int soundIn = 0; + private int soundOut = 0; - static public InChatSounds getInstance(Context context) { + public static InChatSounds getInstance(Context context) { + if (instance == null) { + synchronized (InChatSounds.class) { if (instance == null) { - synchronized (InChatSounds.class) { - if (instance == null) { - instance = new InChatSounds(context); - } - } + instance = new InChatSounds(context); } - return instance; + } } + return instance; + } - private InChatSounds(Context context) { - try { - AudioAttributes audioAttrs = new AudioAttributes.Builder() + private InChatSounds(Context context) { + try { + AudioAttributes audioAttrs = + new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build(); - soundPool = new SoundPool.Builder().setMaxStreams(3).setAudioAttributes(audioAttrs).build(); - soundIn = soundPool.load(context, R.raw.sound_in, 1); - soundOut = soundPool.load(context, R.raw.sound_out, 1); - } catch(Exception e) { - Log.e(TAG, "cannot initialize sounds", e); - } + soundPool = new SoundPool.Builder().setMaxStreams(3).setAudioAttributes(audioAttrs).build(); + soundIn = soundPool.load(context, R.raw.sound_in, 1); + soundOut = soundPool.load(context, R.raw.sound_out, 1); + } catch (Exception e) { + Log.e(TAG, "cannot initialize sounds", e); } + } - public void playSendSound() { - try { - soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f); - } catch(Exception e) { - Log.e(TAG, "cannot play send sound", e); - } + public void playSendSound() { + try { + soundPool.play(soundOut, 1.0f, 1.0f, 1, 0, 1.0f); + } catch (Exception e) { + Log.e(TAG, "cannot play send sound", e); } + } - public void playIncomingSound() { - try { - soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f); - } catch(Exception e) { - Log.e(TAG, "cannot play incoming sound", e); - } + public void playIncomingSound() { + try { + soundPool.play(soundIn, 1.0f, 1.0f, 1, 0, 1.0f); + } catch (Exception e) { + Log.e(TAG, "cannot play incoming sound", e); } + } } diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java b/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java index 3e569c744..0c4b2e75a 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/MarkReadReceiver.java @@ -7,18 +7,17 @@ import static com.b44t.messenger.DcChat.DC_CHAT_NO_CHAT; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; - import com.b44t.messenger.DcContext; - import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.Util; public class MarkReadReceiver extends BroadcastReceiver { - public static final String MARK_NOTICED_ACTION = "org.thoughtcrime.securesms.notifications.MARK_NOTICED"; - public static final String CANCEL_ACTION = "org.thoughtcrime.securesms.notifications.CANCEL"; - public static final String ACCOUNT_ID_EXTRA = "account_id"; - public static final String CHAT_ID_EXTRA = "chat_id"; - public static final String MSG_ID_EXTRA = "msg_id"; + public static final String MARK_NOTICED_ACTION = + "org.thoughtcrime.securesms.notifications.MARK_NOTICED"; + public static final String CANCEL_ACTION = "org.thoughtcrime.securesms.notifications.CANCEL"; + public static final String ACCOUNT_ID_EXTRA = "account_id"; + public static final String CHAT_ID_EXTRA = "chat_id"; + public static final String MSG_ID_EXTRA = "msg_id"; @Override public void onReceive(final Context context, Intent intent) { @@ -34,13 +33,14 @@ public class MarkReadReceiver extends BroadcastReceiver { return; } - Util.runOnAnyBackgroundThread(() -> { - DcHelper.getNotificationCenter(context).removeNotifications(accountId, chatId); - if (markNoticed) { - DcContext dcContext = DcHelper.getAccounts(context).getAccount(accountId); - dcContext.marknoticedChat(chatId); - dcContext.markseenMsgs(new int[]{msgId}); - } - }); + Util.runOnAnyBackgroundThread( + () -> { + DcHelper.getNotificationCenter(context).removeNotifications(accountId, chatId); + if (markNoticed) { + DcContext dcContext = DcHelper.getAccounts(context).getAccount(accountId); + dcContext.marknoticedChat(chatId); + dcContext.markseenMsgs(new int[] {msgId}); + } + }); } } diff --git a/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java b/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java index 21c8de8b0..d036dc092 100644 --- a/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java +++ b/src/main/java/org/thoughtcrime/securesms/notifications/RemoteReplyReceiver.java @@ -24,23 +24,17 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; - import androidx.core.app.RemoteInput; - import com.b44t.messenger.DcContext; import com.b44t.messenger.DcMsg; - import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.util.Util; - -/** - * Get the response text from the Wearable Device and sends an message as a reply - */ +/** Get the response text from the Wearable Device and sends an message as a reply */ public class RemoteReplyReceiver extends BroadcastReceiver { - public static final String TAG = RemoteReplyReceiver.class.getSimpleName(); - public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY"; + public static final String TAG = RemoteReplyReceiver.class.getSimpleName(); + public static final String REPLY_ACTION = "org.thoughtcrime.securesms.notifications.WEAR_REPLY"; public static final String ACCOUNT_ID_EXTRA = "account_id"; public static final String CHAT_ID_EXTRA = "chat_id"; public static final String MSG_ID_EXTRA = "msg_id"; @@ -60,20 +54,21 @@ public class RemoteReplyReceiver extends BroadcastReceiver { final CharSequence responseText = remoteInput.getCharSequence(EXTRA_REMOTE_REPLY); if (responseText != null) { - Util.runOnAnyBackgroundThread(() -> { - DcContext dcContext = DcHelper.getAccounts(context).getAccount(accountId); - dcContext.marknoticedChat(chatId); - dcContext.markseenMsgs(new int[]{msgId}); - if (dcContext.getChat(chatId).isContactRequest()) { - dcContext.acceptChat(chatId); - } + Util.runOnAnyBackgroundThread( + () -> { + DcContext dcContext = DcHelper.getAccounts(context).getAccount(accountId); + dcContext.marknoticedChat(chatId); + dcContext.markseenMsgs(new int[] {msgId}); + if (dcContext.getChat(chatId).isContactRequest()) { + dcContext.acceptChat(chatId); + } - DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); - msg.setText(responseText.toString()); - dcContext.sendMsg(chatId, msg); + DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_TEXT); + msg.setText(responseText.toString()); + dcContext.sendMsg(chatId, msg); - DcHelper.getNotificationCenter(context).removeNotifications(accountId, chatId); - }); + DcHelper.getNotificationCenter(context).removeNotifications(accountId, chatId); + }); } } } diff --git a/src/main/java/org/thoughtcrime/securesms/util/MailtoUtil.java b/src/main/java/org/thoughtcrime/securesms/util/MailtoUtil.java index c2080270b..fbd891540 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/MailtoUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/MailtoUtil.java @@ -2,62 +2,61 @@ package org.thoughtcrime.securesms.util; import android.net.MailTo; import android.net.Uri; - import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; public class MailtoUtil { - private static final String MAILTO = "mailto"; - private static final String SUBJECT = "subject"; - private static final String BODY = "body"; - private static final String QUERY_SEPARATOR = "&"; - private static final String KEY_VALUE_SEPARATOR = "="; + private static final String MAILTO = "mailto"; + private static final String SUBJECT = "subject"; + private static final String BODY = "body"; + private static final String QUERY_SEPARATOR = "&"; + private static final String KEY_VALUE_SEPARATOR = "="; - public static boolean isMailto(Uri uri) { - return uri != null && MAILTO.equals(uri.getScheme()); + public static boolean isMailto(Uri uri) { + return uri != null && MAILTO.equals(uri.getScheme()); + } + + public static String[] getRecipients(Uri uri) { + String[] recipientsArray = new String[0]; + if (uri != null) { + MailTo mailto = MailTo.parse(uri.toString()); + String recipientsList = mailto.getTo(); + if (recipientsList != null && !recipientsList.trim().isEmpty()) { + recipientsArray = recipientsList.trim().split(","); + } } + return recipientsArray; + } - public static String[] getRecipients(Uri uri) { - String[] recipientsArray = new String[0]; - if (uri != null) { - MailTo mailto = MailTo.parse(uri.toString()); - String recipientsList = mailto.getTo(); - if(recipientsList != null && !recipientsList.trim().isEmpty()) { - recipientsArray = recipientsList.trim().split(","); - } + public static String getText(Uri uri) { + Map mailtoQueryMap = getMailtoQueryMap(uri); + String textToShare = mailtoQueryMap.get(SUBJECT); + String body = mailtoQueryMap.get(BODY); + if (body != null && !body.isEmpty()) { + if (textToShare != null && !textToShare.isEmpty()) { + textToShare += "\n" + body; + } else { + textToShare = body; + } + } + return textToShare != null ? textToShare : ""; + } + + private static Map getMailtoQueryMap(Uri uri) { + Map mailtoQueryMap = new HashMap<>(); + String query = uri.getEncodedQuery(); + if (query != null && !query.isEmpty()) { + String[] queryArray = query.split(QUERY_SEPARATOR); + for (String queryEntry : queryArray) { + String[] queryEntryArray = queryEntry.split(KEY_VALUE_SEPARATOR); + try { + mailtoQueryMap.put(queryEntryArray[0], URLDecoder.decode(queryEntryArray[1], "UTF-8")); + } catch (Exception e) { + e.printStackTrace(); } - return recipientsArray; - } - - public static String getText(Uri uri) { - Map mailtoQueryMap = getMailtoQueryMap(uri); - String textToShare = mailtoQueryMap.get(SUBJECT); - String body = mailtoQueryMap.get(BODY); - if (body != null && !body.isEmpty()) { - if (textToShare != null && !textToShare.isEmpty()) { - textToShare += "\n" + body; - } else { - textToShare = body; - } - } - return textToShare != null? textToShare : ""; - } - - private static Map getMailtoQueryMap(Uri uri) { - Map mailtoQueryMap = new HashMap<>(); - String query = uri.getEncodedQuery(); - if (query != null && !query.isEmpty()) { - String[] queryArray = query.split(QUERY_SEPARATOR); - for(String queryEntry : queryArray) { - String[] queryEntryArray = queryEntry.split(KEY_VALUE_SEPARATOR); - try { - mailtoQueryMap.put(queryEntryArray[0], URLDecoder.decode(queryEntryArray[1], "UTF-8")); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - return mailtoQueryMap; + } } + return mailtoQueryMap; + } } diff --git a/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java b/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java index 7b205b992..7bdd1d7ca 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java @@ -8,15 +8,18 @@ import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.webkit.MimeTypeMap; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; - import com.b44t.messenger.DcMsg; import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.resource.gif.GifDrawable; - +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.ExecutionException; import org.thoughtcrime.securesms.mms.AudioSlide; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.mms.DocumentSlide; @@ -30,27 +33,19 @@ import org.thoughtcrime.securesms.mms.VcardSlide; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.providers.PersistentBlobProvider; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.concurrent.ExecutionException; - public class MediaUtil { private static final String TAG = MediaUtil.class.getSimpleName(); - public static final String IMAGE_WEBP = "image/webp"; - public static final String IMAGE_JPEG = "image/jpeg"; - public static final String IMAGE_GIF = "image/gif"; - public static final String AUDIO_AAC = "audio/aac"; + public static final String IMAGE_WEBP = "image/webp"; + public static final String IMAGE_JPEG = "image/jpeg"; + public static final String IMAGE_GIF = "image/gif"; + public static final String AUDIO_AAC = "audio/aac"; public static final String AUDIO_UNSPECIFIED = "audio/*"; public static final String VIDEO_UNSPECIFIED = "video/*"; - public static final String OCTET = "application/octet-stream"; - public static final String WEBXDC = "application/webxdc+zip"; - public static final String VCARD = "text/vcard"; - + public static final String OCTET = "application/octet-stream"; + public static final String WEBXDC = "application/webxdc+zip"; + public static final String VCARD = "text/vcard"; public static Slide getSlideForMsg(Context context, DcMsg dcMsg) { Slide slide = null; @@ -62,13 +57,11 @@ public class MediaUtil { slide = new StickerSlide(context, dcMsg); } else if (dcMsg.getType() == DcMsg.DC_MSG_VIDEO) { slide = new VideoSlide(context, dcMsg); - } else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO - || dcMsg.getType() == DcMsg.DC_MSG_VOICE) { + } else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO || dcMsg.getType() == DcMsg.DC_MSG_VOICE) { slide = new AudioSlide(context, dcMsg); } else if (dcMsg.getType() == DcMsg.DC_MSG_VCARD) { slide = new VcardSlide(context, dcMsg); - } else if (dcMsg.getType() == DcMsg.DC_MSG_FILE - || dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) { + } else if (dcMsg.getType() == DcMsg.DC_MSG_FILE || dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) { slide = new DocumentSlide(context, dcMsg); } @@ -96,20 +89,18 @@ public class MediaUtil { public static @Nullable String getCorrectedMimeType(@Nullable String mimeType) { if (mimeType == null) return null; - switch(mimeType) { - case "image/jpg": - return MimeTypeMap.getSingleton().hasMimeType(IMAGE_JPEG) - ? IMAGE_JPEG - : mimeType; - default: - return mimeType; + switch (mimeType) { + case "image/jpg": + return MimeTypeMap.getSingleton().hasMimeType(IMAGE_JPEG) ? IMAGE_JPEG : mimeType; + default: + return mimeType; } } /** - * This is a version of android.webkit.MimeTypeMap.getFileExtensionFromUrl() that - * doesn't refuse to do its job when there are characters in the URL it doesn't know. - * Using MimeTypeMap.getFileExtensionFromUrl() led to bugs like this one: + * This is a version of android.webkit.MimeTypeMap.getFileExtensionFromUrl() that doesn't refuse + * to do its job when there are characters in the URL it doesn't know. Using + * MimeTypeMap.getFileExtensionFromUrl() led to bugs like this one: * https://github.com/deltachat/deltachat-android/issues/2306 * * @return The url's file extension, or "" if there is none. @@ -128,8 +119,7 @@ public class MediaUtil { } int filenamePos = url.lastIndexOf('/'); - String filename = - 0 <= filenamePos ? url.substring(filenamePos + 1) : url; + String filename = 0 <= filenamePos ? url.substring(filenamePos + 1) : url; if (!filename.isEmpty()) { int dotPos = filename.lastIndexOf('.'); @@ -145,9 +135,9 @@ public class MediaUtil { InputStream in = PartAuthority.getAttachmentStream(context, uri); if (in == null) throw new IOException("Couldn't obtain input stream."); - long size = 0; + long size = 0; byte[] buffer = new byte[4096]; - int read; + int read; while ((read = in.read(buffer)) != -1) { size += read; @@ -158,7 +148,8 @@ public class MediaUtil { } @WorkerThread - public static Pair getDimensions(@NonNull Context context, @Nullable String contentType, @Nullable Uri uri) { + public static Pair getDimensions( + @NonNull Context context, @Nullable String contentType, @Nullable Uri uri) { if (uri == null || !MediaUtil.isImageType(contentType)) { return new Pair<>(0, 0); } @@ -167,7 +158,8 @@ public class MediaUtil { if (MediaUtil.isGif(contentType)) { try { - GifDrawable drawable = GlideApp.with(context) + GifDrawable drawable = + GlideApp.with(context) .asGif() .skipMemoryCache(true) .diskCacheStrategy(DiskCacheStrategy.NONE) @@ -257,22 +249,24 @@ public class MediaUtil { this.width = width; this.height = height; } + public int width; public int height; } - public static boolean createVideoThumbnailIfNeeded(Context context, Uri dataUri, Uri thumbnailUri, ThumbnailSize retWh) { + public static boolean createVideoThumbnailIfNeeded( + Context context, Uri dataUri, Uri thumbnailUri, ThumbnailSize retWh) { boolean success = false; try { File thumbnailFile = new File(thumbnailUri.getPath()); File dataFile = new File(dataUri.getPath()); - if (!thumbnailFile.exists() || dataFile.lastModified()>thumbnailFile.lastModified()) { + if (!thumbnailFile.exists() || dataFile.lastModified() > thumbnailFile.lastModified()) { Bitmap bitmap = null; MediaMetadataRetriever retriever = new MediaMetadataRetriever(); retriever.setDataSource(context, dataUri); bitmap = retriever.getFrameAtTime(-1); - if (retWh!=null) { + if (retWh != null) { retWh.width = bitmap.getWidth(); retWh.height = bitmap.getHeight(); } @@ -284,20 +278,19 @@ public class MediaUtil { success = true; } } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); } return success; } public static String getExtensionFromMimeType(String contentType) { - String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType); + String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(contentType); if (extension != null) { return extension; } - //custom handling needed for unsupported extensions on Android 4.X + // custom handling needed for unsupported extensions on Android 4.X switch (contentType) { case AUDIO_AAC: return "aac"; @@ -310,5 +303,4 @@ public class MediaUtil { } return null; } - } diff --git a/src/main/java/org/thoughtcrime/securesms/util/Pair.java b/src/main/java/org/thoughtcrime/securesms/util/Pair.java index 2492b1722..1b7a37b88 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/Pair.java +++ b/src/main/java/org/thoughtcrime/securesms/util/Pair.java @@ -1,7 +1,7 @@ /** * Copyright (C) 2014-2016 Open Whisper Systems * - * Licensed according to the LICENSE file in this repository. + *

Licensed according to the LICENSE file in this repository. */ package org.thoughtcrime.securesms.util; @@ -14,18 +14,18 @@ public class Pair { this.v2 = v2; } - public T1 first(){ + public T1 first() { return v1; } - public T2 second(){ + public T2 second() { return v2; } public boolean equals(Object o) { - return o instanceof Pair && - equal(((Pair) o).first(), first()) && - equal(((Pair) o).second(), second()); + return o instanceof Pair + && equal(((Pair) o).first(), first()) + && equal(((Pair) o).second(), second()); } public int hashCode() { diff --git a/src/main/java/org/thoughtcrime/securesms/util/ParcelUtil.java b/src/main/java/org/thoughtcrime/securesms/util/ParcelUtil.java index c1eda1232..13055b0c6 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/ParcelUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/ParcelUtil.java @@ -24,5 +24,4 @@ public class ParcelUtil { Parcel parcel = deserialize(bytes); return creator.createFromParcel(parcel); } - } diff --git a/src/main/java/org/thoughtcrime/securesms/util/Prefs.java b/src/main/java/org/thoughtcrime/securesms/util/Prefs.java index 8a5fc4b1a..13e3f76b0 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/Prefs.java +++ b/src/main/java/org/thoughtcrime/securesms/util/Prefs.java @@ -7,84 +7,98 @@ import android.net.Uri; import android.preference.PreferenceManager; import android.provider.ContactsContract; import android.provider.Settings; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; - import com.b44t.messenger.DcContext; - -import org.thoughtcrime.securesms.BuildConfig; -import org.thoughtcrime.securesms.connect.DcHelper; -import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; - import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; +import org.thoughtcrime.securesms.BuildConfig; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference; public class Prefs { private static final String TAG = Prefs.class.getSimpleName(); - public static final String RELIABLE_SERVICE_PREF = "pref_reliable_service"; - public static final String DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase"; - public static final String THEME_PREF = "pref_theme"; - public static final String BACKGROUND_PREF = "pref_chat_background"; + public static final String RELIABLE_SERVICE_PREF = "pref_reliable_service"; + public static final String DISABLE_PASSPHRASE_PREF = "pref_disable_passphrase"; + public static final String THEME_PREF = "pref_theme"; + public static final String BACKGROUND_PREF = "pref_chat_background"; - private static final String DATABASE_ENCRYPTED_SECRET = "pref_database_encrypted_secret_"; // followed by account-id - private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret_"; // followed by account-id + private static final String DATABASE_ENCRYPTED_SECRET = + "pref_database_encrypted_secret_"; // followed by account-id + private static final String DATABASE_UNENCRYPTED_SECRET = + "pref_database_unencrypted_secret_"; // followed by account-id - public static final String RINGTONE_PREF = "pref_key_ringtone"; - private static final String VIBRATE_PREF = "pref_key_vibrate"; - private static final String CHAT_VIBRATE = "pref_chat_vibrate_"; // followed by chat-id - public static final String LED_COLOR_PREF = "pref_led_color"; - private static final String CHAT_RINGTONE = "pref_chat_ringtone_"; // followed by chat-id - public static final String SCREEN_SECURITY_PREF = "pref_screen_security"; - private static final String ENTER_SENDS_PREF = "pref_enter_sends"; - private static final String PROMPTED_DOZE_MSG_ID_PREF = "pref_prompted_doze_msg_id"; - private static final String STATS_DEVICE_MSG_ID_PREF = "pref_stats_device_msg_id"; - public static final String DOZE_ASKED_DIRECTLY = "pref_doze_asked_directly"; - public static final String ASKED_FOR_NOTIFICATION_PERMISSION= "pref_asked_for_notification_permission"; - private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications"; + public static final String RINGTONE_PREF = "pref_key_ringtone"; + private static final String VIBRATE_PREF = "pref_key_vibrate"; + private static final String CHAT_VIBRATE = "pref_chat_vibrate_"; // followed by chat-id + public static final String LED_COLOR_PREF = "pref_led_color"; + private static final String CHAT_RINGTONE = "pref_chat_ringtone_"; // followed by chat-id + public static final String SCREEN_SECURITY_PREF = "pref_screen_security"; + private static final String ENTER_SENDS_PREF = "pref_enter_sends"; + private static final String PROMPTED_DOZE_MSG_ID_PREF = "pref_prompted_doze_msg_id"; + private static final String STATS_DEVICE_MSG_ID_PREF = "pref_stats_device_msg_id"; + public static final String DOZE_ASKED_DIRECTLY = "pref_doze_asked_directly"; + public static final String ASKED_FOR_NOTIFICATION_PERMISSION = + "pref_asked_for_notification_permission"; + private static final String IN_THREAD_NOTIFICATION_PREF = "pref_key_inthread_notifications"; - public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy"; - public static final String NOTIFICATION_PRIORITY_PREF = "pref_notification_priority"; + public static final String NOTIFICATION_PRIVACY_PREF = "pref_notification_privacy"; + public static final String NOTIFICATION_PRIORITY_PREF = "pref_notification_priority"; - private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id"; - public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard"; + private static final String PROFILE_AVATAR_ID_PREF = "pref_profile_avatar_id"; + public static final String INCOGNITO_KEYBORAD_PREF = "pref_incognito_keyboard"; private static final String PREF_CONTACT_PHOTO_IDENTIFIERS = "pref_contact_photo_identifiers"; - public static final String ALWAYS_LOAD_REMOTE_CONTENT = "pref_always_load_remote_content"; - public static final boolean ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT = false; + public static final String ALWAYS_LOAD_REMOTE_CONTENT = "pref_always_load_remote_content"; + public static final boolean ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT = false; - public static final String LAST_DEVICE_MSG_LABEL = "pref_last_device_msg_id"; - public static final String WEBXDC_STORE_URL_PREF = "pref_webxdc_store_url"; - public static final String DEFAULT_WEBXDC_STORE_URL = "https://webxdc.org/apps/"; + public static final String LAST_DEVICE_MSG_LABEL = "pref_last_device_msg_id"; + public static final String WEBXDC_STORE_URL_PREF = "pref_webxdc_store_url"; + public static final String DEFAULT_WEBXDC_STORE_URL = "https://webxdc.org/apps/"; public enum VibrateState { - DEFAULT(0), ENABLED(1), DISABLED(2); + DEFAULT(0), + ENABLED(1), + DISABLED(2); private final int id; - VibrateState(int id) { this.id = id; } - public int getId() { return id; } - public static VibrateState fromId(int id) { return values()[id]; } + + VibrateState(int id) { + this.id = id; + } + + public int getId() { + return id; + } + + public static VibrateState fromId(int id) { + return values()[id]; + } } - public static void setDatabaseEncryptedSecret(@NonNull Context context, @NonNull String secret, int accountId) { + public static void setDatabaseEncryptedSecret( + @NonNull Context context, @NonNull String secret, int accountId) { setStringPreference(context, DATABASE_ENCRYPTED_SECRET + accountId, secret); } - public static void setDatabaseUnencryptedSecret(@NonNull Context context, @Nullable String secret, int accountId) { + public static void setDatabaseUnencryptedSecret( + @NonNull Context context, @Nullable String secret, int accountId) { setStringPreference(context, DATABASE_UNENCRYPTED_SECRET + accountId, secret); } - public static @Nullable String getDatabaseUnencryptedSecret(@NonNull Context context, int accountId) { + public static @Nullable String getDatabaseUnencryptedSecret( + @NonNull Context context, int accountId) { return getStringPreference(context, DATABASE_UNENCRYPTED_SECRET + accountId, null); } - public static @Nullable String getDatabaseEncryptedSecret(@NonNull Context context, int accountId) { + public static @Nullable String getDatabaseEncryptedSecret( + @NonNull Context context, int accountId) { return getStringPreference(context, DATABASE_ENCRYPTED_SECRET + accountId, null); } @@ -101,11 +115,14 @@ public class Prefs { } public static int getNotificationPriority(Context context) { - return Integer.valueOf(getStringPreference(context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH))); + return Integer.valueOf( + getStringPreference( + context, NOTIFICATION_PRIORITY_PREF, String.valueOf(NotificationCompat.PRIORITY_HIGH))); } public static NotificationPrivacyPreference getNotificationPrivacy(Context context) { - return new NotificationPrivacyPreference(getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")); + return new NotificationPrivacyPreference( + getStringPreference(context, NOTIFICATION_PRIVACY_PREF, "all")); } public static boolean isInChatNotifications(Context context) { @@ -133,7 +150,10 @@ public class Prefs { } public static String getTheme(Context context) { - return getStringPreference(context, THEME_PREF, DynamicTheme.systemThemeAvailable() ? DynamicTheme.SYSTEM : DynamicTheme.LIGHT); + return getStringPreference( + context, + THEME_PREF, + DynamicTheme.systemThemeAvailable() ? DynamicTheme.SYSTEM : DynamicTheme.LIGHT); } public static String getWebxdcStoreUrl(Context context) { @@ -162,11 +182,12 @@ public class Prefs { } public static boolean isPushEnabled(Context context) { - return BuildConfig.USE_PLAY_SERVICES; + return BuildConfig.USE_PLAY_SERVICES; } public static boolean isHardCompressionEnabled(Context context) { - return DcHelper.getContext(context).getConfigInt(DcHelper.CONFIG_MEDIA_QUALITY) == DcContext.DC_MEDIA_QUALITY_WORSE; + return DcHelper.getContext(context).getConfigInt(DcHelper.CONFIG_MEDIA_QUALITY) + == DcContext.DC_MEDIA_QUALITY_WORSE; } public static boolean isLocationStreamingEnabled(Context context) { @@ -177,7 +198,6 @@ public class Prefs { return true; } - public static boolean isCallsEnabled(Context context) { return true; } @@ -185,7 +205,9 @@ public class Prefs { // ringtone public static @NonNull Uri getNotificationRingtone(Context context) { - String result = getStringPreference(context, RINGTONE_PREF, Settings.System.DEFAULT_NOTIFICATION_URI.toString()); + String result = + getStringPreference( + context, RINGTONE_PREF, Settings.System.DEFAULT_NOTIFICATION_URI.toString()); if (result != null && result.startsWith("file:")) { result = Settings.System.DEFAULT_NOTIFICATION_URI.toString(); @@ -203,19 +225,20 @@ public class Prefs { } public static void setChatRingtone(Context context, int accountId, int chatId, Uri ringtone) { - final String KEY = (accountId != 0 && chatId != 0)? CHAT_RINGTONE+accountId+"."+chatId : CHAT_RINGTONE; - if(ringtone!=null) { + final String KEY = + (accountId != 0 && chatId != 0) ? CHAT_RINGTONE + accountId + "." + chatId : CHAT_RINGTONE; + if (ringtone != null) { setStringPreference(context, KEY, ringtone.toString()); - } - else { + } else { removePreference(context, KEY); } } public static @Nullable Uri getChatRingtone(Context context, int accountId, int chatId) { - final String KEY = (accountId != 0 && chatId != 0)? CHAT_RINGTONE+accountId+"."+chatId : CHAT_RINGTONE; + final String KEY = + (accountId != 0 && chatId != 0) ? CHAT_RINGTONE + accountId + "." + chatId : CHAT_RINGTONE; String result = getStringPreference(context, KEY, null); - return result==null? null : Uri.parse(result); + return result == null ? null : Uri.parse(result); } public static void setReliableService(Context context, boolean value) { @@ -227,7 +250,8 @@ public class Prefs { if (prefs.contains(RELIABLE_SERVICE_PREF)) { try { return prefs.getBoolean(RELIABLE_SERVICE_PREF, true); - } catch(Exception e) {} + } catch (Exception e) { + } } // if the key was unset, then calculate default value @@ -240,18 +264,20 @@ public class Prefs { return getBooleanPreference(context, VIBRATE_PREF, true); } - public static void setChatVibrate(Context context, int accountId, int chatId, VibrateState vibrateState) { - final String KEY = (accountId != 0 && chatId != 0)? CHAT_VIBRATE+accountId+"."+chatId : CHAT_VIBRATE; - if(vibrateState!=VibrateState.DEFAULT) { + public static void setChatVibrate( + Context context, int accountId, int chatId, VibrateState vibrateState) { + final String KEY = + (accountId != 0 && chatId != 0) ? CHAT_VIBRATE + accountId + "." + chatId : CHAT_VIBRATE; + if (vibrateState != VibrateState.DEFAULT) { setIntegerPreference(context, KEY, vibrateState.getId()); - } - else { + } else { removePreference(context, KEY); } } public static VibrateState getChatVibrate(Context context, int accountId, int chatId) { - final String KEY = (accountId != 0 && chatId != 0)? CHAT_VIBRATE+accountId+"."+chatId : CHAT_VIBRATE; + final String KEY = + (accountId != 0 && chatId != 0) ? CHAT_VIBRATE + accountId + "." + chatId : CHAT_VIBRATE; return VibrateState.fromId(getIntegerPreference(context, KEY, VibrateState.DEFAULT.getId())); } @@ -264,16 +290,16 @@ public class Prefs { // misc. public static String getBackgroundImagePath(Context context, int accountId) { - return getStringPreference(context, BACKGROUND_PREF+accountId, ""); + return getStringPreference(context, BACKGROUND_PREF + accountId, ""); } public static void setBackgroundImagePath(Context context, int accountId, String path) { - setStringPreference(context, BACKGROUND_PREF+accountId, path); + setStringPreference(context, BACKGROUND_PREF + accountId, path); } public static boolean getAlwaysLoadRemoteContent(Context context) { - return getBooleanPreference(context, Prefs.ALWAYS_LOAD_REMOTE_CONTENT, - Prefs.ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT); + return getBooleanPreference( + context, Prefs.ALWAYS_LOAD_REMOTE_CONTENT, Prefs.ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT); } // generic preference functions @@ -314,7 +340,8 @@ public class Prefs { PreferenceManager.getDefaultSharedPreferences(context).edit().remove(key).apply(); } - private static Set getStringSetPreference(Context context, String key, Set defaultValues) { + private static Set getStringSetPreference( + Context context, String key, Set defaultValues) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (prefs.contains(key)) { return prefs.getStringSet(key, Collections.emptySet()); @@ -324,12 +351,17 @@ public class Prefs { } public static void setSystemContactPhotos(Context context, Set contactPhotoIdentifiers) { - PreferenceManager.getDefaultSharedPreferences(context).edit().putStringSet(PREF_CONTACT_PHOTO_IDENTIFIERS, contactPhotoIdentifiers).apply(); + PreferenceManager.getDefaultSharedPreferences(context) + .edit() + .putStringSet(PREF_CONTACT_PHOTO_IDENTIFIERS, contactPhotoIdentifiers) + .apply(); } public static Uri getSystemContactPhoto(Context context, String identifier) { - List contactPhotoIdentifiers = new ArrayList<>(getStringSetPreference(context, PREF_CONTACT_PHOTO_IDENTIFIERS, new HashSet<>())); - for(String contactPhotoIdentifier : contactPhotoIdentifiers) { + List contactPhotoIdentifiers = + new ArrayList<>( + getStringSetPreference(context, PREF_CONTACT_PHOTO_IDENTIFIERS, new HashSet<>())); + for (String contactPhotoIdentifier : contactPhotoIdentifiers) { if (contactPhotoIdentifier.contains(identifier)) { String[] parts = contactPhotoIdentifier.split("\\|"); long contactId = Long.valueOf(parts[1]); @@ -338,5 +370,4 @@ public class Prefs { } return null; } - } diff --git a/src/main/java/org/thoughtcrime/securesms/util/ResUtil.java b/src/main/java/org/thoughtcrime/securesms/util/ResUtil.java index 865fc3fcc..847b34c25 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/ResUtil.java +++ b/src/main/java/org/thoughtcrime/securesms/util/ResUtil.java @@ -1,20 +1,17 @@ /** * Copyright (C) 2015 Open Whisper Systems * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + *

This program is free software: you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + *

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + *

You should have received a copy of the GNU General Public License along with this program. If + * not, see . */ - package org.thoughtcrime.securesms.util; import android.content.Context; @@ -22,15 +19,14 @@ import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.TypedValue; - import androidx.annotation.AttrRes; import androidx.core.content.ContextCompat; public class ResUtil { public static int getColor(Context context, @AttrRes int attr) { - final TypedArray styledAttributes = context.obtainStyledAttributes(new int[]{attr}); - final int result = styledAttributes.getColor(0, -1); + final TypedArray styledAttributes = context.obtainStyledAttributes(new int[] {attr}); + final int result = styledAttributes.getColor(0, -1); styledAttributes.recycle(); return result; } diff --git a/src/main/java/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java b/src/main/java/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java index 90a4eedb4..90abcd27a 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java +++ b/src/main/java/org/thoughtcrime/securesms/util/concurrent/AssertedSuccessListener.java @@ -1,8 +1,7 @@ package org.thoughtcrime.securesms.util.concurrent; -import java.util.concurrent.ExecutionException; - import chat.delta.util.ListenableFuture; +import java.util.concurrent.ExecutionException; public abstract class AssertedSuccessListener implements ListenableFuture.Listener { @Override diff --git a/src/main/java/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java b/src/main/java/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java index b15c96137..f2c47b9ce 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java +++ b/src/main/java/org/thoughtcrime/securesms/util/spans/CenterAlignedRelativeSizeSpan.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.util.spans; - import android.text.TextPaint; import android.text.style.MetricAffectingSpan; diff --git a/src/main/java/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java b/src/main/java/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java index 23d76efcd..5c1f2c0a5 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java +++ b/src/main/java/org/thoughtcrime/securesms/util/task/ProgressDialogAsyncTask.java @@ -3,27 +3,25 @@ package org.thoughtcrime.securesms.util.task; import android.content.Context; import android.content.DialogInterface.OnCancelListener; import android.os.AsyncTask; - import androidx.annotation.Nullable; - +import java.lang.ref.WeakReference; import org.thoughtcrime.securesms.util.views.ProgressDialog; -import java.lang.ref.WeakReference; - -public abstract class ProgressDialogAsyncTask extends AsyncTask { +public abstract class ProgressDialogAsyncTask + extends AsyncTask { private final WeakReference contextReference; - private ProgressDialog progress; - private final String title; - private final String message; - private boolean cancelable; - private OnCancelListener onCancelListener; + private ProgressDialog progress; + private final String title; + private final String message; + private boolean cancelable; + private OnCancelListener onCancelListener; public ProgressDialogAsyncTask(Context context, String title, String message) { super(); this.contextReference = new WeakReference<>(context); - this.title = title; - this.message = message; + this.title = title; + this.message = message; } public void setCancelable(@Nullable OnCancelListener onCancelListener) { @@ -43,7 +41,7 @@ public abstract class ProgressDialogAsyncTask extends protected void onPostExecute(Result result) { try { if (progress != null) progress.dismiss(); - } catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); } } @@ -52,4 +50,3 @@ public abstract class ProgressDialogAsyncTask extends return contextReference.get(); } } - diff --git a/src/main/java/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java b/src/main/java/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java index 19e644a81..f0e8d0201 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java +++ b/src/main/java/org/thoughtcrime/securesms/util/task/SnackbarAsyncTask.java @@ -2,48 +2,43 @@ package org.thoughtcrime.securesms.util.task; import android.os.AsyncTask; import android.view.View; - import androidx.annotation.Nullable; - import com.google.android.material.snackbar.Snackbar; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.views.ProgressDialog; -public abstract class SnackbarAsyncTask - extends AsyncTask - implements View.OnClickListener -{ +public abstract class SnackbarAsyncTask extends AsyncTask + implements View.OnClickListener { - private final View view; - private final String snackbarText; - private final String snackbarActionText; - private final int snackbarDuration; + private final View view; + private final String snackbarText; + private final String snackbarActionText; + private final int snackbarDuration; private final boolean showProgress; - private @Nullable Params reversibleParameter; + private @Nullable Params reversibleParameter; private @Nullable ProgressDialog progressDialog; - public SnackbarAsyncTask(View view, - String snackbarText, - String snackbarActionText, - int snackbarDuration, - boolean showProgress) - { - this.view = view; - this.snackbarText = snackbarText; - this.snackbarActionText = snackbarActionText; - this.snackbarDuration = snackbarDuration; - this.showProgress = showProgress; + public SnackbarAsyncTask( + View view, + String snackbarText, + String snackbarActionText, + int snackbarDuration, + boolean showProgress) { + this.view = view; + this.snackbarText = snackbarText; + this.snackbarActionText = snackbarActionText; + this.snackbarDuration = snackbarDuration; + this.showProgress = showProgress; } @Override protected void onPreExecute() { if (this.showProgress) { - this.progressDialog = ProgressDialog.show(view.getContext(), - "", view.getContext().getString(R.string.one_moment), true, false); - } - else { + this.progressDialog = + ProgressDialog.show( + view.getContext(), "", view.getContext().getString(R.string.one_moment), true, false); + } else { this.progressDialog = null; } } @@ -51,7 +46,7 @@ public abstract class SnackbarAsyncTask @SafeVarargs @Override protected final Void doInBackground(Params... params) { - this.reversibleParameter = params != null && params.length > 0 ?params[0] : null; + this.reversibleParameter = params != null && params.length > 0 ? params[0] : null; executeAction(reversibleParameter); return null; } @@ -64,9 +59,9 @@ public abstract class SnackbarAsyncTask } Snackbar.make(view, snackbarText, snackbarDuration) - .setAction(snackbarActionText, this) - .setActionTextColor(view.getResources().getColor(R.color.white)) - .show(); + .setAction(snackbarActionText, this) + .setActionTextColor(view.getResources().getColor(R.color.white)) + .show(); } @Override @@ -75,7 +70,7 @@ public abstract class SnackbarAsyncTask @Override protected void onPreExecute() { if (showProgress) progressDialog = ProgressDialog.show(view.getContext(), "", "", true); - else progressDialog = null; + else progressDialog = null; } @Override @@ -95,6 +90,6 @@ public abstract class SnackbarAsyncTask } protected abstract void executeAction(@Nullable Params parameter); - protected abstract void reverseAction(@Nullable Params parameter); + protected abstract void reverseAction(@Nullable Params parameter); } diff --git a/src/main/java/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java b/src/main/java/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java index df2ba39b4..c02abd5ed 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java +++ b/src/main/java/org/thoughtcrime/securesms/util/views/ConversationAdaptiveActionsToolbar.java @@ -5,22 +5,23 @@ import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Menu; import android.view.MenuItem; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.ViewUtil; /** - * This class was pulled from Signal (AdaptiveActionsToolbar) and then adapted to the ConversationActivity. + * This class was pulled from Signal (AdaptiveActionsToolbar) and then adapted to the + * ConversationActivity. */ public class ConversationAdaptiveActionsToolbar extends Toolbar { - private static final int NAVIGATION_DP = 56; - private static final int TITLE_DP = 48; // estimated, only a number (if >1 items are selected there is more room anyway as there are fewer options) - private static final int ACTION_VIEW_WIDTH_DP = 48; + private static final int NAVIGATION_DP = 56; + private static final int TITLE_DP = + 48; // estimated, only a number (if >1 items are selected there is more room anyway as there + // are fewer options) + private static final int ACTION_VIEW_WIDTH_DP = 48; private static final int OVERFLOW_VIEW_WIDTH_DP = 36; private static final int ID_ACTION_1 = R.id.menu_context_edit; @@ -29,20 +30,23 @@ public class ConversationAdaptiveActionsToolbar extends Toolbar { private static final int ID_ACTION_4 = R.id.menu_context_forward; private static final int ID_ACTION_5 = R.id.menu_context_delete_message; - private final int maxShown; + private final int maxShown; public ConversationAdaptiveActionsToolbar(@NonNull Context context) { this(context, null); } - public ConversationAdaptiveActionsToolbar(@NonNull Context context, @Nullable AttributeSet attrs) { + public ConversationAdaptiveActionsToolbar( + @NonNull Context context, @Nullable AttributeSet attrs) { this(context, attrs, R.attr.toolbarStyle); } - public ConversationAdaptiveActionsToolbar(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + public ConversationAdaptiveActionsToolbar( + @NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.ConversationAdaptiveActionsToolbar); + TypedArray array = + context.obtainStyledAttributes(attrs, R.styleable.ConversationAdaptiveActionsToolbar); maxShown = array.getInteger(R.styleable.ConversationAdaptiveActionsToolbar_aat_max_shown, 100); @@ -77,11 +81,12 @@ public class ConversationAdaptiveActionsToolbar extends Toolbar { for (int i = 0; i < menu.size(); i++) { MenuItem item = menu.getItem(i); - boolean showAsAction = item.getItemId() == ID_ACTION_1 - || item.getItemId() == ID_ACTION_2 - || item.getItemId() == ID_ACTION_3 - || item.getItemId() == ID_ACTION_4 - || item.getItemId() == ID_ACTION_5; + boolean showAsAction = + item.getItemId() == ID_ACTION_1 + || item.getItemId() == ID_ACTION_2 + || item.getItemId() == ID_ACTION_3 + || item.getItemId() == ID_ACTION_4 + || item.getItemId() == ID_ACTION_5; if (showAsAction && item.isVisible() && nItemsToShow > 0) { item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); diff --git a/src/main/java/org/thoughtcrime/securesms/util/views/ProgressDialog.java b/src/main/java/org/thoughtcrime/securesms/util/views/ProgressDialog.java index 583c0f893..a9fe62da9 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/views/ProgressDialog.java +++ b/src/main/java/org/thoughtcrime/securesms/util/views/ProgressDialog.java @@ -9,59 +9,123 @@ import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; - import org.thoughtcrime.securesms.R; public class ProgressDialog extends AlertDialog { - private boolean indeterminate; - private String message; - private TextView textView; - private ProgressBar progressBar; + private boolean indeterminate; + private String message; + private TextView textView; + private ProgressBar progressBar; - public ProgressDialog(@NonNull Context context) { - super(context); + public ProgressDialog(@NonNull Context context) { + super(context); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + View dialogView = View.inflate(getContext(), R.layout.dialog_progress, null); + setView(dialogView); + super.onCreate(savedInstanceState); + } + + @Override + public void setMessage(CharSequence message) { + this.message = message.toString(); + if (textView != null) { + textView.setText(message); + } + } + + private boolean isButtonVisible(int which) { + Button button = getButton(which); + if (button == null) { + return false; + } + return button.getVisibility() == View.VISIBLE; + } + + @Override + public void show() { + super.show(); + + if (isButtonVisible(Dialog.BUTTON_POSITIVE) + || isButtonVisible(Dialog.BUTTON_NEGATIVE) + || isButtonVisible(Dialog.BUTTON_NEUTRAL)) { + findViewById(R.id.noButtonsSpacer).setVisibility(View.GONE); } - @Override - protected void onCreate(Bundle savedInstanceState) { - View dialogView = View.inflate(getContext(), R.layout.dialog_progress, null); - setView(dialogView); - super.onCreate(savedInstanceState); + progressBar = findViewById(R.id.progressBar); + textView = findViewById(R.id.text); + setupProgressBar(); + setupTextView(); + } + + private void setupProgressBar() { + if (progressBar != null) { + progressBar + .getIndeterminateDrawable() + .setColorFilter( + ContextCompat.getColor(getContext(), R.color.delta_accent), PorterDuff.Mode.SRC_IN); + progressBar.setIndeterminate(indeterminate); } + } - @Override - public void setMessage(CharSequence message) { - this.message = message.toString(); - if (textView != null) { - textView.setText(message); - } + private void setupTextView() { + if (textView != null && message != null && !message.isEmpty()) { + textView.setText(message); } + } - private boolean isButtonVisible(int which) { - Button button = getButton(which); - if (button==null) { - return false; - } - return button.getVisibility()==View.VISIBLE; + private void setIndeterminate(boolean indeterminate) { + this.indeterminate = indeterminate; + if (progressBar != null) { + progressBar.setIndeterminate(indeterminate); } + } - @Override - public void show() { - super.show(); + // Source: + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ProgressDialog.java + public static ProgressDialog show( + Context context, CharSequence title, CharSequence message, boolean indeterminate) { + return show(context, title, message, indeterminate, false, null); + } - if (isButtonVisible(Dialog.BUTTON_POSITIVE) || isButtonVisible(Dialog.BUTTON_NEGATIVE) || isButtonVisible(Dialog.BUTTON_NEUTRAL)) { - findViewById(R.id.noButtonsSpacer).setVisibility(View.GONE); - } + // Source: + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ProgressDialog.java + public static ProgressDialog show( + Context context, + CharSequence title, + CharSequence message, + boolean indeterminate, + boolean cancelable) { + return show(context, title, message, indeterminate, cancelable, null); + } - progressBar = findViewById(R.id.progressBar); - textView = findViewById(R.id.text); - setupProgressBar(); - setupTextView(); + // Source: + // https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/app/ProgressDialog.java + public static ProgressDialog show( + Context context, + CharSequence title, + CharSequence message, + boolean indeterminate, + boolean cancelable, + OnCancelListener cancelListener) { + ProgressDialog dialog = new ProgressDialog(context); + dialog.setTitle(title); + dialog.setMessage(message); + dialog.setIndeterminate(indeterminate); + dialog.setCancelable(cancelable); + dialog.setOnCancelListener(cancelListener); + if (cancelable) { + dialog.setCanceledOnTouchOutside(false); + dialog.setButton( + DialogInterface.BUTTON_NEGATIVE, + context.getString(R.string.cancel), + ((dialog1, which) -> cancelListener.onCancel(dialog))); } private void setupProgressBar() { diff --git a/src/main/java/org/thoughtcrime/securesms/util/views/Stub.java b/src/main/java/org/thoughtcrime/securesms/util/views/Stub.java index 7d9648444..1ba1dbd93 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/views/Stub.java +++ b/src/main/java/org/thoughtcrime/securesms/util/views/Stub.java @@ -1,8 +1,6 @@ package org.thoughtcrime.securesms.util.views; - import android.view.ViewStub; - import androidx.annotation.NonNull; public class Stub { @@ -16,7 +14,7 @@ public class Stub { public T get() { if (view == null) { - view = (T)viewStub.inflate(); + view = (T) viewStub.inflate(); viewStub = null; } @@ -26,5 +24,4 @@ public class Stub { public boolean resolved() { return view != null; } - } diff --git a/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java b/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java index eb46b5f8f..f0f11f1d2 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java +++ b/src/main/java/org/thoughtcrime/securesms/video/VideoPlayer.java @@ -21,10 +21,8 @@ import android.util.AttributeSet; import android.view.Window; import android.view.WindowManager; import android.widget.FrameLayout; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.LoadControl; import com.google.android.exoplayer2.MediaItem; @@ -41,7 +39,6 @@ import com.google.android.exoplayer2.ui.StyledPlayerView; import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; - import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.mms.VideoSlide; import org.thoughtcrime.securesms.util.ViewUtil; @@ -51,8 +48,8 @@ public class VideoPlayer extends FrameLayout { @Nullable private final StyledPlayerView exoView; - @Nullable private SimpleExoPlayer exoPlayer; - @Nullable private Window window; + @Nullable private SimpleExoPlayer exoPlayer; + @Nullable private Window window; public VideoPlayer(Context context) { this(context, null); @@ -70,8 +67,7 @@ public class VideoPlayer extends FrameLayout { this.exoView = ViewUtil.findById(this, R.id.video_view); } - public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) - { + public void setVideoSource(@NonNull VideoSlide videoSource, boolean autoplay) { setExoViewSource(videoSource, autoplay); } @@ -91,27 +87,30 @@ public class VideoPlayer extends FrameLayout { this.window = window; } - private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) - { - BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(getContext()).build(); - TrackSelector trackSelector = new DefaultTrackSelector(getContext()); - LoadControl loadControl = new DefaultLoadControl(); + private void setExoViewSource(@NonNull VideoSlide videoSource, boolean autoplay) { + BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder(getContext()).build(); + TrackSelector trackSelector = new DefaultTrackSelector(getContext()); + LoadControl loadControl = new DefaultLoadControl(); - exoPlayer = new SimpleExoPlayer.Builder(getContext()) - .setTrackSelector(trackSelector) - .setBandwidthMeter(bandwidthMeter) - .setLoadControl(loadControl) - .build(); + exoPlayer = + new SimpleExoPlayer.Builder(getContext()) + .setTrackSelector(trackSelector) + .setBandwidthMeter(bandwidthMeter) + .setLoadControl(loadControl) + .build(); exoPlayer.addListener(new ExoPlayerListener(window)); //noinspection ConstantConditions exoView.setPlayer(exoPlayer); - DefaultDataSourceFactory defaultDataSourceFactory = new DefaultDataSourceFactory(getContext(), "GenericUserAgent", null); - AttachmentDataSourceFactory attachmentDataSourceFactory = new AttachmentDataSourceFactory(defaultDataSourceFactory); - ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); + DefaultDataSourceFactory defaultDataSourceFactory = + new DefaultDataSourceFactory(getContext(), "GenericUserAgent", null); + AttachmentDataSourceFactory attachmentDataSourceFactory = + new AttachmentDataSourceFactory(defaultDataSourceFactory); + ExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); - MediaSource mediaSource = new ProgressiveMediaSource.Factory(attachmentDataSourceFactory, extractorsFactory) - .createMediaSource(MediaItem.fromUri(videoSource.getUri())); + MediaSource mediaSource = + new ProgressiveMediaSource.Factory(attachmentDataSourceFactory, extractorsFactory) + .createMediaSource(MediaItem.fromUri(videoSource.getUri())); exoPlayer.prepare(new LoopingMediaSource(mediaSource)); exoPlayer.setPlayWhenReady(autoplay); @@ -126,7 +125,7 @@ public class VideoPlayer extends FrameLayout { @Override public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { - switch(playbackState) { + switch (playbackState) { case Player.STATE_IDLE: case Player.STATE_BUFFERING: case Player.STATE_ENDED: @@ -144,6 +143,4 @@ public class VideoPlayer extends FrameLayout { } } } - - } diff --git a/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java b/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java index af37ca8d0..656e1cf45 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java +++ b/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSource.java @@ -1,13 +1,10 @@ package org.thoughtcrime.securesms.video.exo; - import android.net.Uri; - import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DefaultDataSource; import com.google.android.exoplayer2.upstream.TransferListener; - import java.io.IOException; import java.util.Collections; import java.util.List; @@ -24,8 +21,7 @@ public class AttachmentDataSource implements DataSource { } @Override - public void addTransferListener(TransferListener transferListener) { - } + public void addTransferListener(TransferListener transferListener) {} @Override public long open(DataSpec dataSpec) throws IOException { diff --git a/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java b/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java index 1638db7d4..a9a243abe 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java +++ b/src/main/java/org/thoughtcrime/securesms/video/exo/AttachmentDataSourceFactory.java @@ -1,8 +1,6 @@ package org.thoughtcrime.securesms.video.exo; - import androidx.annotation.NonNull; - import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; @@ -10,8 +8,7 @@ public class AttachmentDataSourceFactory implements DataSource.Factory { private final DefaultDataSourceFactory defaultDataSourceFactory; - public AttachmentDataSourceFactory(@NonNull DefaultDataSourceFactory defaultDataSourceFactory) - { + public AttachmentDataSourceFactory(@NonNull DefaultDataSourceFactory defaultDataSourceFactory) { this.defaultDataSourceFactory = defaultDataSourceFactory; } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/InputSurface.java b/src/main/java/org/thoughtcrime/securesms/video/recode/InputSurface.java index 07ef7fdc0..c649ac81c 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/InputSurface.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/InputSurface.java @@ -28,107 +28,109 @@ import android.view.Surface; @TargetApi(17) public class InputSurface { - private static final int EGL_RECORDABLE_ANDROID = 0x3142; - private static final int EGL_OPENGL_ES2_BIT = 4; - private EGLDisplay mEGLDisplay; - private EGLContext mEGLContext; - private EGLSurface mEGLSurface; - private Surface mSurface; + private static final int EGL_RECORDABLE_ANDROID = 0x3142; + private static final int EGL_OPENGL_ES2_BIT = 4; + private EGLDisplay mEGLDisplay; + private EGLContext mEGLContext; + private EGLSurface mEGLSurface; + private Surface mSurface; - public InputSurface(Surface surface) { - if (surface == null) { - throw new NullPointerException(); - } - mSurface = surface; - eglSetup(); + public InputSurface(Surface surface) { + if (surface == null) { + throw new NullPointerException(); + } + mSurface = surface; + eglSetup(); + } + + private void eglSetup() { + mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL14 display"); + } + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { + mEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL14"); } - private void eglSetup() { - mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); - if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { - throw new RuntimeException("unable to get EGL14 display"); - } - int[] version = new int[2]; - if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { - mEGLDisplay = null; - throw new RuntimeException("unable to initialize EGL14"); - } - - int[] attribList = { - EGL14.EGL_RED_SIZE, 8, - EGL14.EGL_GREEN_SIZE, 8, - EGL14.EGL_BLUE_SIZE, 8, - EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_RECORDABLE_ANDROID, 1, - EGL14.EGL_NONE - }; - EGLConfig[] configs = new EGLConfig[1]; - int[] numConfigs = new int[1]; - if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length, - numConfigs, 0)) { - throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); - } - - int[] attrib_list = { - EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, - EGL14.EGL_NONE - }; - - mEGLContext = EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0); - checkEglError("eglCreateContext"); - if (mEGLContext == null) { - throw new RuntimeException("null context"); - } - - int[] surfaceAttribs = { - EGL14.EGL_NONE - }; - mEGLSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, - surfaceAttribs, 0); - checkEglError("eglCreateWindowSurface"); - if (mEGLSurface == null) { - throw new RuntimeException("surface was null"); - } + int[] attribList = { + EGL14.EGL_RED_SIZE, + 8, + EGL14.EGL_GREEN_SIZE, + 8, + EGL14.EGL_BLUE_SIZE, + 8, + EGL14.EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, + EGL_RECORDABLE_ANDROID, + 1, + EGL14.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!EGL14.eglChooseConfig( + mEGLDisplay, attribList, 0, configs, 0, configs.length, numConfigs, 0)) { + throw new RuntimeException("unable to find RGB888+recordable ES2 EGL config"); } - public void release() { - if (EGL14.eglGetCurrentContext().equals(mEGLContext)) { - EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); - } - EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); - EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); - mSurface.release(); - mEGLDisplay = null; - mEGLContext = null; - mEGLSurface = null; - mSurface = null; + int[] attrib_list = {EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE}; + + mEGLContext = + EGL14.eglCreateContext(mEGLDisplay, configs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0); + checkEglError("eglCreateContext"); + if (mEGLContext == null) { + throw new RuntimeException("null context"); } - public void makeCurrent() { - if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { - throw new RuntimeException("eglMakeCurrent failed"); - } + int[] surfaceAttribs = {EGL14.EGL_NONE}; + mEGLSurface = + EGL14.eglCreateWindowSurface(mEGLDisplay, configs[0], mSurface, surfaceAttribs, 0); + checkEglError("eglCreateWindowSurface"); + if (mEGLSurface == null) { + throw new RuntimeException("surface was null"); } + } - public boolean swapBuffers() { - return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); + public void release() { + if (EGL14.eglGetCurrentContext().equals(mEGLContext)) { + EGL14.eglMakeCurrent( + mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT); } + EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface); + EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); + mSurface.release(); + mEGLDisplay = null; + mEGLContext = null; + mEGLSurface = null; + mSurface = null; + } - public Surface getSurface() { - return mSurface; + public void makeCurrent() { + if (!EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); } + } - public void setPresentationTime(long nsecs) { - EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); - } + public boolean swapBuffers() { + return EGL14.eglSwapBuffers(mEGLDisplay, mEGLSurface); + } - private void checkEglError(String msg) { - boolean failed = false; - while (EGL14.eglGetError() != EGL14.EGL_SUCCESS) { - failed = true; - } - if (failed) { - throw new RuntimeException("EGL error encountered (see log)"); - } + public Surface getSurface() { + return mSurface; + } + + public void setPresentationTime(long nsecs) { + EGLExt.eglPresentationTimeANDROID(mEGLDisplay, mEGLSurface, nsecs); + } + + private void checkEglError(String msg) { + boolean failed = false; + while (EGL14.eglGetError() != EGL14.EGL_SUCCESS) { + failed = true; } + if (failed) { + throw new RuntimeException("EGL error encountered (see log)"); + } + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/MP4Builder.java b/src/main/java/org/thoughtcrime/securesms/video/recode/MP4Builder.java index b5502928a..06f1989b8 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/MP4Builder.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/MP4Builder.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.video.recode; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; - import com.coremedia.iso.BoxParser; import com.coremedia.iso.IsoFile; import com.coremedia.iso.IsoTypeWriter; @@ -29,7 +28,6 @@ import com.coremedia.iso.boxes.TrackBox; import com.coremedia.iso.boxes.TrackHeaderBox; import com.googlecode.mp4parser.DataSource; import com.googlecode.mp4parser.util.Matrix; - import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; @@ -44,394 +42,396 @@ import java.util.List; @TargetApi(16) public class MP4Builder { - private InterleaveChunkMdat mdat = null; - private Mp4Movie currentMp4Movie = null; - private FileOutputStream fos = null; - private FileChannel fc = null; + private InterleaveChunkMdat mdat = null; + private Mp4Movie currentMp4Movie = null; + private FileOutputStream fos = null; + private FileChannel fc = null; + private long dataOffset = 0; + private long writedSinceLastMdat = 0; + private boolean writeNewMdat = true; + private final HashMap track2SampleSizes = new HashMap<>(); + private ByteBuffer sizeBuffer = null; + + public MP4Builder createMovie(Mp4Movie mp4Movie) throws Exception { + currentMp4Movie = mp4Movie; + + fos = new FileOutputStream(mp4Movie.getCacheFile()); + fc = fos.getChannel(); + + FileTypeBox fileTypeBox = createFileTypeBox(); + fileTypeBox.getBox(fc); + dataOffset += fileTypeBox.getSize(); + writedSinceLastMdat += dataOffset; + + mdat = new InterleaveChunkMdat(); + + sizeBuffer = ByteBuffer.allocateDirect(4); + + return this; + } + + private void flushCurrentMdat() throws Exception { + long oldPosition = fc.position(); + fc.position(mdat.getOffset()); + mdat.getBox(fc); + fc.position(oldPosition); + mdat.setDataOffset(0); + mdat.setContentSize(0); + fos.flush(); + } + + public boolean writeSampleData( + int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo, boolean isAudio) + throws Exception { + if (writeNewMdat) { + mdat.setContentSize(0); + mdat.getBox(fc); + mdat.setDataOffset(dataOffset); + dataOffset += 16; + writedSinceLastMdat += 16; + writeNewMdat = false; + } + + mdat.setContentSize(mdat.getContentSize() + bufferInfo.size); + writedSinceLastMdat += bufferInfo.size; + + boolean flush = false; + if (writedSinceLastMdat >= 32 * 1024) { + flushCurrentMdat(); + writeNewMdat = true; + flush = true; + writedSinceLastMdat -= 32 * 1024; + } + + currentMp4Movie.addSample(trackIndex, dataOffset, bufferInfo); + byteBuf.position(bufferInfo.offset + (isAudio ? 0 : 4)); + byteBuf.limit(bufferInfo.offset + bufferInfo.size); + + if (!isAudio) { + sizeBuffer.position(0); + sizeBuffer.putInt(bufferInfo.size - 4); + sizeBuffer.position(0); + fc.write(sizeBuffer); + } + + fc.write(byteBuf); + dataOffset += bufferInfo.size; + + if (flush) { + fos.flush(); + } + return flush; + } + + public int addTrack(MediaFormat mediaFormat, boolean isAudio) throws Exception { + return currentMp4Movie.addTrack(mediaFormat, isAudio); + } + + public void finishMovie(boolean error) throws Exception { + if (mdat.getContentSize() != 0) { + flushCurrentMdat(); + } + + for (Track track : currentMp4Movie.getTracks()) { + List samples = track.getSamples(); + long[] sizes = new long[samples.size()]; + for (int i = 0; i < sizes.length; i++) { + sizes[i] = samples.get(i).getSize(); + } + track2SampleSizes.put(track, sizes); + } + + Box moov = createMovieBox(currentMp4Movie); + moov.getBox(fc); + fos.flush(); + + fc.close(); + fos.close(); + } + + protected FileTypeBox createFileTypeBox() { + LinkedList minorBrands = new LinkedList<>(); + minorBrands.add("isom"); + minorBrands.add("3gp4"); + return new FileTypeBox("isom", 0, minorBrands); + } + + private class InterleaveChunkMdat implements Box { + private Container parent; + private long contentSize = 1024 * 1024 * 1024; private long dataOffset = 0; - private long writedSinceLastMdat = 0; - private boolean writeNewMdat = true; - private final HashMap track2SampleSizes = new HashMap<>(); - private ByteBuffer sizeBuffer = null; - public MP4Builder createMovie(Mp4Movie mp4Movie) throws Exception { - currentMp4Movie = mp4Movie; - - fos = new FileOutputStream(mp4Movie.getCacheFile()); - fc = fos.getChannel(); - - FileTypeBox fileTypeBox = createFileTypeBox(); - fileTypeBox.getBox(fc); - dataOffset += fileTypeBox.getSize(); - writedSinceLastMdat += dataOffset; - - mdat = new InterleaveChunkMdat(); - - sizeBuffer = ByteBuffer.allocateDirect(4); - - return this; + public Container getParent() { + return parent; } - private void flushCurrentMdat() throws Exception { - long oldPosition = fc.position(); - fc.position(mdat.getOffset()); - mdat.getBox(fc); - fc.position(oldPosition); - mdat.setDataOffset(0); - mdat.setContentSize(0); - fos.flush(); + public long getOffset() { + return dataOffset; } - public boolean writeSampleData(int trackIndex, ByteBuffer byteBuf, MediaCodec.BufferInfo bufferInfo, boolean isAudio) throws Exception { - if (writeNewMdat) { - mdat.setContentSize(0); - mdat.getBox(fc); - mdat.setDataOffset(dataOffset); - dataOffset += 16; - writedSinceLastMdat += 16; - writeNewMdat = false; - } - - mdat.setContentSize(mdat.getContentSize() + bufferInfo.size); - writedSinceLastMdat += bufferInfo.size; - - boolean flush = false; - if (writedSinceLastMdat >= 32 * 1024) { - flushCurrentMdat(); - writeNewMdat = true; - flush = true; - writedSinceLastMdat -= 32 * 1024; - } - - currentMp4Movie.addSample(trackIndex, dataOffset, bufferInfo); - byteBuf.position(bufferInfo.offset + (isAudio ? 0 : 4)); - byteBuf.limit(bufferInfo.offset + bufferInfo.size); - - if (!isAudio) { - sizeBuffer.position(0); - sizeBuffer.putInt(bufferInfo.size - 4); - sizeBuffer.position(0); - fc.write(sizeBuffer); - } - - fc.write(byteBuf); - dataOffset += bufferInfo.size; - - if (flush) { - fos.flush(); - } - return flush; + public void setDataOffset(long offset) { + dataOffset = offset; } - public int addTrack(MediaFormat mediaFormat, boolean isAudio) throws Exception { - return currentMp4Movie.addTrack(mediaFormat, isAudio); + public void setParent(Container parent) { + this.parent = parent; } - public void finishMovie(boolean error) throws Exception { - if (mdat.getContentSize() != 0) { - flushCurrentMdat(); - } - - for (Track track : currentMp4Movie.getTracks()) { - List samples = track.getSamples(); - long[] sizes = new long[samples.size()]; - for (int i = 0; i < sizes.length; i++) { - sizes[i] = samples.get(i).getSize(); - } - track2SampleSizes.put(track, sizes); - } - - Box moov = createMovieBox(currentMp4Movie); - moov.getBox(fc); - fos.flush(); - - fc.close(); - fos.close(); + public void setContentSize(long contentSize) { + this.contentSize = contentSize; } - protected FileTypeBox createFileTypeBox() { - LinkedList minorBrands = new LinkedList<>(); - minorBrands.add("isom"); - minorBrands.add("3gp4"); - return new FileTypeBox("isom", 0, minorBrands); + public long getContentSize() { + return contentSize; } - private class InterleaveChunkMdat implements Box { - private Container parent; - private long contentSize = 1024 * 1024 * 1024; - private long dataOffset = 0; - - public Container getParent() { - return parent; - } - - public long getOffset() { - return dataOffset; - } - - public void setDataOffset(long offset) { - dataOffset = offset; - } - - public void setParent(Container parent) { - this.parent = parent; - } - - public void setContentSize(long contentSize) { - this.contentSize = contentSize; - } - - public long getContentSize() { - return contentSize; - } - - public String getType() { - return "mdat"; - } - - public long getSize() { - return 16 + contentSize; - } - - private boolean isSmallBox(long contentSize) { - return (contentSize + 8) < 4294967296L; - } - - @Override - public void parse(DataSource dataSource, ByteBuffer header, long contentSize, BoxParser boxParser) throws IOException { - - } - - public void getBox(WritableByteChannel writableByteChannel) throws IOException { - ByteBuffer bb = ByteBuffer.allocate(16); - long size = getSize(); - if (isSmallBox(size)) { - IsoTypeWriter.writeUInt32(bb, size); - } else { - IsoTypeWriter.writeUInt32(bb, 1); - } - bb.put(IsoFile.fourCCtoBytes("mdat")); - if (isSmallBox(size)) { - bb.put(new byte[8]); - } else { - IsoTypeWriter.writeUInt64(bb, size); - } - bb.rewind(); - writableByteChannel.write(bb); - } + public String getType() { + return "mdat"; } - public static long gcd(long a, long b) { - if (b == 0) { - return a; - } - return gcd(b, a % b); + public long getSize() { + return 16 + contentSize; } - public long getTimescale(Mp4Movie mp4Movie) { - long timescale = 0; - if (!mp4Movie.getTracks().isEmpty()) { - timescale = mp4Movie.getTracks().iterator().next().getTimeScale(); - } - for (Track track : mp4Movie.getTracks()) { - timescale = gcd(track.getTimeScale(), timescale); - } - return timescale; + private boolean isSmallBox(long contentSize) { + return (contentSize + 8) < 4294967296L; } - protected MovieBox createMovieBox(Mp4Movie movie) { - MovieBox movieBox = new MovieBox(); - MovieHeaderBox mvhd = new MovieHeaderBox(); + @Override + public void parse( + DataSource dataSource, ByteBuffer header, long contentSize, BoxParser boxParser) + throws IOException {} - mvhd.setCreationTime(new Date()); - mvhd.setModificationTime(new Date()); - mvhd.setMatrix(Matrix.ROTATE_0); - long movieTimeScale = getTimescale(movie); - long duration = 0; + public void getBox(WritableByteChannel writableByteChannel) throws IOException { + ByteBuffer bb = ByteBuffer.allocate(16); + long size = getSize(); + if (isSmallBox(size)) { + IsoTypeWriter.writeUInt32(bb, size); + } else { + IsoTypeWriter.writeUInt32(bb, 1); + } + bb.put(IsoFile.fourCCtoBytes("mdat")); + if (isSmallBox(size)) { + bb.put(new byte[8]); + } else { + IsoTypeWriter.writeUInt64(bb, size); + } + bb.rewind(); + writableByteChannel.write(bb); + } + } - for (Track track : movie.getTracks()) { - long tracksDuration = track.getDuration() * movieTimeScale / track.getTimeScale(); - if (tracksDuration > duration) { - duration = tracksDuration; - } + public static long gcd(long a, long b) { + if (b == 0) { + return a; + } + return gcd(b, a % b); + } + + public long getTimescale(Mp4Movie mp4Movie) { + long timescale = 0; + if (!mp4Movie.getTracks().isEmpty()) { + timescale = mp4Movie.getTracks().iterator().next().getTimeScale(); + } + for (Track track : mp4Movie.getTracks()) { + timescale = gcd(track.getTimeScale(), timescale); + } + return timescale; + } + + protected MovieBox createMovieBox(Mp4Movie movie) { + MovieBox movieBox = new MovieBox(); + MovieHeaderBox mvhd = new MovieHeaderBox(); + + mvhd.setCreationTime(new Date()); + mvhd.setModificationTime(new Date()); + mvhd.setMatrix(Matrix.ROTATE_0); + long movieTimeScale = getTimescale(movie); + long duration = 0; + + for (Track track : movie.getTracks()) { + long tracksDuration = track.getDuration() * movieTimeScale / track.getTimeScale(); + if (tracksDuration > duration) { + duration = tracksDuration; + } + } + + mvhd.setDuration(duration); + mvhd.setTimescale(movieTimeScale); + mvhd.setNextTrackId(movie.getTracks().size() + 1); + + movieBox.addBox(mvhd); + for (Track track : movie.getTracks()) { + movieBox.addBox(createTrackBox(track, movie)); + } + return movieBox; + } + + protected TrackBox createTrackBox(Track track, Mp4Movie movie) { + TrackBox trackBox = new TrackBox(); + TrackHeaderBox tkhd = new TrackHeaderBox(); + + tkhd.setEnabled(true); + tkhd.setInMovie(true); + tkhd.setInPreview(true); + if (track.isAudio()) { + tkhd.setMatrix(Matrix.ROTATE_0); + } else { + tkhd.setMatrix(movie.getMatrix()); + } + tkhd.setAlternateGroup(0); + tkhd.setCreationTime(track.getCreationTime()); + tkhd.setDuration(track.getDuration() * getTimescale(movie) / track.getTimeScale()); + tkhd.setHeight(track.getHeight()); + tkhd.setWidth(track.getWidth()); + tkhd.setLayer(0); + tkhd.setModificationTime(new Date()); + tkhd.setTrackId(track.getTrackId() + 1); + tkhd.setVolume(track.getVolume()); + + trackBox.addBox(tkhd); + + MediaBox mdia = new MediaBox(); + trackBox.addBox(mdia); + MediaHeaderBox mdhd = new MediaHeaderBox(); + mdhd.setCreationTime(track.getCreationTime()); + mdhd.setDuration(track.getDuration()); + mdhd.setTimescale(track.getTimeScale()); + mdhd.setLanguage("eng"); + mdia.addBox(mdhd); + HandlerBox hdlr = new HandlerBox(); + hdlr.setName(track.isAudio() ? "SoundHandle" : "VideoHandle"); + hdlr.setHandlerType(track.getHandler()); + + mdia.addBox(hdlr); + + MediaInformationBox minf = new MediaInformationBox(); + minf.addBox(track.getMediaHeaderBox()); + + DataInformationBox dinf = new DataInformationBox(); + DataReferenceBox dref = new DataReferenceBox(); + dinf.addBox(dref); + DataEntryUrlBox url = new DataEntryUrlBox(); + url.setFlags(1); + dref.addBox(url); + minf.addBox(dinf); + + Box stbl = createStbl(track); + minf.addBox(stbl); + mdia.addBox(minf); + + return trackBox; + } + + protected Box createStbl(Track track) { + SampleTableBox stbl = new SampleTableBox(); + + createStsd(track, stbl); + createStts(track, stbl); + createStss(track, stbl); + createStsc(track, stbl); + createStsz(track, stbl); + createStco(track, stbl); + + return stbl; + } + + protected void createStsd(Track track, SampleTableBox stbl) { + stbl.addBox(track.getSampleDescriptionBox()); + } + + protected void createStts(Track track, SampleTableBox stbl) { + TimeToSampleBox.Entry lastEntry = null; + List entries = new ArrayList<>(); + + for (long delta : track.getSampleDurations()) { + if (lastEntry != null && lastEntry.getDelta() == delta) { + lastEntry.setCount(lastEntry.getCount() + 1); + } else { + lastEntry = new TimeToSampleBox.Entry(1, delta); + entries.add(lastEntry); + } + } + TimeToSampleBox stts = new TimeToSampleBox(); + stts.setEntries(entries); + stbl.addBox(stts); + } + + protected void createStss(Track track, SampleTableBox stbl) { + long[] syncSamples = track.getSyncSamples(); + if (syncSamples != null && syncSamples.length > 0) { + SyncSampleBox stss = new SyncSampleBox(); + stss.setSampleNumber(syncSamples); + stbl.addBox(stss); + } + } + + protected void createStsc(Track track, SampleTableBox stbl) { + SampleToChunkBox stsc = new SampleToChunkBox(); + stsc.setEntries(new LinkedList()); + + long lastOffset; + int lastChunkNumber = 1; + int lastSampleCount = 0; + + int previousWritedChunkCount = -1; + + int samplesCount = track.getSamples().size(); + for (int a = 0; a < samplesCount; a++) { + Sample sample = track.getSamples().get(a); + long offset = sample.getOffset(); + long size = sample.getSize(); + + lastOffset = offset + size; + lastSampleCount++; + + boolean write = false; + if (a != samplesCount - 1) { + Sample nextSample = track.getSamples().get(a + 1); + if (lastOffset != nextSample.getOffset()) { + write = true; } - - mvhd.setDuration(duration); - mvhd.setTimescale(movieTimeScale); - mvhd.setNextTrackId(movie.getTracks().size() + 1); - - movieBox.addBox(mvhd); - for (Track track : movie.getTracks()) { - movieBox.addBox(createTrackBox(track, movie)); + } else { + write = true; + } + if (write) { + if (previousWritedChunkCount != lastSampleCount) { + stsc.getEntries().add(new SampleToChunkBox.Entry(lastChunkNumber, lastSampleCount, 1)); + previousWritedChunkCount = lastSampleCount; } - return movieBox; + lastSampleCount = 0; + lastChunkNumber++; + } + } + stbl.addBox(stsc); + } + + protected void createStsz(Track track, SampleTableBox stbl) { + SampleSizeBox stsz = new SampleSizeBox(); + stsz.setSampleSizes(track2SampleSizes.get(track)); + stbl.addBox(stsz); + } + + protected void createStco(Track track, SampleTableBox stbl) { + ArrayList chunksOffsets = new ArrayList<>(); + long lastOffset = -1; + for (Sample sample : track.getSamples()) { + long offset = sample.getOffset(); + if (lastOffset != -1 && lastOffset != offset) { + lastOffset = -1; + } + if (lastOffset == -1) { + chunksOffsets.add(offset); + } + lastOffset = offset + sample.getSize(); + } + long[] chunkOffsetsLong = new long[chunksOffsets.size()]; + for (int a = 0; a < chunksOffsets.size(); a++) { + chunkOffsetsLong[a] = chunksOffsets.get(a); } - protected TrackBox createTrackBox(Track track, Mp4Movie movie) { - TrackBox trackBox = new TrackBox(); - TrackHeaderBox tkhd = new TrackHeaderBox(); - - tkhd.setEnabled(true); - tkhd.setInMovie(true); - tkhd.setInPreview(true); - if (track.isAudio()) { - tkhd.setMatrix(Matrix.ROTATE_0); - } else { - tkhd.setMatrix(movie.getMatrix()); - } - tkhd.setAlternateGroup(0); - tkhd.setCreationTime(track.getCreationTime()); - tkhd.setDuration(track.getDuration() * getTimescale(movie) / track.getTimeScale()); - tkhd.setHeight(track.getHeight()); - tkhd.setWidth(track.getWidth()); - tkhd.setLayer(0); - tkhd.setModificationTime(new Date()); - tkhd.setTrackId(track.getTrackId() + 1); - tkhd.setVolume(track.getVolume()); - - trackBox.addBox(tkhd); - - MediaBox mdia = new MediaBox(); - trackBox.addBox(mdia); - MediaHeaderBox mdhd = new MediaHeaderBox(); - mdhd.setCreationTime(track.getCreationTime()); - mdhd.setDuration(track.getDuration()); - mdhd.setTimescale(track.getTimeScale()); - mdhd.setLanguage("eng"); - mdia.addBox(mdhd); - HandlerBox hdlr = new HandlerBox(); - hdlr.setName(track.isAudio() ? "SoundHandle" : "VideoHandle"); - hdlr.setHandlerType(track.getHandler()); - - mdia.addBox(hdlr); - - MediaInformationBox minf = new MediaInformationBox(); - minf.addBox(track.getMediaHeaderBox()); - - DataInformationBox dinf = new DataInformationBox(); - DataReferenceBox dref = new DataReferenceBox(); - dinf.addBox(dref); - DataEntryUrlBox url = new DataEntryUrlBox(); - url.setFlags(1); - dref.addBox(url); - minf.addBox(dinf); - - Box stbl = createStbl(track); - minf.addBox(stbl); - mdia.addBox(minf); - - return trackBox; - } - - protected Box createStbl(Track track) { - SampleTableBox stbl = new SampleTableBox(); - - createStsd(track, stbl); - createStts(track, stbl); - createStss(track, stbl); - createStsc(track, stbl); - createStsz(track, stbl); - createStco(track, stbl); - - return stbl; - } - - protected void createStsd(Track track, SampleTableBox stbl) { - stbl.addBox(track.getSampleDescriptionBox()); - } - - protected void createStts(Track track, SampleTableBox stbl) { - TimeToSampleBox.Entry lastEntry = null; - List entries = new ArrayList<>(); - - for (long delta : track.getSampleDurations()) { - if (lastEntry != null && lastEntry.getDelta() == delta) { - lastEntry.setCount(lastEntry.getCount() + 1); - } else { - lastEntry = new TimeToSampleBox.Entry(1, delta); - entries.add(lastEntry); - } - } - TimeToSampleBox stts = new TimeToSampleBox(); - stts.setEntries(entries); - stbl.addBox(stts); - } - - protected void createStss(Track track, SampleTableBox stbl) { - long[] syncSamples = track.getSyncSamples(); - if (syncSamples != null && syncSamples.length > 0) { - SyncSampleBox stss = new SyncSampleBox(); - stss.setSampleNumber(syncSamples); - stbl.addBox(stss); - } - } - - protected void createStsc(Track track, SampleTableBox stbl) { - SampleToChunkBox stsc = new SampleToChunkBox(); - stsc.setEntries(new LinkedList()); - - long lastOffset; - int lastChunkNumber = 1; - int lastSampleCount = 0; - - int previousWritedChunkCount = -1; - - int samplesCount = track.getSamples().size(); - for (int a = 0; a < samplesCount; a++) { - Sample sample = track.getSamples().get(a); - long offset = sample.getOffset(); - long size = sample.getSize(); - - lastOffset = offset + size; - lastSampleCount++; - - boolean write = false; - if (a != samplesCount - 1) { - Sample nextSample = track.getSamples().get(a + 1); - if (lastOffset != nextSample.getOffset()) { - write = true; - } - } else { - write = true; - } - if (write) { - if (previousWritedChunkCount != lastSampleCount) { - stsc.getEntries().add(new SampleToChunkBox.Entry(lastChunkNumber, lastSampleCount, 1)); - previousWritedChunkCount = lastSampleCount; - } - lastSampleCount = 0; - lastChunkNumber++; - } - } - stbl.addBox(stsc); - } - - protected void createStsz(Track track, SampleTableBox stbl) { - SampleSizeBox stsz = new SampleSizeBox(); - stsz.setSampleSizes(track2SampleSizes.get(track)); - stbl.addBox(stsz); - } - - protected void createStco(Track track, SampleTableBox stbl) { - ArrayList chunksOffsets = new ArrayList<>(); - long lastOffset = -1; - for (Sample sample : track.getSamples()) { - long offset = sample.getOffset(); - if (lastOffset != -1 && lastOffset != offset) { - lastOffset = -1; - } - if (lastOffset == -1) { - chunksOffsets.add(offset); - } - lastOffset = offset + sample.getSize(); - } - long[] chunkOffsetsLong = new long[chunksOffsets.size()]; - for (int a = 0; a < chunksOffsets.size(); a++) { - chunkOffsetsLong[a] = chunksOffsets.get(a); - } - - StaticChunkOffsetBox stco = new StaticChunkOffsetBox(); - stco.setChunkOffsets(chunkOffsetsLong); - stbl.addBox(stco); - } + StaticChunkOffsetBox stco = new StaticChunkOffsetBox(); + stco.setChunkOffsets(chunkOffsetsLong); + stbl.addBox(stco); + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/Mp4Movie.java b/src/main/java/org/thoughtcrime/securesms/video/recode/Mp4Movie.java index 6d2a9b001..fa9eaf57c 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/Mp4Movie.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/Mp4Movie.java @@ -3,71 +3,70 @@ package org.thoughtcrime.securesms.video.recode; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; - import com.googlecode.mp4parser.util.Matrix; - import java.io.File; import java.util.ArrayList; @TargetApi(16) public class Mp4Movie { - private Matrix matrix = Matrix.ROTATE_0; - private final ArrayList tracks = new ArrayList<>(); - private File cacheFile; - private int width; - private int height; + private Matrix matrix = Matrix.ROTATE_0; + private final ArrayList tracks = new ArrayList<>(); + private File cacheFile; + private int width; + private int height; - public Matrix getMatrix() { - return matrix; - } + public Matrix getMatrix() { + return matrix; + } - public int getWidth() { - return width; - } + public int getWidth() { + return width; + } - public int getHeight() { - return height; - } + public int getHeight() { + return height; + } - public void setCacheFile(File file) { - cacheFile = file; - } + public void setCacheFile(File file) { + cacheFile = file; + } - public void setRotation(int angle) { - if (angle == 0) { - matrix = Matrix.ROTATE_0; - } else if (angle == 90) { - matrix = Matrix.ROTATE_90; - } else if (angle == 180) { - matrix = Matrix.ROTATE_180; - } else if (angle == 270) { - matrix = Matrix.ROTATE_270; - } + public void setRotation(int angle) { + if (angle == 0) { + matrix = Matrix.ROTATE_0; + } else if (angle == 90) { + matrix = Matrix.ROTATE_90; + } else if (angle == 180) { + matrix = Matrix.ROTATE_180; + } else if (angle == 270) { + matrix = Matrix.ROTATE_270; } + } - public void setSize(int w, int h) { - width = w; - height = h; - } + public void setSize(int w, int h) { + width = w; + height = h; + } - public ArrayList getTracks() { - return tracks; - } + public ArrayList getTracks() { + return tracks; + } - public File getCacheFile() { - return cacheFile; - } + public File getCacheFile() { + return cacheFile; + } - public void addSample(int trackIndex, long offset, MediaCodec.BufferInfo bufferInfo) throws Exception { - if (trackIndex < 0 || trackIndex >= tracks.size()) { - return; - } - Track track = tracks.get(trackIndex); - track.addSample(offset, bufferInfo); + public void addSample(int trackIndex, long offset, MediaCodec.BufferInfo bufferInfo) + throws Exception { + if (trackIndex < 0 || trackIndex >= tracks.size()) { + return; } + Track track = tracks.get(trackIndex); + track.addSample(offset, bufferInfo); + } - public int addTrack(MediaFormat mediaFormat, boolean isAudio) throws Exception { - tracks.add(new Track(tracks.size(), mediaFormat, isAudio)); - return tracks.size() - 1; - } + public int addTrack(MediaFormat mediaFormat, boolean isAudio) throws Exception { + tracks.add(new Track(tracks.size(), mediaFormat, isAudio)); + return tracks.size() - 1; + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/OutputSurface.java b/src/main/java/org/thoughtcrime/securesms/video/recode/OutputSurface.java index dd6937288..c73a9cff7 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/OutputSurface.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/OutputSurface.java @@ -20,10 +20,8 @@ import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.opengl.GLES20; import android.view.Surface; - import java.nio.ByteBuffer; import java.nio.ByteOrder; - import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; @@ -33,175 +31,173 @@ import javax.microedition.khronos.egl.EGLSurface; @TargetApi(16) public class OutputSurface implements SurfaceTexture.OnFrameAvailableListener { - private static final int EGL_OPENGL_ES2_BIT = 4; - private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - private EGL10 mEGL; - private EGLDisplay mEGLDisplay = null; - private EGLContext mEGLContext = null; - private EGLSurface mEGLSurface = null; - private SurfaceTexture mSurfaceTexture; - private Surface mSurface; - private final Object mFrameSyncObject = new Object(); - private boolean mFrameAvailable; - private TextureRenderer mTextureRender; - private int mWidth; - private int mHeight; - private int rotateRender = 0; - private ByteBuffer mPixelBuf; + private static final int EGL_OPENGL_ES2_BIT = 4; + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + private EGL10 mEGL; + private EGLDisplay mEGLDisplay = null; + private EGLContext mEGLContext = null; + private EGLSurface mEGLSurface = null; + private SurfaceTexture mSurfaceTexture; + private Surface mSurface; + private final Object mFrameSyncObject = new Object(); + private boolean mFrameAvailable; + private TextureRenderer mTextureRender; + private int mWidth; + private int mHeight; + private int rotateRender = 0; + private ByteBuffer mPixelBuf; - public OutputSurface(int width, int height, int rotate) { - if (width <= 0 || height <= 0) { - throw new IllegalArgumentException(); + public OutputSurface(int width, int height, int rotate) { + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException(); + } + mWidth = width; + mHeight = height; + rotateRender = rotate; + mPixelBuf = ByteBuffer.allocateDirect(mWidth * mHeight * 4); + mPixelBuf.order(ByteOrder.LITTLE_ENDIAN); + eglSetup(width, height); + makeCurrent(); + setup(); + } + + public OutputSurface() { + setup(); + } + + private void setup() { + mTextureRender = new TextureRenderer(rotateRender); + mTextureRender.surfaceCreated(); + mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); + mSurfaceTexture.setOnFrameAvailableListener(this); + mSurface = new Surface(mSurfaceTexture); + } + + private void eglSetup(int width, int height) { + mEGL = (EGL10) EGLContext.getEGL(); + mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + + if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("unable to get EGL10 display"); + } + + if (!mEGL.eglInitialize(mEGLDisplay, null)) { + mEGLDisplay = null; + throw new RuntimeException("unable to initialize EGL10"); + } + + int[] attribList = { + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 8, + EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_NONE + }; + EGLConfig[] configs = new EGLConfig[1]; + int[] numConfigs = new int[1]; + if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) { + throw new RuntimeException("unable to find RGB888+pbuffer EGL config"); + } + int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE}; + mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); + checkEglError("eglCreateContext"); + if (mEGLContext == null) { + throw new RuntimeException("null context"); + } + int[] surfaceAttribs = { + EGL10.EGL_WIDTH, width, + EGL10.EGL_HEIGHT, height, + EGL10.EGL_NONE + }; + mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs); + checkEglError("eglCreatePbufferSurface"); + if (mEGLSurface == null) { + throw new RuntimeException("surface was null"); + } + } + + public void release() { + if (mEGL != null) { + if (mEGL.eglGetCurrentContext().equals(mEGLContext)) { + mEGL.eglMakeCurrent( + mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); + } + mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface); + mEGL.eglDestroyContext(mEGLDisplay, mEGLContext); + } + mSurface.release(); + mEGLDisplay = null; + mEGLContext = null; + mEGLSurface = null; + mEGL = null; + mTextureRender = null; + mSurface = null; + mSurfaceTexture = null; + } + + public void makeCurrent() { + if (mEGL == null) { + throw new RuntimeException("not configured for makeCurrent"); + } + checkEglError("before makeCurrent"); + if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { + throw new RuntimeException("eglMakeCurrent failed"); + } + } + + public Surface getSurface() { + return mSurface; + } + + public void changeFragmentShader(String fragmentShader) { + mTextureRender.changeFragmentShader(fragmentShader); + } + + public void awaitNewImage() { + final int TIMEOUT_MS = 2500; + synchronized (mFrameSyncObject) { + while (!mFrameAvailable) { + try { + mFrameSyncObject.wait(TIMEOUT_MS); + if (!mFrameAvailable) { + throw new RuntimeException("Surface frame wait timed out"); + } + } catch (InterruptedException ie) { + throw new RuntimeException(ie); } - mWidth = width; - mHeight = height; - rotateRender = rotate; - mPixelBuf = ByteBuffer.allocateDirect(mWidth * mHeight * 4); - mPixelBuf.order(ByteOrder.LITTLE_ENDIAN); - eglSetup(width, height); - makeCurrent(); - setup(); + } + mFrameAvailable = false; } + mTextureRender.checkGlError("before updateTexImage"); + mSurfaceTexture.updateTexImage(); + } - public OutputSurface() { - setup(); + public void drawImage(boolean invert) { + mTextureRender.drawFrame(mSurfaceTexture, invert); + } + + @Override + public void onFrameAvailable(SurfaceTexture st) { + synchronized (mFrameSyncObject) { + if (mFrameAvailable) { + throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); + } + mFrameAvailable = true; + mFrameSyncObject.notifyAll(); } + } - private void setup() { - mTextureRender = new TextureRenderer(rotateRender); - mTextureRender.surfaceCreated(); - mSurfaceTexture = new SurfaceTexture(mTextureRender.getTextureId()); - mSurfaceTexture.setOnFrameAvailableListener(this); - mSurface = new Surface(mSurfaceTexture); - } - - private void eglSetup(int width, int height) { - mEGL = (EGL10) EGLContext.getEGL(); - mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - - if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) { - throw new RuntimeException("unable to get EGL10 display"); - } - - if (!mEGL.eglInitialize(mEGLDisplay, null)) { - mEGLDisplay = null; - throw new RuntimeException("unable to initialize EGL10"); - } - - int[] attribList = { - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_ALPHA_SIZE, 8, - EGL10.EGL_SURFACE_TYPE, EGL10.EGL_PBUFFER_BIT, - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_NONE - }; - EGLConfig[] configs = new EGLConfig[1]; - int[] numConfigs = new int[1]; - if (!mEGL.eglChooseConfig(mEGLDisplay, attribList, configs, configs.length, numConfigs)) { - throw new RuntimeException("unable to find RGB888+pbuffer EGL config"); - } - int[] attrib_list = { - EGL_CONTEXT_CLIENT_VERSION, 2, - EGL10.EGL_NONE - }; - mEGLContext = mEGL.eglCreateContext(mEGLDisplay, configs[0], EGL10.EGL_NO_CONTEXT, attrib_list); - checkEglError("eglCreateContext"); - if (mEGLContext == null) { - throw new RuntimeException("null context"); - } - int[] surfaceAttribs = { - EGL10.EGL_WIDTH, width, - EGL10.EGL_HEIGHT, height, - EGL10.EGL_NONE - }; - mEGLSurface = mEGL.eglCreatePbufferSurface(mEGLDisplay, configs[0], surfaceAttribs); - checkEglError("eglCreatePbufferSurface"); - if (mEGLSurface == null) { - throw new RuntimeException("surface was null"); - } - } - - public void release() { - if (mEGL != null) { - if (mEGL.eglGetCurrentContext().equals(mEGLContext)) { - mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); - } - mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface); - mEGL.eglDestroyContext(mEGLDisplay, mEGLContext); - } - mSurface.release(); - mEGLDisplay = null; - mEGLContext = null; - mEGLSurface = null; - mEGL = null; - mTextureRender = null; - mSurface = null; - mSurfaceTexture = null; - } - - public void makeCurrent() { - if (mEGL == null) { - throw new RuntimeException("not configured for makeCurrent"); - } - checkEglError("before makeCurrent"); - if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) { - throw new RuntimeException("eglMakeCurrent failed"); - } - } - - public Surface getSurface() { - return mSurface; - } - - public void changeFragmentShader(String fragmentShader) { - mTextureRender.changeFragmentShader(fragmentShader); - } - - public void awaitNewImage() { - final int TIMEOUT_MS = 2500; - synchronized (mFrameSyncObject) { - while (!mFrameAvailable) { - try { - mFrameSyncObject.wait(TIMEOUT_MS); - if (!mFrameAvailable) { - throw new RuntimeException("Surface frame wait timed out"); - } - } catch (InterruptedException ie) { - throw new RuntimeException(ie); - } - } - mFrameAvailable = false; - } - mTextureRender.checkGlError("before updateTexImage"); - mSurfaceTexture.updateTexImage(); - } - - public void drawImage(boolean invert) { - mTextureRender.drawFrame(mSurfaceTexture, invert); - } - - @Override - public void onFrameAvailable(SurfaceTexture st) { - synchronized (mFrameSyncObject) { - if (mFrameAvailable) { - throw new RuntimeException("mFrameAvailable already set, frame could be dropped"); - } - mFrameAvailable = true; - mFrameSyncObject.notifyAll(); - } - } - - public ByteBuffer getFrame() { - mPixelBuf.rewind(); - GLES20.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf); - return mPixelBuf; - } - - private void checkEglError(String msg) { - if (mEGL.eglGetError() != EGL10.EGL_SUCCESS) { - throw new RuntimeException("EGL error encountered (see log)"); - } + public ByteBuffer getFrame() { + mPixelBuf.rewind(); + GLES20.glReadPixels(0, 0, mWidth, mHeight, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, mPixelBuf); + return mPixelBuf; + } + + private void checkEglError(String msg) { + if (mEGL.eglGetError() != EGL10.EGL_SUCCESS) { + throw new RuntimeException("EGL error encountered (see log)"); } + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/Sample.java b/src/main/java/org/thoughtcrime/securesms/video/recode/Sample.java index 9b1b3da1d..8ec136701 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/Sample.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/Sample.java @@ -1,19 +1,19 @@ package org.thoughtcrime.securesms.video.recode; public class Sample { - private long offset = 0; - private long size = 0; + private long offset = 0; + private long size = 0; - public Sample(long offset, long size) { - this.offset = offset; - this.size = size; - } + public Sample(long offset, long size) { + this.offset = offset; + this.size = size; + } - public long getOffset() { - return offset; - } + public long getOffset() { + return offset; + } - public long getSize() { - return size; - } + public long getSize() { + return size; + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/TextureRenderer.java b/src/main/java/org/thoughtcrime/securesms/video/recode/TextureRenderer.java index 03c98dc6a..b0e3567da 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/TextureRenderer.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/TextureRenderer.java @@ -21,7 +21,6 @@ import android.graphics.SurfaceTexture; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.opengl.Matrix; - import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.FloatBuffer; @@ -29,185 +28,204 @@ import java.nio.FloatBuffer; @TargetApi(16) public class TextureRenderer { - private static final int FLOAT_SIZE_BYTES = 4; - private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; - private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; - private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; - private static final float[] mTriangleVerticesData = { - -1.0f, -1.0f, 0, 0.f, 0.f, - 1.0f, -1.0f, 0, 1.f, 0.f, - -1.0f, 1.0f, 0, 0.f, 1.f, - 1.0f, 1.0f, 0, 1.f, 1.f, - }; - private final FloatBuffer mTriangleVertices; + private static final int FLOAT_SIZE_BYTES = 4; + private static final int TRIANGLE_VERTICES_DATA_STRIDE_BYTES = 5 * FLOAT_SIZE_BYTES; + private static final int TRIANGLE_VERTICES_DATA_POS_OFFSET = 0; + private static final int TRIANGLE_VERTICES_DATA_UV_OFFSET = 3; + private static final float[] mTriangleVerticesData = { + -1.0f, -1.0f, 0, 0.f, 0.f, + 1.0f, -1.0f, 0, 1.f, 0.f, + -1.0f, 1.0f, 0, 0.f, 1.f, + 1.0f, 1.0f, 0, 1.f, 1.f, + }; + private final FloatBuffer mTriangleVertices; - private static final String VERTEX_SHADER = - "uniform mat4 uMVPMatrix;\n" + - "uniform mat4 uSTMatrix;\n" + - "attribute vec4 aPosition;\n" + - "attribute vec4 aTextureCoord;\n" + - "varying vec2 vTextureCoord;\n" + - "void main() {\n" + - " gl_Position = uMVPMatrix * aPosition;\n" + - " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + - "}\n"; + private static final String VERTEX_SHADER = + "uniform mat4 uMVPMatrix;\n" + + "uniform mat4 uSTMatrix;\n" + + "attribute vec4 aPosition;\n" + + "attribute vec4 aTextureCoord;\n" + + "varying vec2 vTextureCoord;\n" + + "void main() {\n" + + " gl_Position = uMVPMatrix * aPosition;\n" + + " vTextureCoord = (uSTMatrix * aTextureCoord).xy;\n" + + "}\n"; - private static final String FRAGMENT_SHADER = - "#extension GL_OES_EGL_image_external : require\n" + - "precision mediump float;\n" + - "varying vec2 vTextureCoord;\n" + - "uniform samplerExternalOES sTexture;\n" + - "void main() {\n" + - " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + - "}\n"; + private static final String FRAGMENT_SHADER = + "#extension GL_OES_EGL_image_external : require\n" + + "precision mediump float;\n" + + "varying vec2 vTextureCoord;\n" + + "uniform samplerExternalOES sTexture;\n" + + "void main() {\n" + + " gl_FragColor = texture2D(sTexture, vTextureCoord);\n" + + "}\n"; - private final float[] mMVPMatrix = new float[16]; - private final float[] mSTMatrix = new float[16]; - private int mProgram; - private int mTextureID = -12345; - private int muMVPMatrixHandle; - private int muSTMatrixHandle; - private int maPositionHandle; - private int maTextureHandle; - private int rotationAngle = 0; + private final float[] mMVPMatrix = new float[16]; + private final float[] mSTMatrix = new float[16]; + private int mProgram; + private int mTextureID = -12345; + private int muMVPMatrixHandle; + private int muSTMatrixHandle; + private int maPositionHandle; + private int maTextureHandle; + private int rotationAngle = 0; - public TextureRenderer(int rotation) { - rotationAngle = rotation; - mTriangleVertices = ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES).order(ByteOrder.nativeOrder()).asFloatBuffer(); - mTriangleVertices.put(mTriangleVerticesData).position(0); - Matrix.setIdentityM(mSTMatrix, 0); + public TextureRenderer(int rotation) { + rotationAngle = rotation; + mTriangleVertices = + ByteBuffer.allocateDirect(mTriangleVerticesData.length * FLOAT_SIZE_BYTES) + .order(ByteOrder.nativeOrder()) + .asFloatBuffer(); + mTriangleVertices.put(mTriangleVerticesData).position(0); + Matrix.setIdentityM(mSTMatrix, 0); + } + + public int getTextureId() { + return mTextureID; + } + + public void drawFrame(SurfaceTexture st, boolean invert) { + checkGlError("onDrawFrame start"); + st.getTransformMatrix(mSTMatrix); + + if (invert) { + mSTMatrix[5] = -mSTMatrix[5]; + mSTMatrix[13] = 1.0f - mSTMatrix[13]; } - public int getTextureId() { - return mTextureID; + GLES20.glUseProgram(mProgram); + checkGlError("glUseProgram"); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); + GLES20.glVertexAttribPointer( + maPositionHandle, + 3, + GLES20.GL_FLOAT, + false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, + mTriangleVertices); + checkGlError("glVertexAttribPointer maPosition"); + GLES20.glEnableVertexAttribArray(maPositionHandle); + checkGlError("glEnableVertexAttribArray maPositionHandle"); + mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); + GLES20.glVertexAttribPointer( + maTextureHandle, + 2, + GLES20.GL_FLOAT, + false, + TRIANGLE_VERTICES_DATA_STRIDE_BYTES, + mTriangleVertices); + checkGlError("glVertexAttribPointer maTextureHandle"); + GLES20.glEnableVertexAttribArray(maTextureHandle); + checkGlError("glEnableVertexAttribArray maTextureHandle"); + GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); + GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); + checkGlError("glDrawArrays"); + GLES20.glFinish(); + } + + public void surfaceCreated() { + mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); } - - public void drawFrame(SurfaceTexture st, boolean invert) { - checkGlError("onDrawFrame start"); - st.getTransformMatrix(mSTMatrix); - - if (invert) { - mSTMatrix[5] = -mSTMatrix[5]; - mSTMatrix[13] = 1.0f - mSTMatrix[13]; - } - - GLES20.glUseProgram(mProgram); - checkGlError("glUseProgram"); - GLES20.glActiveTexture(GLES20.GL_TEXTURE0); - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); - mTriangleVertices.position(TRIANGLE_VERTICES_DATA_POS_OFFSET); - GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); - checkGlError("glVertexAttribPointer maPosition"); - GLES20.glEnableVertexAttribArray(maPositionHandle); - checkGlError("glEnableVertexAttribArray maPositionHandle"); - mTriangleVertices.position(TRIANGLE_VERTICES_DATA_UV_OFFSET); - GLES20.glVertexAttribPointer(maTextureHandle, 2, GLES20.GL_FLOAT, false, TRIANGLE_VERTICES_DATA_STRIDE_BYTES, mTriangleVertices); - checkGlError("glVertexAttribPointer maTextureHandle"); - GLES20.glEnableVertexAttribArray(maTextureHandle); - checkGlError("glEnableVertexAttribArray maTextureHandle"); - GLES20.glUniformMatrix4fv(muSTMatrixHandle, 1, false, mSTMatrix, 0); - GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mMVPMatrix, 0); - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - checkGlError("glDrawArrays"); - GLES20.glFinish(); + maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); + checkGlError("glGetAttribLocation aPosition"); + if (maPositionHandle == -1) { + throw new RuntimeException("Could not get attrib location for aPosition"); } - - public void surfaceCreated() { - mProgram = createProgram(VERTEX_SHADER, FRAGMENT_SHADER); - if (mProgram == 0) { - throw new RuntimeException("failed creating program"); - } - maPositionHandle = GLES20.glGetAttribLocation(mProgram, "aPosition"); - checkGlError("glGetAttribLocation aPosition"); - if (maPositionHandle == -1) { - throw new RuntimeException("Could not get attrib location for aPosition"); - } - maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); - checkGlError("glGetAttribLocation aTextureCoord"); - if (maTextureHandle == -1) { - throw new RuntimeException("Could not get attrib location for aTextureCoord"); - } - muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); - checkGlError("glGetUniformLocation uMVPMatrix"); - if (muMVPMatrixHandle == -1) { - throw new RuntimeException("Could not get attrib location for uMVPMatrix"); - } - muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); - checkGlError("glGetUniformLocation uSTMatrix"); - if (muSTMatrixHandle == -1) { - throw new RuntimeException("Could not get attrib location for uSTMatrix"); - } - int[] textures = new int[1]; - GLES20.glGenTextures(1, textures, 0); - mTextureID = textures[0]; - GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); - checkGlError("glBindTexture mTextureID"); - GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); - GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); - GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); - checkGlError("glTexParameter"); - - Matrix.setIdentityM(mMVPMatrix, 0); - if (rotationAngle != 0) { - Matrix.rotateM(mMVPMatrix, 0, rotationAngle, 0, 0, 1); - } + maTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTextureCoord"); + checkGlError("glGetAttribLocation aTextureCoord"); + if (maTextureHandle == -1) { + throw new RuntimeException("Could not get attrib location for aTextureCoord"); } - - public void changeFragmentShader(String fragmentShader) { - GLES20.glDeleteProgram(mProgram); - mProgram = createProgram(VERTEX_SHADER, fragmentShader); - if (mProgram == 0) { - throw new RuntimeException("failed creating program"); - } + muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix"); + checkGlError("glGetUniformLocation uMVPMatrix"); + if (muMVPMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uMVPMatrix"); } - - private int loadShader(int shaderType, String source) { - int shader = GLES20.glCreateShader(shaderType); - checkGlError("glCreateShader type=" + shaderType); - GLES20.glShaderSource(shader, source); - GLES20.glCompileShader(shader); - int[] compiled = new int[1]; - GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); - if (compiled[0] == 0) { - GLES20.glDeleteShader(shader); - shader = 0; - } - return shader; + muSTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uSTMatrix"); + checkGlError("glGetUniformLocation uSTMatrix"); + if (muSTMatrixHandle == -1) { + throw new RuntimeException("Could not get attrib location for uSTMatrix"); } + int[] textures = new int[1]; + GLES20.glGenTextures(1, textures, 0); + mTextureID = textures[0]; + GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mTextureID); + checkGlError("glBindTexture mTextureID"); + GLES20.glTexParameterf( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); + GLES20.glTexParameterf( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR); + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameteri( + GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE); + checkGlError("glTexParameter"); - private int createProgram(String vertexSource, String fragmentSource) { - int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); - if (vertexShader == 0) { - return 0; - } - int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); - if (pixelShader == 0) { - return 0; - } - int program = GLES20.glCreateProgram(); - checkGlError("glCreateProgram"); - if (program == 0) { - return 0; - } - GLES20.glAttachShader(program, vertexShader); - checkGlError("glAttachShader"); - GLES20.glAttachShader(program, pixelShader); - checkGlError("glAttachShader"); - GLES20.glLinkProgram(program); - int[] linkStatus = new int[1]; - GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); - if (linkStatus[0] != GLES20.GL_TRUE) { - GLES20.glDeleteProgram(program); - program = 0; - } - return program; + Matrix.setIdentityM(mMVPMatrix, 0); + if (rotationAngle != 0) { + Matrix.rotateM(mMVPMatrix, 0, rotationAngle, 0, 0, 1); } + } - public void checkGlError(String op) { - int error; - if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { - throw new RuntimeException(op + ": glError " + error); - } + public void changeFragmentShader(String fragmentShader) { + GLES20.glDeleteProgram(mProgram); + mProgram = createProgram(VERTEX_SHADER, fragmentShader); + if (mProgram == 0) { + throw new RuntimeException("failed creating program"); } + } + + private int loadShader(int shaderType, String source) { + int shader = GLES20.glCreateShader(shaderType); + checkGlError("glCreateShader type=" + shaderType); + GLES20.glShaderSource(shader, source); + GLES20.glCompileShader(shader); + int[] compiled = new int[1]; + GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); + if (compiled[0] == 0) { + GLES20.glDeleteShader(shader); + shader = 0; + } + return shader; + } + + private int createProgram(String vertexSource, String fragmentSource) { + int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); + if (vertexShader == 0) { + return 0; + } + int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); + if (pixelShader == 0) { + return 0; + } + int program = GLES20.glCreateProgram(); + checkGlError("glCreateProgram"); + if (program == 0) { + return 0; + } + GLES20.glAttachShader(program, vertexShader); + checkGlError("glAttachShader"); + GLES20.glAttachShader(program, pixelShader); + checkGlError("glAttachShader"); + GLES20.glLinkProgram(program); + int[] linkStatus = new int[1]; + GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); + if (linkStatus[0] != GLES20.GL_TRUE) { + GLES20.glDeleteProgram(program); + program = 0; + } + return program; + } + + public void checkGlError(String op) { + int error; + if ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) { + throw new RuntimeException(op + ": glError " + error); + } + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/Track.java b/src/main/java/org/thoughtcrime/securesms/video/recode/Track.java index 523208690..d3c0db584 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/Track.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/Track.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.video.recode; import android.annotation.TargetApi; import android.media.MediaCodec; import android.media.MediaFormat; - import com.coremedia.iso.boxes.AbstractMediaHeaderBox; import com.coremedia.iso.boxes.SampleDescriptionBox; import com.coremedia.iso.boxes.SoundMediaHeaderBox; @@ -16,7 +15,6 @@ import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.DecoderConfigDescrip import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.ESDescriptor; import com.googlecode.mp4parser.boxes.mp4.objectdescriptors.SLConfigDescriptor; import com.mp4parser.iso14496.part15.AvcConfigurationBox; - import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Date; @@ -26,231 +24,232 @@ import java.util.Map; @TargetApi(16) public class Track { - private long trackId = 0; - private final ArrayList samples = new ArrayList<>(); - private long duration = 0; - private String handler; - private AbstractMediaHeaderBox headerBox = null; - private SampleDescriptionBox sampleDescriptionBox = null; - private LinkedList syncSamples = null; - private int timeScale; - private final Date creationTime = new Date(); - private int height; - private int width; - private float volume = 0; - private final ArrayList sampleDurations = new ArrayList<>(); - private boolean isAudio = false; - private static final Map samplingFrequencyIndexMap = new HashMap<>(); - private long lastPresentationTimeUs = 0; - private boolean first = true; + private long trackId = 0; + private final ArrayList samples = new ArrayList<>(); + private long duration = 0; + private String handler; + private AbstractMediaHeaderBox headerBox = null; + private SampleDescriptionBox sampleDescriptionBox = null; + private LinkedList syncSamples = null; + private int timeScale; + private final Date creationTime = new Date(); + private int height; + private int width; + private float volume = 0; + private final ArrayList sampleDurations = new ArrayList<>(); + private boolean isAudio = false; + private static final Map samplingFrequencyIndexMap = new HashMap<>(); + private long lastPresentationTimeUs = 0; + private boolean first = true; - static { - samplingFrequencyIndexMap.put(96000, 0x0); - samplingFrequencyIndexMap.put(88200, 0x1); - samplingFrequencyIndexMap.put(64000, 0x2); - samplingFrequencyIndexMap.put(48000, 0x3); - samplingFrequencyIndexMap.put(44100, 0x4); - samplingFrequencyIndexMap.put(32000, 0x5); - samplingFrequencyIndexMap.put(24000, 0x6); - samplingFrequencyIndexMap.put(22050, 0x7); - samplingFrequencyIndexMap.put(16000, 0x8); - samplingFrequencyIndexMap.put(12000, 0x9); - samplingFrequencyIndexMap.put(11025, 0xa); - samplingFrequencyIndexMap.put(8000, 0xb); - } + static { + samplingFrequencyIndexMap.put(96000, 0x0); + samplingFrequencyIndexMap.put(88200, 0x1); + samplingFrequencyIndexMap.put(64000, 0x2); + samplingFrequencyIndexMap.put(48000, 0x3); + samplingFrequencyIndexMap.put(44100, 0x4); + samplingFrequencyIndexMap.put(32000, 0x5); + samplingFrequencyIndexMap.put(24000, 0x6); + samplingFrequencyIndexMap.put(22050, 0x7); + samplingFrequencyIndexMap.put(16000, 0x8); + samplingFrequencyIndexMap.put(12000, 0x9); + samplingFrequencyIndexMap.put(11025, 0xa); + samplingFrequencyIndexMap.put(8000, 0xb); + } - public Track(int id, MediaFormat format, boolean audio) throws Exception { - trackId = id; - isAudio = audio; - if (!isAudio) { - sampleDurations.add((long) 3015); - duration = 3015; - width = format.getInteger(MediaFormat.KEY_WIDTH); - height = format.getInteger(MediaFormat.KEY_HEIGHT); - timeScale = 90000; - syncSamples = new LinkedList<>(); - handler = "vide"; - headerBox = new VideoMediaHeaderBox(); - sampleDescriptionBox = new SampleDescriptionBox(); - String mime = format.getString(MediaFormat.KEY_MIME); - if (mime.equals("video/avc")) { - VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1"); - visualSampleEntry.setDataReferenceIndex(1); - visualSampleEntry.setDepth(24); - visualSampleEntry.setFrameCount(1); - visualSampleEntry.setHorizresolution(72); - visualSampleEntry.setVertresolution(72); - visualSampleEntry.setWidth(width); - visualSampleEntry.setHeight(height); + public Track(int id, MediaFormat format, boolean audio) throws Exception { + trackId = id; + isAudio = audio; + if (!isAudio) { + sampleDurations.add((long) 3015); + duration = 3015; + width = format.getInteger(MediaFormat.KEY_WIDTH); + height = format.getInteger(MediaFormat.KEY_HEIGHT); + timeScale = 90000; + syncSamples = new LinkedList<>(); + handler = "vide"; + headerBox = new VideoMediaHeaderBox(); + sampleDescriptionBox = new SampleDescriptionBox(); + String mime = format.getString(MediaFormat.KEY_MIME); + if (mime.equals("video/avc")) { + VisualSampleEntry visualSampleEntry = new VisualSampleEntry("avc1"); + visualSampleEntry.setDataReferenceIndex(1); + visualSampleEntry.setDepth(24); + visualSampleEntry.setFrameCount(1); + visualSampleEntry.setHorizresolution(72); + visualSampleEntry.setVertresolution(72); + visualSampleEntry.setWidth(width); + visualSampleEntry.setHeight(height); - AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox(); + AvcConfigurationBox avcConfigurationBox = new AvcConfigurationBox(); - if (format.getByteBuffer("csd-0") != null) { - ArrayList spsArray = new ArrayList<>(); - ByteBuffer spsBuff = format.getByteBuffer("csd-0"); - spsBuff.position(4); - byte[] spsBytes = new byte[spsBuff.remaining()]; - spsBuff.get(spsBytes); - spsArray.add(spsBytes); + if (format.getByteBuffer("csd-0") != null) { + ArrayList spsArray = new ArrayList<>(); + ByteBuffer spsBuff = format.getByteBuffer("csd-0"); + spsBuff.position(4); + byte[] spsBytes = new byte[spsBuff.remaining()]; + spsBuff.get(spsBytes); + spsArray.add(spsBytes); - ArrayList ppsArray = new ArrayList<>(); - ByteBuffer ppsBuff = format.getByteBuffer("csd-1"); - ppsBuff.position(4); - byte[] ppsBytes = new byte[ppsBuff.remaining()]; - ppsBuff.get(ppsBytes); - ppsArray.add(ppsBytes); - avcConfigurationBox.setSequenceParameterSets(spsArray); - avcConfigurationBox.setPictureParameterSets(ppsArray); - } - - avcConfigurationBox.setAvcLevelIndication(13); - avcConfigurationBox.setAvcProfileIndication(100); - avcConfigurationBox.setBitDepthLumaMinus8(-1); - avcConfigurationBox.setBitDepthChromaMinus8(-1); - avcConfigurationBox.setChromaFormat(-1); - avcConfigurationBox.setConfigurationVersion(1); - avcConfigurationBox.setLengthSizeMinusOne(3); - avcConfigurationBox.setProfileCompatibility(0); - - visualSampleEntry.addBox(avcConfigurationBox); - sampleDescriptionBox.addBox(visualSampleEntry); - } else if (mime.equals("video/mp4v")) { - VisualSampleEntry visualSampleEntry = new VisualSampleEntry("mp4v"); - visualSampleEntry.setDataReferenceIndex(1); - visualSampleEntry.setDepth(24); - visualSampleEntry.setFrameCount(1); - visualSampleEntry.setHorizresolution(72); - visualSampleEntry.setVertresolution(72); - visualSampleEntry.setWidth(width); - visualSampleEntry.setHeight(height); - - sampleDescriptionBox.addBox(visualSampleEntry); - } - } else { - sampleDurations.add((long) 1024); - duration = 1024; - volume = 1; - timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); - handler = "soun"; - headerBox = new SoundMediaHeaderBox(); - sampleDescriptionBox = new SampleDescriptionBox(); - AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a"); - audioSampleEntry.setChannelCount(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); - audioSampleEntry.setSampleRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE)); - audioSampleEntry.setDataReferenceIndex(1); - audioSampleEntry.setSampleSize(16); - - ESDescriptorBox esds = new ESDescriptorBox(); - ESDescriptor descriptor = new ESDescriptor(); - descriptor.setEsId(0); - - SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor(); - slConfigDescriptor.setPredefined(2); - descriptor.setSlConfigDescriptor(slConfigDescriptor); - - DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor(); - decoderConfigDescriptor.setObjectTypeIndication(0x40); - decoderConfigDescriptor.setStreamType(5); - decoderConfigDescriptor.setBufferSizeDB(1536); - decoderConfigDescriptor.setMaxBitRate(96000); - decoderConfigDescriptor.setAvgBitRate(96000); - - AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig(); - audioSpecificConfig.setAudioObjectType(2); - audioSpecificConfig.setSamplingFrequencyIndex(samplingFrequencyIndexMap.get((int) audioSampleEntry.getSampleRate())); - audioSpecificConfig.setChannelConfiguration(audioSampleEntry.getChannelCount()); - decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig); - - descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor); - - ByteBuffer data = descriptor.serialize(); - esds.setEsDescriptor(descriptor); - esds.setData(data); - audioSampleEntry.addBox(esds); - sampleDescriptionBox.addBox(audioSampleEntry); - } - } - - public long getTrackId() { - return trackId; - } - - public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) { - long delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs; - if (delta < 0) { - return; - } - boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; - samples.add(new Sample(offset, bufferInfo.size)); - if (syncSamples != null && isSyncFrame) { - syncSamples.add(samples.size()); + ArrayList ppsArray = new ArrayList<>(); + ByteBuffer ppsBuff = format.getByteBuffer("csd-1"); + ppsBuff.position(4); + byte[] ppsBytes = new byte[ppsBuff.remaining()]; + ppsBuff.get(ppsBytes); + ppsArray.add(ppsBytes); + avcConfigurationBox.setSequenceParameterSets(spsArray); + avcConfigurationBox.setPictureParameterSets(ppsArray); } - delta = (delta * timeScale + 500000L) / 1000000L; - lastPresentationTimeUs = bufferInfo.presentationTimeUs; - if (!first) { - sampleDurations.add(sampleDurations.size() - 1, delta); - duration += delta; - } - first = false; + avcConfigurationBox.setAvcLevelIndication(13); + avcConfigurationBox.setAvcProfileIndication(100); + avcConfigurationBox.setBitDepthLumaMinus8(-1); + avcConfigurationBox.setBitDepthChromaMinus8(-1); + avcConfigurationBox.setChromaFormat(-1); + avcConfigurationBox.setConfigurationVersion(1); + avcConfigurationBox.setLengthSizeMinusOne(3); + avcConfigurationBox.setProfileCompatibility(0); + + visualSampleEntry.addBox(avcConfigurationBox); + sampleDescriptionBox.addBox(visualSampleEntry); + } else if (mime.equals("video/mp4v")) { + VisualSampleEntry visualSampleEntry = new VisualSampleEntry("mp4v"); + visualSampleEntry.setDataReferenceIndex(1); + visualSampleEntry.setDepth(24); + visualSampleEntry.setFrameCount(1); + visualSampleEntry.setHorizresolution(72); + visualSampleEntry.setVertresolution(72); + visualSampleEntry.setWidth(width); + visualSampleEntry.setHeight(height); + + sampleDescriptionBox.addBox(visualSampleEntry); + } + } else { + sampleDurations.add((long) 1024); + duration = 1024; + volume = 1; + timeScale = format.getInteger(MediaFormat.KEY_SAMPLE_RATE); + handler = "soun"; + headerBox = new SoundMediaHeaderBox(); + sampleDescriptionBox = new SampleDescriptionBox(); + AudioSampleEntry audioSampleEntry = new AudioSampleEntry("mp4a"); + audioSampleEntry.setChannelCount(format.getInteger(MediaFormat.KEY_CHANNEL_COUNT)); + audioSampleEntry.setSampleRate(format.getInteger(MediaFormat.KEY_SAMPLE_RATE)); + audioSampleEntry.setDataReferenceIndex(1); + audioSampleEntry.setSampleSize(16); + + ESDescriptorBox esds = new ESDescriptorBox(); + ESDescriptor descriptor = new ESDescriptor(); + descriptor.setEsId(0); + + SLConfigDescriptor slConfigDescriptor = new SLConfigDescriptor(); + slConfigDescriptor.setPredefined(2); + descriptor.setSlConfigDescriptor(slConfigDescriptor); + + DecoderConfigDescriptor decoderConfigDescriptor = new DecoderConfigDescriptor(); + decoderConfigDescriptor.setObjectTypeIndication(0x40); + decoderConfigDescriptor.setStreamType(5); + decoderConfigDescriptor.setBufferSizeDB(1536); + decoderConfigDescriptor.setMaxBitRate(96000); + decoderConfigDescriptor.setAvgBitRate(96000); + + AudioSpecificConfig audioSpecificConfig = new AudioSpecificConfig(); + audioSpecificConfig.setAudioObjectType(2); + audioSpecificConfig.setSamplingFrequencyIndex( + samplingFrequencyIndexMap.get((int) audioSampleEntry.getSampleRate())); + audioSpecificConfig.setChannelConfiguration(audioSampleEntry.getChannelCount()); + decoderConfigDescriptor.setAudioSpecificInfo(audioSpecificConfig); + + descriptor.setDecoderConfigDescriptor(decoderConfigDescriptor); + + ByteBuffer data = descriptor.serialize(); + esds.setEsDescriptor(descriptor); + esds.setData(data); + audioSampleEntry.addBox(esds); + sampleDescriptionBox.addBox(audioSampleEntry); + } + } + + public long getTrackId() { + return trackId; + } + + public void addSample(long offset, MediaCodec.BufferInfo bufferInfo) { + long delta = bufferInfo.presentationTimeUs - lastPresentationTimeUs; + if (delta < 0) { + return; + } + boolean isSyncFrame = !isAudio && (bufferInfo.flags & MediaCodec.BUFFER_FLAG_SYNC_FRAME) != 0; + samples.add(new Sample(offset, bufferInfo.size)); + if (syncSamples != null && isSyncFrame) { + syncSamples.add(samples.size()); } - public ArrayList getSamples() { - return samples; + delta = (delta * timeScale + 500000L) / 1000000L; + lastPresentationTimeUs = bufferInfo.presentationTimeUs; + if (!first) { + sampleDurations.add(sampleDurations.size() - 1, delta); + duration += delta; } + first = false; + } - public long getDuration() { - return duration; - } + public ArrayList getSamples() { + return samples; + } - public String getHandler() { - return handler; - } + public long getDuration() { + return duration; + } - public AbstractMediaHeaderBox getMediaHeaderBox() { - return headerBox; - } + public String getHandler() { + return handler; + } - public SampleDescriptionBox getSampleDescriptionBox() { - return sampleDescriptionBox; - } + public AbstractMediaHeaderBox getMediaHeaderBox() { + return headerBox; + } - public long[] getSyncSamples() { - if (syncSamples == null || syncSamples.isEmpty()) { - return null; - } - long[] returns = new long[syncSamples.size()]; - for (int i = 0; i < syncSamples.size(); i++) { - returns[i] = syncSamples.get(i); - } - return returns; - } + public SampleDescriptionBox getSampleDescriptionBox() { + return sampleDescriptionBox; + } - public int getTimeScale() { - return timeScale; + public long[] getSyncSamples() { + if (syncSamples == null || syncSamples.isEmpty()) { + return null; } + long[] returns = new long[syncSamples.size()]; + for (int i = 0; i < syncSamples.size(); i++) { + returns[i] = syncSamples.get(i); + } + return returns; + } - public Date getCreationTime() { - return creationTime; - } + public int getTimeScale() { + return timeScale; + } - public int getWidth() { - return width; - } + public Date getCreationTime() { + return creationTime; + } - public int getHeight() { - return height; - } + public int getWidth() { + return width; + } - public float getVolume() { - return volume; - } + public int getHeight() { + return height; + } - public ArrayList getSampleDurations() { - return sampleDurations; - } + public float getVolume() { + return volume; + } - public boolean isAudio() { - return isAudio; - } + public ArrayList getSampleDurations() { + return sampleDurations; + } + + public boolean isAudio() { + return isAudio; + } } diff --git a/src/main/java/org/thoughtcrime/securesms/video/recode/VideoRecoder.java b/src/main/java/org/thoughtcrime/securesms/video/recode/VideoRecoder.java index a8b1cbd65..a2c8a1792 100644 --- a/src/main/java/org/thoughtcrime/securesms/video/recode/VideoRecoder.java +++ b/src/main/java/org/thoughtcrime/securesms/video/recode/VideoRecoder.java @@ -6,9 +6,7 @@ import android.media.MediaCodecInfo; import android.media.MediaExtractor; import android.media.MediaFormat; import android.util.Log; - import androidx.appcompat.app.AlertDialog; - import com.b44t.messenger.DcMsg; import com.coremedia.iso.IsoFile; import com.coremedia.iso.boxes.Box; @@ -19,20 +17,18 @@ import com.coremedia.iso.boxes.TrackBox; import com.coremedia.iso.boxes.TrackHeaderBox; import com.googlecode.mp4parser.util.Matrix; import com.googlecode.mp4parser.util.Path; - -import org.thoughtcrime.securesms.connect.DcHelper; -import org.thoughtcrime.securesms.util.Prefs; -import org.thoughtcrime.securesms.util.Util; - import java.io.File; import java.nio.ByteBuffer; import java.util.List; +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.util.Prefs; +import org.thoughtcrime.securesms.util.Util; public class VideoRecoder { private static final String TAG = VideoRecoder.class.getSimpleName(); - private final static String MIME_TYPE = "video/avc"; + private static final String MIME_TYPE = "video/avc"; private final boolean cancelCurrentVideoConversion = false; private final Object videoConvertSync = new Object(); @@ -64,7 +60,15 @@ public class VideoRecoder { return -5; } - private long readAndWriteTrack(MediaExtractor extractor, MP4Builder mediaMuxer, MediaCodec.BufferInfo info, long start, long end, File file, boolean isAudio) throws Exception { + private long readAndWriteTrack( + MediaExtractor extractor, + MP4Builder mediaMuxer, + MediaCodec.BufferInfo info, + long start, + long end, + File file, + boolean isAudio) + throws Exception { int trackIndex = selectTrack(extractor, isAudio); if (trackIndex >= 0) { extractor.selectTrack(trackIndex); @@ -106,7 +110,7 @@ public class VideoRecoder { info.offset = 0; info.flags = extractor.getSampleFlags(); if (mediaMuxer.writeSampleData(muxerTrackIndex, buffer, info, isAudio)) { - //didWriteData(messageObject, file, false, false); + // didWriteData(messageObject, file, false, false); } } lastTimestamp = info.presentationTimeUs; @@ -166,7 +170,7 @@ public class VideoRecoder { File inputFile = new File(videoEditedInfo.originalPath); if (!inputFile.canRead()) { - //didWriteData(messageObject, cacheFile, true, true); + // didWriteData(messageObject, cacheFile, true, true); Log.w(TAG, "Could not read video file to be recoded"); return false; } @@ -192,7 +196,10 @@ public class VideoRecoder { checkConversionCanceled(); - if (resultVideoBitrate= 0) { @@ -210,7 +217,7 @@ public class VideoRecoder { int colorFormat; colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface; - //Log.i("DeltaChat", "colorFormat = " + colorFormat); + // Log.i("DeltaChat", "colorFormat = " + colorFormat); extractor.selectTrack(videoIndex); if (startTime > 0) { @@ -220,9 +227,11 @@ public class VideoRecoder { } MediaFormat inputFormat = extractor.getTrackFormat(videoIndex); - MediaFormat outputFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight); + MediaFormat outputFormat = + MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight); outputFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat); - outputFormat.setInteger(MediaFormat.KEY_BIT_RATE, resultVideoBitrate != 0 ? resultVideoBitrate : 921600); + outputFormat.setInteger( + MediaFormat.KEY_BIT_RATE, resultVideoBitrate != 0 ? resultVideoBitrate : 921600); outputFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 25); outputFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10); @@ -255,10 +264,12 @@ public class VideoRecoder { inputBuf = decoder.getInputBuffer(inputBufIndex); int chunkSize = extractor.readSampleData(inputBuf, 0); if (chunkSize < 0) { - decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + decoder.queueInputBuffer( + inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); inputDone = true; } else { - decoder.queueInputBuffer(inputBufIndex, 0, chunkSize, extractor.getSampleTime(), 0); + decoder.queueInputBuffer( + inputBufIndex, 0, chunkSize, extractor.getSampleTime(), 0); extractor.advance(); } } @@ -268,7 +279,8 @@ public class VideoRecoder { if (eof) { int inputBufIndex = decoder.dequeueInputBuffer(TIMEOUT_USEC); if (inputBufIndex >= 0) { - decoder.queueInputBuffer(inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); + decoder.queueInputBuffer( + inputBufIndex, 0, 0, 0L, MediaCodec.BUFFER_FLAG_END_OF_STREAM); inputDone = true; } } @@ -288,17 +300,19 @@ public class VideoRecoder { videoTrackIndex = mediaMuxer.addTrack(newFormat, false); } } else if (encoderStatus < 0) { - throw new RuntimeException("unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); + throw new RuntimeException( + "unexpected result from encoder.dequeueOutputBuffer: " + encoderStatus); } else { ByteBuffer encodedData; encodedData = encoder.getOutputBuffer(encoderStatus); if (encodedData == null) { - throw new RuntimeException("encoderOutputBuffer " + encoderStatus + " was null"); + throw new RuntimeException( + "encoderOutputBuffer " + encoderStatus + " was null"); } if (info.size > 1) { if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) { if (mediaMuxer.writeSampleData(videoTrackIndex, encodedData, info, false)) { - //didWriteData(messageObject, cacheFile, false, false); + // didWriteData(messageObject, cacheFile, false, false); } } else if (videoTrackIndex == -5) { byte[] csd = new byte[info.size]; @@ -309,7 +323,10 @@ public class VideoRecoder { ByteBuffer pps = null; for (int a = info.size - 1; a >= 0; a--) { if (a > 3) { - if (csd[a] == 1 && csd[a - 1] == 0 && csd[a - 2] == 0 && csd[a - 3] == 0) { + if (csd[a] == 1 + && csd[a - 1] == 0 + && csd[a - 2] == 0 + && csd[a - 3] == 0) { sps = ByteBuffer.allocate(a - 3); pps = ByteBuffer.allocate(info.size - (a - 3)); sps.put(csd, 0, a - 3).position(0); @@ -321,7 +338,8 @@ public class VideoRecoder { } } - MediaFormat newFormat = MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight); + MediaFormat newFormat = + MediaFormat.createVideoFormat(MIME_TYPE, resultWidth, resultHeight); if (sps != null && pps != null) { newFormat.setByteBuffer("csd-0", sps); newFormat.setByteBuffer("csd-1", pps); @@ -344,9 +362,10 @@ public class VideoRecoder { } else if (decoderStatus == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) { MediaFormat newFormat = decoder.getOutputFormat(); - //Log.i("DeltaChat", "newFormat = " + newFormat); + // Log.i("DeltaChat", "newFormat = " + newFormat); } else if (decoderStatus < 0) { - throw new RuntimeException("unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus); + throw new RuntimeException( + "unexpected result from decoder.dequeueOutputBuffer: " + decoderStatus); } else { boolean doRender; doRender = info.size != 0; @@ -359,7 +378,8 @@ public class VideoRecoder { if (startTime > 0 && videoTime == -1) { if (info.presentationTimeUs < startTime) { doRender = false; - //Log.i("DeltaChat", "drop frame startTime = " + startTime + " present time = " + info.presentationTimeUs); + // Log.i("DeltaChat", "drop frame startTime = " + startTime + " present + // time = " + info.presentationTimeUs); } else { videoTime = info.presentationTimeUs; } @@ -381,7 +401,7 @@ public class VideoRecoder { } if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) { decoderOutputAvailable = false; - //Log.i("DeltaChat", "decoder stream end"); + // Log.i("DeltaChat", "decoder stream end"); encoder.signalEndOfInputStream(); } } @@ -392,7 +412,7 @@ public class VideoRecoder { videoStartTime = videoTime; } } catch (Exception e) { - Log.w(TAG,"Recoding video failed unexpectedly", e); + Log.w(TAG, "Recoding video failed unexpectedly", e); error = true; } @@ -416,7 +436,8 @@ public class VideoRecoder { checkConversionCanceled(); } } else { - long videoTime = readAndWriteTrack(extractor, mediaMuxer, info, startTime, endTime, cacheFile, false); + long videoTime = + readAndWriteTrack(extractor, mediaMuxer, info, startTime, endTime, cacheFile, false); if (videoTime != -1) { videoStartTime = videoTime; } @@ -425,7 +446,7 @@ public class VideoRecoder { readAndWriteTrack(extractor, mediaMuxer, info, videoStartTime, endTime, cacheFile, true); } } catch (Exception e) { - Log.w(TAG,"Recoding video failed unexpectedly/2", e); + Log.w(TAG, "Recoding video failed unexpectedly/2", e); error = true; } finally { if (extractor != null) { @@ -435,38 +456,38 @@ public class VideoRecoder { try { mediaMuxer.finishMovie(false); } catch (Exception e) { - Log.w(TAG,"Flushing video failed unexpectedly", e); + Log.w(TAG, "Flushing video failed unexpectedly", e); } } - //Log.i("DeltaChat", "time = " + (System.currentTimeMillis() - time)); + // Log.i("DeltaChat", "time = " + (System.currentTimeMillis() - time)); } } else { - //didWriteData(messageObject, cacheFile, true, true); - Log.w(TAG,"Video width or height are 0, refusing recode."); + // didWriteData(messageObject, cacheFile, true, true); + Log.w(TAG, "Video width or height are 0, refusing recode."); return false; } - //didWriteData(messageObject, cacheFile, true, error); + // didWriteData(messageObject, cacheFile, true, error); return true; } private static class VideoEditedInfo { String originalPath; - float originalDurationMs; - long originalAudioBytes; - int originalRotationValue; - int originalWidth; - int originalHeight; - int originalVideoBitrate; + float originalDurationMs; + long originalAudioBytes; + int originalRotationValue; + int originalWidth; + int originalHeight; + int originalVideoBitrate; - long startTime; - long endTime; - int rotationValue; + long startTime; + long endTime; + int rotationValue; - int resultWidth; - int resultHeight; - int resultVideoBitrate; + int resultWidth; + int resultHeight; + int resultVideoBitrate; - int estimatedBytes; + int estimatedBytes; } private static VideoEditedInfo getVideoEditInfoFromFile(String videoPath) { @@ -492,11 +513,13 @@ public class VideoRecoder { try { MediaBox mediaBox = trackBox.getMediaBox(); MediaHeaderBox mediaHeaderBox = mediaBox.getMediaHeaderBox(); - SampleSizeBox sampleSizeBox = mediaBox.getMediaInformationBox().getSampleTableBox().getSampleSizeBox(); + SampleSizeBox sampleSizeBox = + mediaBox.getMediaInformationBox().getSampleTableBox().getSampleSizeBox(); for (long size : sampleSizeBox.getSampleSizes()) { sampleSizes += size; } - float originalVideoSeconds = (float) mediaHeaderBox.getDuration() / (float) mediaHeaderBox.getTimescale(); + float originalVideoSeconds = + (float) mediaHeaderBox.getDuration() / (float) mediaHeaderBox.getTimescale(); trackBitrate = (int) (sampleSizes * 8 / originalVideoSeconds); vei.originalDurationMs = originalVideoSeconds * 1000; } catch (Exception e) { @@ -534,20 +557,22 @@ public class VideoRecoder { return vei; } - private static int calculateEstimatedSize(float timeDelta, int resultBitrate, float originalDurationMs, long originalAudioBytes) { - long videoFramesSize = (long) (resultBitrate / 8 * (originalDurationMs /1000)); + private static int calculateEstimatedSize( + float timeDelta, int resultBitrate, float originalDurationMs, long originalAudioBytes) { + long videoFramesSize = (long) (resultBitrate / 8 * (originalDurationMs / 1000)); int size = (int) ((originalAudioBytes + videoFramesSize) * timeDelta); return size; } - private static void alert(Context context, String str) - { + private static void alert(Context context, String str) { Log.e(TAG, str); - Util.runOnMain(() -> new AlertDialog.Builder(context) - .setCancelable(false) - .setMessage(str) - .setPositiveButton(android.R.string.ok, null) - .show()); + Util.runOnMain( + () -> + new AlertDialog.Builder(context) + .setCancelable(false) + .setMessage(str) + .setPositiveButton(android.R.string.ok, null) + .show()); } // prepareVideo() assumes the msg object is set up properly to being sent; @@ -555,7 +580,8 @@ public class VideoRecoder { // return: true=video might be prepared, can be sent, false=error public static boolean prepareVideo(Context context, int chatId, DcMsg msg) { final long MAX_BYTES = DcHelper.getInt(context, "sys.msgsize_max_recommended"); - final String TOO_BIG_FILE = "Video cannot be compressed to a reasonable size. Try a shorter video or a lower quality."; + final String TOO_BIG_FILE = + "Video cannot be compressed to a reasonable size. Try a shorter video or a lower quality."; try { String inPath = msg.getFile(); Log.i(TAG, "Preparing video: " + inPath); @@ -564,7 +590,7 @@ public class VideoRecoder { VideoEditedInfo vei = getVideoEditInfoFromFile(inPath); if (vei == null) { Log.w(TAG, String.format("Recoding failed for %s: cannot get info", inPath)); - if (msg.getFilebytes() > MAX_BYTES+MAX_BYTES/4) { + if (msg.getFilebytes() > MAX_BYTES + MAX_BYTES / 4) { alert(context, TOO_BIG_FILE); return false; } @@ -582,19 +608,27 @@ public class VideoRecoder { } else { msg.setDimension(vei.originalWidth, vei.originalHeight); } - msg.setDuration((int)vei.originalDurationMs); + msg.setDuration((int) vei.originalDurationMs); // check if video bitrate is already reasonable - final int MAX_KBPS = 1500000; + final int MAX_KBPS = 1500000; long inBytes = new File(inPath).length(); - if (inBytes > 0 && inBytes <= MAX_BYTES && vei.originalVideoBitrate <= MAX_KBPS*2 /*be tolerant as long the file size matches*/) { - Log.i(TAG, String.format("recoding for %s is not needed, %d bytes and %d kbps are ok", inPath, inBytes, vei.originalVideoBitrate)); + if (inBytes > 0 + && inBytes <= MAX_BYTES + && vei.originalVideoBitrate + <= MAX_KBPS * 2 /*be tolerant as long the file size matches*/) { + Log.i( + TAG, + String.format( + "recoding for %s is not needed, %d bytes and %d kbps are ok", + inPath, inBytes, vei.originalVideoBitrate)); return true; } // calculate new video bitrate, sth. between 200 kbps and 1500 kbps long resultDurationMs = (long) vei.originalDurationMs; - long maxVideoBytes = MAX_BYTES - vei.originalAudioBytes - resultDurationMs /*10 kbps codec overhead*/; + long maxVideoBytes = + MAX_BYTES - vei.originalAudioBytes - resultDurationMs /*10 kbps codec overhead*/; vei.resultVideoBitrate = (int) (maxVideoBytes / Math.max(1, resultDurationMs / 1000) * 8); if (vei.resultVideoBitrate < 200000) { @@ -615,7 +649,10 @@ public class VideoRecoder { vei.resultWidth = vei.originalWidth; vei.resultHeight = vei.originalHeight; if (vei.resultWidth > maxSide || vei.resultHeight > maxSide) { - float scale = vei.resultWidth > vei.resultHeight ? (float) maxSide / vei.resultWidth : (float) maxSide / vei.resultHeight; + float scale = + vei.resultWidth > vei.resultHeight + ? (float) maxSide / vei.resultWidth + : (float) maxSide / vei.resultHeight; vei.resultWidth *= scale; vei.resultHeight *= scale; } @@ -629,10 +666,14 @@ public class VideoRecoder { msg.setDuration((int) resultDurationMs); // calculate bytes - vei.estimatedBytes = VideoRecoder.calculateEstimatedSize((float) resultDurationMs / vei.originalDurationMs, - vei.resultVideoBitrate, vei.originalDurationMs, vei.originalAudioBytes); + vei.estimatedBytes = + VideoRecoder.calculateEstimatedSize( + (float) resultDurationMs / vei.originalDurationMs, + vei.resultVideoBitrate, + vei.originalDurationMs, + vei.originalAudioBytes); - if (vei.estimatedBytes > MAX_BYTES+MAX_BYTES/4) { + if (vei.estimatedBytes > MAX_BYTES + MAX_BYTES / 4) { alert(context, TOO_BIG_FILE); return false; } @@ -641,15 +682,17 @@ public class VideoRecoder { String tempPath = DcHelper.getBlobdirFile(DcHelper.getContext(context), inPath); VideoRecoder videoRecoder = new VideoRecoder(); if (!videoRecoder.convertVideo(vei, tempPath)) { - alert(context, String.format("Recoding failed for %s: cannot convert to temporary file %s", inPath, tempPath)); + alert( + context, + String.format( + "Recoding failed for %s: cannot convert to temporary file %s", inPath, tempPath)); return false; } msg.setFileAndDeduplicate(tempPath, msg.getFilename(), msg.getFilemime()); Log.i(TAG, String.format("recoding for %s done", inPath)); - } - catch(Exception e) { + } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/java/org/thoughtcrime/securesms/webxdc/WebxdcGarbageCollectionWorker.java b/src/main/java/org/thoughtcrime/securesms/webxdc/WebxdcGarbageCollectionWorker.java index b5ce3ae4b..a773f3a75 100644 --- a/src/main/java/org/thoughtcrime/securesms/webxdc/WebxdcGarbageCollectionWorker.java +++ b/src/main/java/org/thoughtcrime/securesms/webxdc/WebxdcGarbageCollectionWorker.java @@ -10,7 +10,6 @@ import androidx.work.WorkerParameters; import chat.delta.rpc.Rpc; import chat.delta.rpc.RpcException; import com.google.common.util.concurrent.ListenableFuture; - import java.util.Collections; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -30,54 +29,59 @@ public class WebxdcGarbageCollectionWorker extends ListenableWorker { Log.i(TAG, "Running Webxdc storage garbage collection..."); final Pattern WEBXDC_URL_PATTERN = - Pattern.compile("^https?://acc(\\d+)-msg(\\d+)\\.localhost/?"); + Pattern.compile("^https?://acc(\\d+)-msg(\\d+)\\.localhost/?"); - return CallbackToFutureAdapter.getFuture(completer -> { - WebStorage webStorage = WebStorage.getInstance(); + return CallbackToFutureAdapter.getFuture( + completer -> { + WebStorage webStorage = WebStorage.getInstance(); - webStorage.getOrigins((origins) -> { - if (origins == null || origins.isEmpty()) { - Log.i(TAG, "Done, no WebView origins found."); - completer.set(Result.success()); - return; - } + webStorage.getOrigins( + (origins) -> { + if (origins == null || origins.isEmpty()) { + Log.i(TAG, "Done, no WebView origins found."); + completer.set(Result.success()); + return; + } - Rpc rpc = DcHelper.getRpc(context); - if (rpc == null) { - Log.e(TAG, "Failed to get access to RPC, Webxdc storage garbage collection aborted."); - completer.set(Result.failure()); - return; - } + Rpc rpc = DcHelper.getRpc(context); + if (rpc == null) { + Log.e( + TAG, + "Failed to get access to RPC, Webxdc storage garbage collection aborted."); + completer.set(Result.failure()); + return; + } - for (Object key : origins.keySet()) { - String url = (String)key; - Matcher m = WEBXDC_URL_PATTERN.matcher(url); - if (m.matches()) { - int accId = Integer.parseInt(m.group(1)); - int msgId = Integer.parseInt(m.group(2)); - try { - if (rpc.getExistingMsgIds(accId, Collections.singletonList(msgId)).isEmpty()) { - webStorage.deleteOrigin(url); - Log.i(TAG, String.format("Deleted webxdc origin: %s", url)); - } else { - Log.i(TAG, String.format("Existing webxdc origin: %s", url)); - } - } catch (RpcException e) { - Log.e(TAG, "error calling rpc.getExistingMsgIds()", e); - completer.set(Result.failure()); - return; - } - } else { // old webxdc URL schemes, etc - webStorage.deleteOrigin(url); - Log.i(TAG, String.format("Deleted unknown origin: %s", url)); - } - } + for (Object key : origins.keySet()) { + String url = (String) key; + Matcher m = WEBXDC_URL_PATTERN.matcher(url); + if (m.matches()) { + int accId = Integer.parseInt(m.group(1)); + int msgId = Integer.parseInt(m.group(2)); + try { + if (rpc.getExistingMsgIds(accId, Collections.singletonList(msgId)) + .isEmpty()) { + webStorage.deleteOrigin(url); + Log.i(TAG, String.format("Deleted webxdc origin: %s", url)); + } else { + Log.i(TAG, String.format("Existing webxdc origin: %s", url)); + } + } catch (RpcException e) { + Log.e(TAG, "error calling rpc.getExistingMsgIds()", e); + completer.set(Result.failure()); + return; + } + } else { // old webxdc URL schemes, etc + webStorage.deleteOrigin(url); + Log.i(TAG, String.format("Deleted unknown origin: %s", url)); + } + } - Log.i(TAG, "Done running Webxdc storage garbage collection."); - completer.set(Result.success()); - }); + Log.i(TAG, "Done running Webxdc storage garbage collection."); + completer.set(Result.success()); + }); - return "Webxdc Garbage Collector"; - }); + return "Webxdc Garbage Collector"; + }); } } diff --git a/src/main/res/layout/call_item_view.xml b/src/main/res/layout/call_item_view.xml index 84eabaea9..616bac31a 100644 --- a/src/main/res/layout/call_item_view.xml +++ b/src/main/res/layout/call_item_view.xml @@ -9,42 +9,43 @@ android:gravity="center_vertical" android:focusable="true"> - + - - - + android:layout_weight="1" + android:layout_marginStart="6dp" + android:layout_marginEnd="6dp" + android:orientation="vertical"> - + - + + + diff --git a/src/main/res/layout/contact_filter_toolbar.xml b/src/main/res/layout/contact_filter_toolbar.xml index 94237fcef..48e60aba7 100644 --- a/src/main/res/layout/contact_filter_toolbar.xml +++ b/src/main/res/layout/contact_filter_toolbar.xml @@ -1,14 +1,16 @@ - + - + - + diff --git a/src/main/res/menu/conversation.xml b/src/main/res/menu/conversation.xml index 49b703b06..daab6b679 100644 --- a/src/main/res/menu/conversation.xml +++ b/src/main/res/menu/conversation.xml @@ -78,15 +78,15 @@ android:title="@string/menu_add_attachment" android:id="@+id/menu_add_attachment" /> + + - - diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 263cba76a..2d7461f71 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -281,6 +281,7 @@ Add Attachment Leave Group Leave Channel + Leave & Delete for Me Delete Chat Clear Chat @@ -387,15 +388,25 @@ Call + Audio Call + Video Call Answer Decline + + Audio call + + Video call + Outgoing audio call + Outgoing video call + Incoming audio call + Incoming video call Declined call Canceled call