Skip to content

Commit

Permalink
Changed: Add ShorcutFile to hold shortcut info and move shortcut fi…
Browse files Browse the repository at this point in the history
…le enumeration to `ShortcutUtils`

Co-authored-by: Fabian Thomas <fabian@fabianthomas.de>
Co-authored-by: agnostic-apollo <agnosticapollo@gmail.com>
  • Loading branch information
fabian-thomas and agnostic-apollo committed Oct 29, 2022
1 parent d4771d6 commit 0a0c8d9
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 155 deletions.
119 changes: 119 additions & 0 deletions app/src/main/java/com/termux/widget/ShortcutFile.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package com.termux.widget;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
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.widget.RemoteViews;

import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;

import com.google.common.base.Joiner;
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.TERMUX_APP.TERMUX_SERVICE;
import com.termux.shared.termux.TermuxConstants.TERMUX_WIDGET.TERMUX_WIDGET_PROVIDER;
import com.termux.shared.termux.TermuxConstants.TERMUX_WIDGET;
import com.termux.shared.termux.TermuxConstants;
import com.termux.widget.utils.ShortcutUtils;

import java.io.File;

public final class ShortcutFile {

private static final String LOG_TAG = "ShortcutFile";

public final File file;
public final String label;

public ShortcutFile(File file, int depth) {
this.file = file;
this.label = (depth > 0 ? (file.getParentFile().getName() + "/") : "")
+ file.getName();
}

public Intent getExecutionIntent(Context context) {
String path = file.getAbsolutePath();
Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(path).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;
}

@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);

// Set icon if existent.
File shortcutIconFile = TermuxCreateShortcutActivity.getShortcutIconFile(context, ShellUtils.getExecutableBasename(path));
if (shortcutIconFile != null)
builder.setIcon(Icon.createWithBitmap(((BitmapDrawable) Drawable.createFromPath(shortcutIconFile.getAbsolutePath())).getBitmap()));
else
builder.setIcon(Icon.createWithResource(context, R.drawable.ic_launcher));

return builder.build();
}

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);

// 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);

return rv;
}

@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);

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, context.getString(R.string.msg_shortcut_icon_file_used, shortcutIconFilePath), true);

return new File(shortcutIconFilePath);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
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;

import java.io.File;
import java.util.Arrays;
Expand Down Expand Up @@ -77,7 +78,7 @@ protected void onResume() {

private void updateListview(File directory) {
mCurrentDirectory = directory;
mCurrentFiles = directory.listFiles(TermuxWidgetService.SHORTCUT_FILES_FILTER);
mCurrentFiles = directory.listFiles(ShortcutUtils.SHORTCUT_FILES_FILTER);

if (mCurrentFiles == null) mCurrentFiles = new File[0];

Expand Down Expand Up @@ -207,9 +208,9 @@ public static File getShortcutIconFile(Context context, String shortcutFileName)
}

// Do not allow shortcut icons files not under SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST
if (!FileUtils.isPathInDirPaths(shortcutIconFilePath, TermuxWidgetService.SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST, true)) {
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(TermuxWidgetService.SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST))) +
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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.widget.utils.ShortcutUtils;

import org.reactivestreams.FlowAdapters;

Expand Down Expand Up @@ -248,7 +249,7 @@ private void addShortcutFile(File dir, List<File> shortcutFiles, int depth) {
// max depth defined from TermuxWidgetService so using same here
return;
}
File[] files = dir.listFiles(TermuxWidgetService.SHORTCUT_FILES_FILTER);
File[] files = dir.listFiles(ShortcutUtils.SHORTCUT_FILES_FILTER);

if (files == null) {
return;
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/com/termux/widget/TermuxWidgetProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.termux.shared.termux.TermuxConstants.TERMUX_APP.TERMUX_SERVICE;
import com.termux.shared.termux.TermuxConstants.TERMUX_WIDGET.TERMUX_WIDGET_PROVIDER;
import com.termux.shared.termux.TermuxUtils;
import com.termux.widget.utils.ShortcutUtils;

import java.io.File;

Expand Down Expand Up @@ -184,9 +185,9 @@ public static void sendExecutionIntentToTermuxService(final Context context, Str
executionCommand.executable = FileUtils.getCanonicalPath(executionCommand.executable, null);

// If executable is not under SHORTCUT_FILES_ALLOWED_PATHS_LIST
if (!FileUtils.isPathInDirPaths(executionCommand.executable, TermuxWidgetService.SHORTCUT_FILES_ALLOWED_PATHS_LIST, true)) {
if (!FileUtils.isPathInDirPaths(executionCommand.executable, ShortcutUtils.SHORTCUT_FILES_ALLOWED_PATHS_LIST, true)) {
errmsg = context.getString(R.string.error_executable_not_under_shortcuts_directories,
Joiner.on(", ").skipNulls().join(TermuxFileUtils.getUnExpandedTermuxPaths(TermuxWidgetService.SHORTCUT_FILES_ALLOWED_PATHS_LIST))) +
Joiner.on(", ").skipNulls().join(TermuxFileUtils.getUnExpandedTermuxPaths(ShortcutUtils.SHORTCUT_FILES_ALLOWED_PATHS_LIST))) +
"\n" + context.getString(R.string.msg_executable_absolute_path, executionCommand.executable);
Logger.logErrorAndShowToast(context, logTag, errmsg);
return;
Expand Down
101 changes: 7 additions & 94 deletions app/src/main/java/com/termux/widget/TermuxWidgetService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,21 @@
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;

import com.termux.shared.file.FileUtils;
import com.termux.shared.termux.TermuxConstants;
import com.termux.shared.termux.TermuxConstants.TERMUX_WIDGET.TERMUX_WIDGET_PROVIDER;
import com.termux.widget.utils.ShortcutUtils;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public final class TermuxWidgetService extends RemoteViewsService {

/* Allowed paths under which shortcut files can exist. */
public static final List<String> SHORTCUT_FILES_ALLOWED_PATHS_LIST = Arrays.asList(
TermuxConstants.TERMUX_SHORTCUT_SCRIPTS_DIR_PATH,
TermuxConstants.TERMUX_DATA_HOME_DIR_PATH);

/* Allowed paths under which shortcut icons files can exist. */
public static final List<String> SHORTCUT_ICONS_FILES_ALLOWED_PATHS_LIST = Arrays.asList(
TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH,
TermuxConstants.TERMUX_DATA_HOME_DIR_PATH);

public static final FileFilter SHORTCUT_FILES_FILTER = new FileFilter() {
public boolean accept(File file) {
// Do not show hidden files starting with a dot.
if (file.getName().startsWith("."))
return false;
// Do not show broken symlinks
else if (!FileUtils.fileExists(file.getAbsolutePath(), true))
return false;
// Do not show files that are not under SHORTCUT_FILES_ALLOWED_PATHS_LIST
else if (!FileUtils.isPathInDirPaths(file.getAbsolutePath(), SHORTCUT_FILES_ALLOWED_PATHS_LIST, true))
return false;
// Do not show files under TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH
else if (TermuxConstants.TERMUX_SHORTCUT_SCRIPTS_DIR.equals(file.getParentFile()) &&
file.getName().equals(TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_BASENAME))
return false;
return true;
}
};

public static final class TermuxWidgetItem {

/** Label to display in the list. */
public final String mLabel;
/** The file which this item represents, sent with the {@link TERMUX_WIDGET_PROVIDER#EXTRA_FILE_CLICKED} extra. */
public final String mFile;

public TermuxWidgetItem(File file, int depth) {
this.mLabel = (depth > 0 ? (file.getParentFile().getName() + "/") : "")
+ file.getName();
this.mFile = file.getAbsolutePath();
}

}

@Override
public RemoteViewsFactory onGetViewFactory(Intent intent) {
return new ListRemoteViewsFactory(getApplicationContext());
}

public static class ListRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
private final List<TermuxWidgetItem> mWidgetItems = new ArrayList<>();
private final List<ShortcutFile> shortcutFiles = new ArrayList<>();
private final Context mContext;

public ListRemoteViewsFactory(Context context) {
Expand All @@ -83,34 +35,18 @@ public void onCreate() {

@Override
public void onDestroy() {
mWidgetItems.clear();
shortcutFiles.clear();
}

@Override
public int getCount() {
return mWidgetItems.size();
return shortcutFiles.size();
}

@Override
public RemoteViews getViewAt(int position) {
// Position will always range from 0 to getCount() - 1.
TermuxWidgetItem widgetItem = mWidgetItems.get(position);

// Construct remote views item based on the item xml file and set text based on position.
RemoteViews rv = new RemoteViews(mContext.getPackageName(), R.layout.widget_item);
rv.setTextViewText(R.id.widget_item, widgetItem.mLabel);

// 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, widgetItem.mFile);
rv.setOnClickFillInIntent(R.id.widget_item_layout, fillInIntent);

// You can do heaving lifting in here, synchronously. For example, if you need to
// process an image, fetch something from the network, etc., it is ok to do it here,
// synchronously. A loading view will show up in lieu of the actual contents in the
// interim.

return rv;
return shortcutFiles.get(position).getListWidgetView(mContext);
}

@Override
Expand Down Expand Up @@ -143,35 +79,12 @@ public void onDataSetChanged() {
// from the network, etc., it is ok to do it here, synchronously. The widget will remain
// in its current state while work is being done here, so you don't need to worry about
// locking up the widget.
mWidgetItems.clear();
shortcutFiles.clear();
// Create directory if necessary so user more easily finds where to put shortcuts:
TermuxConstants.TERMUX_SHORTCUT_SCRIPTS_DIR.mkdirs();

addFile(TermuxConstants.TERMUX_SHORTCUT_SCRIPTS_DIR, mWidgetItems, 0);
ShortcutUtils.enumerateShortcutFiles(shortcutFiles, true);
}
}

private static void addFile(File dir, List<TermuxWidgetItem> widgetItems, int depth) {
if (depth > 5) return;

File[] files = dir.listFiles(TermuxWidgetService.SHORTCUT_FILES_FILTER);

if (files == null) return;
Arrays.sort(files, (lhs, rhs) -> {
if (lhs.isDirectory() != rhs.isDirectory()) {
return lhs.isDirectory() ? 1 : -1;
}
return NaturalOrderComparator.compare(lhs.getName(), rhs.getName());
});

for (File file : files) {
if (file.isDirectory()) {
addFile(file, widgetItems, depth + 1);
} else {
widgetItems.add(new TermuxWidgetItem(file, depth));
}
}

}

}
Loading

0 comments on commit 0a0c8d9

Please sign in to comment.