How to determine the current IME in Android? - android

I have an application where I would like to warn the user if they are not using the default Android softkeyboard. (i.e. they are using Swype or some thing else).
How can I check which input method they currently have selected?

You can get a default IME, use:
Settings.Secure.getString(getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);

InputMethodManager has getEnabledInputMethodList(). You get an InputMethodManager from getSystemService() in your Activity.

Here's a bit of code I used to determine if GoogleKeyboard, Samsung Keyboard, or Swype Keyboard is used. The value returned by reflection for mCurId indicates the IME ID.
Test with the different keyboards/input methods you are looking for to find the relevant one
public boolean usingSamsungKeyboard(Context context){
return usingKeyboard(context, "com.sec.android.inputmethod/.SamsungKeypad");
}
public boolean usingSwypeKeyboard(Context context){
return usingKeyboard(context, "com.nuance.swype.input/.IME");
}
public boolean usingGoogleKeyboard(Context context){
return usingKeyboard(context, "com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME");
}
public boolean usingKeyboard(Context context, String keyboardId)
{
final InputMethodManager richImm =
(InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
boolean isKeyboard = false;
final Field field;
try
{
field = richImm.getClass().getDeclaredField("mCurId");
field.setAccessible(true);
Object value = field.get(richImm);
isKeyboard = value.equals(keyboardId);
}
catch (IllegalAccessException e)
{
}
catch (NoSuchFieldException e)
{
}
return isKeyboard;
}

Related

How to catch Paste event from keyboards Clipboard android?

I want to catch paste event on edittext, with Context menu i can able to catch paste event on edit text like below.
etMobileNumber.customInsertionActionModeCallback = object : ActionMode.Callback {
override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
if(item?.itemId == android.R.id.paste){
//log.d(“pastevent”)
}
But if text pasted from keyboards clipoard this event is not firing. how to trigger that event ?
I struggled with your issue this afternoon while a customer has filed an issue where the paste keyboard action did not paste the whole ClipData in my EditTexts.
I got to implement a 2FA code input with a unique EditText for each 6 digits to fit the designed UI so I limited each EditText to 1 character.
But the keyboard paste action set only the first character into the currently focused EditText, as I could see it in its TextWatcher.
Going up in the call stack, I saw the action came from the InputConnection.
So what I have done is create my own EditText overriding the InputConnection onCreateInputConnection(EditorInfo) method to return my own InputConnectionWrapper as follow:
public interface OnTFACodePastedListener
{
void onTFACodePasted(#NonNull final String code);
}
public final class TFADigitEditText extends TextInputEditText
implements OnTFACodePastedListener
{
#NonNull
#Override
public InputConnection onCreateInputConnection(#NonNull EditorInfo outAttrs)
{
InputConnection ic = super.onCreateInputConnection(outAttrs);
return new TFAInputConnectionWrapper(this, ic, false);
}
#Override
public void onTFACodePasted(#NonNull String code)
{
// TODO: call void setText(CharSequence).
// In my case, I had to dispatch it to all EditTexts holding the TFA digits
// through their parent custom view.
}
}
public class TFAInputConnectionWrapper extends InputConnectionWrapper
{
private final OnTFACodePastedListener mCodePastedListener;
public TFAInputConnectionWrapper(#NonNull OnTFACodePastedListener pListener,
InputConnection target, boolean mutable)
{
super(target, mutable);
mCodePastedListener = pListener;
}
#Override
public boolean commitText(CharSequence text, int newCursorPosition)
{
String tfaCode = text.toString();
// Just a regex to avoid dispatching incorrect 2FA code for me
if (isValidTFACode(tfaCode))
{
mCodePastedListener.onTFACodePasted(tfaCode);
// Returning false here avoid the wrapped InputConnection
// to dispatch it further has we already handled it.
return false;
}
// On my side, I return false here too to only handle pasting the 2FA code my way
// but it could be useful to keep it in your case
return super.commitText(text, newCursorPosition);
}
}
Finally, keep in mind that we should use the Receive rich content Unified API ending up with the following code:
ViewCompat.setOnReceiveContentListener(myEditText, new String[]{"text/*"}
(view, payload) -> {
ClipData clip = payload.getClip();
if (clip.getItemCount() > 0)
{
String text = clip.getItemAt(0).coerceToText(getContext()).toString();
if (isValidTFACode(text))
{
onTFACodePasted(text);
return null;
}
}
// We should return only unused ClipData.Items, not the whole payload...
return payload;
});
but I don't understand why the keyboard paste action is not handled the same way...
Hope it will be helpful :)

Settings.canDrawOverlays is returning false even after turning the permission on from settings

I am trying billow Code from this answer to check if the permission is enabled. but it is returning false even when the permission is enabled from the settings.
public static boolean canDrawOverlayViews(Context con){
if(Build.VERSION.SDK_INT< Build.VERSION_CODES.LOLLIPOP){return true;}
try {
return Settings.canDrawOverlays(con);
}
catch(NoSuchMethodError e){
return canDrawOverlaysUsingReflection(con);
}
}
public static boolean canDrawOverlaysUsingReflection(Context context) {
try {
AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Class clazz = AppOpsManager.class;
Method dispatchMethod = clazz.getMethod("checkOp", new Class[] { int.class, int.class, String.class });
//AppOpsManager.OP_SYSTEM_ALERT_WINDOW = 24
int mode = (Integer) dispatchMethod.invoke(manager, new Object[] { 24, Binder.getCallingUid(), context.getApplicationContext().getPackageName() });
return AppOpsManager.MODE_ALLOWED == mode;
} catch (Exception e) { return false; }
}
Recently I've also faced the same issue and got the following workaround .
Referenced from
https://code.google.com/p/android/issues/detail?id=198671#c7
public boolean getWindoOverLayAddedOrNot2() {
String sClassName = "android.provider.Settings";
try {
Class classToInvestigate = Class.forName(sClassName);
if (context == null)
context = activity;
Method method = classToInvestigate.getDeclaredMethod("isCallingPackageAllowedToDrawOverlays", Context.class, int.class, String.class, boolean.class);
Object value = method.invoke(null, context, Process.myUid(), context.getPackageName(), false);
Log.i("Tag", value.toString());
// Dynamically do stuff with this class
// List constructors, fields, methods, etc.
} catch (ClassNotFoundException e) {
// Class not found!
} catch (Exception e) {
// Unknown exception
e.printStackTrace();
}
return false;
}
does the check involves the device admin?
I have encountered this problem when disabling device admin, I have checked this permission in the DeviceAdminReceiver->onDisabled() and on some devices, and canDrawOverlays returned false, despite the fact i had the permission.
The above answer helped sometimes but not all the time. the thing that did work is Thread.sleep before the check.
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// some exception here
}
The minimal time that worked for me was 20 millis. than canDrawOverlays returned true
Note: this is not a good practice however this is the only thing that worked for me
Based on BennyP's answer, I've made a Runnable run the required code after 500ms and that worked very well. The feedback is a bit delayed, but the user won't even notice the delay.
This is the code I've added to my onResume()
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
if(!Settings.canDrawOverlays(ControllerActivity.this)){
//Handle overlay permission not given here
}
else{
//Handle overlay permission given here
}
}
}, 500);
Hope it helps!
I tried restarting the activity after the user accessed the setting . This is code :
public static void restartActivity(Activity act){
Intent intent = getIntent();
finish();
startActivity(intent);
}
First of all, I am really very surprised with this strange behaviour of
Settings.canDrawOverlays(this);
I also faced the same issue with its usage, it was returning false even if the permission is already assigned.
What I noticed that, I was using this check in my onStart() method, where it was creating this wired behavior. To resolve this, I searched over internet and no result was there that can satisfy me and the one I can use.
Solution
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Log.e("Overlay Permission", "" + Settings.canDrawOverlays(this));
if (!Settings.canDrawOverlays(this)) {
MyPreferences.saveBoolean(HomeScreen.this, "showOverlayPermissionDialog", true);
} else {
MyPreferences.saveBoolean(HomeScreen.this, "showOverlayPermissionDialog", false);
}
}
I did something lake this, in my onCreate(). Here I saved the values accordingly in my SharedPreferences, and according to these Shared Preference values, I created a check for showing an overlay dialog in my onStart(). This worked fine!
You can try this solution, and mark this answer useful if your problem is solved.
Thanks

Android NumberPicker with Formatter doesn't format on first rendering

I have a NumberPicker that has a formatter that formats the displayed numbers either when the NumberPicker spins or when a value is entered manually. This works fine, but when the NumberPicker is first shown and I initialize it with setValue(0) the 0 does not get formatted (it should display as "-" instead of 0). As soon as I spin the NumberPicker from that point on everything works.
How can I force the NumberPicker to format always - Both on first rendering and also when I enter a number manually with the keyboard?
This is my formatter
public class PickerFormatter implements Formatter {
private String mSingle;
private String mMultiple;
public PickerFormatter(String single, String multiple) {
mSingle = single;
mMultiple = multiple;
}
#Override
public String format(int num) {
if (num == 0) {
return "-";
}
if (num == 1) {
return num + " " + mSingle;
}
return num + " " + mMultiple;
}
}
I add my formatter to the picker with setFormatter(), this is all I do to the picker.
picker.setMaxValue(max);
picker.setMinValue(min);
picker.setFormatter(new PickerFormatter(single, multiple));
picker.setWrapSelectorWheel(wrap);
dgel's solution doesn't work for me: when I tap on the picker, formatting disappears again. This bug is caused by input filter set on EditText inside NumberPicker when setDisplayValues isn't used. So I came up with this workaround:
Field f = NumberPicker.class.getDeclaredField("mInputText");
f.setAccessible(true);
EditText inputText = (EditText)f.get(mPicker);
inputText.setFilters(new InputFilter[0]);
I also encountered this annoying little bug. Used a technique from this answer to come up with a nasty but effective fix.
NumberPicker picker = (NumberPicker)view.findViewById(id.picker);
picker.setMinValue(1);
picker.setMaxValue(5);
picker.setWrapSelectorWheel(false);
picker.setFormatter(new NumberPicker.Formatter() {
#Override
public String format(int value) {
return my_formatter(value);
}
});
try {
Method method = picker.getClass().getDeclaredMethod("changeValueByOne", boolean.class);
method.setAccessible(true);
method.invoke(picker, true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
Calling that private changeValueByOne method immediately after instantiating my number picker seems to kick the formatter enough to behave how it should. The number picker comes up nice and clean with the first value formatted correctly. Like I said, nasty but effective.
I had the same problem and I used the setDisplayedValues() method instead.
int max = 99;
String[] values = new String[99];
values[0] = “-” + mSingle
values[1] =
for(int i=2; i<=max; i++){
makeNames[i] = String.valueOf(i) + mMultiple;
}
picker.setMinValue(0);
picker.setMaxValue(max);
picker.setDisplayedValues(values)
This doesn't allow the user to set the value manually in the picker though.
The following solution worked out for me for APIs 18-26 without using reflection, and without using setDisplayedValues().
It consists of two steps:
Make sure the first element shows by setting it's visibility to invisible (I used Layout Inspector to see the difference with when it shows, it's not logical but View.INVISIBLE actually makes the view visible).
private void initNumberPicker() {
// Inflate or create your BugFixNumberPicker class
// Do your initialization on bugFixNumberPicker...
bugFixNumberPicker.setFormatter(new NumberPicker.Formatter() {
#Override
public String format(final int value) {
// Format to your needs
return aFormatMethod(value);
}
});
// Fix for bug in Android Picker where the first element is not shown
View firstItem = bugFixNumberPicker.getChildAt(0);
if (firstItem != null) {
firstItem.setVisibility(View.INVISIBLE);
}
}
Subclass NumberPicker and make sure no click events go through so the glitch where picker elements disapear on touch can't happen.
public class BugFixNumberPicker extends NumberPicker {
public BugFixNumberPicker(Context context) {
super(context);
}
public BugFixNumberPicker(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BugFixNumberPicker(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
public boolean performClick() {
return false;
}
#Override
public boolean performLongClick() {
return false;
}
#Override
public boolean onInterceptTouchEvent(MotionEvent event) {
return false;
}
}
Here's my solution based on answers by torvin and Sebastian. You don't have to subclass anything or use reflection.
View editView = numberPicker.getChildAt(0);
if (editView instanceof EditText) {
// Remove default input filter
((EditText) editView).setFilters(new InputFilter[0]);
}
Calling the private method changeValueByOne() via reflection as described in an earlier answer works for me on API Level 16 (Android 4.1.2 and up), but it does not seem to help on API Level 15 (Android 4.0.3), however!
What works for me on API Level 15 (and up) is to use your own custom formatter to create String array and pass that with the method setDisplayedValues() to the number picker.
See also: Android 3.x and 4.x NumberPicker Example
The answer provided by NoActivity worked for me but I only had to do:
View firstItem = bugFixNumberPicker.getChildAt(0);
if (firstItem != null) {
firstItem.setVisibility(View.INVISIBLE);
}
to fix the issue. I did not need to subclass NumberPicker. I did not see the issue where picker elements disappear on touch.
Kotlin version based on Nikolai's answer
private fun initNumberPicker() {
nrPicker.children.iterator().forEach {
if (it is EditText) it.filters = arrayOfNulls(0) // remove default input filter
}
}
I managed to fix it by calling
picker.invalidate();
just after setting the formatter.
Improved Nikolai answer if selected index is not 0. Not to great for performances but fix the problem..
for(index in numberPicker.minValue..numberPicker.maxValue) {
val editView = numberPicker.getChildAt(index-numberPicker.minValue)
if (editView != null && editView is EditText) {
// Remove default input filter
(editView as EditText).filters = arrayOfNulls(0)
}
}

Retrieve User-Agent programmatically

Is there a way to retrieve Browser's user-agent without having a WebView in activity?
I know it is possible to get it via WebView:
WebView view = (WebView) findViewById(R.id.someview);
String ua = view.getSettings().getUserAgentString() ;
But in my case I don't have/need a webview object and I don't want to create it just for retrieving user-agent string.
If you don't have one you can try taking it like this
String ua=new WebView(this).getSettings().getUserAgentString();
Edit-
The doc for getUserAgentString() says
Return the WebView's user-agent string.
So i don't think you can get it unless you declare one. Some one correct me if i am wrong
There is a much simpler way if you are on Android 2.1 or above. Granted this isn't the exact same User Agent string that a webview would return, but might serve you well enough for your purposes.
As an additional advantage to pulling from web view, you can use this from any thread (not just the UI thread).
There is a system property called http.agent, which can be used to retrieve the User-Agent string.
String userAgent = System.getProperty("http.agent");
See Programmatically get User-Agent String for more details.
I used to use solution proposed by DeRagan. But it turned out that creating a single WebView instance starts a thread "WebViewCoreThread" which stays on the background until application is terminated by the system. Maybe it doesn't consume too much resources but I don't like it anyway. So I use slightly different method now, which tries to avoid WebViewCoreThread creation:
// You may uncomment next line if using Android Annotations library, otherwise just be sure to run it in on the UI thread
// #UiThread
public static String getDefaultUserAgentString(Context context) {
if (Build.VERSION.SDK_INT >= 17) {
return NewApiWrapper.getDefaultUserAgent(context);
}
try {
Constructor<WebSettings> constructor = WebSettings.class.getDeclaredConstructor(Context.class, WebView.class);
constructor.setAccessible(true);
try {
WebSettings settings = constructor.newInstance(context, null);
return settings.getUserAgentString();
} finally {
constructor.setAccessible(false);
}
} catch (Exception e) {
return new WebView(context).getSettings().getUserAgentString();
}
}
#TargetApi(17)
static class NewApiWrapper {
static String getDefaultUserAgent(Context context) {
return WebSettings.getDefaultUserAgent(context);
}
}
It creates WebSettings instance directly using package-visible constructor and if that is not available for some reason (e.g. due to API changes in future Android versions) - silently falls back to "WebView-like" solution.
UPDATE
As pointed by #Skywalker5446, starting from Android 4.2/API 17, there is a public static method to get default user agent value. I've updated my code to use that method on the supported platforms.
Since Android 2.1 you should use System.getProperty("http.agent");
You also dont need to create a WebView first AND , thats the advantage,
you can use it inside a non-uithread.
greetings steve
This is an updated solution based on previous answers that works when you compile for KitKat. Now the WebSettings class is abstract and the WebSettingsClassic class has been removed.
#TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static String getUserAgent(final Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return WebSettings.getDefaultUserAgent(context);
}
else {
try {
final Class<?> webSettingsClassicClass = Class.forName("android.webkit.WebSettingsClassic");
final Constructor<?> constructor = webSettingsClassicClass.getDeclaredConstructor(Context.class, Class.forName("android.webkit.WebViewClassic"));
constructor.setAccessible(true);
final Method method = webSettingsClassicClass.getMethod("getUserAgentString");
return (String) method.invoke(constructor.newInstance(context, null));
}
catch (final Exception e) {
return new WebView(context).getSettings()
.getUserAgentString();
}
}
}
Thanks to Idolon's answer my app could process this in the background.
But somehow on HTC Inspire 4G from AT&T that runs 2.3.3, it goes to the catch statement and it can be no longer run on the background thread.
My solution for this is the following:
public static String getUserAgent(Context context) {
try {
Constructor<WebSettings> constructor = WebSettings.class.getDeclaredConstructor(Context.class, WebView.class);
constructor.setAccessible(true);
try {
WebSettings settings = constructor.newInstance(context, null);
return settings.getUserAgentString();
} finally {
constructor.setAccessible(false);
}
} catch (Exception e) {
String ua;
if(Thread.currentThread().getName().equalsIgnoreCase("main")){
WebView m_webview = new WebView(context);
ua = m_webview.getSettings().getUserAgentString();
}else{
mContext = context;
((Activity) mContext).runOnUiThread(new Runnable() {
#Override
public void run() {
WebView webview = new WebView(mContext);
mUserAgent = webview.getSettings().getUserAgentString();
}
});
return mUserAgent;
}
return ua;
}
}
(suppose you have mContext and mUserAgent in the field)

Selecting text in a WebView?

The browser does this by calling public void emulateShiftHeld() method on the WebView which is hidden in the SDK.
Any other options?
From the class that extends WebView:
public void selectAndCopyText() {
try {
Method m = WebView.class.getMethod("emulateShiftHeld", null);
m.invoke(this, null);
} catch (Exception e) {
e.printStackTrace();
// fallback
KeyEvent shiftPressEvent = new KeyEvent(0,0,
KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_SHIFT_LEFT,0,0);
shiftPressEvent.dispatch(this);
}
}
And then you have to use ClipboardManager to watch for new text.
Works on Android 1.5 - 2.3. emulateShiftHeld() made public since 2.2.
This chunk of code does the exact same thing as emulateshiftheld(). It allows the user to select text. Then automatically copies it to the clipboard.
KeyEvent shiftPressEvent = new KeyEvent(0,0,
KeyEvent.ACTION_DOWN,KeyEvent.KEYCODE_SHIFT_LEFT,0,0);
shiftPressEvent.dispatch(portal);

Categories

Resources