mirror of
https://github.com/deltachat/deltachat-android.git
synced 2025-10-03 17:59:39 +02:00
webxdc (#2174)
* add new w30 APIs * create the webview, disable internet access, inject deltachat.js * connect deltachat to the webview * promisify api * use msgActionButton to start the w30 apps * cleanup - create observers in onCreate() to avoid memory leak, - derive from WebViewActivity to easier deal with particularities and to saves >100 loc - reorder some methods to reflect lifetimes * make it more clear, which uri-part is 'domain' and which one is 'path' * unify logging * it is 'statusUpdate' not 'stateUpdate'; not sure if promise is needed at the end, we can readd that as needed, simple code for now * use core implementation for status updates * disable debugging enabled by default, streamline code * use same name for InternalJSApi for both, js and java * getStatusUpdates() always return an array * call JSON.stringify() on payload * fix typo, fix equal operator * use shorter function names in js land * adapt to new zipped w30 format * load any file from w30 archives * add fallback if getMimeTypeFromExtension() fails * rename w30 to webxdc * add selfName() * return selfAddr() if selfName() is empty * rename deltachat.js to webxdc.js * observer correct event * rename getBlobFromArchive() to getWebxdcBlob() * show webxdc app name in title bar * swap payload and descr in sendUpdate() (adapt to new core api) * allow user-defined-texts for webxdc apps, make room for icon+name * show webxdc icon and name in chats * render webxdc drafts accordingly * allow configuring drafts * do not destroy webxdc-message to be sent out by removing it via setDraft(null) * fix crash when replying to webxdc messages * add webxdc messages to profile's document tab * hide 'search menu' for webxdc apps * show app summary beside app icon * remove outdated comment * add precautious WebView restrictions * Update src/org/thoughtcrime/securesms/ConversationItem.java Co-authored-by: Asiel Díaz Benítez <adbenitez@nauta.cu> * Update src/org/thoughtcrime/securesms/ConversationItem.java Co-authored-by: Asiel Díaz Benítez <adbenitez@nauta.cu> * Update src/org/thoughtcrime/securesms/ConversationActivity.java Co-authored-by: Hocuri <hocuri@gmx.de> * Webxdc requires at least Android 5 Lollipop see https://github.com/deltachat/deltachat-android/pull/2174#discussion_r785436874 * recognize .xdc files on android10 based on @Hocuri's findings in #2188 Co-authored-by: Simon Laux <mobile.info@simonlaux.de> Co-authored-by: adbenitez <asieldbenitez@gmail.com> Co-authored-by: Asiel Díaz Benítez <adbenitez@nauta.cu> Co-authored-by: Hocuri <hocuri@gmx.de>
This commit is contained in:
parent
aaf53e71a0
commit
9e70aa7cad
21 changed files with 665 additions and 39 deletions
|
@ -277,6 +277,11 @@
|
|||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".WebxdcActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".FullMsgActivity"
|
||||
android:theme="@style/TextSecure.LightTheme"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||
|
|
|
@ -86,6 +86,19 @@ static jstring jstring_new__(JNIEnv* env, const char* a)
|
|||
#define CTIMESTAMP(a) (((jlong)a)/((jlong)1000))
|
||||
|
||||
|
||||
static jbyteArray ptr2jbyteArray(JNIEnv *env, const void* ptr, size_t len) {
|
||||
if (ptr == NULL || len <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
jbyteArray ret = (*env)->NewByteArray(env, len);
|
||||
if (ret == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
(*env)->SetByteArrayRegion(env, ret, 0, len, (const jbyte*)ptr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static jintArray dc_array2jintArray_n_unref(JNIEnv *env, dc_array_t* ca)
|
||||
{
|
||||
/* takes a C-array of type dc_array_t and converts it it a Java-Array.
|
||||
|
@ -704,6 +717,26 @@ JNIEXPORT jint Java_com_b44t_messenger_DcContext_sendVideochatInvitation(JNIEnv
|
|||
}
|
||||
|
||||
|
||||
JNIEXPORT jboolean Java_com_b44t_messenger_DcContext_sendWebxdcStatusUpdate(JNIEnv *env, jobject obj, jint msg_id, jstring payload, jstring descr)
|
||||
{
|
||||
CHAR_REF(payload);
|
||||
CHAR_REF(descr);
|
||||
jboolean ret = dc_send_webxdc_status_update(get_dc_context(env, obj), msg_id, payloadPtr, descrPtr) != 0;
|
||||
CHAR_UNREF(descr);
|
||||
CHAR_UNREF(payload);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcContext_getWebxdcStatusUpdates(JNIEnv *env, jobject obj, jint msg_id, jint status_update_id)
|
||||
{
|
||||
char* temp = dc_get_webxdc_status_updates(get_dc_context(env, obj), msg_id, status_update_id);
|
||||
jstring ret = JSTRING_NEW(temp);
|
||||
dc_str_unref(temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jint Java_com_b44t_messenger_DcContext_addDeviceMsg(JNIEnv *env, jobject obj, jstring label, jobject msg)
|
||||
{
|
||||
CHAR_REF(label);
|
||||
|
@ -1528,6 +1561,28 @@ JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getFilename(JNIEnv *env, jobject
|
|||
}
|
||||
|
||||
|
||||
JNIEXPORT jbyteArray Java_com_b44t_messenger_DcMsg_getWebxdcBlob(JNIEnv *env, jobject obj, jstring filename)
|
||||
{
|
||||
jbyteArray ret = NULL;
|
||||
CHAR_REF(filename)
|
||||
size_t ptrSize = 0;
|
||||
char* ptr = dc_msg_get_webxdc_blob(get_dc_msg(env, obj), filenamePtr, &ptrSize);
|
||||
ret = ptr2jbyteArray(env, ptr, ptrSize);
|
||||
dc_str_unref(ptr);
|
||||
CHAR_UNREF(filename)
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jstring Java_com_b44t_messenger_DcMsg_getWebxdcInfoJson(JNIEnv *env, jobject obj)
|
||||
{
|
||||
char* temp = dc_msg_get_webxdc_info(get_dc_msg(env, obj));
|
||||
jstring ret = JSTRING_NEW(temp);
|
||||
dc_str_unref(temp);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
JNIEXPORT jboolean Java_com_b44t_messenger_DcMsg_isForwarded(JNIEnv *env, jobject obj)
|
||||
{
|
||||
return dc_msg_is_forwarded(get_dc_msg(env, obj))!=0;
|
||||
|
|
|
@ -58,6 +58,17 @@
|
|||
android:padding="8dp"
|
||||
android:background="@drawable/message_bubble_background_sent_alone"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.WebxdcView
|
||||
android:id="@+id/attachment_webxdc"
|
||||
android:layout_width="230dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginRight="10dp"
|
||||
android:padding="8dp"
|
||||
android:background="@drawable/message_bubble_background_sent_alone"/>
|
||||
|
||||
</org.thoughtcrime.securesms.components.RemovableEditableMediaView>
|
||||
|
||||
</FrameLayout>
|
||||
|
|
|
@ -143,6 +143,16 @@
|
|||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/webxdc_view_stub"
|
||||
android:layout="@layout/conversation_item_webxdc"
|
||||
android:layout_width="210dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -121,6 +121,16 @@
|
|||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||
|
||||
<ViewStub
|
||||
android:id="@+id/webxdc_view_stub"
|
||||
android:layout="@layout/conversation_item_webxdc"
|
||||
android:layout_width="210dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/message_bubble_top_padding"
|
||||
android:layout_marginBottom="@dimen/message_bubble_collapsed_footer_padding"
|
||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||
android:layout_marginRight="@dimen/message_bubble_horizontal_padding" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/conversation_item_body"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
10
res/layout/conversation_item_webxdc.xml
Normal file
10
res/layout/conversation_item_webxdc.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.thoughtcrime.securesms.components.WebxdcView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/webxdc_view"
|
||||
android:layout_width="210dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible"/>
|
55
res/layout/webxdc_view.xml
Normal file
55
res/layout/webxdc_view.xml
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context="org.thoughtcrime.securesms.components.WebxdcView">
|
||||
|
||||
<LinearLayout android:id="@+id/webxdc_container"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="false"
|
||||
android:focusable="false"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/webxdc_icon"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chevron_up"/>
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginLeft="6dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:orientation="vertical"
|
||||
android:focusable="false"
|
||||
android:clickable="false">
|
||||
|
||||
<TextView android:id="@+id/webxdc_app_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="@style/Signal.Text.Body"
|
||||
android:singleLine="true"
|
||||
android:maxLines="1"
|
||||
android:clickable="false"
|
||||
android:ellipsize="end"
|
||||
android:textColor="?conversation_item_incoming_text_primary_color"
|
||||
android:textStyle="bold"
|
||||
tools:text="Great App"/>
|
||||
|
||||
<TextView android:id="@+id/webxdc_subtitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
style="@style/Signal.Text.Caption"
|
||||
android:clickable="false"
|
||||
android:textColor="?conversation_item_incoming_text_primary_color"
|
||||
tools:text="24kb"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</merge>
|
26
res/raw/webxdc.js
Normal file
26
res/raw/webxdc.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
window.webxdc = (() => {
|
||||
var update_listener = () => {};
|
||||
|
||||
window.__webxdcUpdate = (updateId) => {
|
||||
var updates = JSON.parse(InternalJSApi.getStatusUpdates(updateId));
|
||||
if (updates.length === 1) {
|
||||
update_listener(updates[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
selfAddr: () => InternalJSApi.selfAddr(),
|
||||
|
||||
selfName: () => InternalJSApi.selfName(),
|
||||
|
||||
setUpdateListener: (cb) => (update_listener = cb),
|
||||
|
||||
getAllUpdates: () => {
|
||||
return JSON.parse(InternalJSApi.getStatusUpdates(0));
|
||||
},
|
||||
|
||||
sendUpdate: (payload, descr) => {
|
||||
InternalJSApi.sendStatusUpdate(JSON.stringify(payload), descr);
|
||||
},
|
||||
};
|
||||
})();
|
|
@ -27,6 +27,7 @@ public class DcContext {
|
|||
public final static int DC_EVENT_SECUREJOIN_JOINER_PROGRESS = 2061;
|
||||
public final static int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
|
||||
public final static int DC_EVENT_SELFAVATAR_CHANGED = 2110;
|
||||
public final static int DC_EVENT_WEBXDC_STATUS_UPDATE = 2120;
|
||||
|
||||
public final static int DC_IMEX_EXPORT_SELF_KEYS = 1;
|
||||
public final static int DC_IMEX_IMPORT_SELF_KEYS = 2;
|
||||
|
@ -195,6 +196,8 @@ public class DcContext {
|
|||
public native int sendMsg (int chat_id, DcMsg msg);
|
||||
public native int sendTextMsg (int chat_id, String text);
|
||||
public native int sendVideochatInvitation(int chat_id);
|
||||
public native boolean sendWebxdcStatusUpdate(int msg_id, String payload, String descr);
|
||||
public native String getWebxdcStatusUpdates(int msg_id, int status_update_id);
|
||||
public native int addDeviceMsg (String label, DcMsg msg);
|
||||
public native boolean wasDeviceMsgEverAdded(String label);
|
||||
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package com.b44t.messenger;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -15,12 +18,14 @@ public class DcMsg {
|
|||
public final static int DC_MSG_VIDEO = 50;
|
||||
public final static int DC_MSG_FILE = 60;
|
||||
public final static int DC_MSG_VIDEOCHAT_INVITATION = 70;
|
||||
public final static int DC_MSG_WEBXDC = 80;
|
||||
|
||||
public final static int DC_STATE_UNDEFINED = 0;
|
||||
public final static int DC_STATE_IN_FRESH = 10;
|
||||
public final static int DC_STATE_IN_NOTICED = 13;
|
||||
public final static int DC_STATE_IN_SEEN = 16;
|
||||
public final static int DC_STATE_OUT_PREPARING = 18;
|
||||
public final static int DC_STATE_OUT_DRAFT = 19;
|
||||
public final static int DC_STATE_OUT_PENDING = 20;
|
||||
public final static int DC_STATE_OUT_ERROR = 24;
|
||||
public final static int DC_STATE_OUT_DELIVERED = 26;
|
||||
|
@ -110,6 +115,15 @@ public class DcMsg {
|
|||
public native String getFilemime ();
|
||||
public native String getFilename ();
|
||||
public native long getFilebytes ();
|
||||
public native byte[] getWebxdcBlob (String filename);
|
||||
public JSONObject getWebxdcInfo () {
|
||||
try {
|
||||
return new JSONObject(getWebxdcInfoJson());
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
return new JSONObject();
|
||||
}
|
||||
}
|
||||
public native boolean isForwarded ();
|
||||
public native boolean isInfo ();
|
||||
public native boolean isSetupMessage ();
|
||||
|
@ -206,4 +220,5 @@ public class DcMsg {
|
|||
private native long getSummaryCPtr (long chatCPtr);
|
||||
private native void setQuoteCPtr (long quoteCPtr);
|
||||
private native long getQuotedMsgCPtr ();
|
||||
private native String getWebxdcInfoJson ();
|
||||
};
|
||||
|
|
|
@ -46,6 +46,7 @@ import android.view.View.OnFocusChangeListener;
|
|||
import android.view.View.OnKeyListener;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
|
@ -836,6 +837,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
case DcMsg.DC_MSG_VIDEO:
|
||||
setMedia(uri, MediaType.VIDEO).addListener(listener);
|
||||
break;
|
||||
case DcMsg.DC_MSG_WEBXDC:
|
||||
setMedia(draft, MediaType.DOCUMENT).addListener(listener);
|
||||
break;
|
||||
default:
|
||||
setMedia(uri, MediaType.DOCUMENT).addListener(listener);
|
||||
break;
|
||||
|
@ -1012,7 +1016,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
return new SettableFuture<>(false);
|
||||
}
|
||||
|
||||
return attachmentManager.setMedia(glideRequests, uri, mediaType, 0, 0);
|
||||
return attachmentManager.setMedia(glideRequests, uri, null, mediaType, 0, 0, chatId);
|
||||
}
|
||||
|
||||
private ListenableFuture<Boolean> setMedia(DcMsg msg, @NonNull MediaType mediaType) {
|
||||
return attachmentManager.setMedia(glideRequests, Uri.fromFile(new File(msg.getFile())), msg, mediaType, 0, 0, chatId);
|
||||
}
|
||||
|
||||
private void addAttachmentContactInfo(Intent data) {
|
||||
|
@ -1029,7 +1037,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
return dcChat.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
|
||||
}
|
||||
|
||||
private String getRealPathFromAttachment(Attachment attachment) {
|
||||
public static String getRealPathFromAttachment(Context context, Attachment attachment) {
|
||||
try {
|
||||
// get file in the blobdir as `<blobdir>/<name>[-<uniqueNumber>].<ext>`
|
||||
String filename = attachment.getFileName();
|
||||
|
@ -1045,11 +1053,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
filename = filename.substring(0, i);
|
||||
}
|
||||
}
|
||||
String path = DcHelper.getBlobdirFile(dcContext, filename, ext);
|
||||
String path = DcHelper.getBlobdirFile(DcHelper.getContext(context), filename, ext);
|
||||
|
||||
// copy content to this file
|
||||
if(path!=null) {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(this, attachment.getDataUri());
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
|
||||
OutputStream outputStream = new FileOutputStream(path);
|
||||
Util.copy(inputStream, outputStream);
|
||||
}
|
||||
|
@ -1095,6 +1103,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
}
|
||||
|
||||
try {
|
||||
if (slideDeck.getWebxdctDraftId() != 0) {
|
||||
msg = dcContext.getDraft(chatId);
|
||||
} else {
|
||||
List<Attachment> attachments = slideDeck.asAttachments();
|
||||
for (Attachment attachment : attachments) {
|
||||
String contentType = attachment.getContentType();
|
||||
|
@ -1102,23 +1113,21 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
msg = new DcMsg(dcContext,
|
||||
MediaUtil.isGif(contentType) ? DcMsg.DC_MSG_GIF : DcMsg.DC_MSG_IMAGE);
|
||||
msg.setDimension(attachment.getWidth(), attachment.getHeight());
|
||||
}
|
||||
else if (MediaUtil.isAudioType(contentType)) {
|
||||
} else if (MediaUtil.isAudioType(contentType)) {
|
||||
msg = new DcMsg(dcContext,
|
||||
attachment.isVoiceNote() ? DcMsg.DC_MSG_VOICE : DcMsg.DC_MSG_AUDIO);
|
||||
}
|
||||
else if (MediaUtil.isVideoType(contentType) && slideDeck.getDocumentSlide()==null) {
|
||||
} else if (MediaUtil.isVideoType(contentType) && slideDeck.getDocumentSlide() == null) {
|
||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_VIDEO);
|
||||
recompress = DcMsg.DC_MSG_VIDEO;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
|
||||
}
|
||||
String path = getRealPathFromAttachment(attachment);
|
||||
String path = getRealPathFromAttachment(this, attachment);
|
||||
msg.setFile(path, null);
|
||||
msg.setText(body);
|
||||
}
|
||||
}
|
||||
msg.setText(body);
|
||||
}
|
||||
catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -1140,7 +1149,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
DcMsg msg = (DcMsg)param[0];
|
||||
Integer recompress = (Integer)param[1];
|
||||
if (action==ACTION_SEND_OUT) {
|
||||
|
||||
// for WEBXDC, drafts are just sent out as is.
|
||||
// for preparations and other cases, cleanup draft soon.
|
||||
if (msg.getType() != DcMsg.DC_MSG_WEBXDC) {
|
||||
dcContext.setDraft(dcChat.getId(), null);
|
||||
}
|
||||
|
||||
if(msg!=null)
|
||||
{
|
||||
|
@ -1369,7 +1383,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
|||
private void sendSticker(@NonNull Uri uri, String contentType) {
|
||||
Attachment attachment = new UriAttachment(uri, null, contentType,
|
||||
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, null, null, false);
|
||||
String path = getRealPathFromAttachment(attachment);
|
||||
String path = getRealPathFromAttachment(this, attachment);
|
||||
|
||||
Optional<QuoteModel> quote = inputPanel.getQuote();
|
||||
inputPanel.clearQuote();
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
|||
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.QuoteView;
|
||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||
|
@ -107,6 +108,7 @@ public class ConversationItem extends BaseConversationItem
|
|||
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
||||
private @NonNull Stub<AudioView> audioViewStub;
|
||||
private @NonNull Stub<DocumentView> documentViewStub;
|
||||
private @NonNull Stub<WebxdcView> webxdcViewStub;
|
||||
private Stub<BorderlessImageView> stickerStub;
|
||||
private @Nullable EventListener eventListener;
|
||||
|
||||
|
@ -139,6 +141,7 @@ public class ConversationItem extends BaseConversationItem
|
|||
this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub));
|
||||
this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub));
|
||||
this.documentViewStub = new Stub<>(findViewById(R.id.document_view_stub));
|
||||
this.webxdcViewStub = new Stub<>(findViewById(R.id.webxdc_view_stub));
|
||||
this.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
|
||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||
this.quoteView = findViewById(R.id.quote_view);
|
||||
|
@ -289,6 +292,11 @@ public class ConversationItem extends BaseConversationItem
|
|||
documentViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
documentViewStub.get().setClickable(batchSelected.isEmpty());
|
||||
}
|
||||
|
||||
if (webxdcViewStub.resolved()) {
|
||||
webxdcViewStub.get().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||
webxdcViewStub.get().setClickable(batchSelected.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasAudio(DcMsg messageRecord) {
|
||||
|
@ -313,9 +321,14 @@ public class ConversationItem extends BaseConversationItem
|
|||
return hasThumbnail(messageRecord) &&
|
||||
!hasAudio(messageRecord) &&
|
||||
!hasDocument(messageRecord) &&
|
||||
!hasWebxdc(messageRecord) &&
|
||||
!hasSticker(messageRecord);
|
||||
}
|
||||
|
||||
private boolean hasWebxdc(DcMsg dcMsg) {
|
||||
return dcMsg.getType()==DcMsg.DC_MSG_WEBXDC;
|
||||
}
|
||||
|
||||
private boolean hasDocument(DcMsg dcMsg) {
|
||||
return dcMsg.getType()==DcMsg.DC_MSG_FILE && !dcMsg.isSetupMessage();
|
||||
}
|
||||
|
@ -361,6 +374,17 @@ public class ConversationItem extends BaseConversationItem
|
|||
passthroughClickListener.onClick(view);
|
||||
}
|
||||
});
|
||||
} else if (messageRecord.getType() == DcMsg.DC_MSG_WEBXDC) {
|
||||
msgActionButton.setVisibility(View.VISIBLE);
|
||||
msgActionButton.setEnabled(true);
|
||||
msgActionButton.setText("Start…");
|
||||
msgActionButton.setOnClickListener(view -> {
|
||||
if (batchSelected.isEmpty()) {
|
||||
WebxdcActivity.openWebxdcActivity(getContext(), messageRecord);
|
||||
} else {
|
||||
passthroughClickListener.onClick(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (messageRecord.hasHtml()) {
|
||||
msgActionButton.setVisibility(View.VISIBLE);
|
||||
|
@ -401,6 +425,7 @@ public class ConversationItem extends BaseConversationItem
|
|||
audioViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
|
@ -422,6 +447,7 @@ public class ConversationItem extends BaseConversationItem
|
|||
documentViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||
|
||||
//noinspection ConstantConditions
|
||||
|
@ -433,10 +459,26 @@ public class ConversationItem extends BaseConversationItem
|
|||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (hasWebxdc(messageRecord)) {
|
||||
webxdcViewStub.get().setVisibility(View.VISIBLE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||
|
||||
webxdcViewStub.get().setWebxdc(messageRecord);
|
||||
webxdcViewStub.get().setWebxdcClickListener(new ThumbnailClickListener());
|
||||
webxdcViewStub.get().setOnLongClickListener(passthroughClickListener);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
footer.setVisibility(VISIBLE);
|
||||
}
|
||||
else if (hasThumbnail(messageRecord)) {
|
||||
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||
|
||||
Slide slide = MediaUtil.getSlideForMsg(context, messageRecord);
|
||||
|
@ -473,6 +515,7 @@ public class ConversationItem extends BaseConversationItem
|
|||
stickerStub.get().setVisibility(View.VISIBLE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
|
||||
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
|
||||
|
@ -492,6 +535,7 @@ public class ConversationItem extends BaseConversationItem
|
|||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||
if (documentViewStub.resolved()) documentViewStub.get().setVisibility(View.GONE);
|
||||
if (webxdcViewStub.resolved()) webxdcViewStub.get().setVisibility(View.GONE);
|
||||
|
||||
ViewUtil.updateLayoutParams(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
@ -725,6 +769,8 @@ public class ConversationItem extends BaseConversationItem
|
|||
public void onClick(final View v, final Slide slide) {
|
||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||
performClick();
|
||||
} else if (messageRecord.getType() == DcMsg.DC_MSG_WEBXDC) {
|
||||
WebxdcActivity.openWebxdcActivity(context, messageRecord);
|
||||
} else if (MediaPreviewActivity.isTypeSupported(slide) && slide.getUri() != null) {
|
||||
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
|
|
@ -103,7 +103,7 @@ public class ProfileDocumentsFragment
|
|||
|
||||
@Override
|
||||
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
|
||||
return new BucketedThreadMediaLoader(getContext(), chatId, DcMsg.DC_MSG_FILE, DcMsg.DC_MSG_AUDIO, 0);
|
||||
return new BucketedThreadMediaLoader(getContext(), chatId, DcMsg.DC_MSG_FILE, DcMsg.DC_MSG_AUDIO, DcMsg.DC_MSG_WEBXDC);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,11 +9,14 @@ import android.util.Log;
|
|||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.webkit.WebSettingsCompat;
|
||||
import androidx.webkit.WebViewFeature;
|
||||
|
@ -75,6 +78,26 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
|||
// if we come over really useful things, we should allow that explicitly.
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
|
||||
WebResourceResponse res = interceptRequest(url);
|
||||
if (res!=null) {
|
||||
return res;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, url);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RequiresApi(21)
|
||||
public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
|
||||
WebResourceResponse res = interceptRequest(request.getUrl().toString());
|
||||
if (res!=null) {
|
||||
return res;
|
||||
}
|
||||
return super.shouldInterceptRequest(view, request);
|
||||
}
|
||||
});
|
||||
webView.setFindListener(this);
|
||||
|
||||
|
@ -245,6 +268,10 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
|||
}
|
||||
}
|
||||
|
||||
protected WebResourceResponse interceptRequest(String url) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
|
||||
super.onActivityResult(reqCode, resultCode, data);
|
||||
|
|
172
src/org/thoughtcrime/securesms/WebxdcActivity.java
Normal file
172
src/org/thoughtcrime/securesms/WebxdcActivity.java
Normal file
|
@ -0,0 +1,172 @@
|
|||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.MimeTypeMap;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcEvent;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.connect.DcEventCenter;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class WebxdcActivity extends WebViewActivity implements DcEventCenter.DcEventDelegate {
|
||||
private static final String TAG = WebxdcActivity.class.getSimpleName();
|
||||
private static final String INTERNAL_SCHEMA = "webxdc";
|
||||
private static final String INTERNAL_DOMAIN = "local.app";
|
||||
private DcContext dcContext;
|
||||
private DcMsg dcAppMsg;
|
||||
|
||||
public static void openWebxdcActivity(Context context, DcMsg instance) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
Intent intent =new Intent(context, WebxdcActivity.class);
|
||||
intent.putExtra("appMessageId", instance.getId());
|
||||
context.startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(context, "At least Android 5.0 (Lollipop) required for Webxdc.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle state, boolean ready) {
|
||||
super.onCreate(state, ready);
|
||||
DcEventCenter eventCenter = DcHelper.getEventCenter(WebxdcActivity.this.getApplicationContext());
|
||||
eventCenter.addObserver(DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE, this);
|
||||
|
||||
Bundle b = getIntent().getExtras();
|
||||
int appMessageId = b.getInt("appMessageId");
|
||||
|
||||
this.dcContext = DcHelper.getContext(getApplicationContext());
|
||||
this.dcAppMsg = this.dcContext.getMsg(appMessageId);
|
||||
|
||||
WebSettings webSettings = webView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
webSettings.setAllowFileAccess(false);
|
||||
webSettings.setBlockNetworkLoads(true);
|
||||
webSettings.setAllowContentAccess(false);
|
||||
webSettings.setGeolocationEnabled(false);
|
||||
webSettings.setAllowFileAccessFromFileURLs(false);
|
||||
webSettings.setAllowUniversalAccessFromFileURLs(false);
|
||||
webView.addJavascriptInterface(new InternalJSApi(), "InternalJSApi");
|
||||
|
||||
webView.loadUrl(INTERNAL_SCHEMA + "://" + INTERNAL_DOMAIN + "/index.html");
|
||||
|
||||
Util.runOnAnyBackgroundThread(() -> {
|
||||
JSONObject info = this.dcAppMsg.getWebxdcInfo();
|
||||
Util.runOnMain(() -> {
|
||||
try {
|
||||
getSupportActionBar().setTitle(info.getString("name"));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
DcHelper.getEventCenter(this.getApplicationContext()).removeObservers(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
// do not call super.onPrepareOptionsMenu() as the default "Search" menu is not needed
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void openOnlineUrl(String url) {
|
||||
Toast.makeText(this, "Please embed needed resources.", Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WebResourceResponse interceptRequest(String rawUrl) {
|
||||
Log.i(TAG, "interceptRequest: " + rawUrl);
|
||||
try {
|
||||
if (rawUrl == null) {
|
||||
throw new Exception("no url specified");
|
||||
}
|
||||
String path = Uri.parse(rawUrl).getPath();
|
||||
if (path.equalsIgnoreCase("/webxdc.js")) {
|
||||
InputStream targetStream = getResources().openRawResource(R.raw.webxdc);
|
||||
return new WebResourceResponse("text/javascript", "UTF-8", targetStream);
|
||||
} else {
|
||||
byte[] blob = this.dcAppMsg.getWebxdcBlob(path);
|
||||
if (blob == null) {
|
||||
throw new Exception("\"" + path + "\" not found");
|
||||
}
|
||||
String ext = MimeTypeMap.getFileExtensionFromUrl(path);
|
||||
String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext);
|
||||
if (mimeType == null) {
|
||||
switch (ext) {
|
||||
case "js": mimeType = "text/javascript"; break;
|
||||
default: mimeType = "application/octet-stream"; Log.i(TAG, "unknown mime type for " + rawUrl); break;
|
||||
}
|
||||
}
|
||||
String encoding = mimeType.startsWith("text/")? "UTF-8" : null;
|
||||
InputStream targetStream = new ByteArrayInputStream(blob);
|
||||
return new WebResourceResponse(mimeType, encoding, targetStream);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
InputStream targetStream = new ByteArrayInputStream(("Webxdc Request Error: " + e.getMessage()).getBytes());
|
||||
return new WebResourceResponse("text/plain", "UTF-8", targetStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleEvent(@NonNull DcEvent event) {
|
||||
int eventId = event.getId();
|
||||
if ((eventId == DcContext.DC_EVENT_WEBXDC_STATUS_UPDATE && event.getData1Int() == dcAppMsg.getId())) {
|
||||
Log.i(TAG, "handleEvent");
|
||||
webView.loadUrl("javascript:window.__webxdcUpdate(" + event.getData2Int() + ");");
|
||||
}
|
||||
}
|
||||
|
||||
class InternalJSApi {
|
||||
@JavascriptInterface
|
||||
public String selfAddr() {
|
||||
return WebxdcActivity.this.dcContext.getConfig("addr");
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String selfName() {
|
||||
String name = WebxdcActivity.this.dcContext.getConfig("displayname");
|
||||
if (TextUtils.isEmpty(name)) {
|
||||
name = selfAddr();
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public boolean sendStatusUpdate(String payload, String descr) {
|
||||
Log.i(TAG, "sendStatusUpdate");
|
||||
return WebxdcActivity.this.dcContext.sendWebxdcStatusUpdate(WebxdcActivity.this.dcAppMsg.getId(), payload, descr);
|
||||
}
|
||||
|
||||
@JavascriptInterface
|
||||
public String getStatusUpdates(int statusUpdateId) {
|
||||
Log.i(TAG, "getStatusUpdates");
|
||||
return WebxdcActivity.this.dcContext.getWebxdcStatusUpdates(WebxdcActivity.this.dcAppMsg.getId(), statusUpdateId);
|
||||
}
|
||||
}
|
||||
}
|
102
src/org/thoughtcrime/securesms/components/WebxdcView.java
Normal file
102
src/org/thoughtcrime/securesms/components/WebxdcView.java
Normal file
|
@ -0,0 +1,102 @@
|
|||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.AttrRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.AppCompatImageView;
|
||||
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.mms.DocumentSlide;
|
||||
import org.thoughtcrime.securesms.mms.SlideClickListener;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
|
||||
public class WebxdcView extends FrameLayout {
|
||||
|
||||
private static final String TAG = WebxdcView.class.getSimpleName();
|
||||
|
||||
private final @NonNull AppCompatImageView icon;
|
||||
private final @NonNull TextView appName;
|
||||
private final @NonNull TextView appSubtitle;
|
||||
|
||||
private @Nullable SlideClickListener viewListener;
|
||||
|
||||
public WebxdcView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public WebxdcView(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public WebxdcView(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
inflate(context, R.layout.webxdc_view, this);
|
||||
|
||||
this.icon = findViewById(R.id.webxdc_icon);
|
||||
this.appName = findViewById(R.id.webxdc_app_name);
|
||||
this.appSubtitle = findViewById(R.id.webxdc_subtitle);
|
||||
}
|
||||
|
||||
public void setWebxdcClickListener(@Nullable SlideClickListener listener) {
|
||||
this.viewListener = listener;
|
||||
}
|
||||
|
||||
public void setWebxdc(final @NonNull DcMsg dcMsg)
|
||||
{
|
||||
try {
|
||||
JSONObject info = dcMsg.getWebxdcInfo();
|
||||
setOnClickListener(new OpenClickedListener(getContext(), dcMsg));
|
||||
|
||||
// icon
|
||||
byte[] blob = dcMsg.getWebxdcBlob(info.getString("icon"));
|
||||
if (blob == null) {
|
||||
throw new Exception("webxdc icon not found");
|
||||
}
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(blob);
|
||||
Drawable drawable = Drawable.createFromStream(is, "icon");
|
||||
icon.setImageDrawable(drawable);
|
||||
|
||||
// name
|
||||
appName.setText(info.getString("name"));
|
||||
|
||||
// subtitle
|
||||
String summary = info.optString("summary");
|
||||
if (summary.isEmpty()) {
|
||||
summary = Util.getPrettyFileSize(dcMsg.getFilebytes()) + " Webxdc";
|
||||
}
|
||||
appSubtitle.setText(summary);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private class OpenClickedListener implements View.OnClickListener {
|
||||
private final @NonNull DocumentSlide slide;
|
||||
|
||||
private OpenClickedListener(Context context, @NonNull DcMsg dcMsg) {
|
||||
this.slide = new DocumentSlide(context, dcMsg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (viewListener != null) {
|
||||
viewListener.onClick(v, slide);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -38,17 +38,25 @@ import android.widget.Toast;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.b44t.messenger.DcContext;
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.ConversationActivity;
|
||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.ShareLocationDialog;
|
||||
import org.thoughtcrime.securesms.WebxdcActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||
import org.thoughtcrime.securesms.components.AudioView;
|
||||
import org.thoughtcrime.securesms.components.DocumentView;
|
||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||
|
@ -84,6 +92,7 @@ public class AttachmentManager {
|
|||
private ThumbnailView thumbnail;
|
||||
private AudioView audioView;
|
||||
private DocumentView documentView;
|
||||
private WebxdcView webxdcView;
|
||||
//private SignalMapView mapView;
|
||||
|
||||
private @NonNull List<Uri> garbage = new LinkedList<>();
|
||||
|
@ -104,6 +113,7 @@ public class AttachmentManager {
|
|||
this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail);
|
||||
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
|
||||
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
|
||||
this.webxdcView = ViewUtil.findById(root, R.id.attachment_webxdc);
|
||||
//this.mapView = ViewUtil.findById(root, R.id.attachment_location);
|
||||
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
|
||||
|
||||
|
@ -113,6 +123,7 @@ public class AttachmentManager {
|
|||
int incomingBubbleColor = ThemeUtil.getThemedColor(context, R.attr.conversation_item_incoming_bubble_color);
|
||||
audioView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||
documentView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||
webxdcView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -220,9 +231,11 @@ public class AttachmentManager {
|
|||
@SuppressLint("StaticFieldLeak")
|
||||
public ListenableFuture<Boolean> setMedia(@NonNull final GlideRequests glideRequests,
|
||||
@NonNull final Uri uri,
|
||||
@Nullable final DcMsg msg,
|
||||
@NonNull final MediaType mediaType,
|
||||
final int width,
|
||||
final int height)
|
||||
final int height,
|
||||
final int chatId)
|
||||
{
|
||||
inflateStub();
|
||||
|
||||
|
@ -238,10 +251,13 @@ public class AttachmentManager {
|
|||
@Override
|
||||
protected @Nullable Slide doInBackground(Void... params) {
|
||||
try {
|
||||
if (PartAuthority.isLocalUri(uri)) {
|
||||
if (msg != null && msg.getType() == DcMsg.DC_MSG_WEBXDC) {
|
||||
return new DocumentSlide(context, msg);
|
||||
}
|
||||
else if (PartAuthority.isLocalUri(uri)) {
|
||||
return getManuallyCalculatedSlideInfo(uri, width, height);
|
||||
} else {
|
||||
Slide result = getContentResolverSlideInfo(uri, width, height);
|
||||
Slide result = getContentResolverSlideInfo(uri, width, height, chatId);
|
||||
|
||||
if (result == null) return getManuallyCalculatedSlideInfo(uri, width, height);
|
||||
else return result;
|
||||
|
@ -291,8 +307,17 @@ public class AttachmentManager {
|
|||
removableMediaView.display(audioView, false);
|
||||
result.set(true);
|
||||
} else if (slide.hasDocument()) {
|
||||
if (slide.isWebxdcDocument()) {
|
||||
DcMsg instance = msg != null ? msg : DcHelper.getContext(context).getMsg(slide.dcMsgId);
|
||||
webxdcView.setWebxdc(instance);
|
||||
webxdcView.setWebxdcClickListener((v, s) -> {
|
||||
WebxdcActivity.openWebxdcActivity(context, instance);
|
||||
});
|
||||
removableMediaView.display(webxdcView, false);
|
||||
} else {
|
||||
documentView.setDocument((DocumentSlide) slide);
|
||||
removableMediaView.display(documentView, false);
|
||||
}
|
||||
result.set(true);
|
||||
} else {
|
||||
Attachment attachment = slide.asAttachment();
|
||||
|
@ -304,7 +329,7 @@ public class AttachmentManager {
|
|||
}
|
||||
}
|
||||
|
||||
private @Nullable Slide getContentResolverSlideInfo(Uri uri, int width, int height) {
|
||||
private @Nullable Slide getContentResolverSlideInfo(Uri uri, int width, int height, int chatId) {
|
||||
Cursor cursor = null;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
|
@ -323,7 +348,7 @@ public class AttachmentManager {
|
|||
}
|
||||
|
||||
Log.w(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, fileSize, width, height);
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, fileSize, width, height, chatId);
|
||||
}
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
|
@ -367,7 +392,7 @@ public class AttachmentManager {
|
|||
}
|
||||
|
||||
Log.w(TAG, "local slide with size " + mediaSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize, width, height);
|
||||
return mediaType.createSlide(context, uri, fileName, mimeType, mediaSize, width, height, chatId);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
|
||||
|
@ -598,7 +623,8 @@ public class AttachmentManager {
|
|||
@Nullable String mimeType,
|
||||
long dataSize,
|
||||
int width,
|
||||
int height)
|
||||
int height,
|
||||
int chatId)
|
||||
{
|
||||
if (mimeType == null) {
|
||||
mimeType = "application/octet-stream";
|
||||
|
@ -609,7 +635,20 @@ public class AttachmentManager {
|
|||
case GIF: return new GifSlide(context, uri, dataSize, width, height);
|
||||
case AUDIO: return new AudioSlide(context, uri, dataSize, false, fileName);
|
||||
case VIDEO: return new VideoSlide(context, uri, dataSize);
|
||||
case DOCUMENT: return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
|
||||
case DOCUMENT:
|
||||
// We have to special-case Webxdc slides: The user can interact with them as soon as a draft
|
||||
// is set. Therefore we need to create a DcMsg already now.
|
||||
if (fileName != null && fileName.endsWith(".xdc")) {
|
||||
DcContext dcContext = DcHelper.getContext(context);
|
||||
DcMsg msg = new DcMsg(dcContext, DcMsg.DC_MSG_WEBXDC);
|
||||
Attachment attachment = new UriAttachment(uri, null, MediaUtil.WEBXDC, AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, fileName, null, false);
|
||||
String path = ConversationActivity.getRealPathFromAttachment(context, attachment);
|
||||
msg.setFile(path, MediaUtil.WEBXDC);
|
||||
dcContext.setDraft(chatId, msg);
|
||||
return new DocumentSlide(context, msg);
|
||||
} else {
|
||||
return new DocumentSlide(context, uri, mimeType, dataSize, fileName);
|
||||
}
|
||||
default: throw new AssertionError("unrecognized enum");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,10 +13,12 @@ import org.thoughtcrime.securesms.attachments.DcAttachment;
|
|||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
|
||||
public class DocumentSlide extends Slide {
|
||||
private int dcMsgType = DcMsg.DC_MSG_UNDEFINED;
|
||||
|
||||
public DocumentSlide(Context context, DcMsg dcMsg) {
|
||||
super(context, new DcAttachment(dcMsg));
|
||||
dcMsgId = dcMsg.getId();
|
||||
dcMsgType = dcMsg.getType();
|
||||
}
|
||||
|
||||
public DocumentSlide(@NonNull Context context, @NonNull Uri uri,
|
||||
|
@ -31,4 +33,8 @@ public class DocumentSlide extends Slide {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isWebxdcDocument() {
|
||||
return dcMsgType == DcMsg.DC_MSG_WEBXDC;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,10 @@ public abstract class Slide {
|
|||
return false;
|
||||
}
|
||||
|
||||
public boolean isWebxdcDocument() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean hasLocation() {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ package org.thoughtcrime.securesms.mms;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.b44t.messenger.DcMsg;
|
||||
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
@ -72,4 +74,14 @@ public class SlideDeck {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Webxdc requires draft-ids to be used; this function returns the previously used draft-id, if any.
|
||||
public int getWebxdctDraftId() {
|
||||
for (Slide slide: slides) {
|
||||
if (slide.isWebxdcDocument()) {
|
||||
return slide.dcMsgId;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,6 +47,7 @@ public class MediaUtil {
|
|||
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 Slide getSlideForMsg(Context context, DcMsg dcMsg) {
|
||||
|
@ -62,7 +63,8 @@ public class MediaUtil {
|
|||
} 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_FILE) {
|
||||
} else if (dcMsg.getType() == DcMsg.DC_MSG_FILE
|
||||
|| dcMsg.getType() == DcMsg.DC_MSG_WEBXDC) {
|
||||
slide = new DocumentSlide(context, dcMsg);
|
||||
}
|
||||
|
||||
|
@ -259,6 +261,8 @@ public class MediaUtil {
|
|||
return "aac";
|
||||
case IMAGE_WEBP:
|
||||
return "webp";
|
||||
case WEBXDC:
|
||||
return "xdc";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue