/**
* The MIT License (MIT)
* Copyright (c) 2012-2014 唐虞科技(TangyuSoft) Corporation
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.tangyu.component.service.remind;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Messenger;
import android.os.Parcel;
import android.util.Log;
import java.util.List;
/**
* Demo: {@link com.tangyu.component.demo.service.remind.TYRemindServiceImpl}
* @author binliu on 12/30/13.
*/
public abstract class TYRemindService extends IntentService {
/**
* Cancel current remind. you should be set to intent which is use to start service.
*/
public static final int CMD_REMIND_CANCEL = 10000000;
/**
* find next remind service. you should be set to intent which is use to start service.
*/
public static final int CMD_REMIND_NEXT = 10000001;
/**
* Reschedule remind service. you should be set to intent which is use to start service.
*/
public static final int CMD_REMIND_RESCHEDULE = 10000002;
/**
* The flag of Invalid command.
*/
public static final int CMD_REMIND_INVALID = -1;
/**
* The command of start service.
*/
public static final String INTENT_REMIND_COMMAND = "INTENT_REMIND_COMMAND";
/**
* @deprecated
* The data of send to BroadCastReceiver.
*/
public static final String INTENT_REMIND_DATA = "INTENT_REMIND_DATA";
/**
* The data of send to BroadCastReceiver. It was marshalled.<br>
* invoking {@link android.content.Intent#getByteArrayExtra(String)} to get byteArray object when broadcast received.<br>
*
* sample code:<br>
* <pre>
* Byte[] byteArrayExtra = intent.getByteArrayExtra(INTENT_REMIND_DATA_MARSHALLED);
* Parcel parcel = Parcel.obtain();
* parcel.unmarshall(byteArrayExtra, 0, byteArrayExtra.length);
* parcel.setDataPosition(0);
* TYRemindData remind = TYRemindData.CREATOR.createFromParcel(parcel);
* </pre>
*
* @see android.os.Parcel#marshall()
*/
public static final String INTENT_REMIND_DATA_MARSHALLED = "INTENT_REMIND_DATA_MARSHALLED";
/**
* Whether the properties remind data is missing or not.
*/
public static final String INTENT_REMIND_DATA_IS_MISSING = "INTENT_REMIND_DATA_IS_MISSING";
/**
* whether should stop service in {@link android.app.Service#onStartCommand(android.content.Intent, int, int)} or not.<br>
* default is stop.
*/
public static final String INTENT_SERVICE_FOCUSES_STOP = "INTENT_SERVICE_FOCUSES_STOP";
/**
* log print controller.
*/
private static final boolean isOutput = true;
/**
* data source
*/
private TYRemindServiceDataSourceAble mDataSource;
/**
* delegate
*/
private TYRemindServiceDelegateAble mDelegate;
/**
* use to communicate with binder activity.
*/
protected final Messenger mMessenger = new Messenger(new CommandHandler());
/**
* The notification encapsulated class.
*/
protected RemindWay mRemindWay;
protected AlarmManager mAlarm;
public TYRemindService() {
this(TYRemindService.class.getSimpleName());
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public TYRemindService(String name) {
super(name);
}
@Override
public void onCreate() {
super.onCreate();
mDataSource = remindServiceDataSource();
mDelegate = remindServiceDelegate();
mRemindWay = new RemindWay(this);
mAlarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
}
@Override
public IBinder onBind(Intent intent) {
return mMessenger.getBinder();
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
}
@Override
protected void onHandleIntent(Intent intent) {
output("service start!!!!! PID = " + android.os.Process.myPid() + " | request command = " + intent.getIntExtra(INTENT_REMIND_COMMAND, CMD_REMIND_INVALID));
if (null != intent && intent.hasExtra(INTENT_REMIND_COMMAND)) {
int command = intent.getIntExtra(INTENT_REMIND_COMMAND, CMD_REMIND_INVALID);
responseCommand(command);
}
}
private void responseCommand(int command) {
if (CMD_REMIND_INVALID != command) {
mDelegate.willResponseCommand(command);
}
switch (command) {
case CMD_REMIND_CANCEL:
cancelAllRemind();
break;
case CMD_REMIND_RESCHEDULE:
{
// 1. cancel current
cancelAllRemind();
final long currentTime = System.currentTimeMillis();
// 2. find and notify next
TYRemindData nextRemind = mDataSource.next(currentTime);
if (null != nextRemind) {
//3. focus to roll all remind to property
mDataSource.focusToRollAllRemindStatusToPropertyBaseOnTime(nextRemind);
//4. notify next
notifyRemind(nextRemind, nextRemind.getmRemindTime(), false);
}
}
break;
case CMD_REMIND_NEXT:
{
// 1. cancel current
cancelAllRemind();
final long currentTime = System.currentTimeMillis();
// 2. find next
TYRemindData nextRemind = mDataSource.next(currentTime);
// 3. focus to roll remind status to property which is Reminding.
mDataSource.focusToRollRemindingStatusToPropertyIfNeed(nextRemind);
if (null != nextRemind) {
final long notifyTime = nextRemind.getmRemindTime();
// 4. find and notify missing if need
List<TYRemindData> missingReminds = mDataSource.missing(nextRemind, notifyTime);
if (null != missingReminds) {
for (TYRemindData missing : missingReminds) {
notifyRemind(missing, currentTime, true);
}
}
// 5. notify next.
notifyRemind(nextRemind, notifyTime, false);
}
}
break;
}
if (CMD_REMIND_INVALID != command) {
mDelegate.didResponseCommand(command);
}
}
/**
* cancel all remind
*/
protected final void cancelAllRemind() {
mRemindWay.cancelAll(mDataSource.intentOfCancelAll());
mDataSource.cancelAll();
}
/**
* notify remind
* @param lastRemind
*/
private void notifyRemind(TYRemindData lastRemind, long notifyTime, boolean isMissingRemind) {
if (null == lastRemind || notifyTime <= 0) return;
Intent it = mDataSource.intentOfNotify(lastRemind.getmRemindId());
// HERE
// if you code like this
// "
// it.putExtra(INTENT_REMIND_DATA, lastRemind);
// "
// that will be get error: "E/Parcel: Class not found when unmarshalling: bala-bala W/Intent: Failure filling in extras bala-bala"
// see http://nocturnaldev.com/blog/2013/09/01/parcelable-in-pendingintent/
Parcel parcel = Parcel.obtain();
lastRemind.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
it.putExtra(INTENT_REMIND_DATA_MARSHALLED, parcel.marshall());
it.putExtra(INTENT_REMIND_COMMAND, CMD_REMIND_RESCHEDULE);
it.putExtra(INTENT_REMIND_DATA_IS_MISSING, isMissingRemind);
// mRemindWay.alarm(it, notifyTime);
PendingIntent pi = PendingIntent.getBroadcast(this, 0, it,
PendingIntent.FLAG_ONE_SHOT);
mAlarm.set(AlarmManager.RTC_WAKEUP, notifyTime, pi);
}
protected void output(String msg) {
if (isOutput) Log.v(getPackageName(), msg);
}
/**
* get the data source from subclass
* @return
*/
public abstract TYRemindServiceDataSourceAble remindServiceDataSource();
/**
* get the delegate from subclass
* @return
*/
public abstract TYRemindServiceDelegateAble remindServiceDelegate();
public static interface TYRemindServiceDataSourceAble<T extends TYRemindData> {
/**
* cancel all remind which is running.
*/
public void cancelAll();
/**
* find out next remind base on param of currentTime.
* subclass will be change {@link com.tangyu.component.service.remind.TYRemindData#mRemindState} from
* {@link com.tangyu.component.service.remind.TYRemindData#REMIND_STATE_UNREMIND} to
* {@link com.tangyu.component.service.remind.TYRemindData#REMIND_STATE_REMINDDING}<br>
* @param currentTime
* @return
*/
public T next(long currentTime);
/**
* Find out missing remind.<br>
* subclass will be change {@link com.tangyu.component.service.remind.TYRemindData#mRemindState} from
* {@link com.tangyu.component.service.remind.TYRemindData#REMIND_STATE_UNREMIND} to
* {@link com.tangyu.component.service.remind.TYRemindData#REMIND_STATE_REMINDED}<br>
*
* @param nextRemind
* @param remindTime
* @return
*/
public List<T> missing(T nextRemind, long remindTime);
/**
* change the status from Reminding to UnRemind or Reminded.
* @param nextRemind
*/
public void focusToRollRemindingStatusToPropertyIfNeed(T nextRemind);
/**
* change all remind status.
* @param nextRemind
*/
public void focusToRollAllRemindStatusToPropertyBaseOnTime(T nextRemind);
/**
* The intent which will be notify.<br>
*
* @param remindId
* @return
*/
public Intent intentOfNotify(int remindId);
/**
* The intent which will be cancel.<br></>
* The rules is base on {@link Intent#filterEquals(android.content.Intent)}
*
* @return
*/
public Intent[] intentOfCancelAll();
}
public static interface TYRemindServiceDelegateAble {
/**
* This will be invoked in {@link android.app.Service#onStartCommand(android.content.Intent, int, int)} on work thread, before response “command”
* <p>In this stage, you can do something like load and filter remind data. and back up the remind status of them.
*
* @param command
*/
public void willResponseCommand(int command);
/**
* This will be invoked in {@link android.app.Service#onStartCommand(android.content.Intent, int, int)} on work thread, after response “command”
* <p>In this stage, you can save status of remind data to file or database.
*
* @param command
*/
public void didResponseCommand(int command);
}
public class CommandHandler extends Handler {
@Override
public void handleMessage(Message msg) {
responseCommand(msg.what);
super.handleMessage(msg);
}
}
}