package akechi.projectl; import android.accounts.Account; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.media.RingtoneManager; import android.os.Binder; import android.os.IBinder; import android.os.IInterface; import android.os.Parcel; import android.os.RemoteException; import android.os.Vibrator; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.content.Loader; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; import com.google.api.client.repackaged.com.google.common.base.Strings; import com.google.api.client.util.DateTime; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import java.io.FileDescriptor; import java.io.IOException; import java.io.Serializable; import java.text.DateFormat; import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.annotation.Nullable; import akechi.projectl.async.LingrTaskLoader; import jp.michikusa.chitose.lingr.Events; import jp.michikusa.chitose.lingr.LingrClient; import jp.michikusa.chitose.lingr.LingrException; import jp.michikusa.chitose.lingr.Room; public class CometService extends Service { public static interface OnCometEventListener { void onCometEvent(Events events); } @Override public IBinder onBind(Intent intent) { throw new AssertionError("Cannot bind this service"); } @Override public void onCreate() { super.onCreate(); this.loader= this.newSubscribeLoader(); final LocalBroadcastManager lbMan= LocalBroadcastManager.getInstance(this.getApplicationContext()); { final IntentFilter ifilter= new IntentFilter(Event.AccountChange.ACTION); final BroadcastReceiver receiver= new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { final CometService that= CometService.this; final Loader<Void> oldLoader= that.loader; oldLoader.abandon(); that.loader= that.newSubscribeLoader(); that.loader.forceLoad(); } }; lbMan.registerReceiver(receiver, ifilter); this.receivers.add(receiver); } { final IntentFilter ifilter= new IntentFilter(Event.PreferenceChange.ACTION); final BroadcastReceiver receiver= new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { final CometService that= CometService.this; final Loader<Void> oldLoader= that.loader; oldLoader.abandon(); that.loader= that.newSubscribeLoader(); that.loader.forceLoad(); } }; lbMan.registerReceiver(receiver, ifilter); this.receivers.add(receiver); } { final IntentFilter ifilter= new IntentFilter(CometService.class.getCanonicalName()); final BroadcastReceiver receiver= new BroadcastReceiver(){ @Override public void onReceive(Context context, Intent intent) { final AppContext appContext= (AppContext)CometService.this.getApplicationContext(); final Iterable<Pattern> patterns= this.buildPatterns(appContext); final Events events= (Events)intent.getSerializableExtra("events"); for(final Events.Event event : events.getEvents()) { if(event.getMessage() == null) { continue; } final Room.Message message= event.getMessage(); // exclude message by myself if(appContext.getAccount().name.equals(message.getSpeakerId())) { continue; } // exclude message by bot if("bot".equals(message.getType())) { continue; } final boolean found= Iterables.any(patterns, new Predicate<Pattern>(){ @Override public boolean apply(Pattern input) { return input.matcher(message.getText()).find(); } }); if(found) { final Intent onClickIntent= new Intent(appContext, HomeActivity.class); onClickIntent.setAction(Event.OnNotificationTapped.ACTION); onClickIntent.putExtra(Event.OnNotificationTapped.KEY_ACCOUNT_NAME, appContext.getAccount().name); onClickIntent.putExtra(Event.OnNotificationTapped.KEY_ROOM_ID, message.getRoom()); onClickIntent.putExtra(Event.OnNotificationTapped.KEY_MESSAGE_ID, message.getId()); final PendingIntent pendingIntent= PendingIntent.getActivity(appContext, 0, onClickIntent, PendingIntent.FLAG_ONE_SHOT); final Notification notif= new NotificationCompat.Builder(CometService.this) .setSmallIcon(R.drawable.icon_notif_star) .setContentTitle("Found the highlighted message") .setContentText("Room: " + message.getRoom()) .setSubText(message.getText()) .setContentInfo("From: " + message.getNickname()) .setTicker(DateFormat.getDateTimeInstance().format(new Date(new DateTime(message.getTimestamp()).getValue()))) .setAutoCancel(true) .setSound(RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)) .setVibrate(new long[]{1000, 1000}) .setContentIntent(pendingIntent) .build() ; NotificationManagerCompat.from(appContext) .notify(0, notif) ; } } } private Iterable<Pattern> buildPatterns(AppContext ctx) { final Iterable<String> patterns= Splitter.on(System.getProperty("line.separator")).split(ctx.getHighlightPattern()); if(Iterables.isEmpty(patterns)) { return null; } final Iterable<Pattern> transformed= Iterables.transform(patterns, new Function<String, Pattern>(){ @Override public Pattern apply(String input) { if(Strings.isNullOrEmpty(input)) { return null; } try { return Pattern.compile(input); } catch(PatternSyntaxException e) { return null; } } }); return ImmutableList.copyOf(Iterables.filter(transformed, Predicates.notNull())); } }; lbMan.registerReceiver(receiver, ifilter); this.receivers.add(receiver); } } @Override public int onStartCommand(Intent intent, int flags, int startId) { super.onStartCommand(intent, flags, startId); Log.i("CometService", "forceLoad()"); this.loader.forceLoad(); return START_STICKY; } @Override public void onDestroy() { super.onDestroy(); final LocalBroadcastManager lbMan= LocalBroadcastManager.getInstance(this.getApplicationContext()); for(final BroadcastReceiver receiver : this.receivers) { lbMan.unregisterReceiver(receiver); } this.receivers.clear(); } private void scheduleNext() { final PendingIntent intent= PendingIntent.getService(this, 0, new Intent(this, this.getClass()), 0); final AlarmManager alarmMan= (AlarmManager)this.getSystemService(ALARM_SERVICE); // re-invoke myself every 500ms alarmMan.set(AlarmManager.RTC, System.currentTimeMillis() + 500, intent); } private SubscribeLoader newSubscribeLoader() { return new SubscribeLoader(this){ @Override protected Void onLoadInBackground() { try { return super.onLoadInBackground(); } finally { CometService.this.scheduleNext(); return null; } } }; } private ObserveLoader newObserveLoader() { return new ObserveLoader(this){ @Override protected Void onLoadInBackground() { try { return super.onLoadInBackground(); } finally { CometService.this.scheduleNext(); } } @Override protected void onLoadingFailed(Throwable e) { if(e instanceof LingrException) { Log.e("CometService", "Oops, restarting service...", e); CometService.this.loader.abandon(); CometService.this.loader= CometService.this.newSubscribeLoader(); CometService.this.scheduleNext(); } else { super.onLoadingFailed(e); } } }; } private class SubscribeLoader extends LingrTaskLoader<Void> { public SubscribeLoader(Context context) { super(context); } @Override public Void loadInBackground(CharSequence authToken, LingrClient lingr) throws IOException, LingrException { final AppContext appContext= this.getApplicationContext(); final Account account= appContext.getAccount(); final Iterable<String> ids= appContext.getRoomIds(account); final Iterable<String> roomIds; if(Iterables.isEmpty(ids)) { roomIds= lingr.getRooms(authToken); } else { roomIds= ids; } lingr.unsubscribe(authToken, roomIds); final long counter= lingr.subscribe(authToken, true, roomIds); // sometimes, lingr returns 0 value for counter // and observe with counter = 0, cause errors Log.i("CometService", "counter = " + counter); if(counter <= 0) { // retry return null; } CometService.this.counter= counter; CometService.this.loader= CometService.this.newObserveLoader(); return null; } @Override protected void showMessage(CharSequence message) { // suppress message Log.i("CometService", "" + message); } } private class ObserveLoader extends LingrTaskLoader<Void> { public ObserveLoader(Context context) { super(context); } @Override public Void loadInBackground(CharSequence authToken, LingrClient lingr) throws IOException, LingrException { Log.i("CometService", "counter = " + CometService.this.counter); final Events events= lingr.observe(authToken, CometService.this.counter, 30, TimeUnit.MINUTES); // sometimes, lingr returns 0 value for counter // and observe with counter = 0, cause errors final long counter= events.getCounter(); Log.i("CometService", "counter = " + counter); if(counter <= 0) { // retry return null; } CometService.this.counter= events.getCounter(); final Intent intent= new Intent(CometService.class.getCanonicalName()); intent.putExtra("events", (Serializable) events); if(CometService.this.loader == this) { final LocalBroadcastManager lbMan= LocalBroadcastManager.getInstance(CometService.this.getApplicationContext()); lbMan.sendBroadcast(intent); } return null; } @Override protected void showMessage(CharSequence message) { // suppress message Log.i("CometService", "" + message); } } private long counter; private Loader<Void> loader; private final List<BroadcastReceiver> receivers= Lists.newLinkedList(); }