From 80b5a3cd1a8a3e831c10e47cf4f52f786f9b9913 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Thu, 3 Nov 2022 18:41:19 +0530 Subject: [PATCH 01/11] Added: Add generic method `startActivityForResult()` to `ActivityUtils` This method allows to launch an Activity for result. --- .../termux/shared/activity/ActivityUtils.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java b/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java index 0ff28126aa..3d2bf644a3 100644 --- a/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/activity/ActivityUtils.java @@ -5,6 +5,7 @@ import android.content.Intent; import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContract; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -134,4 +135,49 @@ else if (context instanceof Activity) return null; } + + + /** + * Wrapper for {@link #startActivityForResult(Context, ActivityResultLauncher, Object, boolean, boolean)}. + */ + public static Error startActivityForResult(Context context, @NonNull ActivityResultLauncher activityResultLauncher, + T input) { + return startActivityForResult(context, activityResultLauncher, input, true, true); + } + + /** + * Generic method to start an {@link Activity} for result. + * @param context The context for operations. It must be an instance of {@link Activity} or + * {@link AppCompatActivity}. It is ignored if {@code activityResultLauncher} + * is not {@code null}. + * @param activityResultLauncher A launcher for start the process of executing an {@link ActivityResultContract}. + * @param input The data required to {@link ActivityResultLauncher#launch(Object) launch} Activity. + * @param logErrorMessage If an error message should be logged if failed to start activity. + * @param showErrorMessage If an error message toast should be shown if failed to start activity + * in addition to logging a message. The {@code context} must not be + * {@code null}. + * @param Type of the input required to {@link ActivityResultLauncher#launch(Object) launch}. + * @return Returns the {@code error} if starting activity was not successful, otherwise {@code null}. + */ + public static Error startActivityForResult(Context context, @NonNull ActivityResultLauncher activityResultLauncher, + T input, boolean logErrorMessage, boolean showErrorMessage) { + Error error; + String activityName = "Unknown"; + + if (input instanceof Intent && ((Intent) input).getComponent() != null) { + activityName = ((Intent) input).getComponent().getClassName(); + } + + try { + activityResultLauncher.launch(input); + } catch (Exception e) { + error = ActivityErrno.ERRNO_START_ACTIVITY_FOR_RESULT_FAILED_WITH_EXCEPTION.getError(e, activityName, e.getMessage()); + if (logErrorMessage) + error.logErrorAndShowToast(showErrorMessage ? context : null, LOG_TAG); + return error; + } + + return null; + } + } From 4d80d119e357afc5137aa0315586c9d9caa1d04a Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Fri, 4 Nov 2022 18:10:21 +0530 Subject: [PATCH 02/11] Added: Add the `ImageUtils` class for images The `ImageUtils` class includes utility functions for image manipulation and storage. It provides functions for loading bitmap or drawable, storing, resizing and compressing images. --- .../com/termux/shared/image/ImageUtils.java | 252 ++++++++++++++++++ 1 file changed, 252 insertions(+) create mode 100644 termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java diff --git a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java new file mode 100644 index 0000000000..902166e47d --- /dev/null +++ b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java @@ -0,0 +1,252 @@ +package com.termux.shared.image; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.BitmapFactory; +import android.graphics.BlendMode; +import android.graphics.BlendModeColorFilter; +import android.graphics.ImageDecoder; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.ThumbnailUtils; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; + +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; +import com.termux.shared.file.FileUtilsErrno; +import com.termux.shared.logger.Logger; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public final class ImageUtils { + + /** + * Request code that can be used to distinguish Activity result. + */ + public static final int REQUEST_CODE_IMAGE = 100; + + /** + * Compression quality used to compress image. The value is interpreted differently depending on the {@link CompressFormat CompressFormat}. + */ + public static final int COMPRESS_QUALITY = 80; + + /** + * Tolerance for diffrence in original image required optimized image. + */ + public static final int OPTIMALITY_TOLERANCE = 50; + + public static final String IMAGE_TYPE = "image"; + + public static final String ANY_IMAGE_TYPE = IMAGE_TYPE + "/*"; + + + private static final String LOG_TAG = "FileUtils"; + + /** + * Don't let anyone instantiate this class. + */ + private ImageUtils() { + } + + /** + * Get an {@link Bitmap} image from the {@link Uri}. + * + * @param context The context for the operations. + * @param uri The uri from where image content will be loaded. + * @return Bitmap containing the image, or return {@code null} if failed to + * load bitmap content. + */ + public static Bitmap getBitmap(final Context context, Uri uri) { + Bitmap bitmap = null; + + try { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + bitmap = ImageDecoder.decodeBitmap(ImageDecoder.createSource(context.getContentResolver(), uri)); + } else { + bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); + } + bitmap = MediaStore.Images.Media.getBitmap(context.getContentResolver(), uri); + } catch (IOException e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load bitmap from " + uri, e); + } + return bitmap; + } + + /** + * Creates an centered and resized {@link Bitmap} according to given size. + * + * @param bitmap Original bitmap source to resize. + * @param point Target size containing width and height to resize. + * @return Returns the resized bitmap for given size. + */ + public static Bitmap resizeBitmap(Bitmap bitmap, Point point) { + return ThumbnailUtils.extractThumbnail(bitmap, point.x, point.y); + } + + /** + * Wrapper for {@link #compressAndSaveBitmap(Bitmap, int, String)} with `{@link #COMPRESS_QUALITY} `quality`. + */ + public static Error compressAndSaveBitmap(Bitmap bitmap, String path) { + return compressAndSaveBitmap(bitmap, COMPRESS_QUALITY, path); + } + + /** + * Wrapper for {@link #compressAndSaveBitmap(Bitmap, CompressFormat, int, String)} with `{@link Bitmap.CompressFormat#JPEG}` `format`. + */ + public static Error compressAndSaveBitmap(Bitmap bitmap, int quality, String path) { + return compressAndSaveBitmap(bitmap, Bitmap.CompressFormat.JPEG, quality, path); + } + + /** + * Compress the given bitmap image file for given format and quality. + * + * @param bitmap Original source bitmap. + * @param foramt The format for image compression. + * @param quality Hint to the compressor, 0-100. The value is interpreted differently + * depending on the {@link Bitmap.CompressFormat}. + * @param path The path to store compressed bitmap. + * @return Returns the {@code error} if compression and save operation was not successful, + * otherwise {@code null}. + */ + public static Error compressAndSaveBitmap(Bitmap bitmap, Bitmap.CompressFormat foramt, int quality, String path) { + FileUtils.deleteRegularFile(null, path, true); + Error error = FileUtils.createRegularFile(path); + + if (error != null) + return error; + + try (FileOutputStream out = new FileOutputStream(path)) { + bitmap.compress(foramt, quality, out); + } catch (Exception e) { + FileUtils.deleteRegularFile(null, path, true); + error = FileUtilsErrno.ERRNO_CREATING_FILE_FAILED_WITH_EXCEPTION.getError(e, e.getMessage()); + } + + return error; + } + + + /** + * Wrapper for {@link #getDrawable(String)} with `file.getAbsolutePath()` `path` of file. + */ + public static Drawable getDrawable(File file) { + String path = file.getAbsolutePath(); + + return getDrawable(path); + } + + /** + * Create {@link BitmapDrawable} from specified file path. + * + * @param path The path file to load image bitmap drawable. + * @return Drawable created from image file path. + */ + public static Drawable getDrawable(String path) { + return BitmapDrawable.createFromPath(path); + } + + /** + * Add an overlay color/tint on image with {@link BlendMode#MULTIPLY}. + * + * @param drawable The source image bitmap drawable. + * @param color Overlay color for image. + */ + public static void addOverlay(Drawable drawable, int color) { + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + drawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.MULTIPLY)); + } else { + drawable.setColorFilter(color, PorterDuff.Mode.MULTIPLY); + } + } + + + /** + * Wrapper for {@link #isImageOptimized(String, Point, int)} with `{@link #OPTIMALITY_TOLERANCE}` `tolerance`. + */ + public static boolean isImageOptimized(String path, Point point) { + return isImageOptimized(path, point, OPTIMALITY_TOLERANCE); + } + + /** + * Wrapper for {@link #isImageOptimized(String, int, int, int)} with `width` and `height` obtained from {@link Point}. + */ + public static boolean isImageOptimized(String path, Point point, int tolerance) { + return isImageOptimized(path, point.x, point.y, tolerance); + } + + /** + * Check whether the image file present at file location is optimized corresponding + * to given width and height. It can tolorent error upto given value. + * + * @param path The file path of image. + * @param width The required width of image file. + * @param height The required width of image file. + * @param tolerance The tolerance value upto which diffrence is ignored. + * @return Returns whether the given image is optimized or not. + */ + public static boolean isImageOptimized(String path, int width, int height, int tolerance) { + + if (!FileUtils.regularFileExists(path, false)) { + Logger.logInfo(LOG_TAG, "Image file " + path + " does not exist."); + return false; + } + + BitmapFactory.Options opt = new BitmapFactory.Options(); + BitmapFactory.decodeFile(path, opt); + + opt.inJustDecodeBounds = true; + int imgWidth = opt.outWidth; + int imgHeight = opt.outHeight; + + return Math.abs(imgWidth - width) <= tolerance && Math.abs(imgHeight - height) <= tolerance; + } + + /** + * Resize bitmap for {@link Configuration#ORIENTATION_PORTRAIT Portrait} and {@link + * Configuration#ORIENTATION_LANDSCAPE Landscape} display view. + * Also Compress the image bitmap before saving it to given path. + * + * @param bitmap The original bitmap image to resize and store. + * @param point Display resolution containing width and height. + * @param path1 The path for storing image with width point.x and height point.y + * @param path2 The path for storing image with width point.y and height point.x + * @return Returns the {@code error} if save operation was not successful, + * otherwise {@code null}. + */ + public static Error saveForDisplayResolution(Bitmap bitmap, Point point, String path1, String path2) { + + Error error; + Bitmap bitmap1 = resizeBitmap(bitmap, point); + error = compressAndSaveBitmap(bitmap1, path1); + + if (error != null) { + return error; + } + + Bitmap bitmap2 = resizeBitmap(bitmap, new Point(point.y, point.x)); + error = compressAndSaveBitmap(bitmap2, path2); + + return error; + } + + /** + * Check for the given {@link Drawable} whether it is instance of {@link + * BitmapDrawable} or not. + * + * @param drawable The drawable to check. + * @return Retruns whether drawable is bitmap drawable. + */ + public static boolean isBitmapDrawable(Drawable drawable) { + return drawable instanceof BitmapDrawable; + } + +} From 8a4b16574e20c4ed7df75e42c1a543d629b51e12 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Fri, 4 Nov 2022 18:23:47 +0530 Subject: [PATCH 03/11] Added: Add `getIntColorFromString()` and `swap()` into the `DataUtils` `getIntColorFromString()` can be used to get an int color by parsing the string color. Color can be in the form of `#AARRGGBB` or `#RRGGBB`. `swap()` can be used to exchange x and y value present in `Point`. --- .../com/termux/shared/data/DataUtils.java | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java index f95f2b52f8..5a18012ae6 100644 --- a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java @@ -1,5 +1,7 @@ package com.termux.shared.data; +import android.graphics.Color; +import android.graphics.Point; import android.os.Bundle; import androidx.annotation.NonNull; @@ -10,7 +12,6 @@ import java.io.ByteArrayOutputStream; import java.io.ObjectOutputStream; import java.io.Serializable; -import java.util.Collections; public class DataUtils { @@ -255,4 +256,56 @@ public static long getSerializedSize(Serializable object) { } } + + /** + * Wrapper for {@link #getIntColorFromString(String, int, boolean)} with `setAlpha` `false`. + */ + public static int getIntColorFromString(String value, int def) { + if (value == null) return def; + + try { + return Color.parseColor(value); + } catch (Exception e) { + return def; + } + } + + /** + * Get an {@code int} color from {@link String} with alpha value change. If {@code setAlpha} + * is {@code true} and given value is missing alpha then set it using def alpha. + * + * @param value The {@link String} value. + * @param def The default value if failed to read a valid value. + * @param setAlpha The {@code boolean} value that decides whether to set alpha or not. + * @return Returns the {@code int} color value after parsing the {@link String} + * value, otherwise returns default value. + */ + public static int getIntColorFromString(String value, int def, boolean setAlpha) { + if (value == null) return def; + + try { + int color = Color.parseColor(value); + + if (setAlpha && value.length() == 7) { + // Use alpha value of `def` color and rgb value of given `value`. + color = (def & 0xff000000) | (color & 0x00ffffff); + } + + return color; + } catch (Exception e) { + return def; + } + } + + + /** + * Exchanges the value of x and y in {@link Point}. + * + * @param point The original source point to swap. + * @return Returns new swaped point. + */ + public static Point swap(Point point) { + return new Point(point.y, point.x); + } + } From 7cc8a797ec9aa6a7007160ea003186156c0634a0 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Fri, 4 Nov 2022 19:13:44 +0530 Subject: [PATCH 04/11] Added: Add support to use the image as a background This change allows the user to use an image as the terminal's background. Add option to select or remove the background image in the context menu -> style. `TermuxBackgroundManager` holds the responsibility of making background changes. It provides functionalities: - Change the background color. - Set or remove the background image. - Selecting an image from the gallery. - Restore the image if available. - Notify about the changes made to the background. `updateBackgroundColor()` is moved from `TermuxTerminalSessionActivityClient` to `TermuxBackgroundManager`. Images are stored in the termux data home directory. The image is resized corresponding to the display resolution and compressed before saving. Background overlay color is read from the `termux.properties` file with the key `background-overlay-color`. The default value is set to `#59000000`. To change the background overlay color, add `background-overlay-color=` to `termux.properties` file. Supported color formats are `#AARRGGBB` or `#RRGGBB`. --- .../java/com/termux/app/TermuxActivity.java | 51 +++- .../app/style/TermuxBackgroundManager.java | 254 ++++++++++++++++++ .../TermuxTerminalSessionActivityClient.java | 32 ++- app/src/main/res/values/strings.xml | 11 + .../termux/shared/termux/TermuxConstants.java | 17 ++ .../TermuxAppSharedPreferences.java | 10 + .../TermuxPreferenceConstants.java | 10 + .../properties/TermuxPropertyConstants.java | 12 +- .../properties/TermuxSharedProperties.java | 18 ++ 9 files changed, 398 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index a2bb11e8c4..08ea0a4c35 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -9,6 +9,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; +import android.content.res.Configuration; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -18,6 +19,7 @@ import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -30,6 +32,7 @@ import com.termux.R; import com.termux.app.api.file.FileReceiverActivity; +import com.termux.app.style.TermuxBackgroundManager; import com.termux.app.terminal.TermuxActivityRootView; import com.termux.app.terminal.TermuxTerminalSessionActivityClient; import com.termux.app.terminal.io.TermuxTerminalExtraKeys; @@ -140,6 +143,11 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo */ TermuxSessionsListViewController mTermuxSessionListViewController; + /** + * The termux background manager for updating background. + */ + TermuxBackgroundManager mTermuxBackgroundManager; + /** * The {@link TermuxActivity} broadcast receiver for various things like terminal style configuration changes. */ @@ -185,6 +193,9 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo private static final int CONTEXT_MENU_RESET_TERMINAL_ID = 3; private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4; private static final int CONTEXT_MENU_STYLING_ID = 5; + private static final int CONTEXT_SUBMENU_FONT_AND_COLOR_ID = 11; + private static final int CONTEXT_SUBMENU_SET_BACKROUND_IMAGE_ID = 12; + private static final int CONTEXT_SUBMENU_REMOVE_BACKGROUND_IMAGE_ID = 13; private static final int CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON = 6; private static final int CONTEXT_MENU_HELP_ID = 7; private static final int CONTEXT_MENU_SETTINGS_ID = 8; @@ -242,6 +253,10 @@ public void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); } + // Must be done every time activity is created in order to registerForActivityResult, + // Even if the logic of launching is based on user input. + setBackgroundManager(); + setTermuxTerminalViewAndClients(); setTerminalToolbarView(savedInstanceState); @@ -375,6 +390,14 @@ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { savedInstanceState.putBoolean(ARG_ACTIVITY_RECREATED, true); } + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + Logger.logVerbose(LOG_TAG, "onConfigurationChanged"); + + super.onConfigurationChanged(newConfig); + mTermuxTerminalSessionActivityClient.onConfigurationChanged(newConfig); + } + @@ -595,6 +618,10 @@ private void setToggleKeyboardView() { }); } + private void setBackgroundManager() { + this.mTermuxBackgroundManager = new TermuxBackgroundManager(TermuxActivity.this); + } + @@ -648,7 +675,13 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password); menu.add(Menu.NONE, CONTEXT_MENU_RESET_TERMINAL_ID, Menu.NONE, R.string.action_reset_terminal); menu.add(Menu.NONE, CONTEXT_MENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.action_kill_process, getCurrentSession().getPid())).setEnabled(currentSession.isRunning()); - menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal); + + SubMenu subMenu = menu.addSubMenu(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal); + subMenu.clearHeader(); + subMenu.add(SubMenu.NONE, CONTEXT_SUBMENU_FONT_AND_COLOR_ID, SubMenu.NONE, R.string.action_font_and_color); + subMenu.add(SubMenu.NONE, CONTEXT_SUBMENU_SET_BACKROUND_IMAGE_ID, SubMenu.NONE, R.string.action_set_background_image); + subMenu.add(SubMenu.NONE, CONTEXT_SUBMENU_REMOVE_BACKGROUND_IMAGE_ID, SubMenu.NONE, R.string.action_remove_background_image); + menu.add(Menu.NONE, CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON, Menu.NONE, R.string.action_toggle_keep_screen_on).setCheckable(true).setChecked(mPreferences.shouldKeepScreenOn()); menu.add(Menu.NONE, CONTEXT_MENU_HELP_ID, Menu.NONE, R.string.action_open_help); menu.add(Menu.NONE, CONTEXT_MENU_SETTINGS_ID, Menu.NONE, R.string.action_open_settings); @@ -685,8 +718,14 @@ public boolean onContextItemSelected(MenuItem item) { case CONTEXT_MENU_KILL_PROCESS_ID: showKillSessionDialog(session); return true; - case CONTEXT_MENU_STYLING_ID: - showStylingDialog(); + case CONTEXT_SUBMENU_FONT_AND_COLOR_ID: + showFontAndColorDialog(); + return true; + case CONTEXT_SUBMENU_SET_BACKROUND_IMAGE_ID: + mTermuxBackgroundManager.setBackgroundImage(); + return true; + case CONTEXT_SUBMENU_REMOVE_BACKGROUND_IMAGE_ID: + mTermuxBackgroundManager.removeBackgroundImage(true); return true; case CONTEXT_MENU_TOGGLE_KEEP_SCREEN_ON: toggleKeepScreenOn(); @@ -736,7 +775,7 @@ private void onResetTerminalSession(TerminalSession session) { } } - private void showStylingDialog() { + private void showFontAndColorDialog() { Intent stylingIntent = new Intent(); stylingIntent.setClassName(TermuxConstants.TERMUX_STYLING_PACKAGE_NAME, TermuxConstants.TERMUX_STYLING.TERMUX_STYLING_ACTIVITY_NAME); try { @@ -916,6 +955,10 @@ public TermuxAppSharedProperties getProperties() { return mProperties; } + public TermuxBackgroundManager getmTermuxBackgroundManager() { + return mTermuxBackgroundManager; + } + diff --git a/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java new file mode 100644 index 0000000000..ea7b2462df --- /dev/null +++ b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java @@ -0,0 +1,254 @@ +package com.termux.app.style; + +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContract; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; + +import com.termux.R; +import com.termux.app.TermuxActivity; +import com.termux.shared.activity.ActivityUtils; +import com.termux.shared.data.DataUtils; +import com.termux.shared.errors.Error; +import com.termux.shared.file.FileUtils; +import com.termux.shared.image.ImageUtils; +import com.termux.shared.logger.Logger; +import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; +import com.termux.shared.view.ViewUtils; +import com.termux.terminal.TerminalSession; +import com.termux.terminal.TextStyle; + +public class TermuxBackgroundManager { + + /** Require {@link TermuxActivity} to perform operations. */ + private final TermuxActivity mActivity; + + /** A launcher for start the process of executing an {@link ActivityResultContract}. */ + private final ActivityResultLauncher mActivityResultLauncher; + + /** Termux app shared preferences manager. */ + private final TermuxAppSharedPreferences mPreferences; + + + private static final String LOG_TAG = "TermuxBackgroundManager"; + + public TermuxBackgroundManager(TermuxActivity activity) { + this.mActivity = activity; + this.mPreferences = activity.getPreferences(); + this.mActivityResultLauncher = registerActivityResultLauncher(); + } + + /** + * Registers for activity result launcher. It's safe to call before fragment + * or activity is created. + * + * @return A launcher for executing an {@link ActivityResultContract}. + */ + private ActivityResultLauncher registerActivityResultLauncher() { + return mActivity.registerForActivityResult(new ActivityResultContracts.GetContent(), uri -> { + + if (uri != null) { + try { + new Thread(() -> { + Bitmap bitmap = ImageUtils.getBitmap(mActivity, uri); + + if (bitmap == null) { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed)); + return; + } + + Point size = ViewUtils.getDisplaySize(mActivity, false); + boolean isLandscape = ViewUtils.getDisplayOrientation(mActivity) == Configuration.ORIENTATION_LANDSCAPE; + Error error; + + if (isLandscape) { + error = ImageUtils.saveForDisplayResolution(bitmap, size, TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + + } else { + error = ImageUtils.saveForDisplayResolution(bitmap, DataUtils.swap(size), TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + } + + if (error != null) { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed)); + return; + } + + notifyBackgroundUpdated(true); + + Logger.logInfo(LOG_TAG, "Image received successfully from the gallary."); + Logger.logDebug(LOG_TAG, "Storing background portrait image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + Logger.logDebug(LOG_TAG, "Storing background landscape image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); + }).start(); + + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); + Logger.showToast(mActivity, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed), true); + } + } + }); + } + + /** + * Check whether the optimized background image for {@link Configuration#ORIENTATION_PORTRAIT + * Portrait} and {@link Configuration#ORIENTATION_LANDSCAPE Landscape} display view exist. + * + * @param context The context for operation. + * @return Returns whether the optimized background image exist or not. + */ + public static boolean isImageFilesExist(@NonNull Context context) { + boolean isLandscape = (ViewUtils.getDisplayOrientation(context) == Configuration.ORIENTATION_LANDSCAPE); + Point size = ViewUtils.getDisplaySize(context, false); + + String imagePath1 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH; + + String imagePath2 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH; + + return ImageUtils.isImageOptimized(imagePath1, size) && ImageUtils.isImageOptimized(imagePath2, DataUtils.swap(size)); + } + + + + /** + * Enable background image loading. If the image already exist then ask for restore otherwise pick from gallery. + */ + public void setBackgroundImage() { + if (!mPreferences.isBackgroundImageEnabled() && isImageFilesExist(mActivity)) { + restoreBackgroundImages(); + + } else { + pickImageFromGallery(); + } + } + + /** + * Disable background image loading and notify about the changes. + * If image files are not deleted then it can be used to restore + * when resetting background to image. + * + * @param deleteFiles The {@code boolean} that decides if it should delete the image files. + */ + public void removeBackgroundImage(boolean deleteFiles) { + if (deleteFiles) { + FileUtils.deleteRegularFile(null, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH, true); + FileUtils.deleteRegularFile(null, TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, true); + } + + notifyBackgroundUpdated(false); + } + + /** {@link ActivityResultLauncher#launch(Object) Launch} Activity for result to pick image from gallery. */ + private void pickImageFromGallery() { + ActivityUtils.startActivityForResult(mActivity, mActivityResultLauncher, ImageUtils.ANY_IMAGE_TYPE); + } + + /** + * If the background images exist then ask user whether to restore them or not. + * If denied pick from gallery. + */ + private void restoreBackgroundImages() { + AlertDialog.Builder b = new AlertDialog.Builder(mActivity); + + b.setMessage(R.string.title_restore_background_image); + b.setPositiveButton(R.string.action_yes, (dialog, id) -> { + notifyBackgroundUpdated(true); + }); + + b.setNegativeButton(R.string.action_no, ((dialog, id) -> { + pickImageFromGallery(); + })); + + b.show(); + } + + + /** + * Updates background to image or solid color. If forced then load again even if + * the background is already set. Forced update is require when the display orientation + * is changed. + * + * @param forced Force background update task. + */ + public void updateBackground(boolean forced) { + if (!mActivity.isVisible()) return; + + if (mActivity.getPreferences().isBackgroundImageEnabled()) { + + Drawable drawable = mActivity.getWindow().getDecorView().getBackground(); + + // If it's not forced update and background is already drawn, + // then avoid reloading of image. + if (!forced && ImageUtils.isBitmapDrawable(drawable)) { + return; + } + + updateBackgroundImage(); + } else { + updateBackgroundColor(); + } + } + + /** + * Set background to color. + */ + public void updateBackgroundColor() { + if (!mActivity.isVisible()) return; + + TerminalSession session = mActivity.getCurrentSession(); + + if (session != null && session.getEmulator() != null) { + mActivity.getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); + } + } + + /** + * Set background to image corresponding to display orientation. + */ + public void updateBackgroundImage() { + boolean isLandscape = ViewUtils.getDisplayOrientation(mActivity) == Configuration.ORIENTATION_LANDSCAPE; + + String imagePath = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH; + + if (isImageFilesExist(mActivity)) { + try { + Drawable drawable = ImageUtils.getDrawable(imagePath); + ImageUtils.addOverlay(drawable, mActivity.getProperties().getBackgroundOverlayColor()); + mActivity.getWindow().getDecorView().setBackground(drawable); + + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); + Logger.showToast(mActivity, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed), true); + + // Since loading of image is failed, Set background to solid color. + updateBackgroundColor(); + } + + } else { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_failed)); + + // Image files are unable to load so set background to solid color and notify update. + updateBackgroundColor(); + notifyBackgroundUpdated(false); + } + } + + + + /** + * Notify that the background is changed. New background can be image or solid color. + * + * @param isImage The {@code boolean} indicates that new background is image or not. + */ + public void notifyBackgroundUpdated(boolean isImage) { + mPreferences.setBackgroundImageEnabled(isImage); + TermuxActivity.updateTermuxActivityStyling(mActivity, true); + } + +} diff --git a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java index cd38163116..a6688ebf0c 100644 --- a/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java +++ b/app/src/main/java/com/termux/app/terminal/TermuxTerminalSessionActivityClient.java @@ -7,6 +7,7 @@ import android.content.ClipboardManager; import android.content.Context; import android.content.pm.PackageManager; +import android.content.res.Configuration; import android.graphics.Typeface; import android.media.AudioAttributes; import android.media.SoundPool; @@ -30,7 +31,6 @@ import com.termux.terminal.TerminalColors; import com.termux.terminal.TerminalSession; import com.termux.terminal.TerminalSessionClient; -import com.termux.terminal.TextStyle; import java.io.File; import java.io.FileInputStream; @@ -77,6 +77,10 @@ public void onStart() { // The current terminal session may have changed while being away, force // a refresh of the displayed terminal. mActivity.getTerminalView().onScreenUpdated(); + + // Set background image or color. The display orientation may have changed + // while being away, force a background update. + mActivity.getmTermuxBackgroundManager().updateBackground(true); } /** @@ -104,12 +108,20 @@ public void onStop() { releaseBellSoundPool(); } + public void onConfigurationChanged(@NonNull Configuration newConfig) { + // Display orientation may have changed, force a background update. + mActivity.getmTermuxBackgroundManager().updateBackground(true); + } + /** * Should be called when mActivity.reloadActivityStyling() is called */ public void onReloadActivityStyling() { // Set terminal fonts and colors checkForFontAndColors(); + + // Set background image or color + mActivity.getmTermuxBackgroundManager().updateBackground(true); } @@ -218,7 +230,9 @@ public void onBell(@NonNull TerminalSession session) { @Override public void onColorsChanged(@NonNull TerminalSession changedSession) { if (mActivity.getCurrentSession() == changedSession) - updateBackgroundColor(); + // Background color may have changed. If the background is image and already set, + // no need for update. + mActivity.getmTermuxBackgroundManager().updateBackground(false); } @Override @@ -301,7 +315,10 @@ public void setCurrentSession(TerminalSession session) { // We call the following even when the session is already being displayed since config may // be stale, like current session not selected or scrolled to. checkAndScrollToSession(session); - updateBackgroundColor(); + + // Background color may have changed. If the background is image and already set, + // no need for update. + mActivity.getmTermuxBackgroundManager().updateBackground(false); } void notifyOfSessionChange() { @@ -508,7 +525,6 @@ public void checkForFontAndColors() { if (session != null && session.getEmulator() != null) { session.getEmulator().mColors.reset(); } - updateBackgroundColor(); final Typeface newTypeface = (fontFile.exists() && fontFile.length() > 0) ? Typeface.createFromFile(fontFile) : Typeface.MONOSPACE; mActivity.getTerminalView().setTypeface(newTypeface); @@ -517,12 +533,4 @@ public void checkForFontAndColors() { } } - public void updateBackgroundColor() { - if (!mActivity.isVisible()) return; - TerminalSession session = mActivity.getCurrentSession(); - if (session != null && session.getEmulator() != null) { - mActivity.getWindow().getDecorView().setBackgroundColor(session.getEmulator().mColors.mCurrentColors[TextStyle.COLOR_INDEX_BACKGROUND]); - } - } - } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 794d8df3e0..d8657742c7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,6 +82,10 @@ Really kill this session? Style + Font and Color + Set background image + Remove background image + Keep screen on Help Settings @@ -128,6 +132,13 @@ + + Background image detected. Do you want to restore it? + Couldn\'t load background image. Setting background color. + Failed to load image. Try another image. + + + Failed to start TermuxService. Check logcat for exception message. Failed to start TermuxService while app is in background due to android bg restrictions. diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 59f523af72..bf2e7b2945 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -277,6 +277,9 @@ * * - 0.52.0 (2022-06-18) * - Added `TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY`. + * + * - 0.53.0 (2022-11-04) + * - Added `TERMUX_BACKGROUND_IMAGE_PATH`, `TERMUX_BACKGROUND_IMAGE_FILE`,`TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH` and `TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE` */ /** @@ -678,6 +681,20 @@ public final class TermuxConstants { + /** Termux app backgorund image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background.jpeg" + + /** Termux app backgorund image file */ + public static final File TERMUX_BACKGROUND_IMAGE_FILE = new File(TERMUX_BACKGROUND_IMAGE_PATH); + + /** Termux app landscape backgorund image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background_landscape.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background.jpeg" + + /** Termux app landscape backgorund image file */ + public static final File TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE = new File(TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); + + + /** Termux and plugin apps directory path */ public static final String TERMUX_APPS_DIR_PATH = TERMUX_FILES_DIR_PATH + "/apps"; // Default: "/data/data/com.termux/files/apps" /** Termux and plugin apps directory */ diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java index cd3812fe49..aefc79274e 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxAppSharedPreferences.java @@ -234,6 +234,16 @@ public void setTerminalViewKeyLoggingEnabled(boolean value) { + public boolean isBackgroundImageEnabled() { + return SharedPreferenceUtils.getBoolean(mSharedPreferences, TERMUX_APP.KEY_BACKGROUND_IMAGE_ENABLED, TERMUX_APP.DEFAULT_VALUE_BACKGROUND_IMAGE_ENABLED); + } + + public void setBackgroundImageEnabled(boolean value) { + SharedPreferenceUtils.setBoolean(mSharedPreferences, TERMUX_APP.KEY_BACKGROUND_IMAGE_ENABLED, value, false); + } + + + public boolean arePluginErrorNotificationsEnabled(boolean readFromFile) { if (readFromFile) return SharedPreferenceUtils.getBoolean(mMultiProcessSharedPreferences, TERMUX_APP.KEY_PLUGIN_ERROR_NOTIFICATIONS_ENABLED, TERMUX_APP.DEFAULT_VALUE_PLUGIN_ERROR_NOTIFICATIONS_ENABLED); diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java index 15bc74c9f2..228d94d96d 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/preferences/TermuxPreferenceConstants.java @@ -69,6 +69,10 @@ * - 0.16.0 (2022-06-11) * - Added following to `TERMUX_APP`: * `KEY_APP_SHELL_NUMBER_SINCE_BOOT` and `KEY_TERMINAL_SESSION_NUMBER_SINCE_BOOT`. + * + * - 0.17.0 (2022-11-04) + * - Added following to `TERMUX_APP`: + * `KEY_BACKGROUND_IMAGE_ENABLED` and `DEFAULT_VALUE_BACKGROUND_IMAGE_ENABLED`. */ import com.termux.shared.shell.command.ExecutionCommand; @@ -180,6 +184,12 @@ public static final class TERMUX_APP { public static final String KEY_CRASH_REPORT_NOTIFICATIONS_ENABLED = "crash_report_notifications_enabled"; public static final boolean DEFAULT_VALUE_CRASH_REPORT_NOTIFICATIONS_ENABLED = true; + /** + * Defines the key for whether background image is enabled or not. + */ + public static final String KEY_BACKGROUND_IMAGE_ENABLED = "background_image_enabled"; + public static final boolean DEFAULT_VALUE_BACKGROUND_IMAGE_ENABLED = false; + } diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java index dd935f3ab5..e99fb7a8dc 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxPropertyConstants.java @@ -80,6 +80,9 @@ * * - 0.18.0 (2022-06-13) * - Add `KEY_DISABLE_FILE_SHARE_RECEIVER` and `KEY_DISABLE_FILE_VIEW_RECEIVER`. + * + * - 0.19.0 (2022-11-04) + * - Add `KEY_BACKGROUND_OVERLAY_COLOR` and `DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR` */ /** @@ -384,6 +387,12 @@ public final class TermuxPropertyConstants { + /** Defines the key for background overlay color */ + public static final String KEY_BACKGROUND_OVERLAY_COLOR = "background-overlay-color"; // Default: "background-overlay-color + public static final int DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR = 0x59000000; + + + /** Defines the set for keys loaded by termux @@ -430,7 +439,8 @@ public final class TermuxPropertyConstants { KEY_EXTRA_KEYS_STYLE, KEY_NIGHT_MODE, KEY_SOFT_KEYBOARD_TOGGLE_BEHAVIOUR, - KEY_VOLUME_KEYS_BEHAVIOUR + KEY_VOLUME_KEYS_BEHAVIOUR, + KEY_BACKGROUND_OVERLAY_COLOR )); /** Defines the set for keys loaded by termux that have default boolean behaviour with false as default. diff --git a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java index e277f4506a..58e63359b8 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/settings/properties/TermuxSharedProperties.java @@ -273,6 +273,8 @@ public static Object getInternalTermuxPropertyValueFromValue(Context context, St return (int) getTerminalMarginVerticalInternalPropertyValueFromValue(value); case TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS: return (int) getTerminalTranscriptRowsInternalPropertyValueFromValue(value); + case TermuxPropertyConstants.KEY_BACKGROUND_OVERLAY_COLOR: + return (int) getBackgroundOverlayInternalPropertyValueFromValue(value); /* float */ case TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR: @@ -438,6 +440,18 @@ public static int getTerminalTranscriptRowsInternalPropertyValueFromValue(String true, true, LOG_TAG); } + /** + * Returns the int for the color value if its not null and is in form of {@code #AARRGGBB}. + * If the value does not contain alpha value then it will borrow it from {@link + * TermuxPropertyConstants#DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR} + * + * @param value The {@link String} value to convert. + * @return Returns the internal value for value. + */ + public static int getBackgroundOverlayInternalPropertyValueFromValue(String value) { + return DataUtils.getIntColorFromString(value, TermuxPropertyConstants.DEFAULT_IVALUE_BACKGROUND_OVERLAY_COLOR, true); + } + /** * Returns the int for the value if its not null and is between * {@link TermuxPropertyConstants#IVALUE_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR_MIN} and @@ -654,6 +668,10 @@ public int getTerminalTranscriptRows() { return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TRANSCRIPT_ROWS, true); } + public int getBackgroundOverlayColor() { + return (int) getInternalPropertyValue(TermuxPropertyConstants.KEY_BACKGROUND_OVERLAY_COLOR, true); + } + public float getTerminalToolbarHeightScaleFactor() { return (float) getInternalPropertyValue(TermuxPropertyConstants.KEY_TERMINAL_TOOLBAR_HEIGHT_SCALE_FACTOR, true); } From 4dc3bf6d9ebb1c84fda541a3bf3ba2fd29f8a628 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Fri, 4 Nov 2022 19:24:17 +0530 Subject: [PATCH 05/11] Added: Add setting to enable or disable background image loading The user can enable or disable background image loading from the termux setting available in `Termux Style` preferences with this commit. If some malformed image is stored as background, disable image loading in the setting. Disabling the image from the setting will not delete image files and can be restored. --- .../TermuxStylePreferencesFragment.java | 104 ++++++++++++++++++ app/src/main/res/values/strings.xml | 15 +++ app/src/main/res/xml/termux_preferences.xml | 5 + .../main/res/xml/termux_style_preferences.xml | 16 +++ 4 files changed, 140 insertions(+) create mode 100644 app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java create mode 100644 app/src/main/res/xml/termux_style_preferences.xml diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java new file mode 100644 index 0000000000..cff1f12f3f --- /dev/null +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java @@ -0,0 +1,104 @@ +package com.termux.app.fragments.settings.termux; + +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; +import androidx.preference.PreferenceDataStore; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceManager; +import androidx.preference.SwitchPreferenceCompat; + +import com.termux.R; +import com.termux.app.style.TermuxBackgroundManager; +import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; + +@Keep +public class TermuxStylePreferencesFragment extends PreferenceFragmentCompat { + + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + Context context = getContext(); + if (context == null) return; + + PreferenceManager preferenceManager = getPreferenceManager(); + preferenceManager.setPreferenceDataStore(TermuxStylePreferencesDataStore.getInstance(context)); + + setPreferencesFromResource(R.xml.termux_style_preferences, rootKey); + + configureBackgroundPreferences(context); + } + + /** + * Configure background preferences and make appropriate changes in the state of components. + * + * @param context The context for operations. + */ + private void configureBackgroundPreferences(@NonNull Context context) { + SwitchPreferenceCompat backgroundImagePreference = findPreference("background_image_enabled"); + + if (backgroundImagePreference != null) { + TermuxAppSharedPreferences preferences = TermuxAppSharedPreferences.build(context, true); + + if (preferences == null) return; + + // If background image preference is disabled and background images are + // missing, then don't allow user to enable it from setting. + if (!preferences.isBackgroundImageEnabled() && !TermuxBackgroundManager.isImageFilesExist(context)) { + backgroundImagePreference.setEnabled(false); + } + } + } + +} + +class TermuxStylePreferencesDataStore extends PreferenceDataStore { + + private final Context mContext; + + private final TermuxAppSharedPreferences mPreferences; + + private static TermuxStylePreferencesDataStore mInstance; + + private TermuxStylePreferencesDataStore(Context context) { + mContext = context; + mPreferences = TermuxAppSharedPreferences.build(context, true); + } + + public static synchronized TermuxStylePreferencesDataStore getInstance(Context context) { + if (mInstance == null) { + mInstance = new TermuxStylePreferencesDataStore(context); + } + + return mInstance; + } + + + + @Override + public void putBoolean(String key, boolean value) { + if (mPreferences == null) return; + if (key == null) return; + + switch (key) { + case "background_image_enabled": + mPreferences.setBackgroundImageEnabled(value); + default: + break; + } + } + + @Override + public boolean getBoolean(String key, boolean defValue) { + if (mPreferences == null) return false; + + switch (key) { + case "background_image_enabled": + return mPreferences.isBackgroundImageEnabled(); + default: + return false; + } + } + +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8657742c7..e13755e6fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -214,6 +214,21 @@ If it causes screen flickering on your devices, then disable it. (Default) + + Termux Style + Preferences for termux style + + + Background + + + Background Image Enabled + Background image will be disabled. + To enable it appropriate portrait and landscape file should be present. + If you want to set new background, set it from context menu by long click on terminal. (Default) + Background image will be enabled. + + &TERMUX_API_APP_NAME; diff --git a/app/src/main/res/xml/termux_preferences.xml b/app/src/main/res/xml/termux_preferences.xml index 90353c51e1..79ad123dda 100644 --- a/app/src/main/res/xml/termux_preferences.xml +++ b/app/src/main/res/xml/termux_preferences.xml @@ -15,4 +15,9 @@ app:summary="@string/termux_terminal_view_preferences_summary" app:fragment="com.termux.app.fragments.settings.termux.TerminalViewPreferencesFragment"/> + + diff --git a/app/src/main/res/xml/termux_style_preferences.xml b/app/src/main/res/xml/termux_style_preferences.xml new file mode 100644 index 0000000000..f1eb29791a --- /dev/null +++ b/app/src/main/res/xml/termux_style_preferences.xml @@ -0,0 +1,16 @@ + + + + + + + + + + From 5f4d6efae89ed4a1c6e8cbf145568b5076c30ed8 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Fri, 4 Nov 2022 19:51:15 +0530 Subject: [PATCH 06/11] Fixed: Fix default background landscape path comment Change comment for landscape background from `background.jpeg` to `background_landscape.jpeg`. --- .../src/main/java/com/termux/shared/termux/TermuxConstants.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index bf2e7b2945..642cd6e98f 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -688,7 +688,7 @@ public final class TermuxConstants { public static final File TERMUX_BACKGROUND_IMAGE_FILE = new File(TERMUX_BACKGROUND_IMAGE_PATH); /** Termux app landscape backgorund image file path */ - public static final String TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background_landscape.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background.jpeg" + public static final String TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background_landscape.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background_landscape.jpeg" /** Termux app landscape backgorund image file */ public static final File TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE = new File(TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); From b933cba5da310bc2fe7e23c99860307f4a2305c6 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Sat, 5 Nov 2022 12:07:35 +0530 Subject: [PATCH 07/11] Changed: Change background of the Extra Keys Toolbar corresponding to the preference Set overlay color to the `TerminalToolbarViewPager` background when the image is background. And set the `ExtraKeysView` button to transparent. If the background is color, then set it to the default value. --- .../app/style/TermuxBackgroundManager.java | 35 ++++++++++++++++++- .../terminal/io/TerminalToolbarViewPager.java | 3 ++ .../com/termux/shared/data/DataUtils.java | 8 +---- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java index ea7b2462df..6e1500e877 100644 --- a/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java +++ b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java @@ -3,14 +3,17 @@ import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.Drawable; +import android.util.Log; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContract; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; +import androidx.viewpager.widget.ViewPager; import com.termux.R; import com.termux.app.TermuxActivity; @@ -21,7 +24,9 @@ import com.termux.shared.image.ImageUtils; import com.termux.shared.logger.Logger; import com.termux.shared.termux.TermuxConstants; +import com.termux.shared.termux.extrakeys.ExtraKeysView; import com.termux.shared.termux.settings.preferences.TermuxAppSharedPreferences; +import com.termux.shared.theme.ThemeUtils; import com.termux.shared.view.ViewUtils; import com.termux.terminal.TerminalSession; import com.termux.terminal.TextStyle; @@ -179,6 +184,7 @@ private void restoreBackgroundImages() { public void updateBackground(boolean forced) { if (!mActivity.isVisible()) return; + Log.d("FF", " " + mActivity.getPreferences().isBackgroundImageEnabled()); if (mActivity.getPreferences().isBackgroundImageEnabled()) { Drawable drawable = mActivity.getWindow().getDecorView().getBackground(); @@ -193,6 +199,8 @@ public void updateBackground(boolean forced) { } else { updateBackgroundColor(); } + + updateToolbarBackground(); } /** @@ -228,6 +236,7 @@ public void updateBackgroundImage() { // Since loading of image is failed, Set background to solid color. updateBackgroundColor(); + notifyBackgroundUpdated(false); } } else { @@ -239,6 +248,30 @@ public void updateBackgroundImage() { } } + /** + * Set backgroudn of the ExtraKey toolbar and buttons. + * Must be called when background preference is changed. + */ + public void updateToolbarBackground() { + ViewPager viewPager = mActivity.getTerminalToolbarViewPager(); + ExtraKeysView extraKeysView = mActivity.getExtraKeysView(); + + if (viewPager == null || extraKeysView == null) { + return; + } + + if (mPreferences.isBackgroundImageEnabled()) { + // Set overlay background to ToolbarViewPager and make button transparent. + mActivity.getTerminalToolbarViewPager().setBackgroundColor(mActivity.getProperties().getBackgroundOverlayColor()); + extraKeysView.setButtonBackgroundColor(Color.TRANSPARENT); + + } else { + // Use default background color of ToolbarViewPager and button. + viewPager.setBackgroundColor(Color.BLACK); + extraKeysView.setButtonBackgroundColor(ThemeUtils.getSystemAttrColor(mActivity, ExtraKeysView.ATTR_BUTTON_BACKGROUND_COLOR, ExtraKeysView.DEFAULT_BUTTON_BACKGROUND_COLOR)); + } + } + /** @@ -248,7 +281,7 @@ public void updateBackgroundImage() { */ public void notifyBackgroundUpdated(boolean isImage) { mPreferences.setBackgroundImageEnabled(isImage); - TermuxActivity.updateTermuxActivityStyling(mActivity, true); + TermuxActivity.updateTermuxActivityStyling(mActivity, false); } } diff --git a/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java b/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java index a526570b09..82530d4a3e 100644 --- a/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java +++ b/app/src/main/java/com/termux/app/terminal/io/TerminalToolbarViewPager.java @@ -55,6 +55,9 @@ public Object instantiateItem(@NonNull ViewGroup collection, int position) { FullScreenWorkAround.apply(mActivity); } + // Update toolbar background corresponding to prefs + mActivity.getmTermuxBackgroundManager().updateToolbarBackground(); + } else { layout = inflater.inflate(R.layout.view_terminal_toolbar_text_input, collection, false); final EditText editText = layout.findViewById(R.id.terminal_toolbar_text_input); diff --git a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java index 5a18012ae6..ae7e0d1a5f 100644 --- a/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/data/DataUtils.java @@ -261,13 +261,7 @@ public static long getSerializedSize(Serializable object) { * Wrapper for {@link #getIntColorFromString(String, int, boolean)} with `setAlpha` `false`. */ public static int getIntColorFromString(String value, int def) { - if (value == null) return def; - - try { - return Color.parseColor(value); - } catch (Exception e) { - return def; - } + return getIntColorFromString(value, def, false); } /** From 0b2322ddcf79f3cea1e8435d8b6e0b7ecad1ed48 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Sat, 5 Nov 2022 16:16:25 +0530 Subject: [PATCH 08/11] Fixed: Fix `ImageUtils` log tag value `FileUtils` to `ImageUtils` Co-authored-by: Susko3 --- .../src/main/java/com/termux/shared/image/ImageUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java index 902166e47d..86ec901972 100644 --- a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java @@ -48,7 +48,7 @@ public final class ImageUtils { public static final String ANY_IMAGE_TYPE = IMAGE_TYPE + "/*"; - private static final String LOG_TAG = "FileUtils"; + private static final String LOG_TAG = "ImageUtils"; /** * Don't let anyone instantiate this class. From 49f8a9f755fff9b880ccbd488ac2e7edbe9d21bf Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Sun, 6 Nov 2022 23:37:45 +0530 Subject: [PATCH 09/11] Added|Changed: Create background directory and keep original image Add `TERMUX_BACKGROUND_DIR_PATH` and `TERMUX_BACKGROUND_DIR` to store background images. Use `TERMUX_BACKGROUND_IMAGE_PATH` and `TERMUX_BACKGROUND_IMAGE_FILE` to store original compressed image. Change `TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH` and `TERMUX_BACKGROUND_IMAGE_PORTRAIT_FILE` to store portrait image. --- .../termux/shared/termux/TermuxConstants.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java index 642cd6e98f..38f41120ec 100644 --- a/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java +++ b/termux-shared/src/main/java/com/termux/shared/termux/TermuxConstants.java @@ -279,7 +279,10 @@ * - Added `TERMUX_PREFIX_DIR_IGNORED_SUB_FILES_PATHS_TO_CONSIDER_AS_EMPTY`. * * - 0.53.0 (2022-11-04) - * - Added `TERMUX_BACKGROUND_IMAGE_PATH`, `TERMUX_BACKGROUND_IMAGE_FILE`,`TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH` and `TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE` + * - Added `TERMUX_BACKGROUND_DIR_PATH`, `TERMUX_BACKGROUND_DIR`, + * `TERMUX_BACKGROUND_IMAGE_PATH`, `TERMUX_BACKGROUND_IMAGE_FILE`, + * `TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH`, `TERMUX_BACKGROUND_IMAGE_PORTRAIT_FILE`, + * `TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH`, and `TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE`. */ /** @@ -681,14 +684,25 @@ public final class TermuxConstants { - /** Termux app backgorund image file path */ - public static final String TERMUX_BACKGROUND_IMAGE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background.jpeg" + /** Termux app background directory path */ + public static final String TERMUX_BACKGROUND_DIR_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background"; // Default: "/data/data/com.termux/files/.termux/background" + /** Termux app background directory */ + public static final File TERMUX_BACKGROUND_DIR = new File(TERMUX_BACKGROUND_DIR_PATH); - /** Termux app backgorund image file */ + /** Termux app backgorund original image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_PATH = TERMUX_BACKGROUND_DIR_PATH + "/background.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background.jpeg" + + /** Termux app backgorund original image file */ public static final File TERMUX_BACKGROUND_IMAGE_FILE = new File(TERMUX_BACKGROUND_IMAGE_PATH); + /** Termux app portrait backgorund image file path */ + public static final String TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH = TERMUX_BACKGROUND_DIR_PATH + "/background_portrait.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background/background_portrait.jpeg" + + /** Termux app portrait backgorund image file */ + public static final File TERMUX_BACKGROUND_IMAGE_PORTRAIT_FILE = new File(TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + /** Termux app landscape backgorund image file path */ - public static final String TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH = TERMUX_DATA_HOME_DIR_PATH + "/background_landscape.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background_landscape.jpeg" + public static final String TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH = TERMUX_BACKGROUND_DIR_PATH + "/background_landscape.jpeg"; // Default: "/data/data/com.termux/files/home/.termux/background/background_landscape.jpeg" /** Termux app landscape backgorund image file */ public static final File TERMUX_BACKGROUND_IMAGE_LANDSCAPE_FILE = new File(TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); From ad777481e47256ac96d2ed9822983a52afb0ab55 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Sun, 6 Nov 2022 23:46:47 +0530 Subject: [PATCH 10/11] Fixed: Fix image resizing by regenerating image files Compress to jpeg and store original image to `TERMUX_BACKGROUND_DIR`. Use `Activity` size instead of display size. If portrait and landcape files are deleted or `Activity` size is changed, then regenerate images using original image. --- .../TermuxStylePreferencesFragment.java | 2 +- .../app/style/TermuxBackgroundManager.java | 139 ++++++++++++------ .../com/termux/shared/image/ImageUtils.java | 27 +++- 3 files changed, 118 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java b/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java index cff1f12f3f..b5feb697cc 100644 --- a/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java +++ b/app/src/main/java/com/termux/app/fragments/settings/termux/TermuxStylePreferencesFragment.java @@ -45,7 +45,7 @@ private void configureBackgroundPreferences(@NonNull Context context) { // If background image preference is disabled and background images are // missing, then don't allow user to enable it from setting. - if (!preferences.isBackgroundImageEnabled() && !TermuxBackgroundManager.isImageFilesExist(context)) { + if (!preferences.isBackgroundImageEnabled() && !TermuxBackgroundManager.isImageFilesExist(context, false)) { backgroundImagePreference.setEnabled(false); } } diff --git a/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java index 6e1500e877..4601cd06e4 100644 --- a/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java +++ b/app/src/main/java/com/termux/app/style/TermuxBackgroundManager.java @@ -1,12 +1,14 @@ package com.termux.app.style; +import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; import android.graphics.drawable.Drawable; -import android.util.Log; +import android.os.Handler; +import android.os.Looper; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContract; @@ -31,6 +33,9 @@ import com.termux.terminal.TerminalSession; import com.termux.terminal.TextStyle; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + public class TermuxBackgroundManager { /** Require {@link TermuxActivity} to perform operations. */ @@ -42,6 +47,12 @@ public class TermuxBackgroundManager { /** Termux app shared preferences manager. */ private final TermuxAppSharedPreferences mPreferences; + /** ExecutorService to execute task in background. */ + private final ExecutorService executor; + + /** Handler allows to send and process {@link android.os.Message Message}. */ + private final Handler handler; + private static final String LOG_TAG = "TermuxBackgroundManager"; @@ -49,6 +60,8 @@ public TermuxBackgroundManager(TermuxActivity activity) { this.mActivity = activity; this.mPreferences = activity.getPreferences(); this.mActivityResultLauncher = registerActivityResultLauncher(); + this.executor = Executors.newSingleThreadExecutor(); + this.handler = new Handler(Looper.getMainLooper()); } /** @@ -62,7 +75,7 @@ private ActivityResultLauncher registerActivityResultLauncher() { if (uri != null) { try { - new Thread(() -> { + executor.execute(() -> { Bitmap bitmap = ImageUtils.getBitmap(mActivity, uri); if (bitmap == null) { @@ -70,28 +83,21 @@ private ActivityResultLauncher registerActivityResultLauncher() { return; } - Point size = ViewUtils.getDisplaySize(mActivity, false); - boolean isLandscape = ViewUtils.getDisplayOrientation(mActivity) == Configuration.ORIENTATION_LANDSCAPE; - Error error; + ImageUtils.compressAndSaveBitmap(bitmap, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + boolean success = generateImageFiles(mActivity, bitmap); - if (isLandscape) { - error = ImageUtils.saveForDisplayResolution(bitmap, size, TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + if (success) { + notifyBackgroundUpdated(true); - } else { - error = ImageUtils.saveForDisplayResolution(bitmap, DataUtils.swap(size), TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); - } + Logger.logInfo(LOG_TAG, "Image received successfully from the gallary."); + Logger.logDebug(LOG_TAG, "Storing background original image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + Logger.logDebug(LOG_TAG, "Storing background portrait image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + Logger.logDebug(LOG_TAG, "Storing background landscape image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); - if (error != null) { + } else { Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed)); - return; } - - notifyBackgroundUpdated(true); - - Logger.logInfo(LOG_TAG, "Image received successfully from the gallary."); - Logger.logDebug(LOG_TAG, "Storing background portrait image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); - Logger.logDebug(LOG_TAG, "Storing background landscape image to " + TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH); - }).start(); + }); } catch (Exception e) { Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); @@ -108,15 +114,23 @@ private ActivityResultLauncher registerActivityResultLauncher() { * @param context The context for operation. * @return Returns whether the optimized background image exist or not. */ - public static boolean isImageFilesExist(@NonNull Context context) { + public static boolean isImageFilesExist(@NonNull Context context, boolean shouldGenerate) { boolean isLandscape = (ViewUtils.getDisplayOrientation(context) == Configuration.ORIENTATION_LANDSCAPE); - Point size = ViewUtils.getDisplaySize(context, false); + Point size = ViewUtils.getDisplaySize(context, true); + + String imagePath1 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH; - String imagePath1 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH; + String imagePath2 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH; - String imagePath2 = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH; + boolean exist = ImageUtils.isImageOptimized(imagePath1, size) + && ImageUtils.isImageOptimized(imagePath2, DataUtils.swap(size)); + + if (!exist && shouldGenerate && ImageUtils.isImage(TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH)) { + Bitmap bitmap = ImageUtils.getBitmap(TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH); + return generateImageFiles(context, bitmap); + } - return ImageUtils.isImageOptimized(imagePath1, size) && ImageUtils.isImageOptimized(imagePath2, DataUtils.swap(size)); + return exist; } @@ -125,7 +139,7 @@ public static boolean isImageFilesExist(@NonNull Context context) { * Enable background image loading. If the image already exist then ask for restore otherwise pick from gallery. */ public void setBackgroundImage() { - if (!mPreferences.isBackgroundImageEnabled() && isImageFilesExist(mActivity)) { + if (!mPreferences.isBackgroundImageEnabled() && isImageFilesExist(mActivity, true)) { restoreBackgroundImages(); } else { @@ -142,8 +156,7 @@ public void setBackgroundImage() { */ public void removeBackgroundImage(boolean deleteFiles) { if (deleteFiles) { - FileUtils.deleteRegularFile(null, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH, true); - FileUtils.deleteRegularFile(null, TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, true); + FileUtils.deleteDirectoryFile(null, TermuxConstants.TERMUX_BACKGROUND_DIR_PATH, true); } notifyBackgroundUpdated(false); @@ -173,6 +186,35 @@ private void restoreBackgroundImages() { b.show(); } + /** + * Generate background image files using original image. {@link Context} + * passed to this method must be of an {@link Activity} to determine the size + * of display. + * + * @param context The context require for the operations. + * @param bitmap Image bitmap to save as background. + * @return Returns whether the images generated successfully. + */ + public static boolean generateImageFiles(@NonNull Context context, Bitmap bitmap) { + + if (bitmap == null || !(context instanceof Activity)) { + return false; + } + + Point size = ViewUtils.getDisplaySize(context, true); + boolean isLandscape = ViewUtils.getDisplayOrientation(context) == Configuration.ORIENTATION_LANDSCAPE; + Error error; + + if (isLandscape) { + error = ImageUtils.saveForDisplayResolution(bitmap, size, TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + + } else { + error = ImageUtils.saveForDisplayResolution(bitmap, DataUtils.swap(size), TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH, TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH); + } + + return error == null; + } + /** * Updates background to image or solid color. If forced then load again even if @@ -184,7 +226,6 @@ private void restoreBackgroundImages() { public void updateBackground(boolean forced) { if (!mActivity.isVisible()) return; - Log.d("FF", " " + mActivity.getPreferences().isBackgroundImageEnabled()); if (mActivity.getPreferences().isBackgroundImageEnabled()) { Drawable drawable = mActivity.getWindow().getDecorView().getBackground(); @@ -221,28 +262,30 @@ public void updateBackgroundColor() { */ public void updateBackgroundImage() { boolean isLandscape = ViewUtils.getDisplayOrientation(mActivity) == Configuration.ORIENTATION_LANDSCAPE; + String imagePath = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PORTRAIT_PATH; + + try { + // Performing on main Thread may cause ANR and lag. + executor.execute(() -> { + if (isImageFilesExist(mActivity, true)) { + Drawable drawable = ImageUtils.getDrawable(imagePath); + ImageUtils.addOverlay(drawable, mActivity.getProperties().getBackgroundOverlayColor()); + + handler.post(() -> mActivity.getWindow().getDecorView().setBackground(drawable)); + } else { + Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_failed)); + + // Image files are unable to load so set background to solid color and notify update. + handler.post(this::updateBackgroundColor); + notifyBackgroundUpdated(false); + } + }); - String imagePath = isLandscape ? TermuxConstants.TERMUX_BACKGROUND_IMAGE_LANDSCAPE_PATH : TermuxConstants.TERMUX_BACKGROUND_IMAGE_PATH; - - if (isImageFilesExist(mActivity)) { - try { - Drawable drawable = ImageUtils.getDrawable(imagePath); - ImageUtils.addOverlay(drawable, mActivity.getProperties().getBackgroundOverlayColor()); - mActivity.getWindow().getDecorView().setBackground(drawable); - - } catch (Exception e) { - Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); - Logger.showToast(mActivity, mActivity.getString(R.string.error_background_image_loading_from_gallery_failed), true); - - // Since loading of image is failed, Set background to solid color. - updateBackgroundColor(); - notifyBackgroundUpdated(false); - } - - } else { - Logger.logErrorAndShowToast(mActivity, LOG_TAG, mActivity.getString(R.string.error_background_image_loading_failed)); + } catch (Exception e) { + Logger.logStackTraceWithMessage(LOG_TAG, "Failed to load image", e); + Logger.showToast(mActivity, mActivity.getString(R.string.error_background_image_loading_failed), true); - // Image files are unable to load so set background to solid color and notify update. + // Since loading of image is failed, Set background to solid color. updateBackgroundColor(); notifyBackgroundUpdated(false); } diff --git a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java index 86ec901972..7339711975 100644 --- a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java @@ -80,6 +80,18 @@ public static Bitmap getBitmap(final Context context, Uri uri) { return bitmap; } + /** + * Generate image bitmap from the path. + * + * @param path The path for image file + * @return Bitmap generated from image path, if fails to + * to generate returns {@code null} + */ + public static Bitmap getBitmap(String path) { + return BitmapFactory.decodeFile(path); + } + + /** * Creates an centered and resized {@link Bitmap} according to given size. * @@ -201,15 +213,28 @@ public static boolean isImageOptimized(String path, int width, int height, int t } BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, opt); - opt.inJustDecodeBounds = true; int imgWidth = opt.outWidth; int imgHeight = opt.outHeight; return Math.abs(imgWidth - width) <= tolerance && Math.abs(imgHeight - height) <= tolerance; } + public static boolean isImage(String path) { + if (!FileUtils.regularFileExists(path, false)) { + Logger.logInfo(LOG_TAG, "Image file " + path + " does not exist."); + return false; + } + + BitmapFactory.Options opt = new BitmapFactory.Options(); + opt.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, opt); + + return opt.outWidth != -1 && opt.outHeight != -1; + } + /** * Resize bitmap for {@link Configuration#ORIENTATION_PORTRAIT Portrait} and {@link * Configuration#ORIENTATION_LANDSCAPE Landscape} display view. From 6aa99e7c0874e3c089852eb9e7800374aadec1e5 Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Mon, 7 Nov 2022 16:09:58 +0530 Subject: [PATCH 11/11] Fixed: Fix overlay color filter messing up screen on `API <=28` For Android <= 9, `PorterDuff.Mode.MULTIPLY` causing unusable screen lag. Change to `PorterDuff.Mode.DARKEN`. --- .../src/main/java/com/termux/shared/image/ImageUtils.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java index 7339711975..a021b5002d 100644 --- a/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java +++ b/termux-shared/src/main/java/com/termux/shared/image/ImageUtils.java @@ -10,6 +10,7 @@ import android.graphics.ImageDecoder; import android.graphics.Point; import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.media.ThumbnailUtils; @@ -174,9 +175,9 @@ public static Drawable getDrawable(String path) { public static void addOverlay(Drawable drawable, int color) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - drawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.MULTIPLY)); + drawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.DARKEN)); } else { - drawable.setColorFilter(color, PorterDuff.Mode.MULTIPLY); + drawable.setColorFilter(new PorterDuffColorFilter(color, PorterDuff.Mode.DARKEN)); } }