Skip to content

Commit

Permalink
Changed: Use ShortcutManager APIs to create pinned shortcut on androi…
Browse files Browse the repository at this point in the history
…d >=8

This is better way to create shortcuts since launcher does not get access to shortcut data of the app and android itself stores them. It may even solve problems on some launchers, mainly Samsung launcher where randomly all shortcuts get renamed to the app name, considering it will be queuering android for the shortcut names at startup.

The only issue with this is that shortcuts may have an app badge on the icon as well, depending on launcher. This is likely a safety mechanism to know which app created the shortcut in case a malicious app creates shortcuts with icons of a different app. This is not controlled by termux-widget but the launcher itself, so contact launcher devs for an option to remove them.

For android <8, static shortcuts will still be used since pinned shortcuts are not supported.

For more info on shortcut types, check https://github.com/agnostic-apollo/TaskerLauncherShortcut#shortcut-types

Related issue #28
  • Loading branch information
agnostic-apollo committed Sep 4, 2021
1 parent 43cc79a commit e94d777
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 29 deletions.
127 changes: 103 additions & 24 deletions app/src/main/java/com/termux/widget/TermuxCreateShortcutActivity.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
package com.termux.widget;

import android.annotation.TargetApi;
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 com.termux.shared.data.DataUtils;
import com.termux.shared.file.FileUtils;
import com.termux.shared.file.TermuxFileUtils;
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 java.io.File;
import java.util.Arrays;
Expand All @@ -26,6 +37,8 @@ public class TermuxCreateShortcutActivity extends Activity {
private File mCurrentDirectory;
private File[] mCurrentFiles;

private static final String LOG_TAG = "TermuxCreateShortcutActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand All @@ -37,38 +50,24 @@ protected void onCreate(Bundle savedInstanceState) {
protected void onResume() {
super.onResume();

String errmsg = TermuxUtils.isTermuxAppAccessible(this);
if (errmsg != null) {
Logger.logErrorAndShowToast(this, LOG_TAG, errmsg);
finish();
return;
}

updateListview(TermuxConstants.TERMUX_SHORTCUT_SCRIPTS_DIR);

mListView.setOnItemClickListener((parent, view, position, id) -> {
final Context context = TermuxCreateShortcutActivity.this;
File clickedFile = mCurrentFiles[position];
if (clickedFile.isDirectory()) {
updateListview(clickedFile);
return;
}

Intent.ShortcutIconResource icon = Intent.ShortcutIconResource.fromContext(context, R.drawable.ic_launcher);

Uri scriptUri = new Uri.Builder().scheme(TERMUX_SERVICE.URI_SCHEME_SERVICE_EXECUTE).path(clickedFile.getAbsolutePath()).build();
Intent executeIntent = new Intent(context, TermuxLaunchShortcutActivity.class);
executeIntent.setData(scriptUri);
executeIntent.putExtra(TERMUX_WIDGET.EXTRA_TOKEN_NAME, TermuxWidgetAppSharedPreferences.getGeneratedToken(context));

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, executeIntent);
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, clickedFile.getName());

File scriptIcon = new File(TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH +
"/" + clickedFile.getName() + ".png");
if (scriptIcon.exists()) {
BitmapDrawable bitmapDrawable = (BitmapDrawable)Drawable.createFromPath(scriptIcon.getAbsolutePath());
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, bitmapDrawable.getBitmap());
} else {
intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon);
createShortcut(context, clickedFile);
finish();
}

setResult(RESULT_OK, intent);
finish();
});
}

Expand Down Expand Up @@ -104,10 +103,90 @@ private void updateListview(File directory) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
updateListview(mCurrentDirectory.getParentFile());
updateListview(DataUtils.getDefaultIfNull(mCurrentDirectory.getParentFile(), mCurrentDirectory));
return true;
}
return super.onOptionsItemSelected(item);
}



private void createShortcut(Context context, File clickedFile) {
boolean isPinnedShortcutSupported = false;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
ShortcutManager shortcutManager = (ShortcutManager) context.getSystemService(Context.SHORTCUT_SERVICE);
if (shortcutManager != null && shortcutManager.isRequestPinShortcutSupported())
isPinnedShortcutSupported = true;
}

String shortcutFilePath = FileUtils.getCanonicalPath(clickedFile.getAbsolutePath(), null);

try {
if (isPinnedShortcutSupported)
createPinnedShortcut(context, shortcutFilePath);
else
createStaticShortcut(context, shortcutFilePath);
} 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));
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) {
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(getExecutionIntent(context, shortcutFilePath));
builder.setShortLabel(shortcutFileName);

File shortcutIconFile = getShortcutIconFile(shortcutFileName);
if (shortcutIconFile.exists())
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);
}

private void createStaticShortcut(Context context, String shortcutFilePath) {
String shortcutFileName = ShellUtils.getExecutableBasename(shortcutFilePath);

Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, getExecutionIntent(context, shortcutFilePath));
intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, shortcutFileName);

File shortcutIconFile = getShortcutIconFile(shortcutFileName);
if (shortcutIconFile.exists())
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));

Logger.showToast(context, context.getString(R.string.msg_request_create_static_shortcut,
TermuxFileUtils.getUnExpandedTermuxPath(shortcutFilePath)), true);
setResult(RESULT_OK, intent);
}

private 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;
}

private File getShortcutIconFile(String shortcutFileName) {
return new File(TermuxConstants.TERMUX_SHORTCUT_SCRIPT_ICONS_DIR_PATH +
"/" + shortcutFileName + ".png");
}

}
23 changes: 18 additions & 5 deletions app/src/main/java/com/termux/widget/TermuxWidgetProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import com.termux.shared.data.DataUtils;
import com.termux.shared.data.IntentUtils;
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.models.ExecutionCommand;
Expand All @@ -38,6 +37,13 @@ public final class TermuxWidgetProvider extends AppWidgetProvider {

private static final String LOG_TAG = "TermuxWidgetProvider";

public void onEnabled(Context context) {
String errmsg = TermuxUtils.isTermuxAppAccessible(context);
if (errmsg != null) {
Logger.logErrorAndShowToast(context, LOG_TAG, errmsg);
}
}

/**
* "This is called to update the App Widget at intervals defined by the updatePeriodMillis attribute in the
* AppWidgetProviderInfo (see Adding the AppWidgetProviderInfo Metadata above). This method is also called when the
Expand Down Expand Up @@ -98,6 +104,12 @@ public void onReceive(Context context, Intent intent) {
sendExecutionIntentToTermuxService(context, clickedFilePath, LOG_TAG);
break;
case TERMUX_WIDGET_PROVIDER.ACTION_REFRESH_WIDGET:
String errmsg = TermuxUtils.isTermuxAppAccessible(context);
if (errmsg != null) {
Logger.logErrorAndShowToast(context, LOG_TAG, errmsg);
return;
}

int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_list);

Expand Down Expand Up @@ -150,7 +162,7 @@ public static void sendExecutionIntentToTermuxService(final Context context, Str

// If Termux app is not installed, enabled or accessible with current context or if
// TermuxConstants.TERMUX_PREFIX_DIR_PATH does not exist or has required permissions, then
// return RESULT_CODE_FAILED to plugin host app.
// just return.
errmsg = TermuxUtils.isTermuxAppAccessible(context);
if (errmsg != null) {
Logger.logErrorAndShowToast(context, logTag, errmsg);
Expand All @@ -166,7 +178,7 @@ public static void sendExecutionIntentToTermuxService(final Context context, Str
}

// Get canonical path of executable
executionCommand.executable = TermuxFileUtils.getCanonicalPath(executionCommand.executable, null, true);
executionCommand.executable = FileUtils.getCanonicalPath(executionCommand.executable, null);

// If executable is not under TermuxConstants#TERMUX_SHORTCUT_SCRIPTS_DIR_PATH
if (!FileUtils.isPathInDirPath(executionCommand.executable, TermuxConstants.TERMUX_SHORTCUT_SCRIPTS_DIR_PATH, true)) {
Expand Down Expand Up @@ -227,8 +239,9 @@ public static void sendExecutionIntentToTermuxService(final Context context, Str
context.startService(executionIntent);
}
} catch (Exception e) {
errmsg = Logger.getMessageAndStackTraceString("Failed to send execution intent to " + executionIntent.getComponent().toString(), e);
Logger.logErrorAndShowToast(context, logTag, errmsg);
String message = "Failed to send execution intent to " + executionIntent.getComponent().toString();
Logger.logErrorAndShowToast(context, logTag, message + ": " + e.getMessage());
Logger.logStackTraceWithMessage(logTag, message, e);
}
}

Expand Down
5 changes: 5 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,15 @@
<string name="msg_bad_token">This shortcut is invalid - remove and add again.</string>
<string name="msg_executing_task">Executing task: \"%1$s\"</string>
<string name="msg_executable_absolute_path">Executable Absolute Path: \"%1$s\"</string>
<string name="msg_request_create_pinned_shortcut">Requesting to create pinned shortcut for \"%1$s\"</string>
<string name="msg_request_create_static_shortcut">Requesting to create static shortcut for \"%1$s\"</string>

<string name="action_refresh">Refresh</string>

<string name="error_null_or_empty_executable">The executable is null or empty.</string>
<string name="error_executable_not_under_shortcuts_directory">An executable can only be
executed if its under the &TERMUX_SHORTCUT_SCRIPTS_DIR_PATH_SHORT; directory.</string>
<string name="error_create_pinned_shortcut_failed">Failed to create pinned shortcut for \"%1$s\"</string>
<string name="error_create_static_shortcut_failed">Failed to create static shortcut for \"%1$s\"</string>

</resources>

0 comments on commit e94d777

Please sign in to comment.