|
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.gecko.background.announcements; |
|
6 |
|
7 import org.mozilla.gecko.background.BackgroundService; |
|
8 import org.mozilla.gecko.background.common.GlobalConstants; |
|
9 import org.mozilla.gecko.background.common.log.Logger; |
|
10 |
|
11 import android.app.AlarmManager; |
|
12 import android.app.PendingIntent; |
|
13 import android.content.Context; |
|
14 import android.content.Intent; |
|
15 import android.content.SharedPreferences; |
|
16 import android.content.SharedPreferences.Editor; |
|
17 |
|
18 /** |
|
19 * A service which listens to broadcast intents from the system and from the |
|
20 * browser, registering or unregistering the main |
|
21 * {@link AnnouncementsService} with the {@link AlarmManager}. |
|
22 */ |
|
23 public class AnnouncementsBroadcastService extends BackgroundService { |
|
24 private static final String WORKER_THREAD_NAME = "AnnouncementsBroadcastServiceWorker"; |
|
25 private static final String LOG_TAG = "AnnounceBrSvc"; |
|
26 |
|
27 public AnnouncementsBroadcastService() { |
|
28 super(WORKER_THREAD_NAME); |
|
29 } |
|
30 |
|
31 protected static SharedPreferences getSharedPreferences(Context context) { |
|
32 return context.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, |
|
33 GlobalConstants.SHARED_PREFERENCES_MODE); |
|
34 } |
|
35 |
|
36 protected SharedPreferences getSharedPreferences() { |
|
37 return this.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, |
|
38 GlobalConstants.SHARED_PREFERENCES_MODE); |
|
39 } |
|
40 |
|
41 private void toggleAlarm(final Context context, boolean enabled) { |
|
42 final Class<?> serviceClass = AnnouncementsService.class; |
|
43 Logger.info(LOG_TAG, (enabled ? "R" : "Unr") + "egistering " + serviceClass.getSimpleName() + |
|
44 "."); |
|
45 |
|
46 final Intent service = new Intent(context, serviceClass); |
|
47 final PendingIntent pending = PendingIntent.getService(context, 0, service, |
|
48 PendingIntent.FLAG_CANCEL_CURRENT); |
|
49 |
|
50 if (!enabled) { |
|
51 cancelAlarm(pending); |
|
52 return; |
|
53 } |
|
54 |
|
55 final long pollInterval = getPollInterval(context); |
|
56 scheduleAlarm(pollInterval, pending); |
|
57 } |
|
58 |
|
59 /** |
|
60 * Record the last launch time of our version of Fennec. |
|
61 * |
|
62 * @param context |
|
63 * the <code>Context</code> to use to gain access to |
|
64 * <code>SharedPreferences</code>. |
|
65 */ |
|
66 public static void recordLastLaunch(final Context context) { |
|
67 final long now = System.currentTimeMillis(); |
|
68 final SharedPreferences preferences = getSharedPreferences(context); |
|
69 |
|
70 // One of several things might be true, according to our logs: |
|
71 // |
|
72 // * The new current time is older than the last |
|
73 // * … or way in the future |
|
74 // * … or way in the distant past |
|
75 // * … or it's reasonable. |
|
76 // |
|
77 // Furthermore, when we come to calculate idle we might find that the clock |
|
78 // is dramatically different — that the current time is thirteen years older |
|
79 // than our saved timestamp (system clock resets to 2000 on battery change), |
|
80 // or it's thirty years in the future (previous timestamp was saved as 0). |
|
81 // |
|
82 // We should try to do something vaguely sane in these situations. |
|
83 long previous = preferences.getLong(AnnouncementsConstants.PREF_LAST_LAUNCH, -1); |
|
84 if (previous == -1) { |
|
85 Logger.debug(LOG_TAG, "No previous launch recorded."); |
|
86 } |
|
87 |
|
88 if (now < GlobalConstants.BUILD_TIMESTAMP_MSEC) { |
|
89 Logger.warn(LOG_TAG, "Current time " + now + " is older than build date " + |
|
90 GlobalConstants.BUILD_TIMESTAMP_MSEC + ". Ignoring until clock is corrected."); |
|
91 return; |
|
92 } |
|
93 |
|
94 if (now > AnnouncementsConstants.LATEST_ACCEPTED_LAUNCH_TIMESTAMP_MSEC) { |
|
95 Logger.warn(LOG_TAG, "Launch time " + now + " is later than max sane launch timestamp " + |
|
96 AnnouncementsConstants.LATEST_ACCEPTED_LAUNCH_TIMESTAMP_MSEC + |
|
97 ". Ignoring until clock is corrected."); |
|
98 return; |
|
99 } |
|
100 |
|
101 if (previous > now) { |
|
102 Logger.debug(LOG_TAG, "Previous launch " + previous + " later than current time " + |
|
103 now + ", but new time is sane. Accepting new time."); |
|
104 } |
|
105 |
|
106 preferences.edit().putLong(AnnouncementsConstants.PREF_LAST_LAUNCH, now).commit(); |
|
107 } |
|
108 |
|
109 public static long getPollInterval(final Context context) { |
|
110 final SharedPreferences preferences = getSharedPreferences(context); |
|
111 return preferences.getLong(AnnouncementsConstants.PREF_ANNOUNCE_FETCH_INTERVAL_MSEC, AnnouncementsConstants.DEFAULT_ANNOUNCE_FETCH_INTERVAL_MSEC); |
|
112 } |
|
113 |
|
114 public static void setPollInterval(final Context context, long interval) { |
|
115 final SharedPreferences preferences = getSharedPreferences(context); |
|
116 preferences.edit().putLong(AnnouncementsConstants.PREF_ANNOUNCE_FETCH_INTERVAL_MSEC, interval).commit(); |
|
117 } |
|
118 |
|
119 @Override |
|
120 protected void onHandleIntent(Intent intent) { |
|
121 Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG); |
|
122 |
|
123 // Intent can be null. Bug 1025937. |
|
124 if (intent == null) { |
|
125 Logger.debug(LOG_TAG, "Short-circuiting on null intent."); |
|
126 return; |
|
127 } |
|
128 |
|
129 final String action = intent.getAction(); |
|
130 Logger.debug(LOG_TAG, "Broadcast onReceive. Intent is " + action); |
|
131 |
|
132 if (AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF.equals(action)) { |
|
133 handlePrefIntent(intent); |
|
134 return; |
|
135 } |
|
136 |
|
137 if (Intent.ACTION_BOOT_COMPLETED.equals(action) || |
|
138 Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { |
|
139 BackgroundService.reflectContextToFennec(this, |
|
140 GlobalConstants.GECKO_PREFERENCES_CLASS, |
|
141 GlobalConstants.GECKO_BROADCAST_ANNOUNCEMENTS_PREF_METHOD); |
|
142 return; |
|
143 } |
|
144 |
|
145 // Failure case. |
|
146 Logger.warn(LOG_TAG, "Unknown intent " + action); |
|
147 } |
|
148 |
|
149 /** |
|
150 * Handle the intent sent by the browser when it wishes to notify us |
|
151 * of the value of the user preference. Look at the value and toggle the |
|
152 * alarm service accordingly. |
|
153 * |
|
154 * @param intent must be non-null. |
|
155 */ |
|
156 private void handlePrefIntent(Intent intent) { |
|
157 if (!intent.hasExtra("enabled")) { |
|
158 Logger.warn(LOG_TAG, "Got ANNOUNCEMENTS_PREF intent without enabled. Ignoring."); |
|
159 return; |
|
160 } |
|
161 |
|
162 final boolean enabled = intent.getBooleanExtra("enabled", true); |
|
163 Logger.debug(LOG_TAG, intent.getStringExtra("branch") + "/" + |
|
164 intent.getStringExtra("pref") + " = " + |
|
165 (intent.hasExtra("enabled") ? enabled : "")); |
|
166 |
|
167 toggleAlarm(this, enabled); |
|
168 |
|
169 // Primarily intended for debugging and testing, but this doesn't do any harm. |
|
170 if (!enabled) { |
|
171 Logger.info(LOG_TAG, "!enabled: clearing last fetch."); |
|
172 final SharedPreferences sharedPreferences = getSharedPreferences(); |
|
173 final Editor editor = sharedPreferences.edit(); |
|
174 editor.remove(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME); |
|
175 editor.remove(AnnouncementsConstants.PREF_EARLIEST_NEXT_ANNOUNCE_FETCH); |
|
176 editor.commit(); |
|
177 } |
|
178 } |
|
179 } |