5

I have certain dates which once attained lose their relevance and new dates for these fields in the DB should be calculated, I know I can leverage the AlarmManager class for this, however I have a few concerns regarding this:

1) Note: Beginning with API 19 (KITKAT) alarm delivery is inexact: the OS will shift alarms in order to minimize wakeups and battery use. There are new APIs to support applications which need strict delivery guarantees; see setWindow(int, long, long, PendingIntent) and setExact(int, long, PendingIntent). Applications whose targetSdkVersion is earlier than API 19 will continue to see the previous behavior in which all alarms are delivered exactly when requested.

So do I need to code for both cases separately or if I target kitkat, will that work for older versions too? Also as my code execution is time critical, say after 12AM in the midnight of some date my Data loses relevance, how to overcome shifting of alarms.

2)Registered alarms are retained while the device is asleep (and can optionally wake the device up if they go off during that time), but will be cleared if it is turned off and rebooted.

2.1) Set the RECEIVE_BOOT_COMPLETED permission in your application's manifest. This allows your app to receive the ACTION_BOOT_COMPLETED that is broadcast after the system finishes booting (this only works if the app has already been launched by the user at least once)

2.1.1) If I have set an alarm at 12, the service related to this alarm fires at 12, Now when I reboot the device, the time "at 12" has already passed, the alarm will be fired again immediately and the service will be called again?

At reboot what mechanism do I need to implement in-order to stick to my code execution policy at certain time? How do I set the alarm if the user does not launch my app?

The third thing is that if my app is uninstalled I want to clear all alarms set by my code, how do I listen to when the app is uninstalled?

Also I want to know, my app is very time critical, the values in my DB get obsolete by 12 am each night, while I am updating the app, what would be the result if a user chooses to use my app at 12 while I use a service to update it and its running in the background?

EDIT: What I have tried so far:

I have a Database in which records get stale past midnight, say sharp at 12:00. I invoked an Alarm Manager(In a test project as I like to isolate the problem code) to fire a service. I also acquire a PARTIAL_WAKE_LOCK on the device so that my huge database manipulation is done properly. I have also implemented a thread to do my time consuming task. Following is my MainActivity Class which I invoke at 12 to initiate the alarm(Random time for test purpose):

public class MainActivity extends Activity {
    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    BroadcastReceiver br;
    TextView t; 
    int sum; 


    public void setSum(int s){
        sum = s; 

    //  t = (TextView)findViewById(R.id.textView1);
    //  t.setText(sum);
        System.out.println("In Set Sum"+s);
    }



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setup(); 
        t = (TextView)findViewById(R.id.textView1);

        ComponentName receiver = new ComponentName(getApplicationContext(), SampleBootReceiver.class);
        PackageManager pm = getApplicationContext().getPackageManager();

        pm.setComponentEnabledSetting(receiver,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP);


        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.set(Calendar.HOUR_OF_DAY, 17);
        calendar.set(Calendar.MINUTE, 05); // Particular minute
        calendar.set(Calendar.SECOND, 0);
        alarmMgr = (AlarmManager)getApplicationContext().getSystemService(Context.ALARM_SERVICE);
        alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                1000*60*60*24, alarmIntent);
    }


    public void setup() {
        br = new BroadcastReceiver() {
            @Override
            public void onReceive(Context c, Intent i) {
                Toast.makeText(c, "Rise and Shine!", Toast.LENGTH_LONG).show();
                //Invoke the service here Put the wake lock and initiate bind service
                t.setText("Hello Alarm set");

                startService(new Intent(MainActivity.this, MyService.class));
                stopService(new Intent(MainActivity.this, MyService.class));

            }
        };
        registerReceiver(br, new IntentFilter("com.testrtc") );
        alarmIntent = PendingIntent.getBroadcast( this, 0, new Intent("com.testrtc"),0 );
        alarmMgr = (AlarmManager)(this.getSystemService( Context.ALARM_SERVICE ));
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

This is my SampleBootReceiver Class inorder to check for reboots and set Alarms again after reboot, I am not sure if it works as intended. I could find no means to test if this is working properly but I do receive the Toast message about completion of boot.

public class SampleBootReceiver extends BroadcastReceiver {
    private AlarmManager alarmMgr;
    private PendingIntent alarmIntent;
    BroadcastReceiver br;
    TextView t; 
    MainActivity main; 

    @Override
    public void onReceive(Context context, Intent intent) {
        main= new MainActivity(); 
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            Toast.makeText(context, "Hello from Bootloader", 10000).show();
            Calendar calendar = Calendar.getInstance();
            calendar.setTimeInMillis(System.currentTimeMillis());
            calendar.set(Calendar.HOUR_OF_DAY, 15);
            calendar.set(Calendar.MINUTE, 50); // Particular minute
            calendar.set(Calendar.SECOND, 0);
            alarmMgr = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
            alarmMgr.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(),
                    1000*60*60*24, alarmIntent);

            context.getApplicationContext().registerReceiver(br, new IntentFilter("com.testrtc") );
            alarmIntent = PendingIntent.getBroadcast( context.getApplicationContext(), 0, new Intent("com.testrtc"),
                    0 );
            alarmMgr = (AlarmManager)(context.getApplicationContext().getSystemService( Context.ALARM_SERVICE ));

        }
    }

}

The following is my service Class, unsure about the return I am doing here in the onStartCommand method:

public class MyService extends Service {

    int a = 2; 
    int b = 2; 
    int c = a+b; 


    public MainActivity main = new MainActivity();

    public MyService() {
    }



    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onCreate() {
        Toast.makeText(this, "The new Service was Created", Toast.LENGTH_LONG).show();

    }

    @Override
    public int onStartCommand(Intent i, int flags , int startId){
        WakeLock wakeLock = null;

        try{
            PowerManager mgr = (PowerManager)getApplicationContext().getSystemService(Context.POWER_SERVICE);
            wakeLock = mgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MyWakeLock");
            wakeLock.acquire();

            Toast.makeText(this, " Service Started", Toast.LENGTH_LONG).show();
             new Thread(new Runnable() { 
                    public void run(){


                        //Will be substituted with a time consuming long task. 

                        main.setSum(c); 

                    }
            }).start();

        }catch(Exception e){
            System.out.println(e);
        }finally{

            wakeLock.release();

        }

        return 1; 

    }

    @Override
    public void onDestroy() {
        Toast.makeText(this, "Service Destroyed", Toast.LENGTH_LONG).show();

    }
}

Also in the above, I want to know if the thread I am starting will interfare with how I am acquiring the wake lock. Also if I can use an async task and release the wakelock in onPostExecute?

Finally here is my Manifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.testrtc"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.testrtc.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".SampleBootReceiver"
            android:enabled="false" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" >
                </action>
            </intent-filter>
        </receiver>

        <service
            android:name="com.testrtc.MyService"
            android:enabled="true"
            android:exported="true" >
        </service>

    </application>

</manifest>

THis is my log Cat after reboot, there are many log messages however I find these related to the app, they contain th epackage name:

01-22 15:18:35.652: V/ActivityManager(419): getTasks: max=1, flags=0, receiver=null
01-22 15:18:35.652: V/ActivityManager(419): com.xxx.xxx/.MainActivity: task=TaskRecord{425c58f0 #5 A com.xxx.xxx U 0}
01-22 15:18:35.653: V/ActivityManager(419): We have pending thumbnails: null

More Questions: Where should I set up the Alarm, if I do it in the onCreate of my Splash screen it will be called each time the app starts, maybe overwriting the older values.

Second I want to acquire a lock on the DB when my service is running, if in this time the user tries to open my app what do I do? (As data is getting updated I dont have anything to show).

Third in the above code I am still finding problems to register an alarm after reboot.

Skynet
  • 7,820
  • 5
  • 44
  • 80
  • 1
    You need to register your alarm `BroadcastReceiver` in the manifest instead of at runtime, since dynamically registered `BroadcastReceiver`s will only work while your app is running. You should release your `WakeLock` in the `onDestroy()` method of your `Service`, or perhaps use the `WakefulBroadcastReceiver` from the support library for your alarm `BroadcastReceiver`, and call it's `completeWakefulIntent()` method at the conclusion of your work in the background `Thread`. You might also want to use an `IntentService` instead of normal `Service` for easier handling of your background logic. – corsair992 Jan 22 '14 at 18:46
  • Thanks mate, working on these points. Will get back once done :) – Skynet Jan 23 '14 at 05:08
  • Glad you got it working :) You should accept Harshit Jain's answer though, as he provided the correct answers to your original questions, while I only gave some tips on the implementation. Thanks for the upvotes though (assuming that was you)! I am now quite close to my goal of attaining a thousand reputation :) Good luck! – corsair992 Jan 23 '14 at 13:54

2 Answers2

2

1) To handle alarm at exact times you have 2 options: a) Set minimum SDK level at 18 and not 19. This will make your app work on kitkat at the exact time b) Use the setExact() method to tell Kitkat to keep your alarm at the exact time specified Source: http://developer.android.com/about/versions/android-4.4.html

2) The boot completed notification is the best way to go. You can set up a Broadcast receiver to get the notification on a reboot. You can store alarms in a Database and retrieve on reboot.

2.1) It is horrible programming practice to do something the user did not want you to do, i.e. set an alarm even though user has not opened the app.

3) As long as you don't create files on the SD card, all application data is removed upon uninstall of the app

4) You should lock/unlock data when writing or reading from it to solve your 12AM problem. If the data is locked, it would not get affected until the user has committed the transaction. If you are using SQLite you can get more information on this from: http://www.sqlite.org/lockingv3.html

Harshit Jain
  • 331
  • 1
  • 6
  • What is the context of `Opened the app` does it imply opening up the app at least once on the device, or does it mean opening the app at least once after reboot? – Skynet Jan 21 '14 at 04:22
  • @AstralProjection: Starting from Honeycomb MR1 apps are initially in the stopped state when installed, and return to it when the user force stops them. At this time all `BroadcastReceiver`s are disabled. Therefore, you should also register your alarms in your `Application`'s `onCreate()` method, as well as in the boot completion `BroadcastReceiver`. – corsair992 Jan 22 '14 at 11:57
  • Roger to that, I had this somewhere in the back of my mind. But if I put my code in onCreate, will that schedule a new alarm each time my onCreate is called or will it override the previous one? – Skynet Jan 22 '14 at 12:11
  • I am going to write a full fledged tutorial about this Cron Job thing on Android once I am done getting the facts right. The documentation is too scarce. – Skynet Jan 22 '14 at 12:14
  • @AstralProjection: The previous alarm will be replaced. You're right that the application stopped state doesn't seem to be fully documented, but the other issues are all documented at the appropriate place. – corsair992 Jan 22 '14 at 12:40
  • Also what would be appropriate to do when a user starts the app at the exact same time when the service is running and updating the DB. Currently in this scenario ( As I have acquired an EXCLUSIVE lock on DB) blank fields are displayed. – Skynet Jan 22 '14 at 13:22
  • @AstralProjection: You can display a loading progress bar or similar indicator, as you should be doing in any case. – corsair992 Jan 22 '14 at 13:33
  • Though the service is running in the background, how can I update the foreground. I will have to check on that. Also an idea I have is to create a duplicate DB and swap it with the original one at the scheduled time. That way the down time will be minimal. though I am not sure how feasible that would be. Hey also I want to thank you :) – Skynet Jan 22 '14 at 13:48
  • @AstralProjection: You don't need to interact with your `Activity` from the `Service`; just start a loading progress bar before querying the database, and remove it when the query completes (which will happen after the DB is unlocked). As for making the changes in a temp file, that's exactly how [write-ahead logging](http://www.sqlite.org/wal.html) works, and it does provide better overall performance than the traditional rollback journal. Also, you're welcome, and you might want to thank @HarshitJain too by giving him an upvote and the bounty, since he did answer your questions correctly. – corsair992 Jan 22 '14 at 14:18
-2

I think, you can write a Service something similar to the below one (This will do your task for every 5 minutes) and start the Service from the BroadcastReceiver which listens to BOOT_COMPLETED state.

public class YourService extends IntentService {

    public YourService() {
        super("YourService");
    }

    public YourService(String name) {
        super(name);
    }

    private static Timer timer = new Timer();

    private class mainTask extends TimerTask {
        public void run() {
            // TASK WHATEVER YOU WANT TO DO 
        }
    }

    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        timer.scheduleAtFixedRate(new mainTask(), 0, 300000);  // 5 minutes
    }
}
Manjunath
  • 2,063
  • 2
  • 29
  • 60
  • Though I appreciate this idea, but it drains the battery, every five minutes is inefficient. I know the time when my data gets stale, why not code something which updates the data exactly at the same time? Without the user ever getting to know what is happening. – Skynet Jan 22 '14 at 10:31
  • I agree. But, IntentService will run only if the app is running. If the app is closed, it will be terminated automatically. – Manjunath Jan 22 '14 at 11:04
  • That is where an Alarm Manager comes into picture. Refer to the setup() method in the MainActivity in the problem code above. – Skynet Jan 22 '14 at 11:41