diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 0b24f9439..4f2425e0f 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -70,6 +70,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.content.res.AppCompatResources;
+import androidx.core.content.ContextCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
import androidx.databinding.DataBindingUtil;
@@ -80,6 +81,7 @@ import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.NotNull;
+import java.io.File;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Arrays;
@@ -135,11 +137,13 @@ import eu.siacs.conversations.ui.util.ScrollState;
import eu.siacs.conversations.ui.util.SendButtonAction;
import eu.siacs.conversations.ui.util.SendButtonTool;
import eu.siacs.conversations.ui.util.ShareUtil;
+import eu.siacs.conversations.ui.util.StyledAttributes;
import eu.siacs.conversations.ui.util.ViewUtil;
import eu.siacs.conversations.ui.widget.EditMessage;
import eu.siacs.conversations.ui.widget.HighlighterView;
import eu.siacs.conversations.ui.widget.TabLayout;
import eu.siacs.conversations.utils.AccountUtils;
+import eu.siacs.conversations.utils.ChatBackgroundHelper;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.Emoticons;
import eu.siacs.conversations.utils.GeoHelper;
@@ -1321,6 +1325,12 @@ public class ConversationFragment extends XmppFragment
} else {
this.postponedActivityResult.push(activityResult);
}
+
+ ChatBackgroundHelper.onActivityResult(activity, requestCode, resultCode, data, conversation.getUuid());
+
+ if (requestCode == ChatBackgroundHelper.REQUEST_IMPORT_BACKGROUND) {
+ refresh();
+ }
}
public void unblockConversation(final Blockable conversation) {
@@ -1367,6 +1377,7 @@ public class ConversationFragment extends XmppFragment
final MenuItem menuOngoingCall = menu.findItem(R.id.action_ongoing_call);
final MenuItem menuVideoCall = menu.findItem(R.id.action_video_call);
final MenuItem menuTogglePinned = menu.findItem(R.id.action_toggle_pinned);
+ final MenuItem deleteCustomBg = menu.findItem(R.id.action_delete_custom_bg);
if (conversation != null) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
@@ -1417,6 +1428,8 @@ public class ConversationFragment extends XmppFragment
} else {
menuTogglePinned.setTitle(R.string.add_to_favorites);
}
+
+ deleteCustomBg.setVisible(ChatBackgroundHelper.getBgFile(activity, conversation.getUuid()).exists());
}
Fragment secondaryFragment = activity.getFragmentManager().findFragmentById(R.id.secondary_fragment);
@@ -2032,6 +2045,27 @@ public class ConversationFragment extends XmppFragment
case R.id.action_throttle:
throttleNoisyNoftificationsDialog(conversation);
break;
+ case R.id.action_set_custom_bg:
+ if (activity.hasStoragePermission(ChatBackgroundHelper.REQUEST_IMPORT_BACKGROUND)) {
+ ChatBackgroundHelper.openBGPicker(this);
+ }
+ break;
+ case R.id.action_delete_custom_bg:
+ try {
+ File bgfile = ChatBackgroundHelper.getBgFile(activity, conversation.getUuid());
+ if (bgfile.exists()) {
+ bgfile.delete();
+ Toast.makeText(activity, R.string.delete_background_success,Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(activity, R.string.no_background_set,Toast.LENGTH_LONG).show();
+ }
+
+ refresh();
+ } catch (Exception e) {
+ Toast.makeText(activity, R.string.delete_background_failed,Toast.LENGTH_LONG).show();
+ throw new RuntimeException(e);
+ }
+ break;
case R.id.action_block:
case R.id.action_unblock:
final Activity activity = getActivity();
@@ -2416,6 +2450,8 @@ public class ConversationFragment extends XmppFragment
Toast.LENGTH_SHORT)
.show();
}
+
+ ChatBackgroundHelper.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
if (writeGranted(grantResults, permissions)) {
if (activity != null && activity.xmppConnectionService != null) {
@@ -2429,6 +2465,18 @@ public class ConversationFragment extends XmppFragment
}
}
+ private void updateChatBG() {
+ if (activity != null) {
+ Uri uri = ChatBackgroundHelper.getBgUri(activity, conversation.getUuid());
+ if (uri != null) {
+ binding.backgroundImage.setImageURI(uri);
+ binding.backgroundImage.setVisibility(View.VISIBLE);
+ } else {
+ binding.backgroundImage.setVisibility(View.GONE);
+ }
+ }
+ }
+
public void startDownloadable(Message message) {
if (!hasPermissions(REQUEST_START_DOWNLOAD, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
this.mPendingDownloadableMessage = message;
@@ -2648,6 +2696,7 @@ public class ConversationFragment extends XmppFragment
public void onResume() {
super.onResume();
binding.messagesView.post(this::fireReadEvent);
+ updateChatBG();
}
private void fireReadEvent() {
@@ -2982,6 +3031,8 @@ public class ConversationFragment extends XmppFragment
findAndReInitByUuidOrArchive(uuid);
}
}
+
+ updateChatBG();
}
@Override
@@ -3514,6 +3565,7 @@ public class ConversationFragment extends XmppFragment
"ConversationFragment.refresh() skipped updated because view binding was null");
return;
}
+ updateChatBG();
if (this.conversation != null
&& this.activity != null
&& this.activity.xmppConnectionService != null) {
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 2f1dd3bd3..2a58795b3 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -6,6 +6,10 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Matrix;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -22,12 +26,19 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
+import androidx.exifinterface.media.ExifInterface;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
+import java.io.Closeable;
import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyStoreException;
@@ -48,6 +59,7 @@ import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.services.UnifiedPushDistributor;
import eu.siacs.conversations.ui.util.SettingsUtils;
import eu.siacs.conversations.ui.util.StyledAttributes;
+import eu.siacs.conversations.utils.ChatBackgroundHelper;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.ThemeHelper;
import eu.siacs.conversations.utils.TimeFrameUtils;
@@ -362,6 +374,37 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
privacyCategory.removePreference(omemoPreference);
}
}
+
+ final Preference importBackgroundPreference = mSettingsFragment.findPreference("import_background");
+ if (importBackgroundPreference != null) {
+ importBackgroundPreference.setSummary(getString(R.string.pref_chat_background_summary));
+ importBackgroundPreference.setOnPreferenceClickListener(preference -> {
+ if (hasStoragePermission(ChatBackgroundHelper.REQUEST_IMPORT_BACKGROUND)) {
+ ChatBackgroundHelper.openBGPicker(this);
+ }
+ return true;
+ });
+ }
+
+ final Preference deleteBackgroundPreference = mSettingsFragment.findPreference("delete_background");
+ if (deleteBackgroundPreference != null) {
+ deleteBackgroundPreference.setSummary(getString(R.string.pref_delete_background_summary));
+ deleteBackgroundPreference.setOnPreferenceClickListener(preference -> {
+ try {
+ File bgfile = ChatBackgroundHelper.getBgFile(this, null);
+ if (bgfile.exists()) {
+ bgfile.delete();
+ Toast.makeText(this,R.string.delete_background_success,Toast.LENGTH_LONG).show();
+ } else {
+ Toast.makeText(this,R.string.no_background_set,Toast.LENGTH_LONG).show();
+ }
+ } catch (Exception e) {
+ Toast.makeText(this,R.string.delete_background_failed,Toast.LENGTH_LONG).show();
+ throw new RuntimeException(e);
+ }
+ return true;
+ });
+ }
}
private void changeOmemoSettingSummary() {
@@ -588,6 +631,13 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
SettingsUtils.applyScreenshotPreventionSetting(this);
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ ChatBackgroundHelper.onActivityResult(this, requestCode, resultCode, data, null);
+ }
+
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
@@ -597,6 +647,8 @@ public class SettingsActivity extends XmppActivity implements OnSharedPreference
if (requestCode == REQUEST_CREATE_BACKUP) {
createBackup();
}
+
+ ChatBackgroundHelper.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
} else {
Toast.makeText(
this,
diff --git a/src/main/java/eu/siacs/conversations/utils/ChatBackgroundHelper.java b/src/main/java/eu/siacs/conversations/utils/ChatBackgroundHelper.java
new file mode 100644
index 000000000..2b5a02320
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/ChatBackgroundHelper.java
@@ -0,0 +1,305 @@
+package eu.siacs.conversations.utils;
+
+import static android.app.Activity.RESULT_OK;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.exifinterface.media.ExifInterface;
+
+import org.checkerframework.checker.units.qual.N;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+
+public class ChatBackgroundHelper {
+ public static final int REQUEST_IMPORT_BACKGROUND = 0xbf8704;
+
+ public static void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data, @Nullable String conversationUUID) {
+ if(requestCode == REQUEST_IMPORT_BACKGROUND) {
+ if (resultCode == RESULT_OK) {
+ Uri bguri = data.getData();
+ onPickFile(activity, bguri, conversationUUID);
+ }
+ }
+ }
+
+ public static void onRequestPermissionsResult(Activity activity,
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && requestCode == REQUEST_IMPORT_BACKGROUND) {
+ openBGPicker(activity);
+ }
+ }
+
+ public static void onRequestPermissionsResult(Fragment fragment,
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && requestCode == REQUEST_IMPORT_BACKGROUND) {
+ openBGPicker(fragment);
+ }
+ }
+
+ public static File getBgFile(Activity activity, @Nullable String conversationUUID) {
+ if (conversationUUID == null) {
+ return new File(activity.getFilesDir() + File.separator + "backgrounds" + File.separator + "bg.jpg");
+ } else {
+ return new File(activity.getFilesDir() + File.separator + "backgrounds" + File.separator + "bg_" + conversationUUID + ".jpg");
+ }
+ }
+
+ @Nullable
+ public static Uri getBgUri(Activity activity, String conversationUUID) {
+ File chatBgfileUri = new File(activity.getFilesDir() + File.separator + "backgrounds" + File.separator + "bg_" + conversationUUID + ".jpg");
+ File bgfileUri = new File(activity.getFilesDir() + File.separator + "backgrounds" + File.separator + "bg.jpg");
+ if (chatBgfileUri.exists()) {
+ return Uri.fromFile(chatBgfileUri);
+ } else if (bgfileUri.exists()) {
+ return Uri.fromFile(bgfileUri);
+ } else {
+ return null;
+ }
+ }
+
+ public static void openBGPicker(Activity activity) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
+ activity.startActivityForResult(Intent.createChooser(intent, "Select image"), REQUEST_IMPORT_BACKGROUND);
+ }
+
+ public static void openBGPicker(Fragment fragment) {
+ Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ intent.setType("image/*");
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
+ fragment.startActivityForResult(Intent.createChooser(intent, "Select image"), REQUEST_IMPORT_BACKGROUND);
+ }
+
+ private static void onPickFile(Activity activity, Uri uri, @Nullable String conversationUUID) {
+ if (uri != null) {
+ InputStream in;
+ OutputStream out;
+ try {
+ File bgfolder = new File(activity.getFilesDir() + File.separator + "backgrounds");
+ File bgfile;
+
+ if (conversationUUID != null) {
+ bgfile = new File(activity.getFilesDir() + File.separator + "backgrounds" + File.separator + "bg_" + conversationUUID + ".jpg");
+ } else {
+ bgfile = new File(activity.getFilesDir() + File.separator + "backgrounds" + File.separator + "bg.jpg");
+ }
+ //create output directory if it doesn't exist
+ if (!bgfolder.exists()) {
+ bgfolder.mkdirs();
+ }
+
+ in = activity.getContentResolver().openInputStream(uri);
+ out = new FileOutputStream(bgfile);
+ byte[] buffer = new byte[4096];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ in.close();
+ in = null;
+ // write the output file
+ out.flush();
+ out.close();
+ out = null;
+ compressImage(activity, bgfile, uri, 0);
+ Toast.makeText(activity, R.string.custom_background_set,Toast.LENGTH_LONG).show();
+ } catch (IOException exception) {
+ Toast.makeText(activity,R.string.create_background_failed,Toast.LENGTH_LONG).show();
+ Log.d(Config.LOGTAG, "Could not create background" + exception);
+ }
+ }
+ }
+
+ private static void compressImage(Activity activity, File f, Uri image, int sampleSize) throws IOException {
+ InputStream is = null;
+ OutputStream os = null;
+ int IMAGE_QUALITY = 65;
+ int ImageSize = (int) (0.08 * 1024 * 1024);
+ try {
+ if (!f.exists() && !f.createNewFile()) {
+ throw new IOException(String.valueOf(R.string.error_unable_to_create_temporary_file));
+ }
+ is = activity.getContentResolver().openInputStream(image);
+ if (is == null) {
+ throw new IOException(String.valueOf(R.string.error_not_an_image_file));
+ }
+ final Bitmap originalBitmap;
+ final BitmapFactory.Options options = new BitmapFactory.Options();
+ final int inSampleSize = (int) Math.pow(2, sampleSize);
+ Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
+ options.inSampleSize = inSampleSize;
+ originalBitmap = BitmapFactory.decodeStream(is, null, options);
+ is.close();
+ if (originalBitmap == null) {
+ throw new IOException("Source file was not an image");
+ }
+ if (!"image/jpeg".equals(options.outMimeType) && hasAlpha(originalBitmap)) {
+ originalBitmap.recycle();
+ throw new IOException("Source file had alpha channel");
+ }
+ int size;
+ int resolution = 1920;
+ if (resolution == 0) {
+ int height = originalBitmap.getHeight();
+ int width = originalBitmap.getWidth();
+ size = height > width ? height : width;
+ } else {
+ size = resolution;
+ }
+ Bitmap scaledBitmap = resize(originalBitmap, size);
+ final int rotation = getRotation(activity, image);
+ scaledBitmap = rotate(scaledBitmap, rotation);
+ boolean targetSizeReached = false;
+ int quality = IMAGE_QUALITY;
+ while (!targetSizeReached) {
+ os = new FileOutputStream(f);
+ boolean success = scaledBitmap.compress(Config.IMAGE_FORMAT, quality, os);
+ if (!success) {
+ throw new IOException(String.valueOf(R.string.error_compressing_image));
+ }
+ os.flush();
+ targetSizeReached = (f.length() <= ImageSize && ImageSize != 0) || quality <= 50;
+ quality -= 5;
+ }
+ scaledBitmap.recycle();
+ } catch (final FileNotFoundException e) {
+ cleanup(f);
+ throw new IOException(String.valueOf(R.string.error_file_not_found));
+ } catch (final IOException e) {
+ cleanup(f);
+ throw new IOException(String.valueOf(R.string.error_io_exception));
+ } catch (SecurityException e) {
+ cleanup(f);
+ throw new IOException(String.valueOf(R.string.error_security_exception_during_image_copy));
+ } catch (final OutOfMemoryError e) {
+ ++sampleSize;
+ if (sampleSize <= 3) {
+ compressImage(activity, f, image, sampleSize);
+ } else {
+ throw new IOException(String.valueOf(R.string.error_out_of_memory));
+ }
+ } finally {
+ close(os);
+ close(is);
+ }
+ }
+
+ private static int getRotation(Activity activity, final Uri image) {
+ try (final InputStream is = activity.getContentResolver().openInputStream(image)) {
+ return is == null ? 0 : getRotation(is);
+ } catch (final Exception e) {
+ return 0;
+ }
+ }
+
+ private static int getRotation(final InputStream inputStream) throws IOException {
+ final ExifInterface exif = new ExifInterface(inputStream);
+ final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+ switch (orientation) {
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ return 180;
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ return 90;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ private static Bitmap rotate(final Bitmap bitmap, final int degree) {
+ if (degree == 0) {
+ return bitmap;
+ }
+ final int w = bitmap.getWidth();
+ final int h = bitmap.getHeight();
+ final Matrix matrix = new Matrix();
+ matrix.postRotate(degree);
+ final Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, true);
+ if (!bitmap.isRecycled()) {
+ bitmap.recycle();
+ }
+ return result;
+ }
+
+ private static void close(final Closeable stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (Exception e) {
+ Log.d(Config.LOGTAG, "unable to close stream", e);
+ }
+ }
+ }
+
+ private static void cleanup(final File file) {
+ try {
+ file.delete();
+ } catch (Exception e) {
+
+ }
+ }
+
+ private static Bitmap resize(final Bitmap originalBitmap, int size) throws IOException {
+ int w = originalBitmap.getWidth();
+ int h = originalBitmap.getHeight();
+ if (w <= 0 || h <= 0) {
+ throw new IOException("Decoded bitmap reported bounds smaller 0");
+ } else if (Math.max(w, h) > size) {
+ int scalledW;
+ int scalledH;
+ if (w <= h) {
+ scalledW = Math.max((int) (w / ((double) h / size)), 1);
+ scalledH = size;
+ } else {
+ scalledW = size;
+ scalledH = Math.max((int) (h / ((double) w / size)), 1);
+ }
+ final Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
+ if (!originalBitmap.isRecycled()) {
+ originalBitmap.recycle();
+ }
+ return result;
+ } else {
+ return originalBitmap;
+ }
+ }
+
+ private static boolean hasAlpha(final Bitmap bitmap) {
+ final int w = bitmap.getWidth();
+ final int h = bitmap.getHeight();
+ final int yStep = Math.max(1, w / 100);
+ final int xStep = Math.max(1, h / 100);
+ for (int x = 0; x < w; x += xStep) {
+ for (int y = 0; y < h; y += yStep) {
+ if (Color.alpha(bitmap.getPixel(x, y)) < 255) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml
index 9f6683cec..0efee1f09 100644
--- a/src/main/res/layout/fragment_conversation.xml
+++ b/src/main/res/layout/fragment_conversation.xml
@@ -7,6 +7,13 @@
android:layout_height="match_parent"
android:background="?attr/color_background_secondary">
+
+
+ android:layout_height="fill_parent">
diff --git a/src/main/res/menu/fragment_conversation.xml b/src/main/res/menu/fragment_conversation.xml
index b25d7411a..64257fd9d 100644
--- a/src/main/res/menu/fragment_conversation.xml
+++ b/src/main/res/menu/fragment_conversation.xml
@@ -147,6 +147,18 @@
android:orderInCategory="75"
android:title="@string/refresh_feature_discovery"
app:showAsAction="never" />
+
+
+
+
diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml
index ca06ae91e..2a1c55b68 100644
--- a/src/main/res/values/colors.xml
+++ b/src/main/res/values/colors.xml
@@ -16,6 +16,7 @@
#ff9e9e9e
#ff616161
#66616161
+ #40616161
#ff424242
#ff282828
#fff44336
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index ecb3638e6..3ad0b48a6 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -1090,4 +1090,13 @@
Do you really want to retract this message?
Chats
Accounts
+ Custom background
+ Choose an own image file as chat background.
+ Remove chat background
+ Remove your custom background image from the chat
+ Failed to create background
+ Custom background set
+ Couldn\'t remove background image
+ Background image removed
+ No custom background set
diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml
index 0a5cb1b37..583651446 100644
--- a/src/main/res/xml/preferences.xml
+++ b/src/main/res/xml/preferences.xml
@@ -232,6 +232,14 @@
android:key="font_size"
android:summary="@string/pref_font_size_summary"
android:title="@string/pref_font_size" />
+
+