/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.gecko.background.announcements; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import org.mozilla.gecko.background.BackgroundConstants; import org.mozilla.gecko.sync.Logger; import android.app.AlarmManager; import android.app.IntentService; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; /** * A service which listens to broadcast intents from the system and from the * browser, registering or unregistering the main * {@link AnnouncementsStartReceiver} with the {@link AlarmManager}. */ public class AnnouncementsBroadcastService extends IntentService { private static final String WORKER_THREAD_NAME = "AnnouncementsBroadcastServiceWorker"; private static final String LOG_TAG = "AnnounceBrSvc"; public AnnouncementsBroadcastService() { super(WORKER_THREAD_NAME); } private void toggleAlarm(final Context context, boolean enabled) { Logger.info(LOG_TAG, (enabled ? "R" : "Unr") + "egistering announcements broadcast receiver..."); final AlarmManager alarm = getAlarmManager(context); final Intent service = new Intent(context, AnnouncementsStartReceiver.class); final PendingIntent pending = PendingIntent.getBroadcast(context, 0, service, PendingIntent.FLAG_CANCEL_CURRENT); if (!enabled) { alarm.cancel(pending); return; } final long firstEvent = System.currentTimeMillis(); final long pollInterval = getPollInterval(context); Logger.info(LOG_TAG, "Setting inexact repeating alarm for interval " + pollInterval); alarm.setInexactRepeating(AlarmManager.RTC, firstEvent, pollInterval, pending); } private static AlarmManager getAlarmManager(Context context) { return (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); } /** * Record the last launch time of our version of Fennec. * * @param context * the <code>Context</code> to use to gain access to * <code>SharedPreferences</code>. */ public static void recordLastLaunch(final Context context) { final long now = System.currentTimeMillis(); final SharedPreferences preferences = context.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, BackgroundConstants.SHARED_PREFERENCES_MODE); preferences.edit().putLong(AnnouncementsConstants.PREF_LAST_LAUNCH, now).commit(); } public static long getPollInterval(final Context context) { SharedPreferences preferences = context.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, BackgroundConstants.SHARED_PREFERENCES_MODE); return preferences.getLong(AnnouncementsConstants.PREF_ANNOUNCE_FETCH_INTERVAL_MSEC, AnnouncementsConstants.DEFAULT_ANNOUNCE_FETCH_INTERVAL_MSEC); } public static void setPollInterval(final Context context, long interval) { SharedPreferences preferences = context.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, BackgroundConstants.SHARED_PREFERENCES_MODE); preferences.edit().putLong(AnnouncementsConstants.PREF_ANNOUNCE_FETCH_INTERVAL_MSEC, interval).commit(); } @Override protected void onHandleIntent(Intent intent) { Logger.setThreadLogTag(AnnouncementsConstants.GLOBAL_LOG_TAG); final String action = intent.getAction(); Logger.debug(LOG_TAG, "Broadcast onReceive. Intent is " + action); if (AnnouncementsConstants.ACTION_ANNOUNCEMENTS_PREF.equals(action)) { handlePrefIntent(intent); return; } if (Intent.ACTION_BOOT_COMPLETED.equals(action) || Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(action)) { handleSystemLifetimeIntent(); return; } // Failure case. Logger.warn(LOG_TAG, "Unknown intent " + action); } /** * Handle one of the system intents to which we listen to launch our service * without the browser being opened. * * To avoid tight coupling to Fennec, we use reflection to find * <code>GeckoPreferences</code>, invoking the same code path that * <code>GeckoApp</code> uses on startup to send the <i>other</i> * notification to which we listen. * * All of this is neatly wrapped in <code>try…catch</code>, so this code * will run safely without a Firefox build installed. */ protected void handleSystemLifetimeIntent() { // Ask the browser to tell us the current state of the preference. try { Class<?> geckoPreferences = Class.forName(BackgroundConstants.GECKO_PREFERENCES_CLASS); Method broadcastSnippetsPref = geckoPreferences.getMethod(BackgroundConstants.GECKO_BROADCAST_METHOD, Context.class); broadcastSnippetsPref.invoke(null, this); return; } catch (ClassNotFoundException e) { Logger.error(LOG_TAG, "Class " + BackgroundConstants.GECKO_PREFERENCES_CLASS + " not found!"); return; } catch (NoSuchMethodException e) { Logger.error(LOG_TAG, "Method " + BackgroundConstants.GECKO_PREFERENCES_CLASS + "/" + BackgroundConstants.GECKO_BROADCAST_METHOD + " not found!"); return; } catch (IllegalArgumentException e) { // Fall through. } catch (IllegalAccessException e) { // Fall through. } catch (InvocationTargetException e) { // Fall through. } Logger.error(LOG_TAG, "Got exception invoking " + BackgroundConstants.GECKO_BROADCAST_METHOD + "."); } /** * Handle the intent sent by the browser when it wishes to notify us * of the value of the user preference. Look at the value and toggle the * alarm service accordingly. */ protected void handlePrefIntent(Intent intent) { if (!intent.hasExtra("enabled")) { Logger.warn(LOG_TAG, "Got ANNOUNCEMENTS_PREF intent without enabled. Ignoring."); return; } final boolean enabled = intent.getBooleanExtra("enabled", true); Logger.debug(LOG_TAG, intent.getStringExtra("branch") + "/" + intent.getStringExtra("pref") + " = " + (intent.hasExtra("enabled") ? enabled : "")); toggleAlarm(this, enabled); // Primarily intended for debugging and testing, but this doesn't do any harm. if (!enabled) { Logger.info(LOG_TAG, "!enabled: clearing last fetch."); final SharedPreferences sharedPreferences = this.getSharedPreferences(AnnouncementsConstants.PREFS_BRANCH, BackgroundConstants.SHARED_PREFERENCES_MODE); final Editor editor = sharedPreferences.edit(); editor.remove(AnnouncementsConstants.PREF_LAST_FETCH_LOCAL_TIME); editor.remove(AnnouncementsConstants.PREF_EARLIEST_NEXT_ANNOUNCE_FETCH); editor.commit(); } } }