/* * This source is part of the * _____ ___ ____ * __ / / _ \/ _ | / __/___ _______ _ * / // / , _/ __ |/ _/_/ _ \/ __/ _ `/ * \___/_/|_/_/ |_/_/ (_)___/_/ \_, / * /___/ * repository. * * Copyright (C) 2014-2015 Carmen Alvarez (c@rmen.ca) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ca.rmen.android.networkmonitor.app.service.scheduler; import android.annotation.TargetApi; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Build; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import ca.rmen.android.networkmonitor.Constants; import ca.rmen.android.networkmonitor.util.Log; /** * Schedule a single Runnable periodically. Note that on KitKat devices (and emulator), the specified interval may not be respected, even if the target SDK is * less than 19 or methods like setExact are used. * * The implementation on KitKat is different compared to older devices. On older devices, the setRepeating method of AlarmManager is used, which results in our * task being executed at precise intervals. * On KitKat, there is no exact repeating method. So, we schedule a task one time, and when the task is executed, it reschedules itself. * * For more accurate scheduling, but possible more battery drain, use {@link ExecutorServiceScheduler}. */ public class AlarmManagerScheduler implements Scheduler { private static final String TAG = Constants.TAG + AlarmManagerScheduler.class.getSimpleName(); private static final String ACTION = TAG + "_action"; private PendingIntent mPendingIntent; private HandlerThread mHandlerThread; private AlarmManager mAlarmManager; private Context mContext; private int mInterval; private Runnable mRunnableImpl; @Override public void onCreate(Context context) { Log.v(TAG, "onCreate"); mContext = context; mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); // Register the broadcast receiver in a background thread mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); Handler handler = new Handler(mHandlerThread.getLooper()); mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(ACTION), null, handler); } @Override public void onDestroy() { Log.v(TAG, "onDestroy"); mContext.unregisterReceiver(mBroadcastReceiver); mAlarmManager.cancel(mPendingIntent); mHandlerThread.quit(); } @Override public void schedule(Runnable runnable, int interval) { Log.v(TAG, "schedule at interval " + interval); mRunnableImpl = runnable; Intent intent = new Intent(ACTION); mPendingIntent = PendingIntent.getBroadcast(mContext, TAG.hashCode(), intent, PendingIntent.FLAG_CANCEL_CURRENT); setInterval(interval); } @Override public void setInterval(int interval) { Log.v(TAG, "Set interval " + interval); mInterval = interval; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) scheduleAlarmKitKat(0); else mAlarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime(), interval, mPendingIntent); } @TargetApi(Build.VERSION_CODES.KITKAT) private void scheduleAlarmKitKat(int delay) { Log.v(TAG, "scheduleAlarmKitKat: delay=" + delay); mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + delay, mPendingIntent); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { Log.v(TAG, "onReceive: " + intent); // The AlarmManager called us. if (ACTION.equals(intent.getAction())) { // On KitKat we need to reschedule ourselves: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { Log.v(TAG, "rescheduling for kitkat"); scheduleAlarmKitKat(mInterval); } } try { Log.v(TAG, "Executing task"); mRunnableImpl.run(); } catch (Throwable t) { Log.v(TAG, "Error executing task: " + t.getMessage(), t); } } }; }