Skip to content

Commit b67203a

Browse files
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 <fabian@fabianthomas.de> Co-authored-by: agnostic-apollo <agnosticapollo@gmail.com>
1 parent 527caa6 commit b67203a

File tree

2 files changed

+85
-114
lines changed

2 files changed

+85
-114
lines changed

app/src/main/java/com/termux/widget/ShortcutFile.java

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import android.os.Build;
1111
import android.widget.RemoteViews;
1212

13+
import androidx.annotation.NonNull;
1314
import androidx.annotation.Nullable;
1415
import androidx.annotation.RequiresApi;
1516

1617
import com.google.common.base.Joiner;
18+
import com.termux.shared.data.DataUtils;
1719
import com.termux.shared.file.FileUtils;
1820
import com.termux.shared.file.TermuxFileUtils;
1921
import com.termux.shared.file.filesystem.FileType;
@@ -32,18 +34,57 @@ public final class ShortcutFile {
3234

3335
private static final String LOG_TAG = "ShortcutFile";
3436

35-
public final File file;
36-
public final String label;
37+
public final String mPath;
38+
public String mLabel;
3739

38-
public ShortcutFile(File file, int depth) {
39-
this.file = file;
40-
this.label = (depth > 0 ? (file.getParentFile().getName() + "/") : "")
41-
+ file.getName();
40+
public ShortcutFile(@NonNull String path) {
41+
this(path, null);
42+
}
43+
44+
public ShortcutFile(@NonNull File file) {
45+
this(file.getAbsolutePath(), null);
46+
}
47+
48+
public ShortcutFile(@NonNull File file, int depth) {
49+
this(file.getAbsolutePath(),
50+
(depth > 0 && file.getParentFile() != null ? (file.getParentFile().getName() + "/") : "") + file.getName());
51+
}
52+
53+
public ShortcutFile(@NonNull String path, @Nullable String defaultLabel) {
54+
mPath = path;
55+
mLabel = getLabelForShortcut(defaultLabel);
56+
}
57+
58+
@NonNull
59+
public String getPath() {
60+
return mPath;
61+
}
62+
63+
@NonNull
64+
public String getCanonicalPath() {
65+
return FileUtils.getCanonicalPath(getPath(), null);
66+
}
67+
68+
@NonNull
69+
public String getUnExpandedPath() {
70+
return TermuxFileUtils.getUnExpandedTermuxPath(getCanonicalPath());
71+
}
72+
73+
@NonNull
74+
public String getLabel() {
75+
return mLabel;
76+
}
77+
78+
@NonNull
79+
public String getLabelForShortcut(@Nullable String defaultLabel) {
80+
if (!DataUtils.isNullOrEmpty(defaultLabel))
81+
return defaultLabel;
82+
else
83+
return ShellUtils.getExecutableBasename(mPath);
4284
}
4385

4486
public Intent getExecutionIntent(Context context) {
45-
String path = file.getAbsolutePath();
46-
Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(path).build();
87+
Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(getPath()).build();
4788
Intent executionIntent = new Intent(context, TermuxLaunchShortcutActivity.class);
4889
executionIntent.setAction(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE); // Mandatory for pinned shortcuts
4990
executionIntent.setData(scriptUri);
@@ -53,14 +94,12 @@ public Intent getExecutionIntent(Context context) {
5394

5495
@RequiresApi(api = Build.VERSION_CODES.N_MR1)
5596
public ShortcutInfo getShortcutInfo(Context context) {
56-
String path = file.getAbsolutePath();
57-
58-
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, path);
59-
builder.setIntent(this.getExecutionIntent(context));
60-
builder.setShortLabel(this.label);
97+
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, getPath());
98+
builder.setIntent(getExecutionIntent(context));
99+
builder.setShortLabel(getLabel());
61100

62101
// Set icon if existent.
63-
File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, ShellUtils.getExecutableBasename(path));
102+
File shortcutIconFile = getIconFile(context);
64103
if (shortcutIconFile != null)
65104
builder.setIcon(Icon.createWithBitmap(((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap()));
66105
else
@@ -69,26 +108,40 @@ public ShortcutInfo getShortcutInfo(Context context) {
69108
return builder.build();
70109
}
71110

111+
public Intent getStaticShortcutIntent(Context context) {
112+
Intent intent = new Intent();
113+
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, getExecutionIntent(context));
114+
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getLabel());
115+
116+
// Set icon if existent.
117+
File shortcutIconFile = getIconFile(context);
118+
if (shortcutIconFile != null)
119+
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, ((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap());
120+
else
121+
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.ic_launcher));
122+
123+
return intent;
124+
}
125+
72126
public RemoteViews getListWidgetView(Context context) {
73127
// Position will always range from 0 to getCount() - 1.
74128
// Construct remote views item based on the item xml file and set text based on position.
75-
RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
76-
rv.setTextViewText(R.id.widget_item, this.label);
129+
RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.widget_item);
130+
remoteViews.setTextViewText(R.id.widget_item, getLabel());
77131

78132
// Next, we set a fill-intent which will be used to fill-in the pending intent template
79133
// which is set on the collection view in TermuxAppWidgetProvider.
80-
Intent fillInIntent = new Intent().putExtra(TERMUX_WIDGET_PROVIDER.EXTRA_FILE_CLICKED, this.file.getAbsolutePath());
81-
rv.setOnClickFillInIntent(R.id.widget_item_layout, fillInIntent);
134+
Intent fillInIntent = new Intent().putExtra(TERMUX_WIDGET_PROVIDER.EXTRA_FILE_CLICKED, getPath());
135+
remoteViews.setOnClickFillInIntent(R.id.widget_item_layout, fillInIntent);
82136

83-
return rv;
137+
return remoteViews;
84138
}
85139

86140
@Nullable
87141
private File getIconFile(Context context) {
88142
String errmsg;
89-
String shortcutIconFilePath = FileUtils.getCanonicalPath(
90-
TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH +
91-
"/" + ShellUtils.getExecutableBasename(file.getAbsolutePath()) + ".png", null);
143+
String shortcutIconFilePath = TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH +
144+
"/" + ShellUtils.getExecutableBasename(getPath()) + ".png";
92145

93146
FileType fileType = FileUtils.getFileType(shortcutIconFilePath, true);
94147
// Ensure file or symlink points to a regular file that exists

app/src/main/java/com/termux/widget/TermuxCreateShortcutActivity.java

Lines changed: 10 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,16 @@
44
import android.app.Activity;
55
import android.app.AlertDialog;
66
import android.content.Context;
7-
import android.content.Intent;
8-
import android.content.pm.ShortcutInfo;
97
import android.content.pm.ShortcutManager;
10-
import android.graphics.drawable.BitmapDrawable;
11-
import android.graphics.drawable.Drawable;
12-
import android.graphics.drawable.Icon;
13-
import android.net.Uri;
148
import android.os.Build;
159
import android.os.Bundle;
1610
import android.view.MenuItem;
1711
import android.widget.ArrayAdapter;
1812
import android.widget.ListView;
1913

20-
import androidx.annotation.Nullable;
21-
22-
import com.google.common.base.Joiner;
2314
import com.termux.shared.data.DataUtils;
24-
import com.termux.shared.file.FileUtils;
25-
import com.termux.shared.file.TermuxFileUtils;
26-
import com.termux.shared.file.filesystem.FileType;
2715
import com.termux.shared.logger.Logger;
28-
import com.termux.shared.settings.preferences.TermuxWidgetAppSharedPreferences;
29-
import com.termux.shared.shell.ShellUtils;
3016
import com.termux.shared.termux.TermuxConstants;
31-
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
32-
import com.termux.shared.termux.TermuxConstants.TERMUX_WIDGET;
3317
import com.termux.shared.termux.TermuxUtils;
3418
import com.termux.widget.utils.ShortcutUtils;
3519

@@ -124,102 +108,36 @@ private void createShortcut(Context context, File clickedFile) {
124108
isPinnedShortcutSupported = true;
125109
}
126110

127-
String shortcutFilePath = FileUtils.getCanonicalPath(clickedFile.getAbsolutePath(), null);
111+
ShortcutFile shortcutFile = new ShortcutFile(clickedFile);
128112

129113
try {
130114
if (isPinnedShortcutSupported)
131-
createPinnedShortcut(context, shortcutFilePath);
115+
createPinnedShortcut(context, shortcutFile);
132116
else
133-
createStaticShortcut(context, shortcutFilePath);
117+
createStaticShortcut(context, shortcutFile);
134118
} catch (Exception e) {
135119
String message = context.getString(
136120
isPinnedShortcutSupported ? R.string.error_create_pinned_shortcut_failed : R.string.error_create_static_shortcut_failed,
137-
TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath));
121+
shortcutFile.getUnExpandedPath());
138122
Logger.logErrorAndShowToast(context, LOG_TAG, message + ": " + e.getMessage());
139123
Logger.logStackTraceWithMessage(LOG_TAG, message, e);
140124
}
141125
}
142126

143127
@TargetApi(Build.VERSION_CODES.O)
144-
private void createPinnedShortcut(Context context, String shortcutFilePath) {
128+
private void createPinnedShortcut(Context context, ShortcutFile shortcutFile) {
145129
ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
146130
if (shortcutManager == null) return;
147131

148-
String shortcutFileName = ShellUtils.getExecutableBasename(shortcutFilePath);
149-
150-
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(context, shortcutFilePath);
151-
builder.setIntent(TermuxCreateShortcutActivity.getExecutionIntent(context, shortcutFilePath));
152-
builder.setShortLabel(shortcutFileName);
153-
154-
File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, shortcutFileName);
155-
if (shortcutIconFile != null)
156-
builder.setIcon(Icon.createWithBitmap(((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap()));
157-
else
158-
builder.setIcon(Icon.createWithResource(context, R.drawable.ic_launcher));
159-
160132
Logger.showToast(context, context.getString(R.string.msg_request_create_pinned_shortcut,
161-
TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath)), true);
162-
shortcutManager.requestPinShortcut(builder.build(), null);
133+
shortcutFile.getUnExpandedPath()), true);
134+
shortcutManager.requestPinShortcut(shortcutFile.getShortcutInfo(context), null);
163135
}
164136

165-
private void createStaticShortcut(Context context, String shortcutFilePath) {
166-
String shortcutFileName = ShellUtils.getExecutableBasename(shortcutFilePath);
167-
168-
Intent intent = new Intent();
169-
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, TermuxCreateShortcutActivity.getExecutionIntent(context, shortcutFilePath));
170-
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutFileName);
171-
172-
File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, shortcutFileName);
173-
if (shortcutIconFile != null)
174-
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, ((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap());
175-
else
176-
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, Intent.ShortcutIconResource.fromContext(context, R.drawable.ic_launcher));
177-
137+
private void createStaticShortcut(Context context, ShortcutFile shortcutFile) {
178138
Logger.showToast(context, context.getString(R.string.msg_request_create_static_shortcut,
179-
TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath)), true);
180-
setResult(RESULT_OK, intent);
181-
}
182-
183-
public static Intent getExecutionIntent(Context context, String shortcutFilePath) {
184-
Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(shortcutFilePath).build();
185-
Intent executionIntent = new Intent(context, TermuxLaunchShortcutActivity.class);
186-
executionIntent.setAction(TERMUX_SERVICE.ACTION_SERVICE_EXECUTE); // Mandatory for pinned shortcuts
187-
executionIntent.setData(scriptUri);
188-
executionIntent.putExtra(TERMUX_WIDGET.EXTRA_TOKEN_NAME, TermuxWidgetAppSharedPreferences.getGeneratedToken(context));
189-
return executionIntent;
190-
}
191-
192-
@Nullable
193-
public static File getShortcutIconFile(Context context, String shortcutFileName) {
194-
String errmsg;
195-
String shortcutIconFilePath = FileUtils.getCanonicalPath(
196-
TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH +
197-
"/" + shortcutFileName + ".png", null);
198-
199-
FileType fileType = FileUtils.getFileType(shortcutIconFilePath, true);
200-
// Ensure file or symlink points to a regular file that exists
201-
if (fileType != FileType.REGULAR) {
202-
if (fileType != FileType.NO_EXIST) {
203-
errmsg = context.getString(R.string.error_icon_not_a_regular_file, fileType.getName()) +
204-
"\n" + context.getString(R.string.msg_icon_absolute_path, shortcutIconFilePath);
205-
Logger.logErrorAndShowToast(context, LOG_TAG, errmsg);
206-
}
207-
return null;
208-
}
209-
210-
// Do not allow shortcut icons files not under SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST
211-
if (!FileUtils.isPathInDirPaths(shortcutIconFilePath, ShortcutUtils.SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST, true)) {
212-
errmsg = context.getString(R.string.error_icon_not_under_shortcut_icons_directories,
213-
Joiner.on(", ").skipNulls().join(TermuxFileUtils.getUnExpandedTermuxPaths(ShortcutUtils.SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST))) +
214-
"\n" + context.getString(R.string.msg_icon_absolute_path, shortcutIconFilePath);
215-
Logger.logErrorAndShowToast(context, LOG_TAG, errmsg);
216-
return null;
217-
}
218-
219-
Logger.logInfo(LOG_TAG, "Using file at \"" + shortcutIconFilePath + "\" as shortcut icon file");
220-
Logger.showToast(context, "Using file at \"" + shortcutIconFilePath + "\" as shortcut icon file", true);
221-
222-
return new File(shortcutIconFilePath);
139+
shortcutFile.getUnExpandedPath()), true);
140+
setResult(RESULT_OK, shortcutFile.getStaticShortcutIntent(context));
223141
}
224142

225143
}

0 commit comments

Comments
 (0)