From b141e6ad4429cdf4e609b11ab06607aa4279f5c5 Mon Sep 17 00:00:00 2001 From: Fabian Thomas Date: Fri, 20 May 2022 00:07:53 +0200 Subject: [PATCH] Changed: Refactor `TermuxCreateShortcutActivity` to move shortcut functions to `ShortcutFile` and change shortcut path behaviour The `ShortcutFile` will now store the path instead of the file object This commit will also now pass absolute path of shortcuts when creating intents for shortcuts and widgets instead of canonical path. This will allow path expansion to be done during execution instead of at creation and if underlying symlink destination changes, new destination will be executed. There will still be a security check during execution to check if shortcut is under allowed directories. The shortcut label will now also be generated from absolute path instead of canonical path as mentioned in #59 Co-authored-by: Fabian Thomas Co-authored-by: agnostic-apollo --- .../java/com/termux/widget/ShortcutFile.java | 97 +++++++++++++---- .../widget/TermuxCreateShortcutActivity.java | 102 ++---------------- 2 files changed, 85 insertions(+), 114 deletions(-) diff --git a/app/src/main/java/com/termux/widget/ShortcutFile.java b/app/src/main/java/com/termux/widget/ShortcutFile.java index dfefba3..7036701 100644 --- a/app/src/main/java/com/termux/widget/ShortcutFile.java +++ b/app/src/main/java/com/termux/widget/ShortcutFile.java @@ -10,10 +10,12 @@ import android.os.Build; import android.widget.RemoteViews; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import com.google.common.base.Joiner; +import com.termux.shared.data.DataUtils; import com.termux.shared.file.FileUtils; import com.termux.shared.file.TermuxFileUtils; import com.termux.shared.file.filesystem.FileType; @@ -32,18 +34,57 @@ public final class ShortcutFile { private static final String LOG_TAG = "ShortcutFile"; - public final File file; - public final String label; + public final String mPath; + public String mLabel; - public ShortcutFile(File file, int depth) { - this.file = file; - this.label = (depth > 0 ? (file.getParentFile().getName() + "/") : "") - + file.getName(); + public ShortcutFile(@NonNull String path) { + this(path, null); + } + + public ShortcutFile(@NonNull File file) { + this(file.getAbsolutePath(), null); + } + + public ShortcutFile(@NonNull File file, int depth) { + this(file.getAbsolutePath(), + (depth > 0 && file.getParentFile() != null ? (file.getParentFile().getName() + "/") : "") + file.getName()); + } + + public ShortcutFile(@NonNull String path, @Nullable String defaultLabel) { + mPath = path; + mLabel = getLabelForShortcut(defaultLabel); + } + + @NonNull + public String getPath() { + return mPath; + } + + @NonNull + public String getCanonicalPath() { + return FileUtils.getCanonicalPath(getPath(), null); + } + + @NonNull + public String getUnExpandedPath() { + return TermuxFileUtils.getUnExpandedTermuxPath(getCanonicalPath()); + } + + @NonNull + public String getLabel() { + return mLabel; + } + + @NonNull + public String getLabelForShortcut(@Nullable String defaultLabel) { + if (!DataUtils.isNullOrEmpty(defaultLabel)) + return defaultLabel; + else + return ShellUtils.getExecutableBasename(mPath); } public Intent getExecutionIntent(Context context) { - String path = file.getAbsolutePath(); - Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(path).build(); + Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(getPath()).build(); Intent executionIntent = new Intent(context, TermuxLaunchShortcutActivity.class); executionIntent.setAction(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE); // Mandatory for pinned shortcuts executionIntent.setData(scriptUri); @@ -53,14 +94,12 @@ public Intent getExecutionIntent(Context context) { @RequiresApi(api = Build.VERSION_CODES.N_MR1) public ShortcutInfo getShortcutInfo(Context context) { - String path = file.getAbsolutePath(); - - ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, path); - builder.setIntent(this.getExecutionIntent(context)); - builder.setShortLabel(this.label); + ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, getPath()); + builder.setIntent(getExecutionIntent(context)); + builder.setShortLabel(getLabel()); // Set icon if existent. - File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, ShellUtils.getExecutableBasename(path)); + File shortcutIconFile = getIconFile(context); if (shortcutIconFile != null) builder.setIcon(Icon.createWithBitmap(((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap())); else @@ -69,26 +108,40 @@ public ShortcutInfo getShortcutInfo(Context context) { return builder.build(); } + public Intent getStaticShortcutIntent(Context context) { + Intent intent = new Intent(); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, getExecutionIntent(context)); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getLabel()); + + // Set icon if existent. + File shortcutIconFile = getIconFile(context); + if (shortcutIconFile != null) + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, ((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap()); + else + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.ic_launcher)); + + return intent; + } + public RemoteViews getListWidgetView(Context context) { // Position will always range from 0 to getCount() - 1. // Construct remote views item based on the item xml file and set text based on position. - RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item); - rv.setTextViewText(R.id.widget_item, this.label); + RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_item); + remoteViews.setTextViewText(R.id.widget_item, getLabel()); // Next, we set a fill-intent which will be used to fill-in the pending intent template // which is set on the collection view in TermuxAppWidgetProvider. - Intent fillInIntent = new Intent().putExtra(TERMUX_WIDGET_PROVIDER.EXTRA_FILE_CLICKED, this.file.getAbsolutePath()); - rv.setOnClickFillInIntent(R.id.widget_item_layout, fillInIntent); + Intent fillInIntent = new Intent().putExtra(TERMUX_WIDGET_PROVIDER.EXTRA_FILE_CLICKED, getPath()); + remoteViews.setOnClickFillInIntent(R.id.widget_item_layout, fillInIntent); - return rv; + return remoteViews; } @Nullable private File getIconFile(Context context) { String errmsg; - String shortcutIconFilePath = FileUtils.getCanonicalPath( - TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH + - "/" + ShellUtils.getExecutableBasename(file.getAbsolutePath()) + ".png", null); + String shortcutIconFilePath = TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH + + "/" + ShellUtils.getExecutableBasename(getPath()) + ".png"; FileType fileType = FileUtils.getFileType(shortcutIconFilePath, true); // Ensure file or symlink points to a regular file that exists diff --git a/app/src/main/java/com/termux/widget/TermuxCreateShortcutActivity.java b/app/src/main/java/com/termux/widget/TermuxCreateShortcutActivity.java index 3bf5d1f..383d1b3 100644 --- a/app/src/main/java/com/termux/widget/TermuxCreateShortcutActivity.java +++ b/app/src/main/java/com/termux/widget/TermuxCreateShortcutActivity.java @@ -4,32 +4,16 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.Context; -import android.content.Intent; -import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutManager; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.Icon; -import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.ListView; -import androidx.annotation.Nullable; - -import com.google.common.base.Joiner; import com.termux.shared.data.DataUtils; -import com.termux.shared.file.FileUtils; -import com.termux.shared.file.TermuxFileUtils; -import com.termux.shared.file.filesystem.FileType; import com.termux.shared.logger.Logger; -import com.termux.shared.settings.preferences.TermuxWidgetAppSharedPreferences; -import com.termux.shared.shell.ShellUtils; import com.termux.shared.termux.TermuxConstants; -import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE; -import com.termux.shared.termux.TermuxConstants.TERMUX_WIDGET; import com.termux.shared.termux.TermuxUtils; import com.termux.widget.utils.ShortcutUtils; @@ -124,102 +108,36 @@ private void createShortcut(Context context, File clickedFile) { isPinnedShortcutSupported = true; } - String shortcutFilePath = FileUtils.getCanonicalPath(clickedFile.getAbsolutePath(), null); + ShortcutFile shortcutFile = new ShortcutFile(clickedFile); try { if (isPinnedShortcutSupported) - createPinnedShortcut(context, shortcutFilePath); + createPinnedShortcut(context, shortcutFile); else - createStaticShortcut(context, shortcutFilePath); + createStaticShortcut(context, shortcutFile); } catch (Exception e) { String message = context.getString( isPinnedShortcutSupported ? R.string.error_create_pinned_shortcut_failed : R.string.error_create_static_shortcut_failed, - TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath)); + shortcutFile.getUnExpandedPath()); Logger.logErrorAndShowToast(context, LOG_TAG, message + ": " + e.getMessage()); Logger.logStackTraceWithMessage(LOG_TAG, message, e); } } @TargetApi(Build.VERSION_CODES.O) - private void createPinnedShortcut(Context context, String shortcutFilePath) { + private void createPinnedShortcut(Context context, ShortcutFile shortcutFile) { ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE); if (shortcutManager == null) return; - String shortcutFileName = ShellUtils.getExecutableBasename(shortcutFilePath); - - ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, shortcutFilePath); - builder.setIntent(TermuxCreateShortcutActivity.getExecutionIntent(context, shortcutFilePath)); - builder.setShortLabel(shortcutFileName); - - File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, shortcutFileName); - if (shortcutIconFile != null) - builder.setIcon(Icon.createWithBitmap(((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap())); - else - builder.setIcon(Icon.createWithResource(context, R.drawable.ic_launcher)); - Logger.showToast(context, context.getString(R.string.msg_request_create_pinned_shortcut, - TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath)), true); - shortcutManager.requestPinShortcut(builder.build(), null); + shortcutFile.getUnExpandedPath()), true); + shortcutManager.requestPinShortcut(shortcutFile.getShortcutInfo(context), null); } - private void createStaticShortcut(Context context, String shortcutFilePath) { - String shortcutFileName = ShellUtils.getExecutableBasename(shortcutFilePath); - - Intent intent = new Intent(); - intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, TermuxCreateShortcutActivity.getExecutionIntent(context, shortcutFilePath)); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutFileName); - - File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, shortcutFileName); - if (shortcutIconFile != null) - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, ((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap()); - else - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.ic_launcher)); - + private void createStaticShortcut(Context context, ShortcutFile shortcutFile) { Logger.showToast(context, context.getString(R.string.msg_request_create_static_shortcut, - TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath)), true); - setResult(RESULT_OK, intent); - } - - public static Intent getExecutionIntent(Context context, String shortcutFilePath) { - Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(shortcutFilePath).build(); - Intent executionIntent = new Intent(context, TermuxLaunchShortcutActivity.class); - executionIntent.setAction(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE); // Mandatory for pinned shortcuts - executionIntent.setData(scriptUri); - executionIntent.putExtra(TERMUX_WIDGET.EXTRA_TOKEN_NAME, TermuxWidgetAppSharedPreferences.getGeneratedToken(context)); - return executionIntent; - } - - @Nullable - public static File getShortcutIconFile(Context context, String shortcutFileName) { - String errmsg; - String shortcutIconFilePath = FileUtils.getCanonicalPath( - TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH + - "/" + shortcutFileName + ".png", null); - - FileType fileType = FileUtils.getFileType(shortcutIconFilePath, true); - // Ensure file or symlink points to a regular file that exists - if (fileType != FileType.REGULAR) { - if (fileType != FileType.NO_EXIST) { - errmsg = context.getString(R.string.error_icon_not_a_regular_file, fileType.getName()) + - "\n" + context.getString(R.string.msg_icon_absolute_path, shortcutIconFilePath); - Logger.logErrorAndShowToast(context, LOG_TAG, errmsg); - } - return null; - } - - // Do not allow shortcut icons files not under SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST - if (!FileUtils.isPathInDirPaths(shortcutIconFilePath, ShortcutUtils.SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST, true)) { - errmsg = context.getString(R.string.error_icon_not_under_shortcut_icons_directories, - Joiner.on(", ").skipNulls().join(TermuxFileUtils.getUnExpandedTermuxPaths(ShortcutUtils.SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST))) + - "\n" + context.getString(R.string.msg_icon_absolute_path, shortcutIconFilePath); - Logger.logErrorAndShowToast(context, LOG_TAG, errmsg); - return null; - } - - Logger.logInfo(LOG_TAG, "Using file at \"" + shortcutIconFilePath + "\" as shortcut icon file"); - Logger.showToast(context, "Using file at \"" + shortcutIconFilePath + "\" as shortcut icon file", true); - - return new File(shortcutIconFilePath); + shortcutFile.getUnExpandedPath()), true); + setResult(RESULT_OK, shortcutFile.getStaticShortcutIntent(context)); } }