mirror of
https://github.com/fankes/termux-app.git
synced 2025-10-22 19:59:20 +08:00
Added|Fixed: Do not show AutoFill UI on Termux start and add support for usernames
- The AutoFill type and hints are no longer hardcoded in `TerminalView` class and `TermuxActivity` layout xml. They are dynamically set to required values before making a manual AutoFill request and reverted back afterwards to default values. The hardcoded value `AUTOFILL_TYPE_TEXT` returned by `getAutofillType()` was causing the AutoFill UI to show on Activity starts, this will return `AUTOFILL_TYPE_NONE` by default now so that AutoFill UI isn't shown automatically. - The AutoFill importance is no longer hardcoded in `TermuxActivity` layout xml and is returned by `TerminalView` class itself by `getImportantForAutofill()`. - The AutoFill function in `TermuxActivity` for making a manual AutoFill request is moved to `TerminalView` class. This and moving of hardcoded values to `TerminalView` class mentioned above is done as complete logic of AutoFill should be handled by `TerminalView` class itself and not scattered in various places. - The Terminal context menu now supports AutoFilling a username. Note that GBoard/Google Password Manager seems to have a bug where it will still show `Pick a saved password` instead of username, even though `AUTOFILL_HINT_USERNAME` is being requested, however it will still AutoFill a username of selected entry correctly. - Pressing the back button to close the keyboard will also cancel the current manually requested AutoFill request and UI will not show when keyboard is opened again. Closes #3909
This commit is contained in:
@@ -10,7 +10,6 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.ServiceConnection;
|
import android.content.ServiceConnection;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
@@ -21,7 +20,6 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
import android.view.autofill.AutofillManager;
|
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.ListView;
|
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_SELECT_URL_ID = 0;
|
||||||
private static final int CONTEXT_MENU_SHARE_TRANSCRIPT_ID = 1;
|
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_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_RESET_TERMINAL_ID = 3;
|
||||||
private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4;
|
private static final int CONTEXT_MENU_KILL_PROCESS_ID = 4;
|
||||||
private static final int CONTEXT_MENU_STYLING_ID = 5;
|
private static final int CONTEXT_MENU_STYLING_ID = 5;
|
||||||
@@ -632,20 +631,16 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||||||
TerminalSession currentSession = getCurrentSession();
|
TerminalSession currentSession = getCurrentSession();
|
||||||
if (currentSession == null) return;
|
if (currentSession == null) return;
|
||||||
|
|
||||||
boolean addAutoFillMenu = false;
|
boolean autoFillEnabled = mTerminalView.isAutoFillEnabled();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
AutofillManager autofillManager = getSystemService(AutofillManager.class);
|
|
||||||
if (autofillManager != null && autofillManager.isEnabled()) {
|
|
||||||
addAutoFillMenu = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
menu.add(Menu.NONE, CONTEXT_MENU_SELECT_URL_ID, Menu.NONE, R.string.action_select_url);
|
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);
|
menu.add(Menu.NONE, CONTEXT_MENU_SHARE_TRANSCRIPT_ID, Menu.NONE, R.string.action_share_transcript);
|
||||||
if (!DataUtils.isNullOrEmpty(mTerminalView.getStoredSelectedText()))
|
if (!DataUtils.isNullOrEmpty(mTerminalView.getStoredSelectedText()))
|
||||||
menu.add(Menu.NONE, CONTEXT_MENU_SHARE_SELECTED_TEXT, Menu.NONE, R.string.action_share_selected_text);
|
menu.add(Menu.NONE, CONTEXT_MENU_SHARE_SELECTED_TEXT, Menu.NONE, R.string.action_share_selected_text);
|
||||||
if (addAutoFillMenu)
|
if (autoFillEnabled)
|
||||||
menu.add(Menu.NONE, CONTEXT_MENU_AUTOFILL_ID, Menu.NONE, R.string.action_autofill_password);
|
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_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_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);
|
menu.add(Menu.NONE, CONTEXT_MENU_STYLING_ID, Menu.NONE, R.string.action_style_terminal);
|
||||||
@@ -676,8 +671,11 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||||||
case CONTEXT_MENU_SHARE_SELECTED_TEXT:
|
case CONTEXT_MENU_SHARE_SELECTED_TEXT:
|
||||||
mTermuxTerminalViewClient.shareSelectedText();
|
mTermuxTerminalViewClient.shareSelectedText();
|
||||||
return true;
|
return true;
|
||||||
case CONTEXT_MENU_AUTOFILL_ID:
|
case CONTEXT_MENU_AUTOFILL_USERNAME:
|
||||||
requestAutoFill();
|
mTerminalView.requestAutoFillUsername();
|
||||||
|
return true;
|
||||||
|
case CONTEXT_MENU_AUTOFILL_PASSWORD:
|
||||||
|
mTerminalView.requestAutoFillPassword();
|
||||||
return true;
|
return true;
|
||||||
case CONTEXT_MENU_RESET_TERMINAL_ID:
|
case CONTEXT_MENU_RESET_TERMINAL_ID:
|
||||||
onResetTerminalSession(session);
|
onResetTerminalSession(session);
|
||||||
@@ -760,15 +758,6 @@ public final class TermuxActivity extends AppCompatActivity implements ServiceCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -31,8 +31,6 @@
|
|||||||
android:focusableInTouchMode="true"
|
android:focusableInTouchMode="true"
|
||||||
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
|
android:scrollbarThumbVertical="@drawable/terminal_scroll_shape"
|
||||||
android:scrollbars="vertical"
|
android:scrollbars="vertical"
|
||||||
android:importantForAutofill="no"
|
|
||||||
android:autofillHints="password"
|
|
||||||
tools:ignore="UnusedAttribute" />
|
tools:ignore="UnusedAttribute" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@@ -73,6 +73,7 @@
|
|||||||
<string name="title_share_selected_text">Terminal Text</string>
|
<string name="title_share_selected_text">Terminal Text</string>
|
||||||
<string name="title_share_selected_text_with">Send selected text to:</string>
|
<string name="title_share_selected_text_with">Send selected text to:</string>
|
||||||
|
|
||||||
|
<string name="action_autofill_username">Autofill username</string>
|
||||||
<string name="action_autofill_password">Autofill password</string>
|
<string name="action_autofill_password">Autofill password</string>
|
||||||
|
|
||||||
<string name="action_reset_terminal">Reset</string>
|
<string name="action_reset_terminal">Reset</string>
|
||||||
|
@@ -27,6 +27,7 @@ import android.view.View;
|
|||||||
import android.view.ViewConfiguration;
|
import android.view.ViewConfiguration;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
import android.view.accessibility.AccessibilityManager;
|
import android.view.accessibility.AccessibilityManager;
|
||||||
|
import android.view.autofill.AutofillManager;
|
||||||
import android.view.autofill.AutofillValue;
|
import android.view.autofill.AutofillValue;
|
||||||
import android.view.inputmethod.BaseInputConnection;
|
import android.view.inputmethod.BaseInputConnection;
|
||||||
import android.view.inputmethod.EditorInfo;
|
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. */
|
/** If non-zero, this is the last unicode code point received if that was a combining character. */
|
||||||
int mCombiningAccent;
|
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;
|
private final boolean mAccessibilityEnabled;
|
||||||
|
|
||||||
/** The {@link KeyEvent} is generated from a virtual keyboard, like manually with the {@link KeyEvent#KeyEvent(int, int)} constructor. */
|
/** The {@link KeyEvent} is generated from a virtual keyboard, like manually with the {@link KeyEvent#KeyEvent(int, int)} constructor. */
|
||||||
@@ -609,6 +633,7 @@ public final class TerminalView extends View {
|
|||||||
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
if (TERMINAL_VIEW_KEY_LOGGING_ENABLED)
|
||||||
mClient.logInfo(LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
mClient.logInfo(LOG_TAG, "onKeyPreIme(keyCode=" + keyCode + ", event=" + event + ")");
|
||||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||||
|
cancelRequestAutoFill();
|
||||||
if (isSelectingText()) {
|
if (isSelectingText()) {
|
||||||
stopTextSelectionMode();
|
stopTextSelectionMode();
|
||||||
return true;
|
return true;
|
||||||
@@ -1028,12 +1053,20 @@ public final class TerminalView extends View {
|
|||||||
if (value.isText()) {
|
if (value.isText()) {
|
||||||
mTermSession.write(value.getTextValue().toString());
|
mTermSession.write(value.getTextValue().toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resetAutoFill();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@Override
|
@Override
|
||||||
public int getAutofillType() {
|
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)
|
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||||
@@ -1042,6 +1075,91 @@ public final class TerminalView extends View {
|
|||||||
return AutofillValue.forText("");
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Reference in New Issue
Block a user