Android startActivity in BroadcastReceiver's onReceive function - android

I have the following code in my BroadcastReceiver's onReceive function.
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (action == null) return;
if (action.equals(ACTION_ALARM)) {
Intent alarmPopup = new Intent(context, AlarmPopup.class);
int vibrateDuration = context.getSharedPreferences(PREF, 0)
.getInt(VIBRATE_DURATION, DEFAULT_VIBRATE_DURATION)
alarmPopup.putExtra(VIBRATE_DURATION, vibrateDuration);
alarmPopup.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(alarmPopup);
}
}
This code starts activity AlarmPopup as it receives alarm manager's broadcast.
Once the AlarmPopup activity is started, it shows a typical alarm message and vibrates during vibrateDuration passed through Intent#putExtra.
In AlarmPopup's onCreate method, the activity holds WakeLock to make the device keep turning on.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
wl = getLock(this);
if (!wl.isHeld()) {
Log.d(PREF, "Alarm popup acquires wake lock");
wl.acquire();
thread.run();
}
.
.
.
}
getLock is a synchronized method that manages WakeLock as WakefulIntentService does.
private static volatile PowerManager.WakeLock wlStatic = null;
synchronized private static PowerManager.WakeLock getLock(Context context) {
if (wlStatic == null) {
PowerManager mgr = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wlStatic = mgr.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK
| PowerManager.ACQUIRE_CAUSES_WAKEUP
| PowerManager.ON_AFTER_RELEASE, PREF);
wlStatic.setReferenceCounted(true);
}
return wlStatic;
}
Now here is the problem: even though context.startActivity(alarmPopup) is called, startActivity rarely does not start the activitiy or starts not on time, usually 1-2 minutes later.
It seems that OS kills my AlarmPopup activitiy in the middle of its creation or let the activity be created a little bit later than the time when startActivity was actually called.
What is really interesting is, when the above problem happens, sometimes the log message "Alarm popup acquires wake lock" is recorded and sometimes it is not even recorded. I think, in this case, OS kills the activity while it executes the first or second line of onCreate method.
How can I solve this problem?
Should I put some dummy code that holds the CPU at the end of onReceive while the AlarmPopup activity is being created by another thread?

It seems that OS kills my AlarmPopup activitiy in the middle of its creation or let the activity be created a little bit later than the time when startActivity was actually called.
No. The device simply fell asleep. startActivity() is an asynchronous operation. The WakeLock held by the OS for the AlarmManager work (assuming that you are, indeed, using AlarmManager) will be released when onReceive() returns. onCreate() of your activity will not have run by the time onReceive() returns. Hence, the device might fall asleep in the window of time between the end of onReceive() and when you acquire your WakeLock in onCreate().
How can I solve this problem?
Acquire the WakeLock in onReceive().

Related

Intent Service not working in doze mode

One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Below is the code:
public class GpsTrackingService extends IntentService {
....
#Override
protected void onHandleIntent(Intent intent) {
do{
try{
//make API call here
//then go to sleep for 2 mins
TimeUnit.SECONDS.sleep(120);
} catch(InterruptedException ex){
ex.printStackTrace();
}
} while (preferences.shouldSendGps()); //till the user can send gps.
}
....
}
Manifest
<service android:name=".commons.GpsTrackingService" />
This is working fine when the phone is active. However, whenever the phone goes into doze mode it fails to wake.
Will using alarm manager with WAKE permission solve this?
I have just got the code base and need to fix this within today. It'll be great if someone can help.
As the documentation says:
In Doze mode, the system attempts to conserve battery by restricting
apps' access to network and CPU-intensive services. It also prevents
apps from accessing the network and defers their jobs, syncs, and
standard alarms.
Periodically, the system exits Doze for a brief time to let apps
complete their deferred activities. During this maintenance window,
the system runs all pending syncs, jobs, and alarms, and lets apps
access the network.
In few words, while in Doze mode the system suspends network accesses, ignores Wake Locks, stops acquiring data from sensors, defers AlarmManager jobs to the next Doze maintenance window (which are progressively less frequently called), also WiFi scans, JobScheduler jobs and Sync adapters do not run.
Neither setAndAllowWhileIdle() nor setExactAndAllowWhileIdle() can fire alarms more than once per 9 (?) minutes, per app.
And it seems that the Foreground Services are also involved into this "Doze Drama", at least in MarshMellow (M).
To survive in this situation, tons of applications need to be at least rewiewed. Can you imagine a simple mp3 player which stops playing music when the device enters in Doze Mode?
Doze mode starts automatically, when the device is unplugged from the power supply and left on the table for about 1 hour or so, or even earlier when the user clicks the power button to power down the screen, but I think this could depend by the device manufacturer too.
I tried a lot of countermeasures, some of them really hilarious.
At the end of my tests I reached a possible solution:
One possible (and maybe the only) way to have your app running even when the host device is in Doze mode, is basically to have a ForegroundService (even a fake one, doing no jobs at all) running in another process with an acquired partial WakeLock.
What you need to do is basically the following (you could create a simple project to test it):
1 - In your new project, create a new class which extends Application (myApp), or use the
main activity of the new project.
2 - In myApp onCreate() start a Service (myAntiDozeService)
3 - In myAntiDozeService onStartCommand(), create the Notification
needed to start the service as a foreground service, start the
service with startForeground(id, notification) and acquire the
partial WakeLock.
REMEMBER! This will work, but it is just a starting point, because you have to be careful with the "Side Effects" this approach will generate:
1 - Battery drain: The CPU will work for your app forever if you
don't use some strategy and leave the WakeLock always active.
2 - One notification will be always shown, even in the lockscreen,
and this notification cannot be removed by simply swiping it out, it
will be always there until you'll stop the foreground service.
OK, let's do it.
myApp.java
public class myApp extends Application {
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
// start foreground service
startForeService();
}
private void stopForeService() {
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STOPFOREGROUND_ACTION);
stopService(service);
}
private void startForeService(){
Intent service = new Intent(this, myAntiDozeService.class);
service.setAction(STARTFOREGROUND_ACTION);
startService(service);
}
#Override
public void onTerminate() {
stopForeService();
super.onTerminate();
}
}
myAntiDozeService.java
public class myAntiDozeService extends Service {
private static final String TAG = myAntiDozeService.class.getName();
private static boolean is_service_running = false;
private Context mContext;
private PowerManager.WakeLock mWakeLock;
private static final int NOTIFICATION_ID = 12345678;
private static final String STARTFOREGROUND_ACTION = "STARTFOREGROUND_ACTION";
private static final String STOPFOREGROUND_ACTION = "STOPFOREGROUND_ACTION";
#Override
public void onCreate() {
super.onCreate();
mContext = getApplicationContext();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (!is_service_running && STARTFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Start Foreground Intent ");
showNotification();
is_service_running = true;
acquireWakeLock();
} else if (is_service_running && STOPFOREGROUND_ACTION.equals(intent.getAction())) {
Log.i(TAG, "Received Stop Foreground Intent");
is_service_running = false;
stopForeground(true);
stopSelf();
}
return START_STICKY;
}
#Override
public void onDestroy() {
releaseWakeLock();
super.onDestroy();
}
private void showNotification(){
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, notificationIntent, 0);
Notification notification = new NotificationCompat.Builder(mContext)
.setContentTitle("myApp")
.setTicker("myApp")
.setContentText("Application is running")
.setSmallIcon(R.drawable.ic_launcher)
.setContentIntent(pendingIntent)
.build();
// starts this service as foreground
startForeground(NOTIFICATION_ID, notification);
}
public void acquireWakeLock() {
final PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
releaseWakeLock();
//Acquire new wake lock
mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG+"PARTIAL_WAKE_LOCK");
mWakeLock.acquire();
}
public void releaseWakeLock() {
if (mWakeLock != null && mWakeLock.isHeld()) {
mWakeLock.release();
mWakeLock = null;
}
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
AndroidManifest.xml changes.
In the AndroidManifest.xml add this permission:
<uses-permission android:name="android.permission.WAKE_LOCK" />
Don't forget to add the name of your app in the <application> tag:
<application
....
android:name=".myApp"
....
And finally add your foreground service running into another process:
<service
android:name=".myAntiDozeService"
android:process=":MyAntiDozeProcessName">
</service>
A couple of notes.
In the previous example, the notification created, when clicked,
opens the ActivityMain activity of your test project.
Intent notificationIntent = new Intent(mContext, ActivityMain.class);
but you can use another kind of intent too.
To test it, you have to add some job to be performed into your
ActivityMain.java, for example some repeating alarm (which was
normally stopped when the device falls in Doze Mode), or a ripetitive
network access, or a timed tone played, or.... whatever you want.
Remember that the job performed by the main activity has to run
forever because to test this AntiDoze you need to wait at least 1
hour to be sure the device enters in Doze Mode.
To enter in Doze mode, the device has to be quiet and unplugged, so
you can't test it while you are debugging. Debug your app first,
check that everything is running then stop it, unplug, restart the
app again and leave the device alone and quiet on your desk.
The adb commands suggested by the documentation to simulate Doze
and StandBy modes could and could not give you the right results
(it depends, I suppose, by the device manufacturer, drivers, bla
bla). Please make your tests in the REAL behaviour.
In my first test, I used an AlarmManager and a tone generator to play a tone every 10 minutes just to understand that my app was still active.
And it is still running from about 18 hours, breaking my ears with a loud tone exactly every 10 minutes. :-)
Happy coding!
One of my peer developer has written an intent service that makes an API call and then sleeps for 2 mins. After waking up, it sends again.
Only have a service running while it is actively delivering value to the user. Sitting around for two minutes, watching the clock tick, is not actively delivering value to the user.
Will using alarm manager with WAKE permission solve this?
That depends on what you mean by "solve this". You can use AlarmManager to request to get control every two minutes so that you can do work. While the device is in Doze mode, you will not actually get control every two minutes, but once per maintenance window.

CountdownTimer won't tick even though wakelock was acquired

My app runs smoothly on the emulator - everything working exactly as it should - but not on my phone.
In my app, I use both a countdownTimer which ticks every minute, and an alarm manager, which should ensure that the user will be notified of whatever it needs to be notified of, should the phone be asleep.
Once the receiver receives the Alarm manager's broadcast, I acquire a partial wake lock for 5 seconds (which is even more than should be needed).
I checked, and wakelock.isHeld() returns true. I have the necessary permission, and the onReceive doesn't take that long (it is not at all computationally expensive).
And yet, the countdownTimer doesn't catch up. nothing else is fired until I actually unlock the phone and look at the app.
Any ideas why? I can't for the life of me figure this one out.
edit: Tried moving the wakelock (wl) declaration outside of the function (and even to the outer class), even though things worked as they were on the emulator (and from what I understand it shouldn't make a difference anyway, which it, indeed, did not :( )
code:
public class Class1 extends Binder {
public static class Class1A extends BroadcastReceiver
{
public void onReceive(Context context, Intent intent) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyApp");
//Acquire the lock
wl.acquire(5 * 1000); //5 seconds to expiration
// Do a few things
}
/// Some more code setting up the alarm
}
private class ReminderHandler extends CountDownTimer {
#Override
public void onTick(long millisUntilFinished) {
logTxt.append("\nTicked. ");
}
#Override
public void onFinish() {
GregorianCalendar tmp = new GregorianCalendar();
logTxt.append("\nCountdown completed at "+ frmtr.format(tmp.getTime()));
}
public ReminderHandler(int Len)
{
super((((long) Len)*60*1000),60*999);
}
}
}
}
As I said, the alarm is received, I acquire a wakelock successfully, but the onTick doesn't happen until I actually unlock the phone and open the app.
Well, this is not an answer I am happy with, but for now, this is all I have:
It seems the it takes more than 5 seconds (and even more than the 15 seconds I later gave it to test this) for the CountdownTimer to catch up (even if it should have only had 1 onTick and 1 onFinish call to catch up to).
I simply released the wakelock in the onFinish. I don't like this (since if there is some bug, it could end up holding the wakelock for way too long), but for now, this is all I can do.

Can we have blocking waits in onDestroy()?

I have a BroadcastReceiver which listens for an intent in onDestroy() callback. And there is a blocking while which goes on till bluetooth discoverability is switched off. Once discoverability is off, the changeModeReceiver will call its onReceive() and set destroy_ok to true, and hence breaking out of the while loop. But, this is not giving desired results.
Toast message, "In onDestroy()" is not getting printed
"In onDestroy()" is getting printed in the logcat
The bluetooth is still switched on
The code is as follows.
boolean destroy_ok = false;
protected void onDestroy(){
System.out.println("In onDestroy()");
Toast.makeText(getApplicationContext(), "In onDestroy()", Toast.LENGTH_LONG).show();
BroadcastReceiver changeModeReceiver = new BroadcastReceiver(){
public void onReceive(Context context, Intent intent){
String mode = intent.getStringExtra(BluetoothAdapter.EXTRA_SCAN_MODE);
if (mode.equals(BluetoothAdapter.SCAN_MODE_NONE))
destroy_ok = true;
}
};
IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
registerReceiver (changeModeReceiver, filter);
Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,1);
startActivity(discoverableIntent);
while (!destroy_ok){}
unregisterReceiver(changeModeReceiver);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.isEnabled())
adapter.disable();
System.out.println("Leaving onDestroy()");
super.onDestroy();
}
The onDestroy method (as well as all other activity lifecycle methods, view callback methods, etc.) is called on the application's main UI thread, so no, you shouldn't block for a significant period of time when called. Doing so will likely result in lag, and may even spawn an ANR (application not responding) error if you block for more than 5-10 seconds.
Note: do not count on this method being called as a place for saving
data! For example, if an activity is editing data in a content
provider, those edits should be committed in either onPause() or
onSaveInstanceState(Bundle), not here. This method is usually
implemented to free resources like threads that are associated with an
activity, so that a destroyed activity does not leave such things
around while the rest of its application is still running. There are
situations where the system will simply kill the activity's hosting
process without calling this method (or any others) in it, so it
should not be used to do things that are intended to remain around
after the process goes away.
http://developer.android.com/reference/android/app/Activity.html#onDestroy()
So simply said, use onPause() for such operations. Also, I would use a Service or a new Thread in your case.

WakeLock not releasing and Screen isn't turning off

I've using alarm manager to call an activity and I'm using the wake locker class onRecive() to wake the phone and then calling WakeLocker.release() after the Activity is over but the screen still stays on...
Receive.class:
public class MyScheduledReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
WakeLocker.acquire(context);
Activity.class
#Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
WakeLocker.release();
finish();
}
I've put it in the onPause(), onStop() everywhere... the thing won't release and the screen won't turn off automatically after my app closes...
Make sure you request permission
<uses-permission android:name="android.permission.WAKE_LOCK" />
You are starting the wakelock in a broadcastreceiver and stopping it in an activity.
You are referencing 2 different instances of a wakelock. You should start the activity from the onreceive and in onresume acquire the wake lock, then still release in the onpause if that is where you want it to happen.
You should never start anything that is supposed to be around for awhile within a broadcastreceiver, because the receiver is destroyed as soon as possible.
Try this
PowerManager pm;
PowerManager.WakeLock wakeLock;
pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK,
"x2_wakelook");
wakeLock.acquire();
wakeLock.release();

Lock android app after a certain amount of idle time

My android application requires a password to be entered in the first activity. I want to be able to automatically send the application back to the password entry screen after the application has been idle for a fixed amount of time.
The application has multiple activities, but I would like the timeout to be global for all activities. So, it wouldn't be sufficient to create a timer thread in the onPause() method of an Activity.
I'm not sure what the best definition for the application being idle is, but no activities being active would be sufficient.
I know another answer is accepted already, but I came across this working on a similar problem and think I'm going to try an alternate much simpler approach that I figured I may as well document if anyone else wants to try to go down the same path.enter code here
The general idea is just to track the system clock time in a SharedPreference whenever any Activity pauses - sounds simple enough, but alas, there's a security hole if that's all you use, since that clock resets on reboot. To work around that:
Have an Application subclass or shared static singleton class with a global unlocked-since-boot state (initially false). This value should live as long as your Application's process.
Save the system time (realtime since boot) in every relevant Activity's onPause into a SharedPreference if the current app state is unlocked.
If the appwide unlocked-since-boot state is false (clean app start - either the app or the phone restarted), show the lock screen. Otherwise, check the SharedPreference's value at the lockable activity's onResume; if it's nonexistent or greater than the SharedPreference value + the timeout interval, also show the lock screen.
When the app is unlocked, set the appwide unlocked-since-boot state to true.
Besides the timeout, this approach will also automatically lock your app if your app is killed and restarts or if your phone restarts, but I don't think that's an especially bad problem for most apps. It's a little over-safe and may lock unecessarily on users who task switch a lot, but I think it's a worthwhile tradeoff for reduced code and complexity by a total removal of any background process / wakelock concerns (no services, alarms, or receivers necessary).
To work around process-killing locking the app regardless of time, instead of sharing an appwide singleton for unlocked-since-boot, you could use a SharedPreference and register a listener for the system boot broadcast intent to set that Preference to false. That re-adds some of the complexity of the initial solution with the benefit being a little more convenience in the case that the app's process is killed while backgrounded within the timeout interval, although for most apps it's probably overkill.
I dealt with this by using the AlarmManager to schedule and cancel timeout action.
Then in the onPause() event of all of my activites, I schedule the alarm. In the onResume() event of all of my activities, I check to see if the alarm goes off. If the alarm went off, I shutdown my app. If the alarm hasn't gone off yet I cancel it.
I created Timeout.java to manage my alarms. When the alarm goes off a intent is fired:
public class Timeout {
private static final int REQUEST_ID = 0;
private static final long DEFAULT_TIMEOUT = 5 * 60 * 1000; // 5 minutes
private static PendingIntent buildIntent(Context ctx) {
Intent intent = new Intent(Intents.TIMEOUT);
PendingIntent sender = PendingIntent.getBroadcast(ctx, REQUEST_ID, intent, PendingIntent.FLAG_CANCEL_CURRENT);
return sender;
}
public static void start(Context ctx) {
ctx.startService(new Intent(ctx, TimeoutService.class));
long triggerTime = System.currentTimeMillis() + DEFAULT_TIMEOUT;
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC, triggerTime, buildIntent(ctx));
}
public static void cancel(Context ctx) {
AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE);
am.cancel(buildIntent(ctx));
ctx.startService(new Intent(ctx, TimeoutService.class));
}
}
Then, I created a service to capture the intent generated by the alarm. It sets some global state in my instance of the application class to indicate that the app should lock:
public class TimeoutService extends Service {
private BroadcastReceiver mIntentReceiver;
#Override
public void onCreate() {
super.onCreate();
mIntentReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ( action.equals(Intents.TIMEOUT) ) {
timeout(context);
}
}
};
IntentFilter filter = new IntentFilter();
filter.addAction(Intents.TIMEOUT);
registerReceiver(mIntentReceiver, filter);
}
private void timeout(Context context) {
App.setShutdown();
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.cancelAll();
}
#Override
public void onDestroy() {
super.onDestroy();
unregisterReceiver(mIntentReceiver);
}
public class TimeoutBinder extends Binder {
public TimeoutService getService() {
return TimeoutService.this;
}
}
private final IBinder mBinder = new TimeoutBinder();
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
Finally, I created a subclass of Activity that all of my app's activities subclass from to manage locking and unlocking:
public class LockingActivity extends Activity {
#Override
protected void onPause() {
super.onPause();
Timeout.start(this);
}
#Override
protected void onResume() {
super.onResume();
Timeout.cancel(this);
checkShutdown();
}
private void checkShutdown() {
if ( App.isShutdown() ) {
finish();
}
}
}
Using onPause and onResume to start and stop the timeout gives me the following semantics. As long as one of my application's activities is active, the timeout clock is not running. Since I used an Alarm type of AlarmManager.RTC, whenever the phone goes to sleep the timeout clock runs. If the timeout happens while the phone is asleep, then my service will pick up the timeout as soon as the phone wakes up. Additionally, the clock runs when any other activity is open.
For a more detailed version of these, you can see how I actually implemented them in my application https://github.com/bpellin/keepassdroid
Check out how OpenIntents Safe implements this functionality.
This has been a really helpful post for me. To back the concept given by #Yoni Samlan . I have implemented it this way
public void pause() {
// Record timeout time in case timeout service is killed
long time = System.currentTimeMillis();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor edit = preferences.edit();
edit.putLong("Timeout_key", time);// start recording the current time as soon as app is asleep
edit.apply();
}
public void resume() {
// Check whether the timeout has expired
long cur_time = System.currentTimeMillis();
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
long timeout_start = preferences.getLong("Timeout_key", -1);
// The timeout never started
if (timeout_start == -1) {
return;
}
long timeout;
try {
//timeout = Long.parseLong(sTimeout);
timeout=idle_delay;
} catch (NumberFormatException e) {
timeout = 60000;
}
// We are set to never timeout
if (timeout == -1) {
return;
}
if (idle){
long diff = cur_time - timeout_start;
if (diff >= timeout) {
//Toast.makeText(act, "We have timed out", Toast.LENGTH_LONG).show();
showLockDialog();
}
}
}
Call pause method from onPause and resume method from onResume.

Categories

Resources