1/*
2 * Copyright (c) 2018, Daniel Gultsch All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without modification,
5 * are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice, this
8 * list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright notice,
11 * this list of conditions and the following disclaimer in the documentation and/or
12 * other materials provided with the distribution.
13 *
14 * 3. Neither the name of the copyright holder nor the names of its contributors
15 * may be used to endorse or promote products derived from this software without
16 * specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30package eu.siacs.conversations.ui.util;
31
32import android.content.ActivityNotFoundException;
33import android.content.ClipboardManager;
34import android.content.ClipData;
35import android.content.Context;
36import android.content.Intent;
37import android.net.Uri;
38import android.text.SpannableStringBuilder;
39import android.text.style.URLSpan;
40import android.widget.Toast;
41
42import java.util.regex.Matcher;
43
44import android.os.Build;
45import android.widget.Toast;
46import androidx.annotation.StringRes;
47import com.google.common.collect.Iterables;
48import de.gultsch.common.Linkify;
49import eu.siacs.conversations.R;
50import eu.siacs.conversations.entities.DownloadableFile;
51import eu.siacs.conversations.entities.Message;
52import eu.siacs.conversations.persistance.FileBackend;
53import eu.siacs.conversations.ui.ConversationsActivity;
54import eu.siacs.conversations.ui.XmppActivity;
55import eu.siacs.conversations.utils.XmppUri;
56import eu.siacs.conversations.xmpp.Jid;
57import java.util.Arrays;
58import java.util.Collection;
59
60public class ShareUtil {
61
62 private static final Collection<String> SCHEMES_COPY_PATH_ONLY =
63 Arrays.asList("xmpp", "mailto", "tel");
64
65
66 public static void share(XmppActivity activity, Message message) {
67 Intent shareIntent = new Intent();
68 shareIntent.setAction(Intent.ACTION_SEND);
69 if (message.isGeoUri()) {
70 shareIntent.putExtra(Intent.EXTRA_TEXT, message.getRawBody());
71 shareIntent.setType("text/plain");
72 } else if (!message.isFileOrImage()) {
73 shareIntent.putExtra(Intent.EXTRA_TEXT, message.getQuoteableBody());
74 shareIntent.setType("text/plain");
75 shareIntent.putExtra(
76 ConversationsActivity.EXTRA_AS_QUOTE,
77 message.getStatus() == Message.STATUS_RECEIVED);
78 } else {
79 final DownloadableFile file =
80 activity.xmppConnectionService.getFileBackend().getFile(message);
81 final var fp = message.getFileParams();
82 final var name = fp == null ? null : fp.getName();
83 final var displayName = name == null ? file.getName() : name;
84 try {
85 shareIntent.putExtra(
86 Intent.EXTRA_STREAM, FileBackend.getUriForFile(activity, file, displayName));
87 } catch (SecurityException e) {
88 Toast.makeText(
89 activity,
90 activity.getString(
91 R.string.no_permission_to_access_x, file.getAbsolutePath()),
92 Toast.LENGTH_SHORT)
93 .show();
94 return;
95 }
96 shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
97 String mime = message.getMimeType();
98 if (mime == null) {
99 mime = "*/*";
100 }
101 shareIntent.setType(mime);
102 }
103 try {
104 activity.startActivity(
105 Intent.createChooser(shareIntent, activity.getText(R.string.share_with)));
106 } catch (ActivityNotFoundException e) {
107 // This should happen only on faulty androids because normally chooser is always
108 // available
109 Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT)
110 .show();
111 }
112 }
113
114 public static void copyToClipboard(final XmppActivity activity, final Message message) {
115 if (activity.copyTextToClipboard(message.getQuoteableBody(), R.string.message)
116 && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
117 Toast.makeText(activity, R.string.message_copied_to_clipboard, Toast.LENGTH_SHORT)
118 .show();
119 }
120 }
121
122 public static boolean copyTextToClipboard(Context context, String text, int labelResId) {
123 ClipboardManager mClipBoardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
124 String label = context.getResources().getString(labelResId);
125 if (mClipBoardManager != null) {
126 ClipData mClipData = ClipData.newPlainText(label, text);
127 mClipBoardManager.setPrimaryClip(mClipData);
128 return true;
129 }
130 return false;
131 }
132
133 public static void copyUrlToClipboard(final XmppActivity activity, final Message message) {
134 final String url;
135 final int resId;
136 if (message.isGeoUri()) {
137 resId = R.string.location;
138 url = message.getRawBody();
139 } else if (message.hasFileOnRemoteHost()) {
140 resId = R.string.file_url;
141 url = message.getFileParams().url;
142 } else {
143 final Message.FileParams fileParams = message.getFileParams();
144 url =
145 (fileParams != null && fileParams.url != null)
146 ? fileParams.url
147 : message.getRawBody().trim();
148 resId = R.string.file_url;
149 }
150 if (activity.copyTextToClipboard(url, resId)
151 && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
152 Toast.makeText(activity, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
153 }
154 }
155
156
157 public static void copyLinkToClipboard(final Context context, final String url) {
158 final Uri uri = Uri.parse(url);
159 if ("xmpp".equals(uri.getScheme())) {
160 try {
161 final Jid jid = new XmppUri(uri).getJid();
162 if (copyTextToClipboard(context, jid.asBareJid().toString(), R.string.account_settings_jabber_id)) {
163 Toast.makeText(context, R.string.jabber_id_copied_to_clipboard, Toast.LENGTH_SHORT).show();
164 }
165 } catch (final Exception e) { }
166 } else {
167 if (copyTextToClipboard(context, url, R.string.web_address)) {
168 Toast.makeText(context, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
169 }
170 }
171 }
172
173 public static void copyLinkToClipboard(final XmppActivity activity, final Message message) {
174 final var firstUri = Iterables.getFirst(Linkify.getLinks(message.getBody()), null);
175 if (firstUri == null) {
176 return;
177 }
178 final String clip;
179 if (SCHEMES_COPY_PATH_ONLY.contains(firstUri.getScheme())) {
180 clip = firstUri.getPath();
181 } else {
182 clip = firstUri.getRaw();
183 }
184 final @StringRes int label =
185 switch (firstUri.getScheme()) {
186 case "http", "https", "gemini" -> R.string.web_address;
187 case "xmpp" -> R.string.account_settings_jabber_id;
188 default -> R.string.uri;
189 };
190 final @StringRes int toast =
191 switch (firstUri.getScheme()) {
192 case "http", "https", "gemini", "web+ap" -> R.string.url_copied_to_clipboard;
193 case "xmpp" -> R.string.jabber_id_copied_to_clipboard;
194 case "tel" -> R.string.copied_phone_number;
195 case "mailto" -> R.string.copied_email_address;
196 default -> R.string.uri_copied_to_clipboard;
197 };
198 if (activity.copyTextToClipboard(clip, label)
199 && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
200 Toast.makeText(activity, toast, Toast.LENGTH_SHORT).show();
201 }
202 }
203}