package org.yaxim.androidclient.service; import java.util.HashMap; import java.util.Map; import org.jivesoftware.smack.packet.Message; import org.yaxim.androidclient.chat.ChatWindow; import org.yaxim.androidclient.chat.MUCChatWindow; import org.yaxim.androidclient.data.YaximConfiguration; import org.yaxim.androidclient.util.LogConstants; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.media.RingtoneManager; import android.net.Uri; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.os.Vibrator; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.CarExtender; import android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation; import android.support.v4.app.NotificationManagerCompat; import android.support.v4.app.RemoteInput; import android.support.v4.app.TaskStackBuilder; import android.util.Log; import android.widget.Toast; import org.yaxim.androidclient.R; public abstract class GenericService extends Service { private static final String TAG = "yaxim.Service"; private static final String APP_NAME = "yaxim"; private static final int MAX_TICKER_MSG_LEN = 45; protected NotificationManagerCompat mNotificationMGR; private Notification mNotification; private Vibrator mVibrator; private Intent mNotificationIntent; protected WakeLock mWakeLock; //private int mNotificationCounter = 0; protected Map<String, Integer> notificationCount = new HashMap<String, Integer>(2); protected Map<String, Integer> notificationId = new HashMap<String, Integer>(2); protected Map<String, StringBuilder> notificationBigText = new HashMap<String, StringBuilder>(2); protected static int SERVICE_NOTIFICATION = 1; protected int lastNotificationId = 2; protected YaximConfiguration mConfig; @Override public void onCreate() { Log.i(TAG, "called onCreate()"); super.onCreate(); mConfig = org.yaxim.androidclient.YaximApplication.getConfig(this); mVibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); mWakeLock = ((PowerManager)getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, APP_NAME); addNotificationMGR(); } @Override public void onDestroy() { Log.i(TAG, "called onDestroy()"); super.onDestroy(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "called onStartCommand()"); return START_STICKY; } private void addNotificationMGR() { mNotificationMGR = NotificationManagerCompat.from(this); mNotificationIntent = new Intent(this, ChatWindow.class); } protected void notifyClient(String[] jid, String fromUserName, String message, boolean showNotification, boolean silent_notification, Message.Type msgType) { String fromJid = jid[0]; boolean isMuc = (msgType==Message.Type.groupchat); boolean is_error = (msgType==Message.Type.error); if (message == null) { clearNotification(fromJid); return; } if (!showNotification) { if (is_error) shortToastNotify(getString(R.string.notification_error) + " " + message); // only play sound and return try { Uri sound = isMuc? mConfig.notifySoundMuc : mConfig.notifySound; if (!silent_notification && !Uri.EMPTY.equals(sound)) RingtoneManager.getRingtone(getApplicationContext(), sound).play(); } catch (NullPointerException e) { // ignore NPE when ringtone was not found } return; } mWakeLock.acquire(); // Override silence when notification is created initially // if there is no open notification for that JID, and we get a "silent" // one (i.e. caused by an incoming carbon message), we still ring/vibrate, // but only once. As long as the user ignores the notifications, no more // sounds are made. When the user opens the chat window, the counter is // reset and a new sound can be made. if (silent_notification && !notificationCount.containsKey(fromJid)) { silent_notification = false; } int notifyId = 0; if (notificationId.containsKey(fromJid)) { notifyId = notificationId.get(fromJid); } else { lastNotificationId++; notifyId = lastNotificationId; notificationId.put(fromJid, Integer.valueOf(notifyId)); } // /me processing boolean slash_me = message.startsWith("/me "); if (slash_me) { message = String.format("\u25CF %s %s", isMuc ? jid[1] : fromUserName, message.substring(4)); } StringBuilder msg_long = notificationBigText.get(fromJid); if (msg_long == null) { msg_long = new StringBuilder(); notificationBigText.put(fromJid, msg_long); } else msg_long.append("\n"); if (isMuc && !slash_me) msg_long.append(jid[1]).append("▶ "); msg_long.append(message); setNotification(fromJid, jid[1], fromUserName, message, msg_long.toString(), is_error, isMuc); setLEDNotification(isMuc); if(!silent_notification) { mNotification.sound = isMuc? mConfig.notifySoundMuc : mConfig.notifySound; // If vibration is set to "system default", add the vibration flag to the // notification and let the system decide. String vibration = isMuc ? mConfig.vibraNotifyMuc : mConfig.vibraNotify; if ("SYSTEM".equals(vibration)) { mNotification.defaults |= Notification.DEFAULT_VIBRATE; } else if ("ALWAYS".equals(vibration)) { mVibrator.vibrate(400); } } mNotificationMGR.notify(notifyId, mNotification); mWakeLock.release(); } private void setNotification(String fromJid, String fromResource, String fromUserId, String message, String msg_long, boolean is_error, boolean isMuc) { int mNotificationCounter = 0; if (notificationCount.containsKey(fromJid)) { mNotificationCounter = notificationCount.get(fromJid); } mNotificationCounter++; notificationCount.put(fromJid, mNotificationCounter); String author; if (null == fromUserId || fromUserId.length() == 0) { author = fromJid; } else { author = fromUserId; } String title; if (isMuc) title = getString(R.string.notification_muc_message, fromResource, author/* = name of chatroom */); else title = author; // removed "Message from" prefix for brevity String ticker; if ((!isMuc && mConfig.ticker) || (isMuc && mConfig.tickerMuc)) { int newline = message.indexOf('\n'); int limit = 0; String messageSummary = message; if (newline >= 0) limit = newline; if (limit > MAX_TICKER_MSG_LEN || message.length() > MAX_TICKER_MSG_LEN) limit = MAX_TICKER_MSG_LEN; if (limit > 0) messageSummary = message.substring(0, limit) + " [...]"; ticker = title + ": " + messageSummary; } else ticker = getString(R.string.notification_anonymous_message); Intent msgHeardIntent = new Intent() .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) .setAction("org.yaxim.androidclient.ACTION_MESSAGE_HEARD") .putExtra("jid", fromJid); Intent msgResponseIntent = new Intent() .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) .setAction("org.yaxim.androidclient.ACTION_MESSAGE_REPLY") .putExtra("jid", fromJid); PendingIntent msgHeardPendingIntent = PendingIntent.getBroadcast( getApplicationContext(), notificationId.get(fromJid), msgHeardIntent, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent msgResponsePendingIntent = PendingIntent.getBroadcast( getApplicationContext(), notificationId.get(fromJid), msgResponseIntent, PendingIntent.FLAG_UPDATE_CURRENT); RemoteInput remoteInput = new RemoteInput.Builder("voicereply") .setLabel(getString(R.string.notification_reply)) .build(); UnreadConversation.Builder ucb = new UnreadConversation.Builder(author) .setReadPendingIntent(msgHeardPendingIntent) .setReplyAction(msgResponsePendingIntent, remoteInput); ucb.addMessage(msg_long.replace("▶ ", ": ")).setLatestTimestamp(System.currentTimeMillis()); Uri userNameUri = Uri.parse(fromJid); Intent chatIntent = new Intent(this, isMuc ? MUCChatWindow.class : ChatWindow.class); chatIntent.setData(userNameUri); chatIntent.putExtra(ChatWindow.INTENT_EXTRA_USERNAME, fromUserId); // create back-stack (WTF were you smoking, Google!?) //need to set flag FLAG_UPDATE_CURRENT to get extras transferred PendingIntent pi = TaskStackBuilder.create(this) .addNextIntentWithParentStack(chatIntent) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Action actMarkRead = new NotificationCompat.Action.Builder( android.R.drawable.ic_menu_close_clear_cancel, getString(R.string.notification_mark_read), msgHeardPendingIntent).build(); NotificationCompat.Action actReply = new NotificationCompat.Action.Builder( android.R.drawable.ic_menu_edit, getString(R.string.notification_reply), msgResponsePendingIntent) .addRemoteInput(remoteInput).build(); mNotification = new NotificationCompat.Builder(this) .setContentTitle(title) .setContentText(message) .setStyle(new NotificationCompat.BigTextStyle() .setBigContentTitle(author) .bigText(msg_long)) .setTicker(ticker) .setSmallIcon(R.drawable.sb_message) .setCategory(Notification.CATEGORY_MESSAGE) .setContentIntent(pi) .setAutoCancel(true) //.addAction(actReply) // TODO: use Android7 in-notification reply, fall back to Activity .addAction(actMarkRead) //.addAction(android.R.drawable.ic_menu_share, "Forward", msgHeardPendingIntent) .extend(new CarExtender().setUnreadConversation(ucb.build())) .extend(new NotificationCompat.WearableExtender() .addAction(actReply) .addAction(actMarkRead)) .build(); mNotification.defaults = 0; if (mNotificationCounter > 1) mNotification.number = mNotificationCounter; } private void setLEDNotification(boolean isMuc) { if ((!isMuc && mConfig.isLEDNotify) || (isMuc && mConfig.isLEDNotifyMuc)) { mNotification.ledARGB = Color.MAGENTA; mNotification.ledOnMS = 300; mNotification.ledOffMS = 1000; mNotification.flags |= Notification.FLAG_SHOW_LIGHTS; } } protected void shortToastNotify(String msg) { Toast toast = Toast.makeText(this, msg, Toast.LENGTH_SHORT); toast.show(); } protected void shortToastNotify(Throwable e) { e.printStackTrace(); while (e.getCause() != null) e = e.getCause(); shortToastNotify(e.getMessage()); } public void resetNotificationCounter(String userJid) { notificationCount.remove(userJid); notificationBigText.remove(userJid); } protected void logError(String data) { if (LogConstants.LOG_ERROR) { Log.e(TAG, data); } } protected void logInfo(String data) { if (LogConstants.LOG_INFO) { Log.i(TAG, data); } } public void clearNotification(String Jid) { int notifyId = 0; if (notificationId.containsKey(Jid)) { notifyId = notificationId.get(Jid); mNotificationMGR.cancel(notifyId); resetNotificationCounter(Jid); } } }