diff --git a/app/src/main/java/com/termux/app/TermuxActivity.java b/app/src/main/java/com/termux/app/TermuxActivity.java index 308d1f0b2b..e624d9e100 100644 --- a/app/src/main/java/com/termux/app/TermuxActivity.java +++ b/app/src/main/java/com/termux/app/TermuxActivity.java @@ -10,7 +10,6 @@ import android.content.IntentFilter; import android.content.ServiceConnection; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.view.ContextMenu; @@ -21,7 +20,6 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.view.autofill.AutofillManager; import android.widget.EditText; import android.widget.ImageButton; import android.widget.ListView; @@ -181,7 +179,8 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo private static final int CONTEXT_MENU_SELECT_URL_ID = 0; private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1; private static final int CONTEXT_MENU_SHARE_SELECTED_TEXT = 10; - private static final int CONTEXT_MENU_AUTOFILL_ID = 2; + private static final int CONTEXT_MENU_AUTOFILL_USERNAME = 11; + private static final int CONTEXT_MENU_AUTOFILL_PASSWORD = 2; private static final int CONTEXT_MENU_RESET_TERMINAL_ID = 3; private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4; private static final int CONTEXT_MENU_STYLING_ID = 5; @@ -632,20 +631,16 @@ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuIn TerminalSession currentSession = getCurrentSession(); if (currentSession == null) return; - boolean addAutoFillMenu = false; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AutofillManager autofillManager = getSystemService(AutofillManager.class); - if (autofillManager != null && autofillManager.isEnabled()) { - addAutoFillMenu = true; - } - } + boolean autoFillEnabled = mTerminalView.isAutoFillEnabled(); menu.add(Menu.NONE, CONTEXT_MENU_SELECT_URL_ID, Menu.NONE, R.string.action_select_url); menu.add(Menu.NONE, CONTEXT_MENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.action_share_transcript); if (!DataUtils.isNullOrEmpty(mTerminalView.getStoredSelectedText())) menu.add(Menu.NONE, CONTEXT_MENU_SHARE_SELECTED_TEXT, Menu.NONE, R.string.action_share_selected_text); - if (addAutoFillMenu) - menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password); + if (autoFillEnabled) + menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_USERNAME, Menu.NONE, R.string.action_autofill_username); + if (autoFillEnabled) + menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_PASSWORD, Menu.NONE, R.string.action_autofill_password); menu.add(Menu.NONE, CONTEXT_MENU_RESET_TERMINAL_ID, Menu.NONE, R.string.action_reset_terminal); menu.add(Menu.NONE, CONTEXT_MENU_KILL_PROCESS_ID, Menu.NONE, getResources().getString(R.string.action_kill_process, getCurrentSession().getPid())).setEnabled(currentSession.isRunning()); menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal); @@ -676,8 +671,11 @@ public boolean onContextItemSelected(MenuItem item) { case CONTEXT_MENU_SHARE_SELECTED_TEXT: mTermuxTerminalViewClient.shareSelectedText(); return true; - case CONTEXT_MENU_AUTOFILL_ID: - requestAutoFill(); + case CONTEXT_MENU_AUTOFILL_USERNAME: + mTerminalView.requestAutoFillUsername(); + return true; + case CONTEXT_MENU_AUTOFILL_PASSWORD: + mTerminalView.requestAutoFillPassword(); return true; case CONTEXT_MENU_RESET_TERMINAL_ID: onResetTerminalSession(session); @@ -760,15 +758,6 @@ private void toggleKeepScreenOn() { } } - private void requestAutoFill() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - AutofillManager autofillManager = getSystemService(AutofillManager.class); - if (autofillManager != null && autofillManager.isEnabled()) { - autofillManager.requestAutofill(mTerminalView); - } - } - } - /** diff --git a/app/src/main/res/layout/activity_termux.xml b/app/src/main/res/layout/activity_termux.xml index 484990ff24..831ea7cfb8 100644 --- a/app/src/main/res/layout/activity_termux.xml +++ b/app/src/main/res/layout/activity_termux.xml @@ -31,8 +31,6 @@ android:focusableInTouchMode="true" android:scrollbarThumbVertical="@drawable/terminal_scroll_shape" android:scrollbars="vertical" - android:importantForAutofill="no" - android:autofillHints="password" tools:ignore="UnusedAttribute" /> Terminal Text Send selected text to: + Autofill username Autofill password Reset diff --git a/terminal-view/src/main/java/com/termux/view/TerminalView.java b/terminal-view/src/main/java/com/termux/view/TerminalView.java index b4254a9832..b00d3aa06b 100644 --- a/terminal-view/src/main/java/com/termux/view/TerminalView.java +++ b/terminal-view/src/main/java/com/termux/view/TerminalView.java @@ -27,6 +27,7 @@ import android.view.ViewConfiguration; import android.view.ViewTreeObserver; import android.view.accessibility.AccessibilityManager; +import android.view.autofill.AutofillManager; import android.view.autofill.AutofillValue; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.EditorInfo; @@ -85,6 +86,29 @@ public final class TerminalView extends View { /** If non-zero, this is the last unicode code point received if that was a combining character. */ int mCombiningAccent; + /** + * The current AutoFill type returned for {@link View#getAutofillType()} by {@link #getAutofillType()}. + * + * The default is {@link #AUTOFILL_TYPE_NONE} so that AutoFill UI, like toolbar above keyboard + * is not shown automatically, like on Activity starts/View create. This value should be updated + * to required value, like {@link #AUTOFILL_TYPE_TEXT} before calling + * {@link AutofillManager#requestAutofill(View)} so that AutoFill UI shows. The updated value + * set will automatically be restored to {@link #AUTOFILL_TYPE_NONE} in + * {@link #autofill(AutofillValue)} so that AutoFill UI isn't shown anymore by calling + * {@link #resetAutoFill()}. + */ + @RequiresApi(api = Build.VERSION_CODES.O) + private int mAutoFillType = AUTOFILL_TYPE_NONE; + + /** + * The current AutoFill hints returned for {@link View#getAutofillHints()} ()} by {@link #getAutofillHints()} ()}. + * + * The default is an empty `string[]`. This value should be updated to required value. The + * updated value set will automatically be restored an empty `string[]` in + * {@link #autofill(AutofillValue)} by calling {@link #resetAutoFill()}. + */ + private String[] mAutoFillHints = new String[0]; + private final boolean mAccessibilityEnabled; /** The {@link KeyEvent} is generated from a virtual keyboard, like manually with the {@link KeyEvent#KeyEvent(int, int)} constructor. */ @@ -609,6 +633,7 @@ public boolean onKeyPreIme(int keyCode, KeyEvent event) { if (TERMINAL_VIEW_KEY_LOGGING_ENABLED) mClient.logInfo(LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")"); if (keyCode == KeyEvent.KEYCODE_BACK) { + cancelRequestAutoFill(); if (isSelectingText()) { stopTextSelectionMode(); return true; @@ -1028,12 +1053,20 @@ public void autofill(AutofillValue value) { if (value.isText()) { mTermSession.write(value.getTextValue().toString()); } + + resetAutoFill(); } @RequiresApi(api = Build.VERSION_CODES.O) @Override public int getAutofillType() { - return AUTOFILL_TYPE_TEXT; + return mAutoFillType; + } + + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public String[] getAutofillHints() { + return mAutoFillHints; } @RequiresApi(api = Build.VERSION_CODES.O) @@ -1042,6 +1075,91 @@ public AutofillValue getAutofillValue() { return AutofillValue.forText(""); } + @RequiresApi(api = Build.VERSION_CODES.O) + @Override + public int getImportantForAutofill() { + return IMPORTANT_FOR_AUTOFILL_NO; + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private synchronized void resetAutoFill() { + // Restore none type so that AutoFill UI isn't shown anymore. + mAutoFillType = AUTOFILL_TYPE_NONE; + mAutoFillHints = new String[0]; + } + + public AutofillManager getAutoFillManagerService() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null; + + try { + Context context = getContext(); + if (context == null) return null; + return context.getSystemService(AutofillManager.class); + } catch (Exception e) { + mClient.logStackTraceWithMessage(LOG_TAG, "Failed to get AutofillManager service", e); + return null; + } + } + + public boolean isAutoFillEnabled() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false; + + try { + AutofillManager autofillManager = getAutoFillManagerService(); + return autofillManager != null && autofillManager.isEnabled(); + } catch (Exception e) { + mClient.logStackTraceWithMessage(LOG_TAG, "Failed to check if Autofill is enabled", e); + return false; + } + } + + public synchronized void requestAutoFillUsername() { + requestAutoFill( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? new String[]{View.AUTOFILL_HINT_USERNAME} : + null); + } + + public synchronized void requestAutoFillPassword() { + requestAutoFill( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? new String[]{View.AUTOFILL_HINT_PASSWORD} : + null); + } + + public synchronized void requestAutoFill(String[] autoFillHints) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + if (autoFillHints == null || autoFillHints.length < 1) return; + + try { + AutofillManager autofillManager = getAutoFillManagerService(); + if (autofillManager != null && autofillManager.isEnabled()) { + // Update type that will be returned by `getAutofillType()` so that AutoFill UI is shown. + mAutoFillType = AUTOFILL_TYPE_TEXT; + // Update hints that will be returned by `getAutofillHints()` for which to show AutoFill UI. + mAutoFillHints = autoFillHints; + autofillManager.requestAutofill(this); + } + } catch (Exception e) { + mClient.logStackTraceWithMessage(LOG_TAG, "Failed to request Autofill", e); + } + } + + public synchronized void cancelRequestAutoFill() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + if (mAutoFillType == AUTOFILL_TYPE_NONE) return; + + try { + AutofillManager autofillManager = getAutoFillManagerService(); + if (autofillManager != null && autofillManager.isEnabled()) { + resetAutoFill(); + autofillManager.cancel(); + } + } catch (Exception e) { + mClient.logStackTraceWithMessage(LOG_TAG, "Failed to cancel Autofill request", e); + } + } + + + /**