AccessibilityService - performGlobalAction not working in own app - android

I'm trying to send a system back press event via the AccessibilityService and this works fine, but only if I'm not in my own app.
I'm always getting true from performGlobalAction no matter if I'm in my own app or not, but I only see that the event really is executed if I'm not in my own app but in any other one (in the sense of that the previous activity is shown or similar)
Any ideas why this happens? My app is a sidebar app with an overlay drawn on top in the WindowManager and everything is working (AccessibilityService is running and is handling my custom events and the service always returns success messages for my events, but my own app does not react to the back button event).
My service looks like following:
public class MyAccessibilityService extends AccessibilityService {
public static void sendBackIntent(Context context) {
Intent intent = new Intent(context, MyAccessibilityService.class);
intent.putExtra("action", GLOBAL_ACTION_BACK);
context.startService(intent);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Bundle extras = intent.getExtras();
Integer action = null;
if (extras != null) {
action = extras.getInt("action");
}
if (action != null) {
switch (action) {
case GLOBAL_ACTION_BACK:
boolean result = performGlobalAction(action);
L.d("Action %d executed: %b", action, result);
break;
default:
L.e("Unhandled action %d", action);
break;
}
}
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
#Override
public void onInterrupt() {
}
}
Edit
To make this clear:
I do NOT start this service via MyAccessibilityService.sendBackIntent(context), I send the intent like following: if (isAccessibilityserviceRunning) MyAccessibilityService.sendBackIntent(context)
I start my service via the system service menu by simply enabling it there and let the system start it automatically afterwards
I've setup everything for the AccessibilityService in an accessibilityservice.xml and use this to define my services settings and this is working perfectly fine as well, all events I want to receive are received reliably and correct
EDIT 2
Seems like in my case my overlay is still stealing the focus making it focusable and not has timing problems that sometimes make problems. Still, my solution can be improved by using BroadcastReceiver to communicate with the service, as the startService call is not safe as discussed in the accepted answer

It strikes me that you're doing some very strange things. It appears that you're treating your AccessibilityService as a normal Service. The part of this that suggests this is your implementation of the following to methods:
public static void sendBackIntent(Context context);
#Override
public int onStartCommand(Intent intent, int flags, int startId);
Just by the signatures of these two methods and your calling of
context.startService(intent);
Within your static method, I can tell that you don't understand AccessibilityServices and how they are supposed to perform their jobs. You cannot start your accessibility service, nor interact with it, in the way that you are attempting. Certainly you can use Accessibility Services to perform global actions, but they won't do so accurately and globally, unless you launch them correctly, from the Accessibility Services menu (you know the one where TalkBack shows up).
Your code essentially, isn't running within the Context you think it's running in. So, it runs, and does things. But, AccessibilityServices and their respective power, is in their ability to attach globally to the Operating System. The android API's won't bind an AccessibilityService properly, when you attempt to launch your service with:
context.startService(intent);
You have to launch your Accessibility Service from the Accessibility Services Settings menu.
Even if your service is already launched such a call is unsafe! There's no guarantee your users are going to start the service prior to opening your Activity. Once you have called context.startService and attempted to start your AccessibilityService in this way, it will prevent the Accessibility Settings Menu from starting your service and binding to the OS properly. In fact, once in this situation a user would have to: Turn off the Switch for your service in the Accessibility Settings Menu, force stop (perhaps even uninstall) your application, restart their device, start your service and THEN start your activity, in order for the proper behavior to be achieved.
If you don't do so, it will not bind to the OS properly and its behavior is undefined. Right now, you've essentially created a hack in the OS and are running up against said undefined behavior, that could vary WIDELY across version, manufacturer, etc, because it's behavior isn't covered in the AOSP integration tests.
In fact, you explicitly CANNOT launch Accessibility Services using the context.startService() call. This is a very important security feature of Android, as Accessibility Services can gain access to screen content, and users need fine grain control over the providers and applications they allow this access. So, while you may be getting SOME behavior, it is undefined and dangerous behavior. What you want is something like the following:
With the following service config XML:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="#string/accessibility_service_description"
android:accessibilityEventTypes="typeWindowContentChanged"
android:accessibilityFlags="flagRequestTouchExplorationMode"
android:canRetrieveWindowContent="true"
android:canRequestTouchExplorationMode="true"
android:accessibilityFeedbackType="feedbackGeneric"
android:notificationTimeout="100"
android:settingsActivity="com.service.SettingsActivity"
/>
And the following accessibility service.
class MyA11yService extends AccessibilityService {
#Override public boolean onGesture(int gestureId) {
switch (gestureId) {
case GESTURE_SWIPE_UP_AND_DOWN:
CLog.d("Performing gesture.");
performGlobalAction(GLOBAL_ACTION_BACK);
return true;
default:
return false;
}
}
}
The performGlobalAction call works just fine in any Context. Now, instead of performing this action on the SWIPE_UP_DOWN gesture, what you want to do is set up some sort of inter-process communication with the part of this that you want to be able to trigger the "global back button" action. But, that information is for another question, though if you understand the information in this post, I'm sure how you need to proceed will be clear.

Related

Android Application kill event capture

I want to perform some operation when my application gets killed.
Which method can be used for this? I am working on Android 5.0.
The key of this question is that:
you must understand your application whether can receive any
additional callbacks when your application being killed in any kinds of situation.
The following answer is answered by Devunwired in this question:
Android app doens't call "onDestroy()" when killed (ICS)
This will help you more to understand this.
Your application will not receive any additional callbacks if the process it terminated by external means (i.e. killed for memory reasons or the user Force Stops the application). You will have to make do with the callbacks you received when you app went into the background for your application cleanup.
finish() is only called by the system when the user presses the BACK button from your Activity, although it is often called directly by applications to leave an Activity and return to the previous one. This is not technically a lifecycle callback.
onDestroy() only gets called on an Activity as a result of a call to finish(), so mainly only when the user hits the BACK button. When the user hits the HOME button, the foreground Activity only goes through onPause() and onStop().
This means that Android doesn't provide much feedback to an Activity to differentiate a user going Home versus moving to another Activity (from your app or any other); the Activity itself simply knows it's no longer in the foreground. An Android application is more a loose collection of Activities than it is a tightly integrated singular concept (like you may be used to on other platforms) so there are no real system callbacks to know when your application as a whole has been brought forward or moved backward.
Ultimately, I would urge you to reconsider your application architecture if it relies on the knowledge of whether ANY Activity in your application is in the foreground, but depending on your needs, there may be other ways more friendly to the framework to accomplish this. One option is to implement a bound Service inside of your application that every Activity binds to while active (i.e. between onStart() and onStop()). What this provides you is the ability to leverage the fact that a bound Service only lives as long as clients are bound to it, so you can monitor the onCreate() and onDestroy() methods of the Service to know when the current foreground task is not part of your application.
You might also find this article written by Dianne Hackborn to be interesting covering in more detail the Android architecture and how Google thinks it ought to be used.
You have to use Service Class for it like -
public class Myservice extends Service { #Nullable #Override public IBinder onBind(Intent intent) { return null; }
#Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d(Constants.TAG, "Service Started"); return START_NOT_STICKY; }
#Override public void onDestroy() { super.onDestroy(); Log.d(Constants.TAG, "Service Destroyed"); }
#Override public void onTaskRemoved(Intent rootIntent) { Log.e(Constants.TAG, "END"); //Perfome here want you want to do when app gets kill stopSelf(); } }
In Manifest -
<service android:name="Myservice"
android:stopWithTask="false" />
In Oncreate of your launcher activity or Application Class to start service -
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
You can use your activity's onDestroy() method.

Android- Telephone app that keeps focus on outgoing & incoming phoneCall

Using this simple example to create a PhoneCall application that dials out a hard coded # and monitors phone state.
http://www.mkyong.com/android/how-to-make-a-phone-call-in-android/
Unfortunately, on making the phone call, we always switch to the actual built -in phone application.
I want to avoid this, or at the very least hide the dialer pad button. The user SHOULD NOT have the option to enter a phone#.
Does anyone know of a way to achieve this?
i.e. keep the actual built-in phone application in the background
(I would need to add buttons for speaker, and end call in the primary application)
OR
alternatively, hide just the dial pad button in the native, built-in phone application?
Here is a solution I came up with to hide the caller app shortly after the call is placed. I don't believe there is a way to make it totally transparent without re-writing the Android system. I believe this could be improved by detecting when the caller app is set up and dialing instead of the postDelayed() I'm using which could be unreliable.
EDIT: I tried making a receiver to listen for NEW_OUTGOING_CALL to restart the original Activity, but it doesn't really improve anything, the dialer app must be running for an arbitrary amount of time before it can start it's background service.
EDIT: I tried making a PhoneStateListener that listens for CALL_STATE_OFFHOOK and re starts the Activity there. This doesn't work either as it happens before the dialing app is fully ready to go into the background.
EDIT: You can look at this thread: Reflection to access advanced telephony features, but I believe Google has since locked down all methods of placing a call outside the standard app.
This solution will start the dialing, and then switch back to the original Activity after a couple of seconds.
In my manifest I have:
android:launchMode="singleInstance"
on my Activity so I don't get a new instance.
public class MainActivity extends Activity
{
....
public void clickMe(View view)
{
startService(new Intent(this, PhoneService.class));
}
}
public class PhoneService extends Service
{
#Override
public int onStartCommand(Intent intent, int flags, int startId)
{
Intent call = new Intent(Intent.ACTION_CALL);
call.setData(Uri.parse("tel:XXXXXXXXX"));
call.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(call);
Handler h = new Handler();
h.postDelayed(new Runnable() {
#Override
public void run()
{
Intent act = new Intent(PhoneService.this, MainActivity.class);
act.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(act);
}
}, 4000);
return super.onStartCommand(intent, flags, startId);
}
#Override
public IBinder onBind(Intent arg0)
{
// TODO Auto-generated method stub
return null;
}
}
I believe it impossible to provide a cleaner solution, given the constraints of the SDK.
The functionality you are wanting isn't possible without some type of hack-ish work around. The Android system only allows the Phone app to control the underlying RIL and telephony stack and the Phone app UI responds to the dial URI by presenting this user with the dial screen where they must confirm (or alter) the number. This is a security provision to prevent unwanted apps from using the telephone device without the user knowing about it. Also, due to the way the Intent system works in Android, it is possible for other apps to handle calls using SIP or other VoIP functionality (i.e. Skype). In this case the user may have set a global preference to always use the other app and you have no control over how that app behaves with the dial Intent.

Getting an activity to start as soon as Android app is in the foreground

I'm using a running service to detect whether network is available or not. When it is not available, it calls an activity to start that displays a blank screen with "no network available" on it. When the network is back, it sends a broadcast to finish this activity.
The only problem is that this activity may start at any time (as a popup), even when using other apps. I want it to start (or be visible) only if the network is out and my app is in the foreground. Any help?
One option would be to have your foreground activity register for the broadcast, and then display the relevant notification from within the activity.
Alternatively you could start your service when your foreground activity starts/resumes (i.e, onResume), and stop it when your activity leaves the foreground.
You can use START_STICKY in your service to ensure it stays around until you stop it, like so:
#Override
public int onStartCommand(Intent intent, int flags, int startId){
//On start work here
return START_STICKY;
}
and then stop the service using stopService when your activity leaves the foreground (i.e onPause).
If you need the former behaviour across multiple activities you can register broadcast receivers programmatically:
BroadcastReceiver myBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if(MY_ACTION.equals(intent.getAction()))
{
//show appropriate dialog
}
}
};
IntentFilter myIntentFilter = new IntentFilter();
myIntentFilter.addAction(MY_ACTION);
registerReceiver(myBroadcastReceiver,myIntentFilter);
You can unregister like so:
unregisterReceiver(myBroadcastReceiver);
You could extend Activity and make your own custom subclass that reuses similar code to register and unregister whilst entering/leaving the foreground. Or you can extract this into utility methods/classes and call from the appropriate places.
I think you need Shared Preference to do this. store one Boolean value on finish you activity (you can use onpause() or onStop()) and for showing popup check the value and do what you want
for understnding to use sharePreference see this and developer.android.com
Try the following:
Intent intent = new Intent(this, YourActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
This worked in the context of my own app already running, I'm not sure if it will start your app if it is not already running in the background
EDIT: Not sure if I understand your question entirely. If you only want this activity to come to the foreground while your app is in the foreground, get rid of the addFlags line, also you can do some boolean stuff to check if your app is in the foreground like so, this way your code won't even run if the app isn't in the foreground.
EDIT: There are a few ways to check if your app is in the foreground, the link I posted above has one such solution, another one is to create a static boolean isForeground variable: in the onResume() methods of your app set isForeground = true and in onPuase() set isForeground = false. This isn't the best practice, using ActivityManager is better, but for purposes of testing this should be ok.
Then have something like the following:
if(isForeground){
//Start your activity
}
This should be quick to write, if this is the behavior you want, I would recommend replacing the isForeground static variable with the test for foreground provided by ActivityManager in the link I posted.

Android - calling startActivity() from LocationListener.onLocationChanged(), Is this really what I want?

I am writing a foreground service to receive GPS notifications, send system notifications and make calls. The application has no Activity attached to it, only a service that is launched from a boot receiver. When I was trying to start the calling activity from within onLocationChanged() of the service, I got:
Calling startActivity() from outside of an Activity context requires
the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
Fearing the skeptical question, I decided to look at stackOverFlow, where I found these:
Calling startActivity() from outside of an Activity context, Android: Make phone call from service, android start activity from service - all suggesting to do this exact thing.
So, my question is: Why is it inadvisable to use this flag (something about the history stack)? Is it OK to do it in my case?
A simplified code:
public class CallService extends Service implements LocationListener {
#Override
public void onCreate() {
super.onCreate();
startForeground(1, NotificationGenerator.getNotification());
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_STICKY;
}
#Override
public synchronized void onLocationChanged(Location loc) {
String url = "tel:xxxxxxxxxx";
Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
...
}
The answer to that lies purely in User Experience (UX) domain. Android devices are usually personal devices and you should put that in mind while coding your app.
Users are maybe playing a game or making a phone call, launching your activity without any notification is rude and I would uninstall any app that would do that.
Also if the phone is locked your activity will not actually launch instead it will wait until the user unlocks the phone.
Notifications on the other hand are made to tell the user that the app wants to show you something. So use them instead.
Unless you are building a private app then you know what is better for your requirements.

GPS Tracking App (strategy)

I am currently working on a GPS tracking App (only the distance, not storing each value or displaying in a MapView) for a car-drivers logbook.
Cause of a docked phone, I do not care about power consumption.
My implementation so far is an activity that calls a GPS-Helper class which is getting the long/lat.
The activity itself calculates the driven distance, displays it for the user and starts a notification bar that also displays the distance. To keep the activity active and not killed by the OS, I am using a PARTIAL WakeLock for the activity.
My problem is that this is not working correctly, cause my App seems to be killed by the OS inspite of the WakeLock. I think that it is killed, cause when I click on the notification bar item (after 15-30 min. for example) to see the driven distance in my running activity, the activity is shown as it is to start a new GPS-track instead of displaying the driven distance from the former started track.
The WAKELOCK Permission is correctly set in the Manifest.
My question now is to get know if this costruct could be working or is there a better way to do this?
Your problem may be with the Intent you are launching when you click on the notification. This intent is most likely thinking that you want to launch a brand new Activity rather than returning the old activity to the foreground.
This link may help you to do what you want:
How to bring Android existing activity to front via notification
You should use a service which calls startForground, which requires a notification. This notification will be your entry point back into the app. The service can run in the background and log coordinates without depending on the life cycle of your Activity.
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if(intent.getAction().equals(DRIVELOG_ACTION_STOPLOGGING)){ handleStopLoggingCommand(intent.getBooleanExtra(SAVE_LOG,false));
}
else if(intent.getAction().equals(DRIVELOG_ACTION_STARTLOGGING)){
handleStartLoggingCommand();
}
return START_STICKY;
}
private void handleStartLoggingCommand() {
startForeground(DriveLoggerNotification.notificationId,DriveLoggerNotification.createInDriveLogNotification(this,driveLogLiveData));
if(googleApiClient.isConnected()){
startMonitoringLocation();
}else {
googleApiClient.connect();
}
}
This code is from my GpsLoggingService

Categories

Resources