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">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity android:name=".WebxdcActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".FullMsgActivity"
|
<activity android:name=".FullMsgActivity"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
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))
|
#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)
|
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.
|
/* 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)
|
JNIEXPORT jint Java_com_b44t_messenger_DcContext_addDeviceMsg(JNIEnv *env, jobject obj, jstring label, jobject msg)
|
||||||
{
|
{
|
||||||
CHAR_REF(label);
|
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)
|
JNIEXPORT jboolean Java_com_b44t_messenger_DcMsg_isForwarded(JNIEnv *env, jobject obj)
|
||||||
{
|
{
|
||||||
return dc_msg_is_forwarded(get_dc_msg(env, obj))!=0;
|
return dc_msg_is_forwarded(get_dc_msg(env, obj))!=0;
|
||||||
|
|
|
@ -58,6 +58,17 @@
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:background="@drawable/message_bubble_background_sent_alone"/>
|
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>
|
</org.thoughtcrime.securesms.components.RemovableEditableMediaView>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
@ -143,6 +143,16 @@
|
||||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginRight="@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
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
|
|
@ -121,6 +121,16 @@
|
||||||
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
android:layout_marginLeft="@dimen/message_bubble_horizontal_padding"
|
||||||
android:layout_marginRight="@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
|
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||||
android:id="@+id/conversation_item_body"
|
android:id="@+id/conversation_item_body"
|
||||||
android:layout_width="wrap_content"
|
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_SECUREJOIN_JOINER_PROGRESS = 2061;
|
||||||
public final static int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
|
public final static int DC_EVENT_CONNECTIVITY_CHANGED = 2100;
|
||||||
public final static int DC_EVENT_SELFAVATAR_CHANGED = 2110;
|
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_EXPORT_SELF_KEYS = 1;
|
||||||
public final static int DC_IMEX_IMPORT_SELF_KEYS = 2;
|
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 sendMsg (int chat_id, DcMsg msg);
|
||||||
public native int sendTextMsg (int chat_id, String text);
|
public native int sendTextMsg (int chat_id, String text);
|
||||||
public native int sendVideochatInvitation(int chat_id);
|
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 int addDeviceMsg (String label, DcMsg msg);
|
||||||
public native boolean wasDeviceMsgEverAdded(String label);
|
public native boolean wasDeviceMsgEverAdded(String label);
|
||||||
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
|
public DcLot checkQr (String qr) { return new DcLot(checkQrCPtr(qr)); }
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package com.b44t.messenger;
|
package com.b44t.messenger;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Set;
|
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_VIDEO = 50;
|
||||||
public final static int DC_MSG_FILE = 60;
|
public final static int DC_MSG_FILE = 60;
|
||||||
public final static int DC_MSG_VIDEOCHAT_INVITATION = 70;
|
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_UNDEFINED = 0;
|
||||||
public final static int DC_STATE_IN_FRESH = 10;
|
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_NOTICED = 13;
|
||||||
public final static int DC_STATE_IN_SEEN = 16;
|
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_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_PENDING = 20;
|
||||||
public final static int DC_STATE_OUT_ERROR = 24;
|
public final static int DC_STATE_OUT_ERROR = 24;
|
||||||
public final static int DC_STATE_OUT_DELIVERED = 26;
|
public final static int DC_STATE_OUT_DELIVERED = 26;
|
||||||
|
@ -110,6 +115,15 @@ public class DcMsg {
|
||||||
public native String getFilemime ();
|
public native String getFilemime ();
|
||||||
public native String getFilename ();
|
public native String getFilename ();
|
||||||
public native long getFilebytes ();
|
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 isForwarded ();
|
||||||
public native boolean isInfo ();
|
public native boolean isInfo ();
|
||||||
public native boolean isSetupMessage ();
|
public native boolean isSetupMessage ();
|
||||||
|
@ -206,4 +220,5 @@ public class DcMsg {
|
||||||
private native long getSummaryCPtr (long chatCPtr);
|
private native long getSummaryCPtr (long chatCPtr);
|
||||||
private native void setQuoteCPtr (long quoteCPtr);
|
private native void setQuoteCPtr (long quoteCPtr);
|
||||||
private native long getQuotedMsgCPtr ();
|
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.View.OnKeyListener;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
|
@ -836,6 +837,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
case DcMsg.DC_MSG_VIDEO:
|
case DcMsg.DC_MSG_VIDEO:
|
||||||
setMedia(uri, MediaType.VIDEO).addListener(listener);
|
setMedia(uri, MediaType.VIDEO).addListener(listener);
|
||||||
break;
|
break;
|
||||||
|
case DcMsg.DC_MSG_WEBXDC:
|
||||||
|
setMedia(draft, MediaType.DOCUMENT).addListener(listener);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
setMedia(uri, MediaType.DOCUMENT).addListener(listener);
|
setMedia(uri, MediaType.DOCUMENT).addListener(listener);
|
||||||
break;
|
break;
|
||||||
|
@ -1012,7 +1016,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
return new SettableFuture<>(false);
|
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) {
|
private void addAttachmentContactInfo(Intent data) {
|
||||||
|
@ -1029,7 +1037,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
return dcChat.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
|
return dcChat.getVisibility() == DcChat.DC_CHAT_VISIBILITY_ARCHIVED;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getRealPathFromAttachment(Attachment attachment) {
|
public static String getRealPathFromAttachment(Context context, Attachment attachment) {
|
||||||
try {
|
try {
|
||||||
// get file in the blobdir as `<blobdir>/<name>[-<uniqueNumber>].<ext>`
|
// get file in the blobdir as `<blobdir>/<name>[-<uniqueNumber>].<ext>`
|
||||||
String filename = attachment.getFileName();
|
String filename = attachment.getFileName();
|
||||||
|
@ -1045,11 +1053,11 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
filename = filename.substring(0, i);
|
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
|
// copy content to this file
|
||||||
if(path!=null) {
|
if(path!=null) {
|
||||||
InputStream inputStream = PartAuthority.getAttachmentStream(this, attachment.getDataUri());
|
InputStream inputStream = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
|
||||||
OutputStream outputStream = new FileOutputStream(path);
|
OutputStream outputStream = new FileOutputStream(path);
|
||||||
Util.copy(inputStream, outputStream);
|
Util.copy(inputStream, outputStream);
|
||||||
}
|
}
|
||||||
|
@ -1095,29 +1103,30 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<Attachment> attachments = slideDeck.asAttachments();
|
if (slideDeck.getWebxdctDraftId() != 0) {
|
||||||
for (Attachment attachment : attachments) {
|
msg = dcContext.getDraft(chatId);
|
||||||
String contentType = attachment.getContentType();
|
} else {
|
||||||
if (MediaUtil.isImageType(contentType) && slideDeck.getDocumentSlide()==null) {
|
List<Attachment> attachments = slideDeck.asAttachments();
|
||||||
msg = new DcMsg(dcContext,
|
for (Attachment attachment : attachments) {
|
||||||
MediaUtil.isGif(contentType) ? DcMsg.DC_MSG_GIF : DcMsg.DC_MSG_IMAGE);
|
String contentType = attachment.getContentType();
|
||||||
msg.setDimension(attachment.getWidth(), attachment.getHeight());
|
if (MediaUtil.isImageType(contentType) && slideDeck.getDocumentSlide() == null) {
|
||||||
|
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)) {
|
||||||
|
msg = new DcMsg(dcContext,
|
||||||
|
attachment.isVoiceNote() ? DcMsg.DC_MSG_VOICE : DcMsg.DC_MSG_AUDIO);
|
||||||
|
} else if (MediaUtil.isVideoType(contentType) && slideDeck.getDocumentSlide() == null) {
|
||||||
|
msg = new DcMsg(dcContext, DcMsg.DC_MSG_VIDEO);
|
||||||
|
recompress = DcMsg.DC_MSG_VIDEO;
|
||||||
|
} else {
|
||||||
|
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
|
||||||
|
}
|
||||||
|
String path = getRealPathFromAttachment(this, attachment);
|
||||||
|
msg.setFile(path, null);
|
||||||
}
|
}
|
||||||
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) {
|
|
||||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_VIDEO);
|
|
||||||
recompress = DcMsg.DC_MSG_VIDEO;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
msg = new DcMsg(dcContext, DcMsg.DC_MSG_FILE);
|
|
||||||
}
|
|
||||||
String path = getRealPathFromAttachment(attachment);
|
|
||||||
msg.setFile(path, null);
|
|
||||||
msg.setText(body);
|
|
||||||
}
|
}
|
||||||
|
msg.setText(body);
|
||||||
}
|
}
|
||||||
catch(Exception e) {
|
catch(Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -1140,7 +1149,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
DcMsg msg = (DcMsg)param[0];
|
DcMsg msg = (DcMsg)param[0];
|
||||||
Integer recompress = (Integer)param[1];
|
Integer recompress = (Integer)param[1];
|
||||||
if (action==ACTION_SEND_OUT) {
|
if (action==ACTION_SEND_OUT) {
|
||||||
dcContext.setDraft(dcChat.getId(), null);
|
|
||||||
|
// 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)
|
if(msg!=null)
|
||||||
{
|
{
|
||||||
|
@ -1369,7 +1383,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
|
||||||
private void sendSticker(@NonNull Uri uri, String contentType) {
|
private void sendSticker(@NonNull Uri uri, String contentType) {
|
||||||
Attachment attachment = new UriAttachment(uri, null, contentType,
|
Attachment attachment = new UriAttachment(uri, null, contentType,
|
||||||
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, null, null, false);
|
AttachmentDatabase.TRANSFER_PROGRESS_STARTED, 0, 0, 0, null, null, false);
|
||||||
String path = getRealPathFromAttachment(attachment);
|
String path = getRealPathFromAttachment(this, attachment);
|
||||||
|
|
||||||
Optional<QuoteModel> quote = inputPanel.getQuote();
|
Optional<QuoteModel> quote = inputPanel.getQuote();
|
||||||
inputPanel.clearQuote();
|
inputPanel.clearQuote();
|
||||||
|
|
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.components.ConversationItemFooter;
|
||||||
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
import org.thoughtcrime.securesms.components.ConversationItemThumbnail;
|
||||||
import org.thoughtcrime.securesms.components.DocumentView;
|
import org.thoughtcrime.securesms.components.DocumentView;
|
||||||
import org.thoughtcrime.securesms.components.QuoteView;
|
import org.thoughtcrime.securesms.components.QuoteView;
|
||||||
|
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||||
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
import org.thoughtcrime.securesms.components.emoji.EmojiTextView;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
import org.thoughtcrime.securesms.mms.AudioSlide;
|
import org.thoughtcrime.securesms.mms.AudioSlide;
|
||||||
|
@ -107,6 +108,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
private @NonNull Stub<ConversationItemThumbnail> mediaThumbnailStub;
|
||||||
private @NonNull Stub<AudioView> audioViewStub;
|
private @NonNull Stub<AudioView> audioViewStub;
|
||||||
private @NonNull Stub<DocumentView> documentViewStub;
|
private @NonNull Stub<DocumentView> documentViewStub;
|
||||||
|
private @NonNull Stub<WebxdcView> webxdcViewStub;
|
||||||
private Stub<BorderlessImageView> stickerStub;
|
private Stub<BorderlessImageView> stickerStub;
|
||||||
private @Nullable EventListener eventListener;
|
private @Nullable EventListener eventListener;
|
||||||
|
|
||||||
|
@ -139,6 +141,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub));
|
this.mediaThumbnailStub = new Stub<>(findViewById(R.id.image_view_stub));
|
||||||
this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub));
|
this.audioViewStub = new Stub<>(findViewById(R.id.audio_view_stub));
|
||||||
this.documentViewStub = new Stub<>(findViewById(R.id.document_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.stickerStub = new Stub<>(findViewById(R.id.sticker_view_stub));
|
||||||
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
this.groupSenderHolder = findViewById(R.id.group_sender_holder);
|
||||||
this.quoteView = findViewById(R.id.quote_view);
|
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().setFocusable(!shouldInterceptClicks(messageRecord) && batchSelected.isEmpty());
|
||||||
documentViewStub.get().setClickable(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) {
|
private boolean hasAudio(DcMsg messageRecord) {
|
||||||
|
@ -313,9 +321,14 @@ public class ConversationItem extends BaseConversationItem
|
||||||
return hasThumbnail(messageRecord) &&
|
return hasThumbnail(messageRecord) &&
|
||||||
!hasAudio(messageRecord) &&
|
!hasAudio(messageRecord) &&
|
||||||
!hasDocument(messageRecord) &&
|
!hasDocument(messageRecord) &&
|
||||||
|
!hasWebxdc(messageRecord) &&
|
||||||
!hasSticker(messageRecord);
|
!hasSticker(messageRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean hasWebxdc(DcMsg dcMsg) {
|
||||||
|
return dcMsg.getType()==DcMsg.DC_MSG_WEBXDC;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasDocument(DcMsg dcMsg) {
|
private boolean hasDocument(DcMsg dcMsg) {
|
||||||
return dcMsg.getType()==DcMsg.DC_MSG_FILE && !dcMsg.isSetupMessage();
|
return dcMsg.getType()==DcMsg.DC_MSG_FILE && !dcMsg.isSetupMessage();
|
||||||
}
|
}
|
||||||
|
@ -361,6 +374,17 @@ public class ConversationItem extends BaseConversationItem
|
||||||
passthroughClickListener.onClick(view);
|
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()) {
|
else if (messageRecord.hasHtml()) {
|
||||||
msgActionButton.setVisibility(View.VISIBLE);
|
msgActionButton.setVisibility(View.VISIBLE);
|
||||||
|
@ -401,6 +425,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
audioViewStub.get().setVisibility(View.VISIBLE);
|
audioViewStub.get().setVisibility(View.VISIBLE);
|
||||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.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);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
|
@ -422,6 +447,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
documentViewStub.get().setVisibility(View.VISIBLE);
|
documentViewStub.get().setVisibility(View.VISIBLE);
|
||||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
if (audioViewStub.resolved()) audioViewStub.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);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
//noinspection ConstantConditions
|
//noinspection ConstantConditions
|
||||||
|
@ -433,10 +459,26 @@ public class ConversationItem extends BaseConversationItem
|
||||||
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
ViewUtil.updateLayoutParams(groupSenderHolder, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
footer.setVisibility(VISIBLE);
|
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)) {
|
else if (hasThumbnail(messageRecord)) {
|
||||||
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
mediaThumbnailStub.get().setVisibility(View.VISIBLE);
|
||||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.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);
|
if (stickerStub.resolved()) stickerStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
Slide slide = MediaUtil.getSlideForMsg(context, messageRecord);
|
Slide slide = MediaUtil.getSlideForMsg(context, messageRecord);
|
||||||
|
@ -473,6 +515,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
stickerStub.get().setVisibility(View.VISIBLE);
|
stickerStub.get().setVisibility(View.VISIBLE);
|
||||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.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);
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
|
|
||||||
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
|
bodyBubble.setBackgroundColor(Color.TRANSPARENT);
|
||||||
|
@ -492,6 +535,7 @@ public class ConversationItem extends BaseConversationItem
|
||||||
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
if (mediaThumbnailStub.resolved()) mediaThumbnailStub.get().setVisibility(View.GONE);
|
||||||
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
if (audioViewStub.resolved()) audioViewStub.get().setVisibility(View.GONE);
|
||||||
if (documentViewStub.resolved()) documentViewStub.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(bodyText, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||||
ViewUtil.updateLayoutParams(groupSenderHolder, 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) {
|
public void onClick(final View v, final Slide slide) {
|
||||||
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
if (shouldInterceptClicks(messageRecord) || !batchSelected.isEmpty()) {
|
||||||
performClick();
|
performClick();
|
||||||
|
} else if (messageRecord.getType() == DcMsg.DC_MSG_WEBXDC) {
|
||||||
|
WebxdcActivity.openWebxdcActivity(context, messageRecord);
|
||||||
} else if (MediaPreviewActivity.isTypeSupported(slide) && slide.getUri() != null) {
|
} else if (MediaPreviewActivity.isTypeSupported(slide) && slide.getUri() != null) {
|
||||||
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
Intent intent = new Intent(context, MediaPreviewActivity.class);
|
||||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
|
|
@ -103,7 +103,7 @@ public class ProfileDocumentsFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Loader<BucketedThreadMediaLoader.BucketedThreadMedia> onCreateLoader(int i, Bundle bundle) {
|
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
|
@Override
|
||||||
|
|
|
@ -9,11 +9,14 @@ import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
import android.webkit.WebResourceRequest;
|
||||||
|
import android.webkit.WebResourceResponse;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
import android.webkit.WebViewClient;
|
import android.webkit.WebViewClient;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.widget.SearchView;
|
import androidx.appcompat.widget.SearchView;
|
||||||
import androidx.webkit.WebSettingsCompat;
|
import androidx.webkit.WebSettingsCompat;
|
||||||
import androidx.webkit.WebViewFeature;
|
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.
|
// if we come over really useful things, we should allow that explicitly.
|
||||||
return true;
|
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);
|
webView.setFindListener(this);
|
||||||
|
|
||||||
|
@ -245,6 +268,10 @@ public class WebViewActivity extends PassphraseRequiredActionBarActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected WebResourceResponse interceptRequest(String url) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
|
public void onActivityResult(int reqCode, int resultCode, final Intent data) {
|
||||||
super.onActivityResult(reqCode, resultCode, 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.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.b44t.messenger.DcContext;
|
||||||
|
import com.b44t.messenger.DcMsg;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.ConversationActivity;
|
||||||
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
import org.thoughtcrime.securesms.MediaPreviewActivity;
|
||||||
import org.thoughtcrime.securesms.R;
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.ShareLocationDialog;
|
import org.thoughtcrime.securesms.ShareLocationDialog;
|
||||||
|
import org.thoughtcrime.securesms.WebxdcActivity;
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
import org.thoughtcrime.securesms.attachments.UriAttachment;
|
||||||
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
|
||||||
import org.thoughtcrime.securesms.components.AudioView;
|
import org.thoughtcrime.securesms.components.AudioView;
|
||||||
import org.thoughtcrime.securesms.components.DocumentView;
|
import org.thoughtcrime.securesms.components.DocumentView;
|
||||||
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
import org.thoughtcrime.securesms.components.RemovableEditableMediaView;
|
||||||
import org.thoughtcrime.securesms.components.ThumbnailView;
|
import org.thoughtcrime.securesms.components.ThumbnailView;
|
||||||
|
import org.thoughtcrime.securesms.components.WebxdcView;
|
||||||
import org.thoughtcrime.securesms.connect.DcHelper;
|
import org.thoughtcrime.securesms.connect.DcHelper;
|
||||||
|
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||||
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
import org.thoughtcrime.securesms.geolocation.DcLocationManager;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
import org.thoughtcrime.securesms.providers.PersistentBlobProvider;
|
||||||
|
@ -84,6 +92,7 @@ public class AttachmentManager {
|
||||||
private ThumbnailView thumbnail;
|
private ThumbnailView thumbnail;
|
||||||
private AudioView audioView;
|
private AudioView audioView;
|
||||||
private DocumentView documentView;
|
private DocumentView documentView;
|
||||||
|
private WebxdcView webxdcView;
|
||||||
//private SignalMapView mapView;
|
//private SignalMapView mapView;
|
||||||
|
|
||||||
private @NonNull List<Uri> garbage = new LinkedList<>();
|
private @NonNull List<Uri> garbage = new LinkedList<>();
|
||||||
|
@ -104,6 +113,7 @@ public class AttachmentManager {
|
||||||
this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail);
|
this.thumbnail = ViewUtil.findById(root, R.id.attachment_thumbnail);
|
||||||
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
|
this.audioView = ViewUtil.findById(root, R.id.attachment_audio);
|
||||||
this.documentView = ViewUtil.findById(root, R.id.attachment_document);
|
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.mapView = ViewUtil.findById(root, R.id.attachment_location);
|
||||||
this.removableMediaView = ViewUtil.findById(root, R.id.removable_media_view);
|
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);
|
int incomingBubbleColor = ThemeUtil.getThemedColor(context, R.attr.conversation_item_incoming_bubble_color);
|
||||||
audioView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
audioView.getBackground().setColorFilter(incomingBubbleColor, PorterDuff.Mode.MULTIPLY);
|
||||||
documentView.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")
|
@SuppressLint("StaticFieldLeak")
|
||||||
public ListenableFuture<Boolean> setMedia(@NonNull final GlideRequests glideRequests,
|
public ListenableFuture<Boolean> setMedia(@NonNull final GlideRequests glideRequests,
|
||||||
@NonNull final Uri uri,
|
@NonNull final Uri uri,
|
||||||
|
@Nullable final DcMsg msg,
|
||||||
@NonNull final MediaType mediaType,
|
@NonNull final MediaType mediaType,
|
||||||
final int width,
|
final int width,
|
||||||
final int height)
|
final int height,
|
||||||
|
final int chatId)
|
||||||
{
|
{
|
||||||
inflateStub();
|
inflateStub();
|
||||||
|
|
||||||
|
@ -238,10 +251,13 @@ public class AttachmentManager {
|
||||||
@Override
|
@Override
|
||||||
protected @Nullable Slide doInBackground(Void... params) {
|
protected @Nullable Slide doInBackground(Void... params) {
|
||||||
try {
|
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);
|
return getManuallyCalculatedSlideInfo(uri, width, height);
|
||||||
} else {
|
} else {
|
||||||
Slide result = getContentResolverSlideInfo(uri, width, height);
|
Slide result = getContentResolverSlideInfo(uri, width, height, chatId);
|
||||||
|
|
||||||
if (result == null) return getManuallyCalculatedSlideInfo(uri, width, height);
|
if (result == null) return getManuallyCalculatedSlideInfo(uri, width, height);
|
||||||
else return result;
|
else return result;
|
||||||
|
@ -291,8 +307,17 @@ public class AttachmentManager {
|
||||||
removableMediaView.display(audioView, false);
|
removableMediaView.display(audioView, false);
|
||||||
result.set(true);
|
result.set(true);
|
||||||
} else if (slide.hasDocument()) {
|
} else if (slide.hasDocument()) {
|
||||||
documentView.setDocument((DocumentSlide) slide);
|
if (slide.isWebxdcDocument()) {
|
||||||
removableMediaView.display(documentView, false);
|
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);
|
result.set(true);
|
||||||
} else {
|
} else {
|
||||||
Attachment attachment = slide.asAttachment();
|
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;
|
Cursor cursor = null;
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
|
|
||||||
|
@ -323,7 +348,7 @@ public class AttachmentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.w(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms");
|
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 {
|
} finally {
|
||||||
if (cursor != null) cursor.close();
|
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");
|
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);
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
|
||||||
|
@ -598,7 +623,8 @@ public class AttachmentManager {
|
||||||
@Nullable String mimeType,
|
@Nullable String mimeType,
|
||||||
long dataSize,
|
long dataSize,
|
||||||
int width,
|
int width,
|
||||||
int height)
|
int height,
|
||||||
|
int chatId)
|
||||||
{
|
{
|
||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
mimeType = "application/octet-stream";
|
mimeType = "application/octet-stream";
|
||||||
|
@ -609,7 +635,20 @@ public class AttachmentManager {
|
||||||
case GIF: return new GifSlide(context, uri, dataSize, width, height);
|
case GIF: return new GifSlide(context, uri, dataSize, width, height);
|
||||||
case AUDIO: return new AudioSlide(context, uri, dataSize, false, fileName);
|
case AUDIO: return new AudioSlide(context, uri, dataSize, false, fileName);
|
||||||
case VIDEO: return new VideoSlide(context, uri, dataSize);
|
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");
|
default: throw new AssertionError("unrecognized enum");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,12 @@ import org.thoughtcrime.securesms.attachments.DcAttachment;
|
||||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||||
|
|
||||||
public class DocumentSlide extends Slide {
|
public class DocumentSlide extends Slide {
|
||||||
|
private int dcMsgType = DcMsg.DC_MSG_UNDEFINED;
|
||||||
|
|
||||||
public DocumentSlide(Context context, DcMsg dcMsg) {
|
public DocumentSlide(Context context, DcMsg dcMsg) {
|
||||||
super(context, new DcAttachment(dcMsg));
|
super(context, new DcAttachment(dcMsg));
|
||||||
dcMsgId = dcMsg.getId();
|
dcMsgId = dcMsg.getId();
|
||||||
|
dcMsgType = dcMsg.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
public DocumentSlide(@NonNull Context context, @NonNull Uri uri,
|
public DocumentSlide(@NonNull Context context, @NonNull Uri uri,
|
||||||
|
@ -31,4 +33,8 @@ public class DocumentSlide extends Slide {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isWebxdcDocument() {
|
||||||
|
return dcMsgType == DcMsg.DC_MSG_WEBXDC;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,6 +94,10 @@ public abstract class Slide {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isWebxdcDocument() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean hasLocation() {
|
public boolean hasLocation() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,8 @@ package org.thoughtcrime.securesms.mms;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.b44t.messenger.DcMsg;
|
||||||
|
|
||||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
@ -72,4 +74,14 @@ public class SlideDeck {
|
||||||
|
|
||||||
return null;
|
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 AUDIO_UNSPECIFIED = "audio/*";
|
||||||
public static final String VIDEO_UNSPECIFIED = "video/*";
|
public static final String VIDEO_UNSPECIFIED = "video/*";
|
||||||
public static final String OCTET = "application/octet-stream";
|
public static final String OCTET = "application/octet-stream";
|
||||||
|
public static final String WEBXDC = "application/webxdc+zip";
|
||||||
|
|
||||||
|
|
||||||
public static Slide getSlideForMsg(Context context, DcMsg dcMsg) {
|
public static Slide getSlideForMsg(Context context, DcMsg dcMsg) {
|
||||||
|
@ -62,7 +63,8 @@ public class MediaUtil {
|
||||||
} else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO
|
} else if (dcMsg.getType() == DcMsg.DC_MSG_AUDIO
|
||||||
|| dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
|
|| dcMsg.getType() == DcMsg.DC_MSG_VOICE) {
|
||||||
slide = new AudioSlide(context, dcMsg);
|
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);
|
slide = new DocumentSlide(context, dcMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -259,6 +261,8 @@ public class MediaUtil {
|
||||||
return "aac";
|
return "aac";
|
||||||
case IMAGE_WEBP:
|
case IMAGE_WEBP:
|
||||||
return "webp";
|
return "webp";
|
||||||
|
case WEBXDC:
|
||||||
|
return "xdc";
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue