1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 package org.mozilla.mozstumbler.service.uploadthread;
6 
7 import android.app.AlarmManager;
8 import android.app.IntentService;
9 import android.app.PendingIntent;
10 import android.content.BroadcastReceiver;
11 import android.content.Context;
12 import android.content.Intent;
13 import android.util.Log;
14 
15 import org.mozilla.mozstumbler.service.AppGlobals;
16 import org.mozilla.mozstumbler.service.Prefs;
17 import org.mozilla.mozstumbler.service.stumblerthread.datahandling.DataStorageManager;
18 import org.mozilla.mozstumbler.service.utils.NetworkUtils;
19 
20 // Only if data is queued and device awake: check network availability and upload.
21 // MozStumbler use: this alarm is periodic and repeating.
22 // Fennec use: The alarm is single-shot and it is set to run -if there is data in the queue-
23 // under these conditions:
24 // 1) Fennec start/pause (actually gecko start which is ~4 sec after Fennec start).
25 // 2) Changing the pref in Fennec to stumble or not.
26 // 3) Boot intent (and SD card app available intent).
27 //
28 // Threading:
29 // - scheduled from the stumbler thread
30 // - triggered from the main thread
31 // - actual work is done the upload thread (AsyncUploader)
32 public class UploadAlarmReceiver extends BroadcastReceiver {
33     private static final String LOG_TAG = AppGlobals.makeLogTag(UploadAlarmReceiver.class.getSimpleName());
34     private static final String EXTRA_IS_REPEATING = "is_repeating";
35     private static boolean sIsAlreadyScheduled;
36 
UploadAlarmReceiver()37     public UploadAlarmReceiver() {}
38 
39     public static class UploadAlarmService extends IntentService {
40 
UploadAlarmService(String name)41         public UploadAlarmService(String name) {
42             super(name);
43             // makes the service START_NOT_STICKY, that is, the service is not auto-restarted
44             setIntentRedelivery(false);
45         }
46 
UploadAlarmService()47         public UploadAlarmService() {
48             this(LOG_TAG);
49         }
50 
51         @Override
onHandleIntent(Intent intent)52         protected void onHandleIntent(Intent intent) {
53             if (intent == null) {
54                 return;
55             }
56             boolean isRepeating = intent.getBooleanExtra(EXTRA_IS_REPEATING, true);
57             if (DataStorageManager.getInstance() == null) {
58                 DataStorageManager.createGlobalInstance(this, null);
59             }
60             upload(isRepeating);
61         }
62 
upload(boolean isRepeating)63         void upload(boolean isRepeating) {
64             if (!isRepeating) {
65                 sIsAlreadyScheduled = false;
66             }
67 
68             // Defensive approach: if it is too old, delete all data
69             long oldestMs = DataStorageManager.getInstance().getOldestBatchTimeMs();
70             int maxWeeks = DataStorageManager.getInstance().getMaxWeeksStored();
71             if (oldestMs > 0) {
72                 long currentTime = System.currentTimeMillis();
73                 long msPerWeek = 604800 * 1000;
74                 if (currentTime - oldestMs > maxWeeks * msPerWeek) {
75                     DataStorageManager.getInstance().deleteAll();
76                     UploadAlarmReceiver.cancelAlarm(this, isRepeating);
77                     return;
78                 }
79             }
80 
81             NetworkUtils networkUtils = new NetworkUtils(this);
82             if (networkUtils.isWifiAvailable() &&
83                 !AsyncUploader.isUploading()) {
84                 Log.d(LOG_TAG, "Alarm upload(), call AsyncUploader");
85                 AsyncUploader.AsyncUploadArgs settings =
86                     new AsyncUploader.AsyncUploadArgs(networkUtils,
87                             Prefs.getInstance(this).getWifiScanAlways(),
88                             Prefs.getInstance(this).getUseWifiOnly());
89                 AsyncUploader uploader = new AsyncUploader(settings, null);
90                 uploader.setNickname(Prefs.getInstance(this).getNickname());
91                 uploader.execute();
92                 // we could listen for completion and cancel, instead, cancel on next alarm when db empty
93             }
94         }
95     }
96 
createIntent(Context c, boolean isRepeating)97     static PendingIntent createIntent(Context c, boolean isRepeating) {
98         Intent intent = new Intent(c, UploadAlarmReceiver.class);
99         intent.putExtra(EXTRA_IS_REPEATING, isRepeating);
100         PendingIntent pi = PendingIntent.getBroadcast(c, 0, intent, 0);
101         return pi;
102     }
103 
cancelAlarm(Context c, boolean isRepeating)104     public static void cancelAlarm(Context c, boolean isRepeating) {
105         Log.d(LOG_TAG, "cancelAlarm");
106         // this is to stop scheduleAlarm from constantly rescheduling, not to guard cancellation.
107         sIsAlreadyScheduled = false;
108         AlarmManager alarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
109         PendingIntent pi = createIntent(c, isRepeating);
110         alarmManager.cancel(pi);
111     }
112 
scheduleAlarm(Context c, long secondsToWait, boolean isRepeating)113     public static void scheduleAlarm(Context c, long secondsToWait, boolean isRepeating) {
114         if (sIsAlreadyScheduled) {
115             return;
116         }
117 
118         long intervalMsec = secondsToWait * 1000;
119         Log.d(LOG_TAG, "schedule alarm (ms):" + intervalMsec);
120 
121         sIsAlreadyScheduled = true;
122         AlarmManager alarmManager = (AlarmManager) c.getSystemService(Context.ALARM_SERVICE);
123         PendingIntent pi = createIntent(c, isRepeating);
124 
125         long triggerAtMs = System.currentTimeMillis() + intervalMsec;
126         if (isRepeating) {
127             alarmManager.setInexactRepeating(AlarmManager.RTC, triggerAtMs, intervalMsec, pi);
128         } else {
129             alarmManager.set(AlarmManager.RTC, triggerAtMs, pi);
130         }
131     }
132 
133     @Override
onReceive(final Context context, Intent intent)134     public void onReceive(final Context context, Intent intent) {
135         Intent startServiceIntent = new Intent(context, UploadAlarmService.class);
136         context.startService(startServiceIntent);
137     }
138 }
139