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
Related
My application (Xamarin.Android) runs as foreground service. The service hence has a permanent notification which I update. The app receives data from a bluetooth-enabled medical device. When comms arrive at my app, I update the notification with a counter (patient events in my case).
If I tap on the notification, my app launches so all good there, however, for certain incoming bluetooth packets, I need to actually start (or foreground) my activity, this needs to happen WITHOUT the user tapping on the notification. NB: I am only expecting this to work when device is unlocked, screen on, and with my app not in the foreground.
My code used to work just fine, so I suspect its googles changes to Android 10 and 11 that have stopped this working, but can it still be done?
My current code shown below
Many Thanks
Karen
/// <summary>
/// Assuming that the phone is not locked, and the screen is on, this brings the application to the foreground. It is used, for instance
/// where a patient event is inititiated while the user is viewing another app. The app is brought to the foreground by simply launching (or re-launching)
/// Main Activity
/// </summary>
public void BringToForeground()
{
var context = (Activity)MainApplication.ActivityContext;
KeyguardManager keyguardManager = (KeyguardManager)context.GetSystemService(Context.KeyguardService);
DisplayManager displayManager = (DisplayManager)context.GetSystemService(Context.DisplayService);
var displayOn = false;
foreach (var display in displayManager.GetDisplays())
{
if (display.State == DisplayState.On)
displayOn = true;
}
if (!displayOn || keyguardManager.IsKeyguardLocked)
return;
//Check if we are already foregrounded, if so, return, nothing more to do
var proteusAppProcess = new ActivityManager.RunningAppProcessInfo();
ActivityManager.GetMyMemoryState(proteusAppProcess);
if (proteusAppProcess.Importance == Importance.Foreground)
return;
//Not foregrounded so re-launch intent - since this APP is SingleTop, this will replace any existing activity
Intent resultIntent = new Intent(StaticDefs.Com_Spacelabs_EclipsePatientApp_Android_SwitchScreenIntent);
resultIntent.PutExtra(PageId.PageIdStringIdent, (int)PageId.RequestedPageId.PatientEventListScreen);
resultIntent.SetFlags(ActivityFlags.NoHistory | ActivityFlags.NewTask | ActivityFlags.SingleTop);
context.StartActivity(resultIntent);
}
Right, I think I can answer my own question - its due to the changes from Android 10 re who can launch foreground activities programatically.
To make the above code work, I needed to ask for the SYSTEM_ALERT_WINDOW permission and then manually visit (or programmatically open) the Android settings page for my app and enable the 'Appear On Top' option.
After doing that, my activity starts like it used to.
Unfortunately, given that this is a medical app, likely used by elderly folks, expecting folks to manually re-config the app is not realistic, so I will put up with notification tap to launch activity. It seems that the SYSTEM_ALERT_WINDOW permission is required even though my app runs as a foreground service - foreground services being a way around the Android 9 background execution limits!
Karen
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.
I have built an app for running. It runs an Activity with a timer shown in the user interface, a gps listener that collects coordinates and a lot of other things (the activity does a lot of work).
Now the request of my client is to move all the activity logic in a Service. In this way, when you start a running session, the Service would start and the notification (very simple, just with a static text) would appear. The activity should keep track of the work made in the Service (timer should go on, speed should be shown, ecc...). Tapping on the notification should bring up the activity. If the activity is closed or crashes the Service should keep going on and when you tap on the notification a new Activity should be brought up without the user noticing any difference (the timer should keep showing the right time, the average speed should comprehend the speeds relevated before the activity crash, ecc...).
I know there are a lot of ways to do that.
What I am asking is: what is the best way? Are there examples of such behavior from where to start? What are the common errors I should avoid? Are there best practices to follow?
Thank you
I developed an app with similar service behaviour. It also requires a service which collects data and some activities for showing the data.
For these kind of applications you want to keep the service alive until the user stopps it manualy but it is still possible for android that it kills the service if the device is low on memory.
For the service - activity interaction you need to bind to a service. A good documentation is available here: http://developer.android.com/guide/components/bound-services.html
Be sure to return START_STICKY in the onStartCommand function of the service. This will make sure the intent will be null when the service was restored by the system and tell android that you start and stop your service explicit.
When binding to the service from the activity you need to check if the service is ready (was not restored by the system). This can be done by adding a "ready" field inside the service that is false by default and is set to true if the onStartCommand intent is not null. Therefore you can react properly to a restored service and start the app from the beginning.
To keep the service alive with a high priority you need to call startForeground inside the service. This also requires to show a notification so the users knows a service is running in the background.
Inside service you can use local broadcastmanager.
#Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
broadcaster = LocalBroadcastManager.getInstance(this);
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
handler.removeCallbacks(sendUpdatesToUI);
handler.postDelayed(sendUpdatesToUI, 1000);
return START_STICKY;
}
private Runnable sendUpdatesToUI = new Runnable() {
public void run() {
DisplayLoggingInfo(); //do watever you want to push
handler.postDelayed(this, 1000); // 10 seconds
}
};
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.
I´ve got a background service, which pushes notifications. When you click on the notifications, my activity is opened, but the problem is, the onStartCommand is called then again, which propably could invoke another notification, which means I have a loop.
I´m using
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
return Service.START_NOT_STICKY;
}
also tried Service.START_STICKY with no effect.
In my manifest:
<service
android:name=".notification.NotificationService"
android:exported="false"></service>
what could cause a recall of onStartCommand?
Check your Activity code, sounds like your accidentally starting the Service.
There are some situations where this could happen like
it may be killed by the system if it is under heavy memory pressure.
If this happens, the system will later try to restart the service.
There Is another possibility this might happen. If you rotate your device your Activity will be killed and restarted all according to Android official docs Activity . Your Service will be launched again. Read the docs, save some time and headache. On this link Android official docs Activity, scroll down 2 laps and you see a picture of the lifecycle.