Renamed Send

This commit is contained in:
M66B 2022-07-31 21:41:40 +02:00
parent b5a987c6c7
commit 817088779d
8 changed files with 82 additions and 82 deletions

12
FAQ.md
View File

@ -385,7 +385,7 @@ Fonts, sizes, colors, etc should be material design whenever possible.
* [(180) How do I use LanguageTool?](#user-content-faq180)
* [(181) How do I use VirusTotal?](#user-content-faq181)
* [(182) How can I select how a link should be opened?](#user-content-faq182)
* [(183) How do I use FFSend?](#user-content-faq183)
* [(183) How do I use Send?](#user-content-faq183)
[I have another question.](#user-content-get-support)
@ -4895,19 +4895,19 @@ Please see [this FAQ](#user-content-faq35) on why you should be careful when ope
<br />
<a name="faq183"></a>
**(183) How do I use FFSend?**
**(183) How do I use Send?**
[FFSend](https://github.com/timvisee/send) is designed as temporary encrypted file storage.
[Send](https://github.com/timvisee/send) is designed as temporary encrypted file storage.
Only people with a link to a file can download and decrypt a file.
FFSend integration needs to be enabled in the miscellaneous settings.
Send integration needs to be enabled in the miscellaneous settings.
Optionally, you can change the host address of the FFSend server.
Optionally, you can change the host address of the Send server.
Please [see here](https://github.com/timvisee/send-instances) for a list of public instances.
To upload a file and insert a link, you can use the insert link button in the message editor.
FFSend is only available in non-Play Store versions of the app (since version 1.1947).
Send is only available in non-Play Store versions of the app (since version 1.1947).
<br />

View File

@ -4,7 +4,7 @@ import androidx.documentfile.provider.DocumentFile;
import java.io.InputStream;
public class FFSend {
public class Send {
static final int FF_DEFAULT_DLIMIT = 0;
static final int FF_DEFAULT_TLIMIT = 0;
static final String FF_DEFAULT_SERVER = "";

View File

@ -39,7 +39,7 @@ import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class FFSend {
public class Send {
// https://datatracker.ietf.org/doc/html/rfc8188
// https://github.com/nneonneo/ffsend/blob/master/ffsend.py
@ -50,8 +50,8 @@ public class FFSend {
--data '{"owner_token": "..."}'
*/
static final int FF_DEFAULT_DLIMIT = 10;
static final int FF_DEFAULT_TLIMIT = 24; // hours
static final int DEFAULT_DLIMIT = 10;
static final int DEFAULT_TLIMIT = 24; // hours
static final String FF_DEFAULT_SERVER = "https://send.vis.ee/";
static final String FF_INSTANCES = "https://github.com/timvisee/send-instances/";
@ -76,31 +76,31 @@ public class FFSend {
ws.addListener(new WebSocketAdapter() {
@Override
public void onTextMessage(WebSocket ws, String text) throws Exception {
Log.i("FFSend text message=" + text);
Log.i("Send text message=" + text);
queue.add(text);
sem.release();
}
});
Log.i("FFSend connect");
Log.i("Send connect");
ws.connect();
try {
Log.i("FFSend upload=" + jupload);
Log.i("Send upload=" + jupload);
ws.sendText(jupload.toString());
Log.i("FFSend wait reply");
Log.i("Send wait reply");
sem.tryAcquire(FF_TIMEOUT, TimeUnit.MILLISECONDS);
JSONObject jreply = new JSONObject(queue.remove(0));
Log.i("FFSend reply=" + jreply);
Log.i("Send reply=" + jreply);
if (jreply.has("error"))
throw new IOException("Error: " + jreply.getString("error"));
result = jreply.getString("url") +
"#" + Base64.encodeToString(secret, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
Log.i("FFSend url=" + result);
Log.i("Send url=" + result);
// The record sequence number (SEQ) is a 96-bit unsigned integer in network byte order that starts at zero.
// network byte order = transmitting the most significant byte first
@ -120,7 +120,7 @@ public class FFSend {
// idlen = 0
// keyid = ""
Log.i("FFSend header=" + Helper.hex(header));
Log.i("Send header=" + Helper.hex(header));
ws.sendBinary(header);
// https://datatracker.ietf.org/doc/html/rfc8188#section-2.2
@ -141,7 +141,7 @@ public class FFSend {
hkdf = new HKDFBytesGenerator(new SHA256Digest());
hkdf.init(new HKDFParameters(secret /* ikm */, salt, "Content-Encoding: nonce\0".getBytes()));
hkdf.generateBytes(nonce_base /* okm */, 0, nonce_base.length);
Log.i("FFSend nonce base=" + Helper.hex(nonce_base));
Log.i("Send nonce base=" + Helper.hex(nonce_base));
// TODO zero length files
int len;
@ -149,7 +149,7 @@ public class FFSend {
long fileSize = dfile.length();
// content any length up to rs-17 octets
while ((len = is.read(buffer, 0, buffer.length - 17)) > 0) {
Log.i("FFSend read=" + len);
Log.i("Send read=" + len);
// add a delimiter octet (0x01 or 0x02)
// then 0x00-valued octets to rs-16 (or less on the last record)
@ -163,12 +163,12 @@ public class FFSend {
while (len < buffer.length - 17)
buffer[len++] = 0x00;
}
Log.i("FFSend record len=" + len + " size=" + size + "/" + fileSize);
Log.i("Send record len=" + len + " size=" + size + "/" + fileSize);
byte[] nonce = Arrays.copyOf(nonce_base, nonce_base.length);
ByteBuffer xor = ByteBuffer.wrap(nonce);
xor.putInt(nonce.length - 4, xor.getInt(nonce.length - 4) ^ seq);
Log.i("FFSend seq=" + seq + " nonce=" + Helper.hex(nonce));
Log.i("Send seq=" + seq + " nonce=" + Helper.hex(nonce));
// encrypt with AEAD_AES_128_GCM; final size is rs; the last record can be smaller
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
@ -176,20 +176,20 @@ public class FFSend {
new SecretKeySpec(cek, "AES"),
new GCMParameterSpec(16 * 8, nonce));
byte[] message = cipher.doFinal(buffer, 0, len);
Log.i("FFSend message len=" + message.length);
Log.i("Send message len=" + message.length);
ws.sendBinary(message);
seq++;
}
Log.i("FFSend EOF size=" + size);
Log.i("Send EOF size=" + size);
ws.sendBinary(new byte[]{0}, true);
Log.i("FFSend wait confirm");
Log.i("Send wait confirm");
sem.tryAcquire(FF_TIMEOUT, TimeUnit.MILLISECONDS);
JSONObject jconfirm = new JSONObject(queue.remove(0));
Log.i("FFSend confirm=" + jconfirm);
Log.i("Send confirm=" + jconfirm);
if (!jconfirm.getBoolean("ok"))
throw new FileNotFoundException();
} finally {
@ -225,7 +225,7 @@ public class FFSend {
jmeta.put("type", mimeType);
jmeta.put("manifest", jmanifest);
Log.i("FFSend meta=" + jmeta);
Log.i("Send meta=" + jmeta);
byte[] auth_key = new byte[64];
HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());

View File

@ -71,7 +71,7 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
private static final int METADATA_CONNECT_TIMEOUT = 10 * 1000; // milliseconds
private static final int METADATA_READ_TIMEOUT = 15 * 1000; // milliseconds
private static final int REQUEST_FFSEND = 1;
private static final int REQUEST_SEND = 1;
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
@ -103,7 +103,7 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
Group grpUpload = view.findViewById(R.id.grpUpload);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean ffsend_enabled = prefs.getBoolean("ffsend_enabled", false);
boolean send_enabled = prefs.getBoolean("send_enabled", false);
etLink.addTextChangedListener(new TextWatcher() {
@Override
@ -257,7 +257,7 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType("*/*");
startActivityForResult(Helper.getChooser(getContext(), intent), REQUEST_FFSEND);
startActivityForResult(Helper.getChooser(getContext(), intent), REQUEST_SEND);
}
});
@ -265,7 +265,7 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
progress++;
tvDLimit.setText(getString(R.string.title_style_link_ffsend_dlimit, Integer.toString(progress)));
tvDLimit.setText(getString(R.string.title_style_link_send_dlimit, Integer.toString(progress)));
}
@Override
@ -285,11 +285,11 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
progress++;
if (progress < 24)
tvTLimit.setText(getString(R.string.title_style_link_ffsend_tlimit,
tvTLimit.setText(getString(R.string.title_style_link_send_tlimit,
getResources().getQuantityString(R.plurals.title_hours, progress, progress)));
else {
progress = (progress - 24 + 1);
tvTLimit.setText(getString(R.string.title_style_link_ffsend_tlimit,
tvTLimit.setText(getString(R.string.title_style_link_send_tlimit,
getResources().getQuantityString(R.plurals.title_days, progress, progress)));
}
}
@ -314,12 +314,12 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
etTitle.setText(savedInstanceState.getString("fair:text"));
}
sbDLimit.setProgress(FFSend.FF_DEFAULT_DLIMIT - 1);
sbTLimit.setProgress(FFSend.FF_DEFAULT_TLIMIT - 1);
sbDLimit.setProgress(Send.DEFAULT_DLIMIT - 1);
sbTLimit.setProgress(Send.DEFAULT_TLIMIT - 1);
pbWait.setVisibility(View.GONE);
pbUpload.setVisibility(View.GONE);
grpUpload.setVisibility(ffsend_enabled && !BuildConfig.PLAY_STORE_RELEASE
grpUpload.setVisibility(send_enabled && !BuildConfig.PLAY_STORE_RELEASE
? View.VISIBLE : View.GONE);
return new AlertDialog.Builder(context)
@ -348,9 +348,9 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
try {
switch (requestCode) {
case REQUEST_FFSEND:
case REQUEST_SEND:
if (resultCode == RESULT_OK && data != null)
onFFSend(data.getData());
onSend(data.getData());
break;
}
} catch (Throwable ex) {
@ -358,7 +358,7 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
}
}
private void onFFSend(Uri uri) {
private void onSend(Uri uri) {
int dlimit = sbDLimit.getProgress() + 1;
int tlimit = sbTLimit.getProgress() + 1;
@ -404,26 +404,26 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
throw new FileNotFoundException("dfile");
if (dlimit == 0)
dlimit = FFSend.FF_DEFAULT_DLIMIT;
dlimit = Send.DEFAULT_DLIMIT;
if (tlimit == 0)
tlimit = FFSend.FF_DEFAULT_TLIMIT;
tlimit = Send.DEFAULT_TLIMIT;
Log.i("FFSend uri=" + uri + " dlimit=" + dlimit + " tlimit=" + tlimit);
Log.i("Send uri=" + uri + " dlimit=" + dlimit + " tlimit=" + tlimit);
args.putString("title", dfile.getName());
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String ffsend_host = prefs.getString("ffsend_host", FFSend.FF_DEFAULT_SERVER);
String send_host = prefs.getString("send_host", Send.FF_DEFAULT_SERVER);
ContentResolver resolver = context.getContentResolver();
try (InputStream is = resolver.openInputStream(uri)) {
return FFSend.upload(is, dfile, dlimit, tlimit * 60 * 60, ffsend_host);
return Send.upload(is, dfile, dlimit, tlimit * 60 * 60, send_host);
}
}
@Override
protected void onExecuted(Bundle args, String ffsend) {
etLink.setText(ffsend);
protected void onExecuted(Bundle args, String link) {
etLink.setText(link);
etTitle.setText(args.getString("title"));
}
@ -431,7 +431,7 @@ public class FragmentDialogInsertLink extends FragmentDialogBase {
protected void onException(Bundle args, Throwable ex) {
Log.unexpectedError(getParentFragmentManager(), ex);
}
}.execute(this, args, "ffsend");
}.execute(this, args, "send");
}
private static class OpenGraph {

View File

@ -126,9 +126,9 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private TextView tvVirusTotalPrivacy;
private EditText etVirusTotal;
private ImageButton ibVirusTotal;
private SwitchCompat swFFSend;
private EditText etFFSend;
private ImageButton ibFFSend;
private SwitchCompat swSend;
private EditText etSend;
private ImageButton ibSend;
private SwitchCompat swUpdates;
private ImageButton ibChannelUpdated;
private SwitchCompat swCheckWeekly;
@ -218,7 +218,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private TextView tvPermissions;
private Group grpVirusTotal;
private Group grpFFSend;
private Group grpSend;
private Group grpUpdates;
private Group grpTest;
private CardView cardDebug;
@ -230,7 +230,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
private final static String[] RESET_OPTIONS = new String[]{
"sort_answers", "shortcuts", "fts",
"classification", "class_min_probability", "class_min_difference",
"language", "lt_enabled", "deepl_enabled", "vt_enabled", "vt_apikey", "ffsend_enabled", "ffsend_host",
"language", "lt_enabled", "deepl_enabled", "vt_enabled", "vt_apikey", "send_enabled", "send_host",
"updates", "weekly", "show_changelog",
"crash_reports", "cleanup_attachments",
"watchdog", "experiments", "main_log", "protocol", "log_level", "debug", "leak_canary",
@ -321,9 +321,9 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
tvVirusTotalPrivacy = view.findViewById(R.id.tvVirusTotalPrivacy);
etVirusTotal = view.findViewById(R.id.etVirusTotal);
ibVirusTotal = view.findViewById(R.id.ibVirusTotal);
swFFSend = view.findViewById(R.id.swFFSend);
etFFSend = view.findViewById(R.id.etFFSend);
ibFFSend = view.findViewById(R.id.ibFFSend);
swSend = view.findViewById(R.id.swSend);
etSend = view.findViewById(R.id.etSend);
ibSend = view.findViewById(R.id.ibSend);
swUpdates = view.findViewById(R.id.swUpdates);
ibChannelUpdated = view.findViewById(R.id.ibChannelUpdated);
swCheckWeekly = view.findViewById(R.id.swWeekly);
@ -413,7 +413,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
tvPermissions = view.findViewById(R.id.tvPermissions);
grpVirusTotal = view.findViewById(R.id.grpVirusTotal);
grpFFSend = view.findViewById(R.id.grpFFSend);
grpSend = view.findViewById(R.id.grpSend);
grpUpdates = view.findViewById(R.id.grpUpdates);
grpTest = view.findViewById(R.id.grpTest);
cardDebug = view.findViewById(R.id.cardDebug);
@ -697,15 +697,15 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
}
});
swFFSend.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
swSend.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean checked) {
prefs.edit().putBoolean("ffsend_enabled", checked).apply();
prefs.edit().putBoolean("send_enabled", checked).apply();
}
});
etFFSend.setHint(FFSend.FF_DEFAULT_SERVER);
etFFSend.addTextChangedListener(new TextWatcher() {
etSend.setHint(Send.FF_DEFAULT_SERVER);
etSend.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
@ -720,13 +720,13 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
public void afterTextChanged(Editable s) {
String apikey = s.toString().trim();
if (TextUtils.isEmpty(apikey))
prefs.edit().remove("ffsend_host").apply();
prefs.edit().remove("send_host").apply();
else
prefs.edit().putString("ffsend_host", apikey).apply();
prefs.edit().putString("send_host", apikey).apply();
}
});
ibFFSend.setOnClickListener(new View.OnClickListener() {
ibSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Helper.viewFAQ(v.getContext(), 183);
@ -1676,7 +1676,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
});
grpVirusTotal.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
grpFFSend.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
grpSend.setVisibility(BuildConfig.PLAY_STORE_RELEASE ? View.GONE : View.VISIBLE);
grpUpdates.setVisibility(!BuildConfig.DEBUG &&
(Helper.isPlayStoreInstall() || !Helper.hasValidFingerprint(getContext()))
@ -1751,7 +1751,7 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
if ("last_cleanup".equals(key))
setLastCleanup(prefs.getLong(key, -1));
if ("vt_apikey".equals(key) || "ffsend_host".equals(key))
if ("vt_apikey".equals(key) || "send_host".equals(key))
return;
setOptions();
@ -1898,8 +1898,8 @@ public class FragmentOptionsMisc extends FragmentBase implements SharedPreferenc
swDeepL.setChecked(prefs.getBoolean("deepl_enabled", false));
swVirusTotal.setChecked(prefs.getBoolean("vt_enabled", false));
etVirusTotal.setText(prefs.getString("vt_apikey", null));
swFFSend.setChecked(prefs.getBoolean("ffsend_enabled", false));
etFFSend.setText(prefs.getString("ffsend_host", null));
swSend.setChecked(prefs.getBoolean("send_enabled", false));
etSend.setText(prefs.getString("send_host", null));
swUpdates.setChecked(prefs.getBoolean("updates", true));
swCheckWeekly.setChecked(prefs.getBoolean("weekly", Helper.hasPlayStore(getContext())));
swCheckWeekly.setEnabled(swUpdates.isChecked());

View File

@ -120,7 +120,7 @@
android:layout_marginTop="12dp"
android:drawableEnd="@drawable/twotone_file_upload_24"
android:drawablePadding="6dp"
android:text="@string/title_style_link_ffsend"
android:text="@string/title_style_link_send"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvMetadataRemark" />
@ -141,7 +141,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:labelFor="@+id/sbDLimit"
android:text="@string/title_style_link_ffsend_dlimit"
android:text="@string/title_style_link_send_dlimit"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btnUpload" />
@ -162,7 +162,7 @@
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:labelFor="@+id/sbTLimit"
android:text="@string/title_style_link_ffsend_tlimit"
android:text="@string/title_style_link_send_tlimit"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/sbDLimit" />

View File

@ -439,18 +439,18 @@
app:srcCompat="@drawable/twotone_info_24" />
<androidx.appcompat.widget.SwitchCompat
android:id="@+id/swFFSend"
android:id="@+id/swSend"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/title_advanced_ffsend"
android:text="@string/title_advanced_send"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ibVirusTotal"
app:switchPadding="12dp" />
<EditText
android:id="@+id/etFFSend"
android:id="@+id/etSend"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
@ -459,17 +459,17 @@
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/swFFSend" />
app:layout_constraintTop_toBottomOf="@id/swSend" />
<ImageButton
android:id="@+id/ibFFSend"
android:id="@+id/ibSend"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:contentDescription="@string/title_info"
android:tooltipText="@string/title_info"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/etFFSend"
app:layout_constraintTop_toBottomOf="@id/etSend"
app:srcCompat="@drawable/twotone_info_24" />
<androidx.appcompat.widget.SwitchCompat
@ -481,7 +481,7 @@
android:text="@string/title_advanced_updates"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/ibFFSend"
app:layout_constraintTop_toBottomOf="@id/ibSend"
app:switchPadding="12dp" />
<ImageButton
@ -634,10 +634,10 @@
app:constraint_referenced_ids="swVirusTotal,tvVirusTotalPrivacy,ibVirusTotal,etVirusTotal" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpFFSend"
android:id="@+id/grpSend"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="swFFSend,etFFSend,ibFFSend" />
app:constraint_referenced_ids="swSend,etSend,ibSend" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpUpdates"

View File

@ -744,7 +744,7 @@
<string name="title_advanced_lt">LanguageTool integration</string>
<string name="title_advanced_deepl">DeepL integration</string>
<string name="title_advanced_virus_total">VirusTotal integration</string>
<string name="title_advanced_ffsend" translatable="false">FFSend integration</string>
<string name="title_advanced_send" translatable="false">Send integration</string>
<string name="title_advanced_sdcard">I want to use an sdcard</string>
<string name="title_advanced_watchdog">Periodically check if FairEmail is still active</string>
<string name="title_advanced_updates">Check for GitHub updates</string>
@ -1429,9 +1429,9 @@
<string name="title_style_link_title">Title</string>
<string name="title_style_link_metadata">Fetch title</string>
<string name="title_style_link_metadata_remark">This will fetch the title at the entered address</string>
<string name="title_style_link_ffsend" translatable="false">FFSend</string>
<string name="title_style_link_ffsend_dlimit" translatable="false">Download limit (%1$s)</string>
<string name="title_style_link_ffsend_tlimit" translatable="false">Time limit (%1$s)</string>
<string name="title_style_link_send" translatable="false">Send</string>
<string name="title_style_link_send_dlimit" translatable="false">Download limit (%1$s)</string>
<string name="title_style_link_send_tlimit" translatable="false">Time limit (%1$s)</string>
<string name="title_add_image">Add image</string>
<string name="title_add_image_inline">Insert</string>