11package com .termux .widget .activities ;
22
33import android .os .Bundle ;
4+ import android .view .View ;
45import android .widget .Button ;
6+ import android .widget .LinearLayout ;
57import android .widget .TextView ;
8+ import android .content .Context ;
9+ import android .content .Intent ;
10+ import android .content .pm .ShortcutInfo ;
11+ import android .content .pm .ShortcutManager ;
12+ import android .graphics .drawable .BitmapDrawable ;
13+ import android .graphics .drawable .Drawable ;
14+ import android .graphics .drawable .Icon ;
15+ import android .os .Build ;
616
17+ import androidx .annotation .NonNull ;
18+ import androidx .annotation .RequiresApi ;
719import androidx .appcompat .app .AppCompatActivity ;
820
21+ import com .termux .shared .file .FileUtils ;
22+ import com .termux .shared .file .TermuxFileUtils ;
923import com .termux .shared .logger .Logger ;
24+ import com .termux .shared .models .errors .Error ;
1025import com .termux .shared .packages .PackageUtils ;
1126import com .termux .shared .termux .TermuxConstants ;
1227import com .termux .widget .R ;
28+ import com .termux .shared .shell .ShellUtils ;
29+ import com .termux .widget .TermuxWidgetService ;
30+ import com .termux .widget .TermuxCreateShortcutActivity ;
31+ import com .termux .widget .NaturalOrderComparator ;
32+
33+ import java .io .File ;
34+ import java .util .Arrays ;
35+ import java .util .ArrayList ;
36+ import java .util .List ;
37+ import java .util .regex .Pattern ;
1338
1439public class TermuxWidgetActivity extends AppCompatActivity {
1540
1641 private static final String LOG_TAG = "TermuxWidgetActivity" ;
1742
43+ /** Termux:Widget app data home directory path. */
44+ public static final String TERMUX_WIDGET_DATA_HOME_DIR_PATH = TermuxConstants .TERMUX_DATA_HOME_DIR_PATH + "/widget" ; // Default: "/data/data/com.termux/files/home/.termux/widget"
45+
46+ /** Termux:Widget app directory path to store scripts/binaries to be used as dynamic shortcuts. */
47+ public static final String TERMUX_WIDGET_DYNAMIC_SHORTCUTS_DIR_PATH = TERMUX_WIDGET_DATA_HOME_DIR_PATH + "/dynamic_shortcuts" ; // Default: "/data/data/com.termux/files/home/.termux/widget/dynamic_shortcuts"
48+
49+ public static final String MAX_SHORTCUTS_LIMIT_DOCS_URL = TermuxConstants .TERMUX_WIDGET_GITHUB_REPO_URL + "#max-shortcuts-limit-optional" ; // Default: "https://github.com/termux/termux-widget#max-shortcuts-limit-optional"
50+
1851 @ Override
1952 protected void onCreate (Bundle savedInstanceState ) {
2053 super .onCreate (savedInstanceState );
54+
55+ Logger .logDebug (LOG_TAG , "onCreate" );
56+
2157 setContentView (R .layout .activity_termux_widget );
2258
2359 TextView pluginInfo = findViewById (R .id .textview_plugin_info );
2460 pluginInfo .setText (getString (R .string .plugin_info , TermuxConstants .TERMUX_GITHUB_REPO_URL ,
2561 TermuxConstants .TERMUX_WIDGET_GITHUB_REPO_URL ));
2662
63+ setDisableLauncherIconViews ();
64+ setDynamicShortcutsViews ();
65+ }
66+
67+ @ Override
68+ protected void onResume () {
69+ super .onResume ();
70+
71+ Logger .logVerbose (LOG_TAG , "onResume" );
72+
73+ setMaxShortcutsLimitView ();
74+ }
75+
76+ private void setMaxShortcutsLimitView () {
77+ LinearLayout maxShortcutsInfoLinearLayout = findViewById (R .id .linearlayout_max_shortcuts_limit_info );
78+ maxShortcutsInfoLinearLayout .setVisibility (View .GONE );
79+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N_MR1 ) {
80+ ShortcutManager shortcutManager = getShortcutManager (this , true );
81+ TextView maxShortcutsInfoTextView = findViewById (R .id .textview_max_shortcuts_limit_info );
82+ if (shortcutManager != null ) {
83+ maxShortcutsInfoLinearLayout .setVisibility (View .VISIBLE );
84+ maxShortcutsInfoTextView .setText (getString (R .string .msg_max_shortcuts_limit_info ,
85+ shortcutManager .getMaxShortcutCountPerActivity (), MAX_SHORTCUTS_LIMIT_DOCS_URL ));
86+ }
87+ }
88+ }
89+
90+ private void setDisableLauncherIconViews () {
2791 Button disableLauncherIconButton = findViewById (R .id .btn_disable_launcher_icon );
2892 disableLauncherIconButton .setOnClickListener (v -> {
2993 String message = getString (R .string .msg_disabling_launcher_icon , TermuxConstants .TERMUX_WIDGET_APP_NAME );
@@ -34,4 +98,135 @@ protected void onCreate(Bundle savedInstanceState) {
3498 });
3599 }
36100
101+ private void setDynamicShortcutsViews () {
102+ LinearLayout dynamicShortcutsLinearLayout = findViewById (R .id .linearlayout_dynamic_shortcuts );
103+
104+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .N_MR1 ) {
105+ dynamicShortcutsLinearLayout .setVisibility (View .VISIBLE );
106+
107+ TextView dynamicShortcutsInfoTextView = findViewById (R .id .textview_dynamic_shortcuts_info );
108+ dynamicShortcutsInfoTextView .setText (getString (R .string .msg_dynamic_shortcuts_info ,
109+ TermuxFileUtils .getUnExpandedTermuxPath (TERMUX_WIDGET_DYNAMIC_SHORTCUTS_DIR_PATH )));
110+
111+ Button createDynamicShortcutsButton = findViewById (R .id .btn_create_dynamic_shortcuts );
112+ createDynamicShortcutsButton .setOnClickListener (v -> createDynamicShortcuts (TermuxWidgetActivity .this ));
113+
114+ Button removeDynamicShortcutsButton = findViewById (R .id .btn_remove_dynamic_shortcuts );
115+ removeDynamicShortcutsButton .setOnClickListener (v -> removeDynamicShortcuts (TermuxWidgetActivity .this ));
116+ } else {
117+ dynamicShortcutsLinearLayout .setVisibility (View .GONE );
118+ }
119+ }
120+
121+ @ RequiresApi (api = Build .VERSION_CODES .N_MR1 )
122+ public static ShortcutManager getShortcutManager (@ NonNull Context context , boolean showToast ) {
123+ ShortcutManager shortcutManager = (ShortcutManager ) context .getSystemService (Context .SHORTCUT_SERVICE );
124+ if (shortcutManager == null ) {
125+ Logger .logErrorAndShowToast (showToast ? context : null , LOG_TAG , "Failed to get shortcut manager" );
126+ return null ;
127+ }
128+ return shortcutManager ;
129+ }
130+
131+ private static void enumerateShortcutFiles (List <TermuxWidgetService .TermuxWidgetItem > items , File dir , boolean sorted ) {
132+ enumerateShortcutFiles (items , dir , sorted , 0 );
133+ }
134+
135+ private static void enumerateShortcutFiles (List <TermuxWidgetService .TermuxWidgetItem > items , File dir , boolean sorted , int depth ) {
136+ if (depth > 5 ) return ;
137+
138+ File [] files = dir .listFiles (TermuxWidgetService .SHORTCUT_FILES_FILTER );
139+
140+ if (files == null ) return ;
141+
142+ if (sorted ) {
143+ Arrays .sort (files , (lhs , rhs ) -> {
144+ if (lhs .isDirectory () != rhs .isDirectory ()) {
145+ return lhs .isDirectory () ? 1 : -1 ;
146+ }
147+ return NaturalOrderComparator .compare (lhs .getName (), rhs .getName ());
148+ });
149+ }
150+
151+ for (File file : files ) {
152+ if (file .isDirectory ()) {
153+ enumerateShortcutFiles (items , file , sorted , depth + 1 );
154+ } else {
155+ items .add (new TermuxWidgetService .TermuxWidgetItem (file , depth ));
156+ }
157+ }
158+
159+ }
160+
161+ @ RequiresApi (Build .VERSION_CODES .N_MR1 )
162+ private void createDynamicShortcuts (@ NonNull Context context ) {
163+ ShortcutManager shortcutManager = getShortcutManager (context , true );
164+ if (shortcutManager == null ) return ;
165+
166+ // Create directory if necessary so user more easily finds where to put shortcuts
167+ Error error = FileUtils .createDirectoryFile (TERMUX_WIDGET_DYNAMIC_SHORTCUTS_DIR_PATH );
168+ if (error != null ) {
169+ Logger .logError (LOG_TAG , error .toString ());
170+ Logger .showToast (this , Error .getMinimalErrorLogString (error ), true );
171+ }
172+
173+ List <TermuxWidgetService .TermuxWidgetItem > items = new ArrayList <>();
174+ enumerateShortcutFiles (items , new File (TERMUX_WIDGET_DYNAMIC_SHORTCUTS_DIR_PATH ), false );
175+
176+ if (items .size () == 0 ) {
177+ Logger .showToast (context , getString (R .string .msg_no_shortcut_files_found_in_directory ,
178+ TermuxFileUtils .getUnExpandedTermuxPath (TERMUX_WIDGET_DYNAMIC_SHORTCUTS_DIR_PATH )), true );
179+ return ;
180+ }
181+
182+ List <ShortcutInfo > shortcuts = new ArrayList <>();
183+ for (TermuxWidgetService .TermuxWidgetItem item : items ) {
184+ ShortcutInfo .Builder builder = new ShortcutInfo .Builder (context , item .mFile );
185+ builder .setIntent (TermuxCreateShortcutActivity .getExecutionIntent (context , item .mFile ));
186+ builder .setShortLabel (item .mLabel );
187+
188+ File shortcutIconFile = TermuxCreateShortcutActivity .getShortcutIconFile (context , ShellUtils .getExecutableBasename (item .mFile ));
189+ if (shortcutIconFile != null )
190+ builder .setIcon (Icon .createWithBitmap (((BitmapDrawable ) Drawable .createFromPath (shortcutIconFile .getAbsolutePath ())).getBitmap ()));
191+ else
192+ builder .setIcon (Icon .createWithResource (context , R .drawable .ic_launcher ));
193+
194+ shortcuts .add (builder .build ());
195+ }
196+
197+ // Remove shortcuts that can not be added.
198+ int maxShortcuts = shortcutManager .getMaxShortcutCountPerActivity ();
199+ Logger .logDebug (LOG_TAG , "Found " + items .size () + " shortcuts and max shortcuts limit is " + maxShortcuts );
200+ if (shortcuts .size () > maxShortcuts ) {
201+ Logger .logErrorAndShowToast (context , LOG_TAG , getString (R .string .msg_dynamic_shortcuts_limit_reached , maxShortcuts ));
202+ while (shortcuts .size () > maxShortcuts ) {
203+ String message = getString (R .string .msg_skipping_shortcut ,
204+ shortcuts .get (shortcuts .size () - 1 ).getId ().replaceAll (
205+ "^" + Pattern .quote (TERMUX_WIDGET_DYNAMIC_SHORTCUTS_DIR_PATH + "/" ), "" ));
206+ Logger .showToast (context , message , false );
207+ Logger .logDebug (LOG_TAG , message );
208+ shortcuts .remove (shortcuts .size () - 1 );
209+ }
210+ }
211+
212+ shortcutManager .removeAllDynamicShortcuts ();
213+ shortcutManager .addDynamicShortcuts (shortcuts );
214+ Logger .logDebugAndShowToast (context , LOG_TAG , getString (R .string .msg_created_dynamic_shortcuts_successfully , shortcuts .size ()));
215+ }
216+
217+ @ RequiresApi (Build .VERSION_CODES .N_MR1 )
218+ private void removeDynamicShortcuts (@ NonNull Context context ) {
219+ ShortcutManager shortcutManager = getShortcutManager (context , true );
220+ if (shortcutManager == null ) return ;
221+
222+ List <ShortcutInfo > shortcuts = shortcutManager .getDynamicShortcuts ();
223+ if (shortcuts != null && shortcuts .size () == 0 ) {
224+ Logger .logDebugAndShowToast (context , LOG_TAG , getString (R .string .msg_no_dynamic_shortcuts_currently_created ));
225+ return ;
226+ }
227+
228+ shortcutManager .removeAllDynamicShortcuts ();
229+ Logger .logDebugAndShowToast (context , LOG_TAG , getString (R .string .msg_removed_dynamic_shortcuts_successfully ));
230+ }
231+
37232}
0 commit comments