I'm trying to send messages via Whatsapp programmatically, the code works except the user needs to click the send button. I need the app to do everything (all user interactions). One way to do to it as follows .
Go to Menu Button > Settings > Chats. and check the "Enter is send option"
Here's the code I'm using:
protected void sendwts(){
String smsNumber = "2126123456789"; // E164 format without '+' sign
Intent sendIntent = new Intent(Intent.ACTION_SEND);
// Intent sendIntent = new Intent(Intent.ACTION_SENDTO);
sendIntent.setType("text/plain");
sendIntent.putExtra(Intent.EXTRA_TEXT, "test \n");
sendIntent.putExtra("jid", smsNumber + "#s.whatsapp.net"); //phone number without "+" prefix
sendIntent.setPackage("com.whatsapp");
startActivity(sendIntent);
}
Thank you
You can do that only using the Accessibility API of Android.
The idea is quite simple, you'll actually make Android perform the click on Whatsapp's send button.
So the flow will be:
Send a regular message (with the intent you're currently using) with a suffix at the end of your message content such as "Sent by MY_APP".
Once the text there, your accessibility service will be notified that the EditText of whatsapp is filled.
If the suffix is present on the EditText of whatsapp, Your accessibility service will click on the send button. (this is to avoid performing actions as the user types in naturally a regular message).
Here's an example (which you'll have tweak if you wanna make it more restrictive):
public class WhatsappAccessibilityService extends AccessibilityService {
#Override
public void onAccessibilityEvent (AccessibilityEvent event) {
if (getRootInActiveWindow () == null) {
return;
}
AccessibilityNodeInfoCompat rootInActiveWindow = AccessibilityNodeInfoCompat.wrap (getRootInActiveWindow ());
// Whatsapp Message EditText id
List<AccessibilityNodeInfoCompat> messageNodeList = rootInActiveWindow.findAccessibilityNodeInfosByViewId ("com.whatsapp:id/entry");
if (messageNodeList == null || messageNodeList.isEmpty ()) {
return;
}
// check if the whatsapp message EditText field is filled with text and ending with your suffix (explanation above)
AccessibilityNodeInfoCompat messageField = messageNodeList.get (0);
if (messageField.getText () == null || messageField.getText ().length () == 0
|| !messageField.getText ().toString ().endsWith (getApplicationContext ().getString (R.string.whatsapp_suffix))) { // So your service doesn't process any message, but the ones ending your apps suffix
return;
}
// Whatsapp send button id
List<AccessibilityNodeInfoCompat> sendMessageNodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByViewId ("com.whatsapp:id/send");
if (sendMessageNodeInfoList == null || sendMessageNodeInfoList.isEmpty ()) {
return;
}
AccessibilityNodeInfoCompat sendMessageButton = sendMessageNodeInfoList.get (0);
if (!sendMessageButton.isVisibleToUser ()) {
return;
}
// Now fire a click on the send button
sendMessageButton.performAction (AccessibilityNodeInfo.ACTION_CLICK);
// Now go back to your app by clicking on the Android back button twice:
// First one to leave the conversation screen
// Second one to leave whatsapp
try {
Thread.sleep (500); // hack for certain devices in which the immediate back click is too fast to handle
performGlobalAction (GLOBAL_ACTION_BACK);
Thread.sleep (500); // same hack as above
} catch (InterruptedException ignored) {}
performGlobalAction (GLOBAL_ACTION_BACK);
}
}
Then create its definition in res -> xml -> whatsapp_service.xml:
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowContentChanged"
android:packageNames="com.whatsapp"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"/>
Then declare it in your manifest:
<service
android:name=".services.WhatsappAccessibilityService"
android:label="Accessibility Service"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/whatsapp_service"/>
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
</service>
And last thing, is to check if the accessibility services are enabled for your app or not, and redirect the user to the settings if not:
private boolean isAccessibilityOn (Context context, Class<? extends AccessibilityService> clazz) {
int accessibilityEnabled = 0;
final String service = context.getPackageName () + "/" + clazz.getCanonicalName ();
try {
accessibilityEnabled = Settings.Secure.getInt (context.getApplicationContext ().getContentResolver (), Settings.Secure.ACCESSIBILITY_ENABLED);
} catch (Settings.SettingNotFoundException ignored) { }
TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter (":");
if (accessibilityEnabled == 1) {
String settingValue = Settings.Secure.getString (context.getApplicationContext ().getContentResolver (), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
if (settingValue != null) {
colonSplitter.setString (settingValue);
while (colonSplitter.hasNext ()) {
String accessibilityService = colonSplitter.next ();
if (accessibilityService.equalsIgnoreCase (service)) {
return true;
}
}
}
}
return false;
}
which you'll call with:
if (!isAccessibilityOn (context, WhatsappAccessibilityService.class)) {
Intent intent = new Intent (Settings.ACTION_ACCESSIBILITY_SETTINGS);
context.startActivity (intent);
}
This is purely on the technical aspect of the solution.
Now, the ethical question of "should you do that?", I believe the answer is quite clear:
Except if you are targeting people with disabilities (which is the very purpose of the Accessibility API), you should probably NOT do that.
Related
I have a fully functional custom android keyboard in which i have to add speech recognition. Here are the relevant parts of the implementation i have
public class CustomInputMethodService
extends InputMethodService
implements <random stuff> {
private SpeechRecognizer mSpeechRecognizer;
private RecognitionListener mSpeechlistener;
public void onCreate() {
super.onCreate();
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(this);
mSpeechlistener = new CustomRecognitionListener();
mSpeechRecognizer.setRecognitionListener(mSpeechlistener);
}
#Override
public void onPress(int primaryCode) {
if (primaryCode == KeyCodes.VOICE_INPUT) {
mSpeechRecognizer.startListening(getSpeechIntent());
}else if(..){
...
}
}
private Intent getSpeechIntent() {
Intent speechIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
speechIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
speechIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.getPackageName());
speechIntent.putExtra(RecognizerIntent.EXTRA_PARTIAL_RESULTS, false);
return speechIntent;
}
}
The relevant method of the CustomRecognitionListener is simply:
#Override
public void onResults(Bundle results) {
ArrayList<String> matches = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION);
Log.d(TAG, "onResults: ----> " + matches.get(0));
if(matches != null && matches.size() > 0) {
writeText(matches.get(0));
}
}
This code is working just fine. The twist here is that i want a similar behaviour to what happens on google keyboard when the uset taps the microphone key:
This would ideally by achieved by something like:
Intent voiceIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
try {
startActivityForResult(voiceIntent, Constants.RESULT_SPEECH);
} catch (ActivityNotFoundException ex) {
DebugLog.e(TAG, "Not found excpetion onKeyDown: " + ex);
}
However, since the key listener is on and InputMethodService im not able to call startActivityForResult.
What is the ideal way to accomplish this? Should i simply start a new activity without a layout and have a callback to the inputMethodService? seems messy
Your screenshot shows "Google voice typing", which is an independent IME that is called by the Google Keyboard when its microphone button is pressed. So, your IME should do the same: replace itself with an IME that provides voice typing, and hope that there is a backlink to your IME once the voice typing is done.
The simplest implementation would be Switching among IME Subtypes, but you might want to have more control, e.g. start a specific IME with specific input parameters, etc. I'm not sure what is the best/standard way to achieve this extra control.
For an example of a voice typing IME you could look into (my app) Kõnele.
Simple implementation of the solution:
// on mic tap we call
public void startVoiceListening() {
InputMethodManager imeManager = (InputMethodManager) getApplicationContext().getSystemService(INPUT_METHOD_SERVICE);
String voiceExists = voiceExists(imeManager);
if (voiceExists != null) {
final IBinder token = getWindow().getWindow().getAttributes().token;
imeManager.setInputMethod(token,voiceExists);
}
}
private String voiceExists(InputMethodManager imeManager) {
List<InputMethodInfo> list = imeManager.getInputMethodList();
for (InputMethodInfo el : list) {
// do something to check whatever IME we want.
// in this case "com.google.android.googlequicksearchbox"
}
return null;
}
Once we no longer want to use the current IME just close it and it will fall back to the previous one
im making android voice assistant app...that run service in background for recognizing the voice command .
i want to take picture in default system camera app when the user say's the word "selfie".i already know how to work with voice command but the problem is i cant make the camera app take picture ...
i tried some way but wont helped
1st i tried to simulate android camera key event
Intent intent1 = new Intent("android.intent.action.CAMERA_BUTTON");
intent1.putExtra("android.intent.extra.KEY_EVENT", new KeyEvent(0,
KeyEvent.KEYCODE_CAMERA));
sendOrderedBroadcast(intent1, null);
intent1 = new Intent("android.intent.action.CAMERA_BUTTON");
intent1.putExtra("android.intent.extra.KEY_EVENT", new KeyEvent(1,
KeyEvent.KEYCODE_CAMERA));
sendOrderedBroadcast(intent1, null);
this one open camera but wont take picture in phone's without physical camera key
2nd i tried to inject key event "enter" ... like bluetooth remote shutter ...
KeyEvent eventDown = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER);
KeyEvent eventUp = new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_ENTER);
dispatchKeyEvent(eventDown);
dispatchKeyEvent(eventUp);
but in this one i faced 2 problem 1st this code cant be use in service 2nd its impossible to inject event to other app since that only system app could do this
now the question is how can i fix this problem?
is it possible or not?
i read some thing on web that appium could do this but its online & i want my app working off line
note that : adding camera permission & inject event permission wont help and i don't want to use camera api because i want to take pic in default system camera app.
Yes, Its possible After 2 days investigation I find the solution.
Requirement : Open system camera app and click pic.
Step 1:
Add Camera permission in manifest file:
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-feature
android:name="android.hardware.camera.front"
android:required="false" />
Step 2: Create one service which extends AccessibilityService
<service
android:name=".AccessTest"
android:enabled="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/accessibility_service_config"/>
</service>
Step 3: Start service when you required
Intent mailAccessabilityIntent = new Intent(getApplicationContext(), AccessTest.class);
startService(mailAccessabilityIntent);
Step 4: Add accessibility file.
<?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="flagEnableAccessibilityVolume"
android:canRetrieveWindowContent="true"
android:notificationTimeout="100"
android:packageNames="com.google.android.GoogleCamera"
android:settingsActivity="com.mobiliya.cameraautoclick.MainActivity" />
Step 5: Write service class where you want to handle camera related listener.
public class AccessTest extends AccessibilityService {
private final static String TAG = "Yogesh";
#Override
public void onCreate() {
super.onCreate();
Log.d("Yogesh","I am started");
}
#Override
protected void onServiceConnected() {
super.onServiceConnected();
Log.d(TAG, "onServiceConnected");
}
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(TAG, "ACC::onAccessibilityEvent: " + event.getEventType());
//TYPE_WINDOW_STATE_CHANGED == 32
if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event
.getEventType()) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
return;
}
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
String x = takePictureIntent.resolveActivity(getPackageManager()).getPackageName();
Log.d("Yogesh","Package name " + x);
List<AccessibilityNodeInfo> list1 = nodeInfo.findAccessibilityNodeInfosByText("Switch to front camera");
for (AccessibilityNodeInfo node : list1) {
Log.i(TAG, "ACC::onAccessibilityEvent: click " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
final List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("Take photo");
final android.os.Handler handler = new android.os.Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
for (AccessibilityNodeInfo node : list) {
Log.i(TAG, "ACC::onAccessibilityEvent: click " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
handler.postDelayed(this,5000);
}
},10000);
for (AccessibilityNodeInfo node : list) {
Log.i(TAG, "ACC::onAccessibilityEvent: click " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
Log.d(TAG,"Access " + getAllChildNodeText(nodeInfo).toString());
}
}
private List<CharSequence> getAllChildNodeText(AccessibilityNodeInfo infoCompat) {
List<CharSequence> contents = new ArrayList<>();
if (infoCompat == null)
return contents;
if (infoCompat.getContentDescription() != null) {
contents.add(infoCompat.getContentDescription().toString().isEmpty() ? "unlabelled" : infoCompat.getContentDescription());
} else if (infoCompat.getText() != null) {
contents.add(infoCompat.getText().toString().isEmpty() ? "unlabelled" : infoCompat.getText());
} else {
getTextInChildren(infoCompat, contents);
}
if (infoCompat.isClickable()) {
if (infoCompat.getClassName().toString().contains(Button.class.getSimpleName())) {
if (contents.size() == 0) {
contents.add("Unlabelled button");
} else {
contents.add("button");
}
}
contents.add("Double tap to activate");
}
return contents;
}
private void getTextInChildren(AccessibilityNodeInfo nodeInfoCompat, List<CharSequence> contents) {
if (nodeInfoCompat == null)
return;
if (!nodeInfoCompat.isScrollable()) {
if (nodeInfoCompat.getContentDescription() != null) {
contents.add(nodeInfoCompat.getContentDescription());
} else if (nodeInfoCompat.getText() != null) {
contents.add(nodeInfoCompat.getText());
}
if (nodeInfoCompat.getChildCount() > 0) {
for (int i = 0; i < nodeInfoCompat.getChildCount(); i++) {
if (nodeInfoCompat.getChild(i) != null) {
getTextInChildren(nodeInfoCompat.getChild(i), contents);
}
}
}
}
}
#Override
public void onInterrupt() {
}
}
Here getAllChildNodeText() return all text button which is clickable, Google default application have Take Photo text so for this view you can perform action.
Added handler for capture pic every 10 seconds for more clarification.
If you want to track multiple camera app then remove below line and use Java code for set package more details See -Accessibility Service
android:packageNames="com.google.android.GoogleCamera"
I uploaded working example -> https://github.com/ycrathi/cameraautoclick
Note: In above GitHub repo have multiple unwanted code, which I tried.
This solution is not global for all app. You can find some famous app like google camera and find text and then perform click action package wise.
you can use 3rd-party "fake camera" apps such as:
Image2Camera
Fake Camera by New Horizon Apps
Fake Camera - donate version by Vaclav Balak
alternatively you can use:
ICS emulator - that supports camera
alternatively you can use:
In your AVD advanced settings, you should be able to set front and back cameras to Webcam() or Emulated
I am working from the Dungeons example that the Android provides, and it feels like I almost got it correctly, but I can't figure out how to detect if the person pressed the back button and backed out of the purchase. Here is what I have so far:
Button buy = (Button)findViewById(R.id.buy);
buy.setOnClickListener(new Button.OnClickListener()
{
public void onClick(View v)
{
try
{
if (mBillingService.requestPurchase(issueProductId,
Consts.ITEM_TYPE_INAPP , null))
{
// Not sure what to do here. Has the person been sent to the buy screen yet or no?
// There are these strings I need to check for but not entirely certain how:
if(mBillingService.checkBillingSupported(Consts.ITEM_TYPE_INAPP))
{
// OK
}
else
{
// If billing is not supported, give them the item for free
Intent myIntent = new Intent(ExtraHelpActivity.this, PsychologyActivity.class);
ExtraHelpActivity.this.startActivity(myIntent);
}
try
{
if (mBillingService.requestPurchase(issueProductIdPsych,
Consts.ITEM_TYPE_INAPP , null))
{
// HOW DO I CHECK HERE IF THE USER CANCELED OUT OF THE PURCHASE?
// EVEN ON CANCEL, THEY ARE BEING TAKEN TO THE ITEM THAT THEY NEED TO PAY FOR.
// Send them to the screen of the article.
Intent myIntent = new Intent(ExtraHelpActivity.this, PsychologyActivity.class);
ExtraHelpActivity.this.startActivity(myIntent);
}
}
catch ( Exception e )
{
// Report exception.
}
});
I added some inline comments where I am confused. I am not certain how to read whether the person purchased the item, or cancelled. If someone could help me with this, that would be great.
Right now this code takes the person to the buy screen, and some people buy, but when people press cancel, they still get taken to the item which they didn't buy. :)
EDIT:
By the way, if users already bought the item and want to come back to it, the purchase request state does not change, right? So what method ends up being triggered? I tested with one purchase and I was able to buy access to one screen with an article, but now can not get to it again since the payment screen keeps telling me I already bought that item, and not taking me to the next page.
Here is how my new onStateChanged method looks like:
#Override
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload)
{
if (purchaseState == PurchaseState.PURCHASED)
{
mOwnedItems.add(itemId);
if ( itemId != null && itemId.trim().equals("3") )
{
Intent myIntent = new Intent(ExtraHelpActivity.this, PsychologyActivity.class);
ExtraHelpActivity.this.startActivity(myIntent);
}
if ( itemId != null && itemId.trim().equals("4") )
{
Intent myIntent = new Intent(ExtraHelpActivity.this, NumberOfBusinessesActivity.class);
}
}
else
if (purchaseState == PurchaseState.CANCELED)
{
// purchase canceled
}
else
if (purchaseState == PurchaseState.REFUNDED)
{
// user ask for a refund
}
else
{
if ( itemId != null && itemId.equals("3") )
{
Intent myIntent = new Intent(ExtraHelpActivity.this, PsychologyActivity.class);
ExtraHelpActivity.this.startActivity(myIntent);
}
if ( itemId != null && itemId.equals("4") )
{
Intent myIntent = new Intent(ExtraHelpActivity.this, NumberOfBusinessesActivity.class);
ExtraHelpActivity.this.startActivity(myIntent);
}
}
}
and when I tested it and bought an article, it did get here to the if (purchaseState == PurchaseState.PURCHASED) but not the individual if cases inside it even though the itemId id is "3" or "4"
EDIT:
and this is how I declare the product ids at the beginning of the Activity:
String issueProductIdWebMarketing = "1";
String issueProductIdDonate = "2";
String issueProductIdPsych = "3";
String issueProductIdNumberofBusinesses = "4";
Thanks!!
According to the billing integration guide:
when the requested transaction changes state (for example, the purchase is successfully charged to a credit
card or the user cancels the purchase), the Google Play application sends an IN_APP_NOTIFY broadcast intent.
This message contains a notification ID, which you can use to retrieve the transaction details for the
REQUEST_PURCHASE request.
And the intent com.android.vending.billing.PURCHASE_STATE_CHANGED contains purchaseState
The purchase state of the order. Possible values are 0 (purchased), 1 (canceled), 2 (refunded), or 3
(expired, for subscription purchases only).
source (table 4)
EDIT:
In the dungeons sample, there is an inner class extending PurchaseObserver (see Dungeons activity) that does the job, see especially (onPurchaseStateChange)
EDIT2: some pseudo-code
in the activity (Dungeons class in the sdk sample):
Button buy = (Button)findViewById(R.id.buy);
buy.setEnabled(false);
buy.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v)
{
// show purchase dialog and call mBillingService.requestPurchase() on dialog validation
}
});
// request to see if supported
if (!mBillingService.checkBillingSupported()) {
// not supported
}
in the observer (DungeonsPurchaseObserver in the sample):
public void onBillingSupported(boolean supported, String type) {
if (type == null || type.equals(Consts.ITEM_TYPE_INAPP)) {
if (supported) {
// billing supported: enable buy button
} else {
// billing not supported: disable buy button
}
}
}
public void onPurchaseStateChange(PurchaseState purchaseState, String itemId,
int quantity, long purchaseTime, String developerPayload) {
if (purchaseState == PurchaseState.PURCHASED) {
// item purchased
} else if (purchaseState == PurchaseState.CANCELED) {
// purchase canceled
} else if (purchaseState == PurchaseState.REFUND) {
// user ask for a refund
}
}
The problem is that the call mBillingService.requestPurchase is not synchronous. So you can not check right after if the purchase was successful or not.
I recommend you use the library AndroidBillingLibrary. With it you can easily get all the Google Play's events. For that you can use onRequestPurchaseResponse() and onPurchaseStateChanged() from the helpers AbstractBillingActivity or AbstractBillingFragment.
As I understood this is possible, from here
Detecting toast messages
But I am unable to catch any event with code snippet from the link.
MyAccessibilityService.java
package com.test.toasts2;
import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Notification;
import android.os.Parcelable;
import android.view.accessibility.AccessibilityEvent;
import android.widget.Toast;
public class MyAccessibilityService extends AccessibilityService {
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
System.out.println("event catched");
Toast.makeText(this, "catched " + "!", Toast.LENGTH_SHORT).show();
if(event.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)
return; // event is not a notification
String sourcePackageName = (String)event.getPackageName();
Parcelable parcelable = event.getParcelableData();
if(parcelable instanceof Notification){
// Statusbar Notification
}
else{
// something else, e.g. a Toast message
String log = "Message: "+event.getText().get(0)+" [Source: "+sourcePackageName+"]";
System.out.println(log);
// write `log` to file...
}
}
#Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
#Override
protected void onServiceConnected() {
// TODO Auto-generated method stub
super.onServiceConnected();
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.feedbackType = AccessibilityServiceInfo.DEFAULT;
setServiceInfo(info);
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.toasts2"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="15" />
<application>
<service android:name=".MyAccessibilityService"
android:label="label">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
</application>
</manifest>
Seems like this service is simply not started. What I am doing wrong?
Why I am doing this:
I am installing many shortcuts on the stock launcher from my app. I am having the problem that theese shortcuts are placed one over another in one cell (even Sleep 500 did not help). So I am finding a way to install them one by another. But how to know when shortcut was successfully installed? I have found only a message that ics launcher shows to user.
TYPE_NOTIFICATION_STATE_CHANGED generally refers to NotificationManager and icons placed in the status bar. Nonetheless, the below code should help shed some light as to the origin of a Toast message. On Android 4.0.4 ICS a Toast has a class of android.widget.Toast so getClassName should do the trick.
For what its worth, the change seems to have been made in Android 4.0.3 to add and use the following method in Toast.TN
private void trySendAccessibilityEvent() {
AccessibilityManager accessibilityManager =
AccessibilityManager.getInstance(mView.getContext());
if (!accessibilityManager.isEnabled()) {
return;
}
// treat toasts as notifications since they are used to
// announce a transient piece of information to the user
AccessibilityEvent event = AccessibilityEvent.obtain(
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED);
event.setClassName(getClass().getName());
event.setPackageName(mView.getContext().getPackageName());
mView.dispatchPopulateAccessibilityEvent(event);
accessibilityManager.sendAccessibilityEvent(event);
}
You can see the Toast class in all versions of Android here.
private final String getEventType(AccessibilityEvent event) {
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:
return "TYPE_NOTIFICATION_STATE_CHANGED";
case AccessibilityEvent.TYPE_VIEW_CLICKED:
return "TYPE_VIEW_CLICKED";
case AccessibilityEvent.TYPE_VIEW_FOCUSED:
return "TYPE_VIEW_FOCUSED";
case AccessibilityEvent.TYPE_VIEW_LONG_CLICKED:
return "TYPE_VIEW_LONG_CLICKED";
case AccessibilityEvent.TYPE_VIEW_SELECTED:
return "TYPE_VIEW_SELECTED";
case AccessibilityEvent.TYPE_VIEW_SCROLLED:
return "TYPE_VIEW_SCROLLED";
case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
return "TYPE_VIEW_HOVER_EXIT";
case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
return "TYPE_VIEW_HOVER_ENTER";
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
return "TYPE_TOUCH_EXPLORATION_GESTURE_START";
case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
return "TYPE_TOUCH_EXPLORATION_GESTURE_END";
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
return "TYPE_WINDOW_STATE_CHANGED";
case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
return "TYPE_WINDOW_CONTENT_CHANGED";
case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
return "TYPE_VIEW_TEXT_SELECTION_CHANGED";
case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
return "TYPE_VIEW_TEXT_CHANGED";
}
return "default";
}
private final String getEventText(AccessibilityEvent event) {
StringBuilder sb = new StringBuilder();
for (CharSequence s : event.getText()) {
sb.append(s);
sb.append('\n');
}
return sb.toString();
}
#Override
public void onAccessibilityEvent(AccessibilityEvent event)
{
Log.v(TAG, String.format(
"onAccessibilityEvent: [type] %s [class] %s [package] %s [time]
%s [fullscreen] %s [text] %s", getEventType(event), event.getClassName(),
event.getPackageName(), event.getEventTime(), Boolean.toString(
event.isFullScreen()), getEventText(event)));
if (android.os.Build.VERSION.SDK_INT >= 14)
Log.v(TAG, "Window ID: " + Integer.toString(event.getWindowId()) + ".");
}
private void setServiceInfo(int feedbackType)
{
final AccessibilityServiceInfo info = new AccessibilityServiceInfo();
// We are interested in all types of accessibility events.
info.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;
// We want to provide specific type of feedback.
info.feedbackType = feedbackType;
// We want to receive events in a certain interval.
// info.notificationTimeout = EVENT_NOTIFICATION_TIMEOUT_MILLIS;
// We want to receive accessibility events only from certain packages.
// info.packageNames = PACKAGE_NAMES;
setServiceInfo(info);
}
private boolean isInfrastructureInitialized = false;
#Override
public void onServiceConnected()
{
if (isInfrastructureInitialized) return;
// Claim the events with which to listen to.
setServiceInfo(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
// We are in an initialized state now.
isInfrastructureInitialized = true;
}
Source: personal experience.
create an Activity that will build an intent and use it to start your service.
something like this will be the code inside the activity.
Intent i = new Intent(YourActivity.this, MyAccessibilityService.class);
startService(i);
In your manifest make the intent filter for the activity have the MAIN and LAUNCHER in it so that it will the Activity that is run when the user (or adb) starts your app. The activity will then start your service for you.
EDIT: I assume you saw this note from the post that you linked. And that you aren't trying this on 2.2?
Note: This didn't work for me on Android 2.2 (it doesn't seem to catch Toasts), but it worked on Android 4.0.
First of all you shouldn't be trying to catch the Toast as this is an Asynchronous call that bring up a Toast on the screen and it will stay on the screen depending on the Timing, it is possible to leave the application and still have the toast appear. You should NOT care when the toast is done as this is irrelevant. All a Toast should be used for is JUST for giving the User info about a particular process that is underway/done or just Information. Its not meant for what you're trying to do.
Why don't you just send an internal broadcast into your application and catch it via an Intent Filter and then upon receiving that broadcast, you should start the service.
I hope that this question will not have the same luck with this one.
I want to give my users the ability to close some applications which are in the background via a list. By applications in the background I mean applications that the user started and then press the home button, for example Internet Browser. I have managed to find the background applications via the ActivityManager (both getRunningAppProcesses and getRunningTasks will do the job). However there are some applications in the background which are important for the system to work (i.e. Phone, Launcher, Input Methods etc) and I don't want them in my list.
My question is: How can I distinguish them from the non Important ones. I don't want to use some kind of String checking / filtering (like contains(com.android) etc) because I want to exclude some Important 3rd party apps like non stock Launchers, Dialer, Messaging etc.
Everyone who owns a Galaxy S2 and have used the "Program Monitor Widget" will know what i mean.
Thank you very much for your time and efforts...
Well I finally came up with a function that checks if a running task may considered as an "Important" one. First of all we have to use the getRunningTasks function from the ActivityManager to get a list filled with ActivityManager.RunningTaskInfo objects and then we pass each of these objects to the following function to do the check. Hope this helps someone...
public boolean isRunningTaskImportant(ActivityManager.RunningTaskInfo taskinfo) {
//What makes a running task important is somehow "fluid" but there are a few task categories
//on which is safe to assume that they are important. These categories are:
//1. If task has not any actual activities running. This is because these are not actuall running but they are frozen by the
// the system and they will be killed if needed. They are not Important but we do not want them in our running apps list.
//2. Well known namespaces including our own if we want to
//3. Home Launcher Applications
//4. Phone Handling Applications
boolean result = false;
ComponentName bActivity = taskinfo.baseActivity;
if (bActivity == null) return false; //<-- The task has no base activity so we ignore it...
String pName = bActivity.getPackageName();
if (taskinfo.numRunning == 0) {
result = true;
} else {
if (pName.equalsIgnoreCase("com.android.phone")) {
result = true;
} else if (pName.equalsIgnoreCase("com.android.contacts")) {
result = true;
} else if (pName.equalsIgnoreCase("com.chdcomputers.powerpanel")) {
result = true;
} else {
//Here we are checking if out task is a home launcher application.
//This code is based on this question: http://stackoverflow.com/questions/3293253/getting-list-of-installed-apps-easy-but-how-to-launch-one-of-them
Log.d(TAG, "isRunningTaskImportant checking for launchers");
Intent launchersIntent = new Intent(Intent.ACTION_MAIN, null);
launchersIntent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> list = cx.getPackageManager().queryIntentActivities(launchersIntent,0);
boolean found = false;
for (ResolveInfo ri : list){
if (!found){
Log.d(TAG, "isRunningTaskImportant checking launcher app: " + ri.activityInfo.applicationInfo.packageName);
found = pName.equalsIgnoreCase(ri.activityInfo.applicationInfo.packageName);
}
if (found) break;
}
result = found;
if (!found) {
//Finaly we are going to check if out task is a Phone Handling application
//The idea behind that is to check for what kind of permissions the application wants
//In my opinion any "serious" phone handling app should ask for, at least, the following 9 permissions:
//CALL_PHONE, CALL_PRIVILEGED, READ_CONTACTS, WRITE_CONTACTS, SYSTEM_ALERT_WINDOW, READ_PHONE_STATE,
//MODIFY_PHONE_STATE, PROCESS_OUTGOING_CALLS, RECEIVE_BOOT_COMPLETED
//So if the application asks for those permissions we can assume that is a phone handling application...
Log.d(TAG, "isRunningTaskImportant checking possible phone app: " + pName);
try {
PackageInfo pi = cx.getPackageManager().getPackageInfo(pName,PackageManager.GET_PERMISSIONS);
String[] perms = pi.requestedPermissions;
if (perms == null) {
result = false;
} else {
int pCount = 0;
for (String perm : perms) {
if (perm.equalsIgnoreCase("android.permission.CALL_PHONE")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.CALL_PRIVILEGED")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.READ_CONTACTS")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.WRITE_CONTACTS")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.SYSTEM_ALERT_WINDOW")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.READ_PHONE_STATE")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.MODIFY_PHONE_STATE")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.PROCESS_OUTGOING_CALLS")) {
pCount++;
} else if (perm.equalsIgnoreCase("android.permission.RECEIVE_BOOT_COMPLETED")) {
pCount++;
}
}
result = (pCount == 9);
}
} catch (Exception ex) {
Log.e(TAG, "isRunningTaskImportant checking possible phone app ERROR: " + ex.getMessage());
result = false;
}
}
}
}
return result;
}