How to change Android Notification using Accessibility API - android

I am able to intercept all notifications using an Accessablity service. This block is where the events are seen :
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.d(tag, "Inside onAccessibilityEvent");
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED){
SqlDb db = new SqlDb(this);
NotificationObject no = new NotificationObject();
no.setNoficationPackage(String.valueOf(event.getPackageName()));
no.setNotificationText(String.valueOf(event.getText().toString()));
no.setNotificationDTM(new Date());
db.addNotification(no);
Log.d(tag, "Saved event");
}
}
What I want to do is to change the notification so it is not considered a missed call event. Is this possible to do on OS 4.0+?
Thanks.

Another application's notification is READ-ONLY.
So, code like "notification.a = b; "cause permission problem.
public void onAccessibilityEvent(AccessibilityEvent event) {
// TODO Auto-generated method stub
if (event.getEventType() == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
//Do something, eg getting packagename
final String packagename = String.valueOf(event.getPackageName());
final String text = String.valueOf(event.getText());
if(TARGET_PACKAGE.equals(packagename)){
Notification n = (Notification) event.getParcelableData();
}
}

Related

Click on the Notification programmatically

I trying click on the notification after receiving it.
I'm able to drag the notification drawer using the Accessibility service.
For clicking the notification I'm using accessibilityEvent.getSource() and
accessibilityNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
My code:
public class MyAccessibilityService extends AccessibilityService {
/**
* On receiving the AccessibilityEvent performs the Actions
*
* #param event
*/
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
Log.i(TAG, "Get the Accessibility Event");
if (event.getEventType() == AccessibilityEvent.TYPE_TOUCH_INTERACTION_END) {
handleTypeTouchInteractionEndEvents(event);
}
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
handleTypeWindowStateChangedEvents(event);
}
}
#Override
public void onServiceConnected() {
AccessibilityServiceInfo accessibilityServiceInfo = new AccessibilityServiceInfo();
accessibilityServiceInfo.eventTypes = AccessibilityEvent.TYPE_TOUCH_INTERACTION_END | AccessibilityEvent.TYPE_VIEW_CLICKED |
AccessibilityEvent.TYPE_VIEW_FOCUSED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
accessibilityServiceInfo.feedbackType = AccessibilityServiceInfo.CAPABILITY_CAN_PERFORM_GESTURES;
accessibilityServiceInfo.flags = AccessibilityServiceInfo.DEFAULT | AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
accessibilityServiceInfo.notificationTimeout = NOTIFICATION_TIME_OUT;
this.setServiceInfo(accessibilityServiceInfo);
}
#Override
public void onInterrupt() {
//Do Nothing
}
/**
* Performs the action for TYPE_TOUCH_INTERACTION_END events
*
* #param accessibilityEvent
*/
private void handleTypeTouchInteractionEndEvents(AccessibilityEvent accessibilityEvent) {
switch (accessibilityEvent.getAction()) {
case EXPAND_NOTIFICATIONS_DRAWER:
Log.i(TAG, "perfroming expand notification bar"); performGlobalAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);
break;
default:
Log.i(TAG, "Event type not defined");
}
}
private void handleTypeWindowStateChangedEvents(AccessibilityEvent accessibilityEvent) {
switch (accessibilityEvent.getAction()) {
case ACTION_CLICK:
Log.i(TAG, "Performing click Action");
findNotificationIconAndClick("com.my.app:id/notification_icon", accessibilityEvent);
Log.i(TAG, "Click Action is successfull");
default:
Log.i(TAG, "Event type not defined");
}
}
public void findNotificationIconAndClick(String id, AccessibilityEvent accessibilityEvent) {
AccessibilityNodeInfo accessibilityNodeInfo = accessibilityEvent.getSource();
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null) {
performClick(nodeInfo);
break;
}
}
}
}
}
I'm new to using accessibility service. Can someone tell me if there is any mistake from my side or if it is not possible to click on a notification using accessibility service? Are there any other possibilities without using Accessibility Service to click on the Notification?
If you want to click on the notification, you have to extend the NotificationListenerService, implement what have to be implemented then you can call sbn.getNotification().contentIntent.send(). This way is like if user was clicking on the notification from the notification tray.
Not all accessibility events will return a source. In fact, most (or at least the events that occur most frequently) do not. Make sure you're limiting to a reasonable subset of events in your configuration AND/OR doing a null check on event.getSource().

Force stop android applications

After opening application details settings using
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS), how can I force stop application programmatically?
You can use Accessibility to achieve that (but it needs Accessibility for your app turned on by user)
public class MyAccessibilityService extends AccessibilityService {
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
//TYPE_WINDOW_STATE_CHANGED == 32
if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event
.getEventType()) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> list = nodeInfo
.findAccessibilityNodeInfosByViewId("com.android.settings:id/left_button");
//We can find button using button name or button id
for (AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
list = nodeInfo
.findAccessibilityNodeInfosByViewId("android:id/button1");
for (AccessibilityNodeInfo node : list) {
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
}
#Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
}
You can check it out in this example:
AccessibilityTestService.java
You have two ways, a more rude one and a better one
The good practice
If you have only one activity running
the this.finish(); method will be enough
If you have multiple activities running
You gotta call the this.finishAffinity(); method. This is the best practice in general cases, where you can have both a single or multiple activities
The rude way
System.Exit(0);
I added this only for info, but this might not work with multiple activities and this is not a good way for closing apps. It's mostly like the "Hold power button until the pc shuts down".
Clicking an element of another application on runtime is something that will be considered as a security threat. You would need a hack to go past this hurdle.
There is one hack that I recently found out, you can probably make use of it. You can find the source code here: https://github.com/tfKamran/android-ui-automator
You can add the code in here as a module in your app and invoke a service with action com.tf.uiautomator.ACTION_CLICK_ITEM and send the text of the element you want to click on as an extra with key itemText.
You can test it using adb like:
adb shell am startservice -a com.tf.uiautomator.ACTION_CLICK_ITEM -e itemText "OK"
I found one solution for force stop. After force stop how can i go back to my activity page ?
public class DeviceAccessibilityService extends AccessibilityService {
private static final String TAG = "litan";
private boolean isKilled = false;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
isKilled = false;
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED == event.getEventType()) {
AccessibilityNodeInfo nodeInfo = event.getSource();
Log.i(TAG, "ACC::onAccessibilityEvent: nodeInfo=" + nodeInfo);
if (nodeInfo == null) {
return;
}
List<AccessibilityNodeInfo> list = new ArrayList<>();
if ("com.android.settings.applications.InstalledAppDetailsTop".equals(event.getClassName())) {
if (Build.VERSION.SDK_INT >= 18) {
list = nodeInfo.findAccessibilityNodeInfosByViewId("com.android.settings:id/right_button");
} else if (Build.VERSION.SDK_INT >= 14) {
list = nodeInfo.findAccessibilityNodeInfosByText("com.android.settings:id/right_button");
}
for (AccessibilityNodeInfo node : list) {
Log.i(TAG, "ACC::onAccessibilityEvent: left_button " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
} else if ("android.app.AlertDialog".equals(event.getClassName())) {
list = new ArrayList<>();
if (Build.VERSION.SDK_INT >= 18) {
list = nodeInfo.findAccessibilityNodeInfosByViewId("android:id/button1");
} else if (Build.VERSION.SDK_INT >= 14) {
list = nodeInfo.findAccessibilityNodeInfosByText("android:id/button1");
}
for (final AccessibilityNodeInfo node : list) {
Log.i(TAG, "ACC::onAccessibilityEvent: button1 " + node);
node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
//node.performAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
return;
}
}
#Override
public void onInterrupt() {
// TODO Auto-generated method stub
Log.i("Interrupt", "Interrupt");
}
#Override
protected void onServiceConnected() {
AccessibilityServiceInfo info = getServiceInfo();
info.eventTypes = AccessibilityEvent.TYPE_WINDOWS_CHANGED | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED;
info.flags = AccessibilityServiceInfo.DEFAULT;
info.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
info.flags = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
info.flags = AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
info.flags = AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
// We are keeping the timeout to 0 as we don’t need any delay or to pause our accessibility events
info.feedbackType = AccessibilityEvent.TYPES_ALL_MASK;
info.notificationTimeout = 100;
this.setServiceInfo(info);
// Toast.makeText(getApplicationContext(), "onServiceConnected", Toast.LENGTH_SHORT).show();
}
private static void logd(String msg) {
Log.d(TAG, msg);
}
private static void logw(String msg) {
Log.w(TAG, msg);
}
private static void logi(String msg) {
Log.i(TAG, msg);
}
}

Service OnStartCommand calls again while Kill the process

I'm developing an app that has a recurring task of sending call to the server and check whether the data is available for current date. I have implemented this successfully but I have an issue, when I kill the Process it's call the service again. I have monitored this through Logcat or it generate the PUSH notification too even the flag is not false (According to my logic). Why because I'm using *START_STICKY*?
I have used START_REDELIVER_INTENT flag too, it solved that issue but in the TaskManager of my cellphone my service status is on RESTART when I killed the process. Does the service is still running or it is in RESTART state?
I want my service run 24/7 without RESTART. either I kill the process like (weChat, Whatsapp, Viber) etc.
Can someone please sort out what I'm doing wrong? or give me the right direction. Help should be appreciated. Thanks
Service:
public int onStartCommand(Intent intent, int flags, final int startId) {
// Log.e("LocalService", "Received start id " + startId + ": " + intent);
Log.e("AlertService", "Alert-----Service Created");
// TODO Auto-generated method stub
ScheduledExecutorService scheduler =
Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate
(new Runnable() {
public void run() {
// call service
DateFormat df = new SimpleDateFormat("dd-MM-yyyy");
currentDate = df.format(Calendar.getInstance().getTime());
String[] separated_currentDate = currentDate.split("-");
int actual_date = Integer.parseInt(separated_currentDate[0]);
Log.e("CurrentDate of my Device", currentDate);
Log.e("ad",""+actual_date);
if(flag == false)
{
Log.e("Flag-Status", "FALSE");
try {
savingDateinPref(currentDate);
isInternetPresent = cm.isConnected();
if(isInternetPresent)
{
Log.e("Flag-Status", "Calling Service");
new DoInBackground().execute(currentDate);
// currentDate = null;
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
String val = prefs.getString("TAG_KEY", "defvalue");
String[] separated_val = val.split("-");
int pref_date = Integer.parseInt(separated_val[0]);
Log.e("pd", "" +pref_date);
if( (pref_date != actual_date) || ( pref_date < actual_date) || (pref_date > actual_date) )
{
flag = false;
Log.e("Flag-Status", "FALSE/bcoz date has been changed");
// String empty = null;
// savingDateinPref(empty);
}
}
}, 0, 5, TimeUnit.SECONDS);
return START_REDELIVER_INTENT;
}

Android, prevent to kill service/thread

how can i prevent this service with thread to dont be killed from android, i need this notifications always runnig, but when is mobile locked, nothing will happen. I think android kill service or thread or something like that
MainActivity in onCreate
startService(new Intent(this, NotifyService.class));
My service
public class NotifyService extends Service {
private DatabaseOp mDbHelper;
public Vibrator vibrator;
String username;
#Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
#Override
public void onCreate ()
{
mDbHelper = new DatabaseOp(this);
final boolean cyklus = true;
Thread vlakno = new Thread (new Runnable()
{
#Override
public void run()
{
while (cyklus)
{
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
String sysDate = getSysDate();
String sysDate2 = getSysDate2();
String time = getSysTime();
mDbHelper.open();
Log.v( "sysDate", sysDate );
Cursor cursorU = mDbHelper.fetchUlohaS(0, sysDate);
if (cursorU.getCount() > 0)
{
String idU = cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_ID));
String dbDateU = cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_DATE));
String menoU = cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_NAZOV));
String mHodina = getResources().getString(R.string.cas)+" "+cursorU.getString(cursorU.getColumnIndexOrThrow(DatabaseOp.KEY_HODINA));
Log.v( "task", dbDateU+"/"+sysDate );
if (dbDateU.equals(sysDate))
{
Notify(menoU, mHodina, idU, 0);
}
}
Cursor cursorS = mDbHelper.fetchSviatokS(3, sysDate2);
if (cursorS.getCount() > 0)
{
String idS = cursorS.getString(cursorS.getColumnIndexOrThrow(DatabaseOp.KEY_ID));
String dbDateS = cursorS.getString(cursorS.getColumnIndexOrThrow(DatabaseOp.KEY_DATUM));
String menoS = cursorS.getString(cursorS.getColumnIndexOrThrow(DatabaseOp.KEY_NAZOV));
if (dbDateS.equals(sysDate2) && time.equals("09:00"))
{
Notify(menoS,getResources().getString(R.string.title_section4), idS, 3);
}
}
mDbHelper.close();
}
}
});
vlakno.start();
}
}
Have you tried to use ForgroundService?
Checkout this repo for an example - https://github.com/supercurio/foreground-service-sample-app
I think you should consider AlarmManager. See http://developer.android.com/reference/android/app/AlarmManager.html.
To tip the system to keep your Service alive as long as possible (i.e. before RAM is very short or user kills the service by hand through application info screen), you need to run it as a foreground service -- by using startForeground() method.
If you're looking for a way to run when the device is turned off, read the Keeping The Device Awake training page and consider using AlarmManager instead as suggested by #khris if your task is not very critical in terms of timing precision.

Launching an intent without context

What is the best way to deal with the following situation:
I have a IntentService which does synchronisation with the server (this is triggered by either an Activity coming to the foreground, or a GCM message, so onoy occasional). Sometimes there is a user action needed as a result, and the given command/request is part of the response XML.
There are basically two options, it is either a yes/no question, or a full Activity to for example select the desired language.
How can I do this, or what would be the best way? If I try to launch the Activity with the context of the IntentService nothing happens. I could write a abstract Activity, which I extends in all my Activities and sent a broadcast message which those receive and subsequent start the Activity form the activity which is active, but don't know if that is the best way to do it in Android.
Any suggestions would be appreciated!
[EDIT: as suggested some code]
public class SyncService extends IntentService{
public SyncService(){
super("SyncService");
}
#Override
protected void onHandleIntent(Intent intent) {
iDomsAndroidApp app = ((iDomsAndroidApp) getApplicationContext());
DataManager manager = app.getDataManager();
manager.updateData(this);
}
}
public class DataManager {
// For brevity, this is called with the DOM.Document with the actions to be preformed
private void checkForActions(Document doc, SyncUpdateInterface syncInterface){
NodeList objects = null;
NodeList rootNodes = doc.getElementsByTagName("actions");
for (int j = 0; j < rootNodes.getLength(); j++) {
Element rootElement = (Element) rootNodes.item(j);
if (!rootElement.getParentNode().getNodeName().equals("iDoms")) {
continue;
}
objects = ((Element) rootNodes.item(j)).getElementsByTagName("action");
break;
}
if(objects == null || objects.getLength() == 0){
Log.d(iDomsAndroidApp.TAG, "No actions");
return;
}
for (int j = 0; j < objects.getLength(); j++) {
Element element = (Element) objects.item(j);
String action = ((Element) element.getElementsByTagName("command").item(0)).getTextContent();
if(action == null) return;
Log.d(iDomsAndroidApp.TAG, "Action: " + action);
try{
if(action.equalsIgnoreCase("selectLanguage")){
if(syncInterface == null || syncInterface.getContext() == null) throw new Exception("No context, so cannot perform action");
iDomsAndroidApp app = ((iDomsAndroidApp) iDomsAndroidApp.getAppContext());
// The app.actionIntent is just a central function to pick the right intent for an action.
syncInterface.getContext().startActivity(app.actionIntent("settings", iDomsAndroidApp.context));
} else if (action.equalsIgnoreCase("markAllAsRead")) {
if(syncInterface == null | syncInterface.getContext() == null) throw new Exception("No context, so cannot perform action");
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(syncInterface.getContext());
alertDialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
// User clicked OK, so save the result somewhere
// or return them to the component that opened the dialog
iDomsAndroidApp app = ((iDomsAndroidApp) iDomsAndroidApp.getAppContext());
app.getDataManager().markAllAsRead(null);
}
});
alertDialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int id) {
}
});
alertDialogBuilder.setTitle(iDomsAndroidApp.context.getString(R.string.markAllAsRead));
alertDialogBuilder.setMessage(iDomsAndroidApp.context.getString(R.string.markAllAsReadText));
alertDialogBuilder.show();
}
} catch (Exception e){
Log.w(iDomsAndroidApp.TAG, "Problem performing the action " + element.getTextContent(), e);
sentCrashReport("Problem performing the action " + element.getTextContent(), e);
}
}
}
I tried using the my SyncInterface, as it gives the context of the IntentService, but think it is a but clumsy and doesn't work:
public interface SyncUpdateInterface {
public void doProgress(String message, int increment, int total);
public void doProgress(String message, int increment);
public void doProgress(String message);
public Context getContext();
}
You might have to rethink your approach. The intentservice only lives for the duration of the onHandleIntent() method. That is to say, once the last line of code of the onHandleIntent() method is reached, the IntentService stops itself.
Try EventBus. It provides a solution to similar problems by making communication between components (Activities, Services, Standalone classes) of an application.
Use Gradle to import library
compile 'org.greenrobot:eventbus:3.1.1'
Define an event
public class MessageEvent { /* Additional fields if needed */ }
Launch Event using
EventBus.getDefault().post(new MessageEvent());
Register component to receive event
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
Receive launched even by declaring this method
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
For more information visit https://github.com/greenrobot/EventBus

Categories

Resources