I want to use USSD dialog which comes after dialing any USSD code say *123# which asks user to enter option number to perform specific task(s) depending upon sim card vendors. I need to interact with that dialog to provide input in the text box given into it programmatically.
However, I am able to read the USSD response that comes in Alert Dialog after dialing any USSD code, using AccessibilityService and I'm showing the response in a Toast as shown in the code below. I haven't found any solution to interact with USSD dialog yet.
public class UssdService extends AccessibilityService{
public static String TAG = "USSD";
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "onAccessibilityEvent");
String text = event.getText().toString();
if (event.getClassName().equals("android.app.AlertDialog")) {
Log.d(TAG, text);
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
}
}
#Override
public void onInterrupt() {
}
#Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.d(TAG, "onServiceConnected");
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.flags = AccessibilityServiceInfo.DEFAULT;
info.packageNames = new String[]{"com.android.phone"};
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
setServiceInfo(info);
}
}
Here is the service declaration in Manifest:
<service android:name=".UssdService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data android:name="android.accessibilityservice"
android:resource="#xml/config_service" />
</service>
For interacting with USSD dialog, I used below code.
I used the below code for click event:
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("Send");
for (AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
I used the below code for setText in EditText. This is setText where the current focus is.
AccessibilityNodeInfo nodeInput = nodeInfo.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
Bundle bundle = new Bundle();
bundle.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,pMPIN);
nodeInput.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT,bundle);
nodeInput.refresh();
My company Hover has developed an Android SDK which uses accessibility services to run multi-step USSD sessions and have it appear to happen inside your app. The underlying method is similar to what is outlined in the accepted answer, but there is more of an abstraction layer, support for lots of devices that behave differently, and it hides the session from the user.
You create configurations for USSD services, trigger the session to run from your app and pass in any runtime variables you need. When the response is returned your app is notified and you can parse it as you need. It works on Android 4.3 and above.
The SDK is free to integrate and use until you hit large scale. Please see our docs to get started.
(Disclosure: I am the CTO of Hover)
Related
I'm working on a large app that wants to add NFC communication. This app has many manifests, one for the main shell app and one each for the many separate modules. Initially I registered the NFC service on the main manifest and it works fine. The issue is that the NFC service now triggers anytime the app is open and not when the user is on a specific screen.
So I wanted to ask, is there a way to have the NFC service register/un-register as a user navigates to/away from a specific screen? Or just a way to make it so that NFC communication is restricted to a specific screen? The size of this app is really tripping me up, I appreciate any help people can offer.
So it is not usual just to use manifest Intent filters for NFC operations though possible, usually one of the foreground API's is used instead.
The following solution works for API 19 upwards because it uses the enableReaderMode NFC API's because this is better and enables more control and is more reliable.
The basic concept is that in all Activities you claim to handle all NFC Tag types silently when the Activity is in the foreground, then for the Activity where you want the NFC reading to happen you actually do something when you are notified a Tag has been presented.
In your manifest you only have the following:
<uses-permission android:name="android.permission.NFC" />
and optionally
<uses-permission android:name="android.permission.VIBRATE" />
for user feedback.
You don't have any of the NFC Intent filters.
The in every Activity you have the following boiler plate code:-
public class MainActivity extends AppCompatActivity implements NfcAdapter.ReaderCallback{
private NfcAdapter mNfcAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
// Rest of onCreate
}
#Override
protected void onResume() {
super.onResume();
if(mNfcAdapter!= null) {
Bundle options = new Bundle();
options.putInt(NfcAdapter.EXTRA_READER_PRESENCE_CHECK_DELAY, 250);
mNfcAdapter.enableReaderMode(this, this,
NfcAdapter.FLAG_READER_NFC_A |
NfcAdapter.FLAG_READER_NFC_B |
NfcAdapter.FLAG_READER_NFC_F |
NfcAdapter.FLAG_READER_NFC_V |
NfcAdapter.FLAG_READER_NFC_BARCODE |
NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK |
NfcAdapter.FLAG_READER_NO_PLATFORM_SOUNDS,
options);
// enabling FLAG_READER_SKIP_NDEF_CHECK is optional
// depending on if you are reading NDef data or not.
}
}
#Override
protected void onPause() {
super.onPause();
if(mNfcAdapter!= null)
mNfcAdapter.disableReaderMode(this);
}
public void onTagDiscovered(Tag tag){
// Do nothing when a NFC tag is detected
}
}
In Activities where you want to handle the NFC Tag change onTagDiscovered method to do something with the Tag data.
e.g. for read Ndef data from a Tag onTagDiscovered could look like:-
// This method is run in another thread when a card is discovered
// !!!! This method cannot cannot direct interact with the UI Thread
// Use `runOnUiThread` method to change the UI from this method
public void onTagDiscovered(Tag tag) {
// Read and or write to Tag here to the appropriate Tag Technology type class
// in this example the card should be an Ndef Technology Type
Ndef mNdef = Ndef.get(tag);
// Check that it is an Ndef capable card
if (mNdef!= null) {
// If we want to read
// As we did not turn on the NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK
// We can get the cached Ndef message the system read for us.
NdefMessage mNdefMessage = mNdef.getCachedNdefMessage();
// Now your own code to process the Ndef message
// Finally feedback to the user that the NFC read was a success
// Make a Sound
try {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
Ringtone r = RingtoneManager.getRingtone(getApplicationContext(),
notification);
r.play();
} catch (Exception e) {
// Some error playing sound
}
// Optionally Vibrate as well
Vibrator v = (Vibrator) activity.getSystemService(Context.VIBRATOR_SERVICE);
if (v != null) {
v.vibrate(500);
}
}
}
You can optionally you can also decide on when the NFC is handled when a particular Fragment is shown in your Activity or when a dialog is shown or any other state in your Activity by putting conditional clauses in your onTagDiscovered method.
e.g. Some pseudo code
public void onTagDiscovered(Tag tag) {
if correct fragment is being shown {
Handle NFC reading and feedback to user the NFC has been read
} else {
Do nothing
}
}
or
public void onTagDiscovered(Tag tag) {
if some condition is true {
Handle NFC reading and feedback to user the NFC has been read
} else {
Do nothing
}
}
Normally with Intent filter and enableForegroundDisplatch method of working with NFC the System service Handles the NFC event and notifies the user that an NFC card has been presented and then it interrupts you App by restarting the current Activity or starting another Activity from you App.
With enableReadmode and this method the NFC System Service is silent, because the Event is handled in another thread in your App, what the Activity currently is doing is not interrupted and you get full control about when you handle the NFC event or to silently ignore it.
The problem with any register/de-register approach is that the System NFC Service would always make a sound and possibly launch another App or display a default NFC content screen when you were de-registered.
I've just started coding my app which uses Accessibility Service. I'll explain my problem in detail.
Below is my onServiceConnected method of MyAccessibilityService class
protected void onServiceConnected() {
super.onServiceConnected();
AccessibilityServiceInfo info = getServiceInfo();
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.WINDOWS_CHANGE_ADDED;
info.packageNames = new String[]
{THIRD_PARTY_APP_PACKAGE};
info.notificationTimeout = 100;
this.setServiceInfo(info);
}
The app is detecting events in onAccessibilityEvent() method
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return;
}
Toast.makeText(this, "Event Occured", Toast.LENGTH_SHORT).show();
}
Now when I open the third party app, I'm getting the Toast "Event occured". Now I close the app and when I open it again, the method is not called and I don't get any Toast. To make it working again, I have to disable the accessibility service of my app in my phone's Settings and again enable it.
I know I'm missing something and my only question is what should be the additional part of code or what modifications I need in order to detect the event every time I open the third party app?
Have you tried getting rid of the notification timeout? You probably don't need it, and it isn't the best-tested API.
Background
Back a few years ago, I asked how TeamViewer allows the user to control the device without normal interaction with the device. I was told it's a special "backdoor" that manufacturers allow specifically for this app, and only possible using root priviledge for other apps.
Seeing that an app like "Airplane Mode Shortcut" allows to toggle airplane mode, by automatic navigation to its screen and toggling the switch, it made me realize this situation has changed.
The problem
It is said in the docs:
Starting with Android 4.0 (API Level 14), accessibility services can
act on behalf of users, including changing the input focus and
selecting (activating) user interface elements. In Android 4.1 (API
Level 16) the range of actions has been expanded to include scrolling
lists and interacting with text fields. Accessibility services can
also take global actions, such as navigating to the Home screen,
pressing the Back button, opening the notifications screen and recent
applications list. Android 4.1 also includes a new type of focus,
Accessibilty Focus, which makes all visible elements selectable by an
accessibility service.
These new capabilities make it possible for developers of
accessibility services to create alternative navigation modes such as
gesture navigation, and give users with disabilities improved control
of their Android devices.
But there is no more information about how to use it.
Only samples I've found are at the bottom, but those are very old and a part of the apiDemos bundle.
The question
How do I make a service that can query, focus, click, enter text, and perform other UI related operations?
By implementing AccessibilityService (https://developer.android.com/training/accessibility/service.html) you get access to that features.
You can either inspect or perform action on the element lastly interacted by user or inspect whole application which currently active.
Intercept user events by implementing onAccessibilityEvent(AccessibilityEvent event), here you can retrieve virtual view (representing original view) with event.getSource() and then inspect it with getClassName() or getText() or anything you find in the documentation.
Inspect whole application by calling getRootInActiveWindow() and iterate throught tree of virtaul views with getRootInActiveWindow().getChild(index).
Both getRootInActiveWindow() and event.getSource() return AccessibilityNodeInfo, on which you can invoke performAction(action) and do something like Click, Set Text, etc..
Example: Play Store
Search for 'facebook' app and open it's page on play store, once you opened the play store app.
#Override
public void onAccessibilityEvent(final AccessibilityEvent event) {
AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
//Inspect app elements if ready
if (rootInActiveWindow != null) {
//Search bar is covered with textview which need to be clicked
List<AccessibilityNodeInfo> searchBarIdle = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_idle_text");
if (searchBarIdle.size() > 0) {
AccessibilityNodeInfo searchBar = searchBarIdle.get(0);
searchBar.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
//Check is search bar is visible
List<AccessibilityNodeInfo> searchBars = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_text_input");
if (searchBars.size() > 0) {
AccessibilityNodeInfo searchBar = searchBars.get(0);
//Check is searchbar have the required text, if not set the text
if (searchBar.getText() == null || !searchBar.getText().toString().equalsIgnoreCase("facebook")) {
Bundle args = new Bundle();
args.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "facebook");
searchBar.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
} else {
//There is no way to press Enter to perform search, so find corresponding suggestion and click
List<AccessibilityNodeInfo> searchSuggestions = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/suggest_text");
for (AccessibilityNodeInfo suggestion : searchSuggestions) {
if(suggestion.getText().toString().equals("Facebook")) {
//We found textview, but its not clickable, so we should perform the click on the parent
AccessibilityNodeInfo clickableParent = suggestion.getParent();
clickableParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
}
}
EDIT: full code below:
MyAccessibilityService
public class MyAccessibilityService extends AccessibilityService {
#Override
public void onCreate() {
super.onCreate();
Log.d("MyAccessibilityService", "onCreate");
}
#Override
public void onAccessibilityEvent(final AccessibilityEvent event) {
Log.d("MyAccessibilityService", "onAccessibilityEvent");
AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
//Inspect app elements if ready
if (rootInActiveWindow != null) {
//Search bar is covered with textview which need to be clicked
List<AccessibilityNodeInfo> searchBarIdle = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_idle_text");
if (searchBarIdle.size() > 0) {
AccessibilityNodeInfo searchBar = searchBarIdle.get(0);
searchBar.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
//Check is search bar is visible
List<AccessibilityNodeInfo> searchBars = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/search_box_text_input");
if (searchBars.size() > 0) {
AccessibilityNodeInfo searchBar = searchBars.get(0);
//Check is searchbar have the required text, if not set the text
if (searchBar.getText() == null || !searchBar.getText().toString().equalsIgnoreCase("facebook")) {
Bundle args = new Bundle();
args.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "facebook");
searchBar.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
} else {
//There is no way to press Enter to perform search, so find corresponding suggestion and click
List<AccessibilityNodeInfo> searchSuggestions = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.android.vending:id/suggest_text");
for (AccessibilityNodeInfo suggestion : searchSuggestions) {
if (suggestion.getText().toString().equals("Facebook")) {
//We found textview, but its not clickable, so we should perform the click on the parent
AccessibilityNodeInfo clickableParent = suggestion.getParent();
clickableParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
}
}
}
#Override
public void onInterrupt() {
}
}
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.findfacebookapp">
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service
android:name=".MyAccessibilityService"
android:label="#string/accessibility_service_label"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/accessibility_service_config"/>
</service>
</application>
</manifest>
res/xml/accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault"
android:canRequestEnhancedWebAccessibility="true"
android:canRetrieveWindowContent="true"
android:description="#string/app_name"
android:notificationTimeout="100"/>
MainActivity
public class MainActivity extends AppCompatActivity {
public void onEnableAccClick(View view) {
startActivityForResult(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 1);
}
}
I am trying to get the call block numbers in android N, i want to know the given is block number or not (ex:- 5554 emulator number)
Contacts, sms, phone state permissions has been given to allow to access the block numbers and i followed the "Android Developer" site https://developer.android.com/reference/android/provider/BlockedNumberContract.html
But i am unable to get the block numbers, i am using latest android studio 2.2.2 and checked the functionality in android N emulator i don't have device.
Here is my code.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
// Button onclick method to show the logs
public void displayBlockCursorCount(View view) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
String number = "5552";
if (BlockedNumberContract.canCurrentUserBlockNumbers(MainActivity.this)) {
if (BlockedNumberContract.isBlocked(MainActivity.this, number)) {
Log.e(TAG, "given number is blocked >>>>>> " + number);
}
}
}
}
}
I am getting the
java.lang.SecurityException: Caller must be system, default dialer or default SMS app.
Please post the comment if down comment and thanks for advance.
To access blocked contacts,Your app should be default calling app or Messaging app else it throws security exception.
Add additional check
private boolean isAppAsDefaultDialer() {
TelecomManager telecom = mContext.getSystemService(TelecomManager.class);
if (getApplicationContext().getPackageName().equals(telecom.getDefaultDialerPackage())) {
return true;
}
return false;
}
or check sources https://android.googlesource.com/platform/packages/providers/BlockedNumberProvider/+/android-7.0.0_r1/src/com/android/providers/blockednumber/BlockedNumberProvider.java
And make you app as defaul dialer
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:scheme="tel"/>
</intent-filter>
I am making an application to call multiple numbers.
In that app
When I call to 1 person and if the call is answered by the user then
the loop should be stopped.
But If the call is rejected then the call should be on next number and
loop should be couninue.
My problem is I cant detect whether the call is rejected or answered. when I had search on net some people says it is not possible to detect the call is answered or rejected.
Is it really not possible to detect the call in android If it is possible then how can I do that?
I think you can check outgoing call time of last call in PhoneStateListener class' onCallStateChanged method. Fetch the data if state is idle that is TelephonyManager.CALL_STATE_IDLE.
Something like this:
Cursor mCallCursor = context.getContentResolver().query(android.provider.CallLog.Calls.CONTENT_URI,null,null,null,null);
int duration = mCallCursor.getColumnIndex( CallLog.Calls.DURATION);
while(mCallCursor.moveToFirst())
{
Toast.makeText(context, mCallCursor.getString(duration), Toast.LENGTH_LONG).show();
}
You can find more about that here. I haven't tested the above code. But something like that should work.
You can check if time's 00:00, then call next number of loop. Else you can stop calling.
Hope this helps you.
below is a code of detecting outgoing call by accessibility events -
Add a class which extends AccessibilityService in your projects -
public class CallDetection extends AccessibilityService {
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
acquireLock(this);
Log.d("myaccess","after lock");
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
Log.d("myaccess","in window changed");
AccessibilityNodeInfo info = event.getSource();
if (info != null && info.getText() != null) {
String duration = info.getText().toString();
String zeroSeconds = String.format("%02d:%02d", new Object[]{Integer.valueOf(0), Integer.valueOf(0)});
String firstSecond = String.format("%02d:%02d", new Object[]{Integer.valueOf(0), Integer.valueOf(1)});
Log.d("myaccess","after calculation - "+ zeroSeconds + " --- "+ firstSecond + " --- " + duration);
if (zeroSeconds.equals(duration) || firstSecond.equals(duration)) {
Toast.makeText(getApplicationContext(),"Call answered",Toast.LENGTH_SHORT).show();
// Your Code goes here
}
info.recycle();
}
}
}
#Override
protected void onServiceConnected() {
super.onServiceConnected();
Toast.makeText(this,"Service connected",Toast.LENGTH_SHORT).show();
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
info.notificationTimeout = 0;
info.packageNames = null;
setServiceInfo(info);
}
#Override
public void onInterrupt() {
}
}
But to get the function event.getSource() working you have to specify some of your service configuration through xml, so create a xml folder in your project and add a xml file called serviceconfig.xml (you can give any name you want.
The content of serviceconfig is below -
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="#string/callDetection"
android:accessibilityEventTypes="typeWindowContentChanged"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
/>
You can find more about serviceconfig in Here
Now add your service in you Manifest file like this -
<service android:name=".CallDetection"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:label="#string/callDetection">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/serviceconfig" />
</service>
And youre done, just run the app and go to Accessibility settings in your phone, you will find an option named as detection (or whatever name you have given as your service description), switch that on to give accesibility permissions for you app.
Now you will see a toast when call is answered.
you can Code any code you want in there, also you can call a callback function in your activity
Most important - Dont call your call window(android dialer window) untill the call is answered, otherwise this will not work.
Note - As android doesn't provide any solution to detect if the call is answered or not, this is the best alternative i have made, hope it works for you.