diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 63d1e119d..3dbff5663 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -389,6 +389,11 @@ android:exported="true"> + + + diff --git a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java index d9524d2fe..762e46a3f 100644 --- a/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java +++ b/src/main/java/org/thoughtcrime/securesms/ConversationActivity.java @@ -160,8 +160,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private static final int GROUP_EDIT = 6; private static final int TAKE_PHOTO = 7; private static final int RECORD_VIDEO = 8; - private static final int PICK_LOCATION = 9; // TODO: i think, this can be deleted - private static final int SMS_DEFAULT = 11; // TODO: i think, this can be deleted + private static final int PICK_WEBXDC = 9; private GlideRequests glideRequests; protected ComposeText composeText; @@ -335,8 +334,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity public void onActivityResult(final int reqCode, int resultCode, Intent data) { super.onActivityResult(reqCode, resultCode, data); - if ((data == null && reqCode != TAKE_PHOTO && reqCode != RECORD_VIDEO && reqCode != SMS_DEFAULT) || - (resultCode != RESULT_OK && reqCode != SMS_DEFAULT)) + if (resultCode != RESULT_OK || (data == null && reqCode != TAKE_PHOTO && reqCode != RECORD_VIDEO)) { return; } @@ -372,6 +370,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity setMedia(data.getData(), docMediaType); break; + case PICK_WEBXDC: + setMedia(data.getData(), MediaType.DOCUMENT); + break; + case PICK_CONTACT: addAttachmentContactInfo(data.getIntExtra(AttachContactActivity.CONTACT_ID_EXTRA, 0)); break; @@ -399,16 +401,9 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } break; - case PICK_LOCATION: - break; - case ScribbleActivity.SCRIBBLE_REQUEST_CODE: setMedia(data.getData(), MediaType.IMAGE); break; - - case SMS_DEFAULT: - initializeSecurity(isSecureText, isDefaultSms); - break; } } @@ -931,6 +926,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity case AttachmentTypeSelector.RECORD_VIDEO: attachmentManager.captureVideo(this, RECORD_VIDEO); break; + case AttachmentTypeSelector.ADD_WEBXDC: + AttachmentManager.selectWebxdc(this, PICK_WEBXDC); break; } } diff --git a/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java b/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java new file mode 100644 index 000000000..5c3ae66e1 --- /dev/null +++ b/src/main/java/org/thoughtcrime/securesms/WebxdcStoreActivity.java @@ -0,0 +1,136 @@ +package org.thoughtcrime.securesms; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.MenuItem; +import android.webkit.WebResourceRequest; +import android.webkit.WebResourceResponse; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.Toast; + +import androidx.appcompat.app.ActionBar; + +import com.b44t.messenger.DcContext; +import com.b44t.messenger.rpc.HttpResponse; +import com.b44t.messenger.rpc.Rpc; +import com.b44t.messenger.rpc.RpcException; + +import org.thoughtcrime.securesms.connect.DcHelper; +import org.thoughtcrime.securesms.providers.PersistentBlobProvider; +import org.thoughtcrime.securesms.util.MediaUtil; +import org.thoughtcrime.securesms.util.Prefs; + +import java.io.ByteArrayInputStream; +import java.util.HashMap; + +public class WebxdcStoreActivity extends PassphraseRequiredActionBarActivity { + private static final String TAG = WebxdcStoreActivity.class.getSimpleName(); + + private DcContext dcContext; + private Rpc rpc; + + @Override + protected void onCreate(Bundle state, boolean ready) { + setContentView(R.layout.web_view_activity); + rpc = DcHelper.getRpc(this); + dcContext = DcHelper.getContext(this); + WebView webView = findViewById(R.id.webview); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setTitle(R.string.webxdc_apps); + } + + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + String ext = MediaUtil.getFileExtensionFromUrl(Uri.parse(url).getPath()); + if ("xdc".equals(ext)) { + try { + HttpResponse httpResponse = rpc.getHttpResponse(dcContext.getAccountId(), url); + Uri uri = PersistentBlobProvider.getInstance().create(WebxdcStoreActivity.this, httpResponse.getBlob(), "application/octet-stream", "app.xdc"); + Intent intent = new Intent(); + intent.setData(uri); + setResult(Activity.RESULT_OK, intent); + finish(); + } catch (RpcException e) { + e.printStackTrace(); + Toast.makeText(WebxdcStoreActivity.this, "Error: " + e.getMessage(), Toast.LENGTH_LONG).show(); + } + } else { + WebViewActivity.openUrlInBrowser(WebxdcStoreActivity.this, url); + } + return true; + } + + @TargetApi(Build.VERSION_CODES.N) + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + return shouldOverrideUrlLoading(view, request.getUrl().toString()); + } + + @Override + public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) { + return interceptRequest(request.getUrl().toString()); + } + }); + + WebSettings webSettings = webView.getSettings(); + webSettings.setJavaScriptEnabled(true); + webSettings.setAllowFileAccess(false); + webSettings.setAllowContentAccess(false); + webSettings.setGeolocationEnabled(false); + webSettings.setAllowFileAccessFromFileURLs(false); + webSettings.setAllowUniversalAccessFromFileURLs(false); + webSettings.setDatabaseEnabled(true); + webSettings.setDomStorageEnabled(true); + webView.setNetworkAvailable(true); // this does not block network but sets `window.navigator.isOnline` in js land + + webView.loadUrl(Prefs.getWebxdcStoreUrl(this)); + } + + private WebResourceResponse interceptRequest(String url) { + WebResourceResponse res = null; + try { + if (url == null) { + throw new Exception("no url specified"); + } + HttpResponse httpResponse = rpc.getHttpResponse(dcContext.getAccountId(), url); + String mimeType = httpResponse.getMimetype(); + if (mimeType == null) { + mimeType = "application/octet-stream"; + } + + ByteArrayInputStream data = new ByteArrayInputStream(httpResponse.getBlob()); + res = new WebResourceResponse(mimeType, httpResponse.getEncoding(), data); + } catch (Exception e) { + e.printStackTrace(); + ByteArrayInputStream data = new ByteArrayInputStream(("Could not load apps. Are you online?\n\n" + e.getMessage()).getBytes()); + res = new WebResourceResponse("text/plain", "UTF-8", data); + } + + HashMap headers = new HashMap<>(); + headers.put("access-control-allow-origin", "*"); + res.setResponseHeaders(headers); + return res; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + super.onOptionsItemSelected(item); + switch (item.getItemId()) { + case android.R.id.home: + finish(); + return true; + } + return false; + } + +} diff --git a/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java b/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java index c2c517e08..fcbce7eb2 100644 --- a/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java +++ b/src/main/java/org/thoughtcrime/securesms/components/AttachmentTypeSelector.java @@ -41,6 +41,7 @@ public class AttachmentTypeSelector extends PopupWindow { public static final int TAKE_PHOTO = 5; public static final int ADD_LOCATION = 6; public static final int RECORD_VIDEO = 7; + public static final int ADD_WEBXDC = 8; private static final int ANIMATION_DURATION = 300; @@ -53,7 +54,7 @@ public class AttachmentTypeSelector extends PopupWindow { //private final @NonNull ImageView cameraButton; private final @NonNull ImageView videoButton; private final @NonNull ImageView locationButton; - private final @NonNull ImageView closeButton; + private final @NonNull ImageView webxdcButton; private @Nullable View currentAnchor; private @Nullable AttachmentClickedListener listener; @@ -76,7 +77,7 @@ public class AttachmentTypeSelector extends PopupWindow { //this.cameraButton = ViewUtil.findById(layout, R.id.camera_button); this.videoButton = ViewUtil.findById(layout, R.id.record_video_button); this.locationButton = ViewUtil.findById(layout, R.id.location_button); - this.closeButton = ViewUtil.findById(layout, R.id.close_button); + this.webxdcButton = ViewUtil.findById(layout, R.id.webxdc_button); this.imageButton.setOnClickListener(new PropagatingClickListener(ADD_GALLERY)); this.videoChatButton.setOnClickListener(new PropagatingClickListener(INVITE_VIDEO_CHAT)); @@ -85,7 +86,7 @@ public class AttachmentTypeSelector extends PopupWindow { //this.cameraButton.setOnClickListener(new PropagatingClickListener(TAKE_PHOTO)); this.videoButton.setOnClickListener(new PropagatingClickListener(RECORD_VIDEO)); this.locationButton.setOnClickListener(new PropagatingClickListener(ADD_LOCATION)); - this.closeButton.setOnClickListener(new CloseClickListener()); + this.webxdcButton.setOnClickListener(new PropagatingClickListener(ADD_WEBXDC)); this.recentRail.setListener(new RecentPhotoSelectedListener()); if (!Prefs.isLocationStreamingEnabled(context)) { @@ -140,8 +141,8 @@ public class AttachmentTypeSelector extends PopupWindow { animateButtonIn(contactButton, ANIMATION_DURATION / 3); animateButtonIn(locationButton, ANIMATION_DURATION / 4); animateButtonIn(documentButton, ANIMATION_DURATION / 4); + animateButtonIn(webxdcButton, 0); animateButtonIn(videoChatButton, 0); - animateButtonIn(closeButton, 0); } @Override @@ -289,13 +290,6 @@ public class AttachmentTypeSelector extends PopupWindow { } - private class CloseClickListener implements View.OnClickListener { - @Override - public void onClick(View v) { - dismiss(); - } - } - public interface AttachmentClickedListener { public void onClick(int type); public void onQuickAttachment(Uri uri); diff --git a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java index e70ce5a09..b8ebf4cfb 100644 --- a/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/main/java/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -50,6 +50,7 @@ import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.ShareLocationDialog; +import org.thoughtcrime.securesms.WebxdcStoreActivity; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.UriAttachment; import org.thoughtcrime.securesms.audio.AudioSlidePlayer; @@ -451,6 +452,11 @@ public class AttachmentManager { selectMediaType(activity, "*/*", null, requestCode); } + public static void selectWebxdc(Activity activity, int requestCode) { + Intent intent = new Intent(activity, WebxdcStoreActivity.class); + activity.startActivityForResult(intent, requestCode); + } + public static void selectGallery(Activity activity, int requestCode) { // to enable camera roll, // we're asking for "gallery permissions" also on newer systems that do not strictly require that. diff --git a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java index eb2fd6f92..48319922e 100644 --- a/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java +++ b/src/main/java/org/thoughtcrime/securesms/preferences/AdvancedPreferenceFragment.java @@ -42,6 +42,7 @@ import org.thoughtcrime.securesms.connect.DcEventCenter; import org.thoughtcrime.securesms.connect.DcHelper; import org.thoughtcrime.securesms.mms.AttachmentManager; import org.thoughtcrime.securesms.permissions.Permissions; +import org.thoughtcrime.securesms.util.Prefs; import org.thoughtcrime.securesms.util.ScreenLockUtil; import org.thoughtcrime.securesms.util.StorageUtil; import org.thoughtcrime.securesms.util.StreamUtil; @@ -134,6 +135,10 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment webrtcInstance.setOnPreferenceClickListener(new WebrtcInstanceListener()); updateWebrtcSummary(); + Preference webxdcStore = this.findPreference(Prefs.WEBXDC_STORE_URL_PREF); + webxdcStore.setOnPreferenceClickListener(new WebxdcStoreUrlListener()); + updateWebxdcStoreSummary(); + Preference developerModeEnabled = this.findPreference("pref_developer_mode_enabled"); developerModeEnabled.setOnPreferenceChangeListener((preference, newValue) -> { WebView.setWebContentsDebuggingEnabled((Boolean) newValue); @@ -280,6 +285,29 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment } } + private class WebxdcStoreUrlListener implements Preference.OnPreferenceClickListener { + @Override + public boolean onPreferenceClick(Preference preference) { + View gl = View.inflate(getActivity(), R.layout.single_line_input, null); + EditText inputField = gl.findViewById(R.id.input_field); + inputField.setHint(Prefs.DEFAULT_WEBXDC_STORE_URL); + inputField.setText(Prefs.getWebxdcStoreUrl(getActivity())); + inputField.setSelection(inputField.getText().length()); + inputField.setInputType(TYPE_TEXT_VARIATION_URI); + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.webxdc_store_url) + .setMessage(R.string.webxdc_store_url_explain) + .setView(gl) + .setNegativeButton(android.R.string.cancel, null) + .setPositiveButton(android.R.string.ok, (dlg, btn) -> { + Prefs.setWebxdcStoreUrl(getActivity(), inputField.getText().toString()); + updateWebxdcStoreSummary(); + }) + .show(); + return true; + } + } + private void updateWebrtcSummary() { Preference webrtcInstance = this.findPreference("pref_webrtc_instance"); if (webrtcInstance != null) { @@ -288,6 +316,13 @@ public class AdvancedPreferenceFragment extends ListSummaryPreferenceFragment } } + private void updateWebxdcStoreSummary() { + Preference preference = this.findPreference(Prefs.WEBXDC_STORE_URL_PREF); + if (preference != null) { + preference.setSummary(Prefs.getWebxdcStoreUrl(getActivity())); + } + } + private void openRegistrationActivity() { Intent intent = new Intent(getActivity(), RegistrationActivity.class); startActivity(intent); diff --git a/src/main/java/org/thoughtcrime/securesms/util/Prefs.java b/src/main/java/org/thoughtcrime/securesms/util/Prefs.java index bf6b2f97a..f0bdc039b 100644 --- a/src/main/java/org/thoughtcrime/securesms/util/Prefs.java +++ b/src/main/java/org/thoughtcrime/securesms/util/Prefs.java @@ -62,6 +62,8 @@ public class Prefs { public static final boolean ALWAYS_LOAD_REMOTE_CONTENT_DEFAULT = false; public static final String LAST_DEVICE_MSG_LABEL = "pref_last_device_msg_id"; + public static final String WEBXDC_STORE_URL_PREF = "pref_webxdc_store_url"; + public static final String DEFAULT_WEBXDC_STORE_URL = "https://webxdc.org/apps/"; public enum VibrateState { DEFAULT(0), ENABLED(1), DISABLED(2); @@ -144,6 +146,15 @@ public class Prefs { return getStringPreference(context, THEME_PREF, DynamicTheme.systemThemeAvailable() ? DynamicTheme.SYSTEM : DynamicTheme.LIGHT); } + public static String getWebxdcStoreUrl(Context context) { + return getStringPreference(context, WEBXDC_STORE_URL_PREF, DEFAULT_WEBXDC_STORE_URL); + } + + public static void setWebxdcStoreUrl(Context context, String url) { + if (url == null || url.trim().isEmpty() || DEFAULT_WEBXDC_STORE_URL.equals(url)) url = null; + setStringPreference(context, WEBXDC_STORE_URL_PREF, url); + } + public static void setPromptedDozeMsgId(Context context, int msg_id) { setIntegerPreference(context, PROMPTED_DOZE_MSG_ID_PREF, msg_id); } diff --git a/src/main/res/drawable/baseline_apps_24.xml b/src/main/res/drawable/baseline_apps_24.xml new file mode 100644 index 000000000..18dae5c30 --- /dev/null +++ b/src/main/res/drawable/baseline_apps_24.xml @@ -0,0 +1,3 @@ + + + diff --git a/src/main/res/layout/attachment_type_selector.xml b/src/main/res/layout/attachment_type_selector.xml index f74b01408..b521f9049 100644 --- a/src/main/res/layout/attachment_type_selector.xml +++ b/src/main/res/layout/attachment_type_selector.xml @@ -78,58 +78,50 @@ - + + + + + + + + + android:id="@+id/webxdc_button" + android:layout_width="53dp" + android:layout_height="53dp" + android:src="@drawable/baseline_apps_24" + android:scaleType="center" + android:contentDescription="@string/webxdc_apps" + app:circleColor="@color/apps_icon" + /> - - - - - - - - + android:text="@string/webxdc_apps"/> @@ -169,26 +161,30 @@ - + android:visibility="visible" + android:contentDescription="@string/location" + app:circleColor="@color/location_icon"/> + android:visibility="visible" + style="@style/AttachmentTypeLabel" + android:text="@string/location"/> @@ -216,30 +212,14 @@ - - - - - - + + - diff --git a/src/main/res/values/core_colors.xml b/src/main/res/values/core_colors.xml index 8b999a404..1c91c5e66 100644 --- a/src/main/res/values/core_colors.xml +++ b/src/main/res/values/core_colors.xml @@ -13,6 +13,7 @@ #608496 #a47ad9 #66BB6A + #77bdc4 #ffffff #F9FAFA diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2168f54fc..070912c84 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -190,6 +190,8 @@ App Apps + App Picker URL + If set, the URL will be used as the App Picker instead of the default one Unknown Green diff --git a/src/main/res/xml/preferences_advanced.xml b/src/main/res/xml/preferences_advanced.xml index d87fdd31d..479ab2333 100644 --- a/src/main/res/xml/preferences_advanced.xml +++ b/src/main/res/xml/preferences_advanced.xml @@ -26,6 +26,10 @@ android:title="@string/videochat_instance" android:summary="@string/none"/> + +