package chat.rocket.android.push; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.NotificationCompat; import android.support.v4.app.RemoteInput; import android.support.v4.util.SparseArrayCompat; import android.text.Html; import android.text.Spanned; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Random; import chat.rocket.android.activity.MainActivity; import chat.rocket.android.push.interactors.PushInteractor; public class PushNotificationHandler implements PushConstants { private static final String LOG_TAG = "PushNotificationHandler"; private static SparseArrayCompat<ArrayList<String>> messageMap = new SparseArrayCompat<>(); private Random random = new Random(); public static synchronized void cleanUpNotificationStack(int notId) { messageMap.remove(notId); } private synchronized void setNotification(int notId, String message) { ArrayList<String> messageList = messageMap.get(notId); if (messageList == null) { messageList = new ArrayList<>(); messageMap.put(notId, messageList); } if (message.isEmpty()) { messageList.clear(); } else { messageList.add(message); } } private synchronized ArrayList<String> getMessageList(int notId) { return messageMap.get(notId); } public void showNotificationIfPossible(Context context, PushInteractor pushInteractor, Bundle extras) { // Send a notification if there is a message or title, otherwise just send data String message = extras.getString(MESSAGE); String title = extras.getString(TITLE); String contentAvailable = extras.getString(CONTENT_AVAILABLE); String forceStart = extras.getString(FORCE_START); Log.d(LOG_TAG, "message =[" + message + "]"); Log.d(LOG_TAG, "title =[" + title + "]"); Log.d(LOG_TAG, "contentAvailable =[" + contentAvailable + "]"); Log.d(LOG_TAG, "forceStart =[" + forceStart + "]"); if ((message != null && message.length() != 0) || (title != null && title.length() != 0)) { Log.d(LOG_TAG, "create notification"); if (title == null || title.isEmpty()) { extras.putString(TITLE, getAppName(context)); } createNotification(context, pushInteractor, extras); } } public void createNotification(Context context, PushInteractor pushInteractor, Bundle extras) { NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); String appName = getAppName(context); String packageName = context.getPackageName(); Resources resources = context.getResources(); String serverUrl = getServerUrl(extras); String roomId = getRoomId(extras); if (serverUrl == null || roomId == null) { return; } String serverConfigId = pushInteractor.getServerConfigId(serverUrl); if (serverConfigId == null) { return; } int notId = parseInt(NOT_ID, extras); Intent notificationIntent = new Intent(context, MainActivity.class); notificationIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); notificationIntent.putExtra(PUSH_BUNDLE, extras); notificationIntent.putExtra(SERVER_CONFIG_ID, serverConfigId); notificationIntent.putExtra(ROOM_ID, roomId); notificationIntent.putExtra(NOT_ID, notId); int requestCode = random.nextInt(); PendingIntent contentIntent = PendingIntent .getActivity(context, requestCode, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context) .setWhen(System.currentTimeMillis()) .setContentTitle(fromHtml(extras.getString(TITLE))) .setTicker(fromHtml(extras.getString(TITLE))) .setContentIntent(contentIntent) .setAutoCancel(true); SharedPreferences prefs = context .getSharedPreferences(PushConstants.COM_ADOBE_PHONEGAP_PUSH, Context.MODE_PRIVATE); String localIcon = prefs.getString(ICON, null); String localIconColor = prefs.getString(ICON_COLOR, null); boolean soundOption = prefs.getBoolean(SOUND, true); boolean vibrateOption = prefs.getBoolean(VIBRATE, true); Log.d(LOG_TAG, "stored icon=" + localIcon); Log.d(LOG_TAG, "stored iconColor=" + localIconColor); Log.d(LOG_TAG, "stored sound=" + soundOption); Log.d(LOG_TAG, "stored vibrate=" + vibrateOption); /* * Notification Vibration */ setNotificationVibration(extras, vibrateOption, notificationBuilder); /* * Notification Icon Color * * Sets the small-icon background color of the notification. * To use, add the `iconColor` key to plugin android options * */ setNotificationIconColor(extras.getString("color"), notificationBuilder, localIconColor); /* * Notification Icon * * Sets the small-icon of the notification. * * - checks the plugin options for `icon` key * - if none, uses the application icon * * The icon value must be a string that maps to a drawable resource. * If no resource is found, falls * */ setNotificationSmallIcon(context, extras, packageName, resources, notificationBuilder, localIcon); /* * Notification Large-Icon * * Sets the large-icon of the notification * * - checks the gcm data for the `image` key * - checks to see if remote image, loads it. * - checks to see if assets image, Loads It. * - checks to see if resource image, LOADS IT! * - if none, we don't set the large icon * */ setNotificationLargeIcon(context, extras, packageName, resources, notificationBuilder); /* * Notification Sound */ if (soundOption) { setNotificationSound(context, extras, notificationBuilder); } /* * LED Notification */ setNotificationLedColor(extras, notificationBuilder); /* * Priority Notification */ setNotificationPriority(extras, notificationBuilder); /* * Notification message */ setNotificationMessage(notId, extras, notificationBuilder); /* * Notification count */ setNotificationCount(context, extras, notificationBuilder); /* * Notification count */ setVisibility(context, extras, notificationBuilder); /* * Notification add actions */ createActions(context, extras, notificationBuilder, resources, packageName, notId); mNotificationManager.notify(appName, notId, notificationBuilder.build()); } private void createActions(Context context, Bundle extras, NotificationCompat.Builder mBuilder, Resources resources, String packageName, int notId) { Log.d(LOG_TAG, "create actions: with in-line"); String actions = extras.getString(ACTIONS); if (actions == null) { return; } try { JSONArray actionsArray = new JSONArray(actions); ArrayList<NotificationCompat.Action> wActions = new ArrayList<>(); for (int i = 0; i < actionsArray.length(); i++) { int min = 1; int max = 2000000000; Random random = new Random(); int uniquePendingIntentRequestCode = random.nextInt((max - min) + 1) + min; Log.d(LOG_TAG, "adding action"); JSONObject action = actionsArray.getJSONObject(i); Log.d(LOG_TAG, "adding callback = " + action.getString(CALLBACK)); boolean foreground = action.optBoolean(FOREGROUND, true); boolean inline = action.optBoolean("inline", false); Intent intent; PendingIntent pIntent; if (inline) { Log.d(LOG_TAG, "Version: " + android.os.Build.VERSION.SDK_INT + " = " + android.os.Build.VERSION_CODES.M); if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) { Log.d(LOG_TAG, "push activity"); intent = new Intent(context, MainActivity.class); } else { Log.d(LOG_TAG, "push receiver"); intent = new Intent(context, BackgroundActionButtonHandler.class); } updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId); if (android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.M) { Log.d(LOG_TAG, "push activity for notId " + notId); pIntent = PendingIntent.getActivity(context, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_ONE_SHOT); } else { Log.d(LOG_TAG, "push receiver for notId " + notId); pIntent = PendingIntent .getBroadcast(context, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_ONE_SHOT); } } else if (foreground) { intent = new Intent(context, MainActivity.class); updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId); pIntent = PendingIntent .getActivity(context, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); } else { intent = new Intent(context, BackgroundActionButtonHandler.class); updateIntent(intent, action.getString(CALLBACK), extras, foreground, notId); pIntent = PendingIntent .getBroadcast(context, uniquePendingIntentRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); } NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder( resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName), action.getString(TITLE), pIntent); RemoteInput remoteInput; if (inline) { Log.d(LOG_TAG, "create remote input"); String replyLabel = "Enter your reply here"; remoteInput = new RemoteInput.Builder(INLINE_REPLY) .setLabel(replyLabel) .build(); actionBuilder.addRemoteInput(remoteInput); } NotificationCompat.Action wAction = actionBuilder.build(); wActions.add(actionBuilder.build()); if (inline) { mBuilder.addAction(wAction); } else { mBuilder.addAction( resources.getIdentifier(action.optString(ICON, ""), DRAWABLE, packageName), action.getString(TITLE), pIntent); } wAction = null; pIntent = null; } mBuilder.extend(new NotificationCompat.WearableExtender().addActions(wActions)); wActions.clear(); } catch (JSONException e) { // nope } } private void setNotificationCount(Context context, Bundle extras, NotificationCompat.Builder mBuilder) { int count = extractBadgeCount(extras); if (count >= 0) { Log.d(LOG_TAG, "count =[" + count + "]"); mBuilder.setNumber(count); } } private void setVisibility(Context context, Bundle extras, NotificationCompat.Builder mBuilder) { String visibilityStr = extras.getString(VISIBILITY); if (visibilityStr == null) { return; } try { Integer visibility = Integer.parseInt(visibilityStr); if (visibility >= NotificationCompat.VISIBILITY_SECRET && visibility <= NotificationCompat.VISIBILITY_PUBLIC) { mBuilder.setVisibility(visibility); } else { Log.e(LOG_TAG, "Visibility parameter must be between -1 and 1"); } } catch (NumberFormatException e) { e.printStackTrace(); } } private void setNotificationVibration(Bundle extras, Boolean vibrateOption, NotificationCompat.Builder mBuilder) { String vibrationPattern = extras.getString(VIBRATION_PATTERN); if (vibrationPattern != null) { String[] items = vibrationPattern.replaceAll("\\[", "").replaceAll("\\]", "").split(","); long[] results = new long[items.length]; for (int i = 0; i < items.length; i++) { try { results[i] = Long.parseLong(items[i].trim()); } catch (NumberFormatException nfe) { } } mBuilder.setVibrate(results); } else { if (vibrateOption) { mBuilder.setDefaults(Notification.DEFAULT_VIBRATE); } } } private void setNotificationMessage(int notId, Bundle extras, NotificationCompat.Builder mBuilder) { String message = extras.getString(MESSAGE); String style = extras.getString(STYLE, STYLE_TEXT); if (STYLE_INBOX.equals(style)) { setNotification(notId, message); mBuilder.setContentText(fromHtml(message)); ArrayList<String> messageList = getMessageList(notId); Integer sizeList = messageList.size(); if (sizeList > 1) { String sizeListMessage = sizeList.toString(); String stacking = sizeList + " more"; if (extras.getString(SUMMARY_TEXT) != null) { stacking = extras.getString(SUMMARY_TEXT); stacking = stacking.replace("%n%", sizeListMessage); } NotificationCompat.InboxStyle notificationInbox = new NotificationCompat.InboxStyle() .setBigContentTitle(fromHtml(extras.getString(TITLE))) .setSummaryText(fromHtml(stacking)); for (int i = messageList.size() - 1; i >= 0; i--) { notificationInbox.addLine(fromHtml(messageList.get(i))); } mBuilder.setStyle(notificationInbox); } else { NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle(); if (message != null) { bigText.bigText(fromHtml(message)); bigText.setBigContentTitle(fromHtml(extras.getString(TITLE))); mBuilder.setStyle(bigText); } } } else if (STYLE_PICTURE.equals(style)) { setNotification(notId, ""); NotificationCompat.BigPictureStyle bigPicture = new NotificationCompat.BigPictureStyle(); bigPicture.bigPicture(getBitmapFromURL(extras.getString(PICTURE))); bigPicture.setBigContentTitle(fromHtml(extras.getString(TITLE))); bigPicture.setSummaryText(fromHtml(extras.getString(SUMMARY_TEXT))); mBuilder.setContentTitle(fromHtml(extras.getString(TITLE))); mBuilder.setContentText(fromHtml(message)); mBuilder.setStyle(bigPicture); } else { setNotification(notId, ""); NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle(); if (message != null) { mBuilder.setContentText(fromHtml(message)); bigText.bigText(fromHtml(message)); bigText.setBigContentTitle(fromHtml(extras.getString(TITLE))); String summaryText = extras.getString(SUMMARY_TEXT); if (summaryText != null) { bigText.setSummaryText(fromHtml(summaryText)); } mBuilder.setStyle(bigText); } } } private void setNotificationSound(Context context, Bundle extras, NotificationCompat.Builder mBuilder) { String soundname = extras.getString(SOUNDNAME); if (soundname == null) { soundname = extras.getString(SOUND); } if (SOUND_RINGTONE.equals(soundname)) { mBuilder.setSound(android.provider.Settings.System.DEFAULT_RINGTONE_URI); } else if (soundname != null && !soundname.contentEquals(SOUND_DEFAULT)) { Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.getPackageName() + "/raw/" + soundname); Log.d(LOG_TAG, sound.toString()); mBuilder.setSound(sound); } else { mBuilder.setSound(android.provider.Settings.System.DEFAULT_NOTIFICATION_URI); } } private void setNotificationLedColor(Bundle extras, NotificationCompat.Builder mBuilder) { String ledColor = extras.getString(LED_COLOR); if (ledColor == null) { return; } // Converts parse Int Array from ledColor String[] items = ledColor.replaceAll("\\[", "").replaceAll("\\]", "").split(","); int[] results = new int[items.length]; for (int i = 0; i < items.length; i++) { try { results[i] = Integer.parseInt(items[i].trim()); } catch (NumberFormatException nfe) { } } if (results.length == 4) { mBuilder.setLights(Color.argb(results[0], results[1], results[2], results[3]), 500, 500); } else { Log.e(LOG_TAG, "ledColor parameter must be an array of length == 4 (ARGB)"); } } private void setNotificationPriority(Bundle extras, NotificationCompat.Builder mBuilder) { String priorityStr = extras.getString(PRIORITY); if (priorityStr == null) { return; } try { Integer priority = Integer.parseInt(priorityStr); if (priority >= NotificationCompat.PRIORITY_MIN && priority <= NotificationCompat.PRIORITY_MAX) { mBuilder.setPriority(priority); } else { Log.e(LOG_TAG, "Priority parameter must be between -2 and 2"); } } catch (NumberFormatException e) { e.printStackTrace(); } } private void setNotificationLargeIcon(Context context, Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder) { String gcmLargeIcon = extras.getString(IMAGE); // from gcm if (gcmLargeIcon == null || "".equals(gcmLargeIcon)) { return; } if (gcmLargeIcon.startsWith("http://") || gcmLargeIcon.startsWith("https://")) { mBuilder.setLargeIcon(getBitmapFromURL(gcmLargeIcon)); Log.d(LOG_TAG, "using remote large-icon from gcm"); } else { AssetManager assetManager = context.getAssets(); InputStream istr; try { istr = assetManager.open(gcmLargeIcon); Bitmap bitmap = BitmapFactory.decodeStream(istr); mBuilder.setLargeIcon(bitmap); Log.d(LOG_TAG, "using assets large-icon from gcm"); } catch (IOException e) { int largeIconId = resources.getIdentifier(gcmLargeIcon, DRAWABLE, packageName); if (largeIconId != 0) { Bitmap largeIconBitmap = BitmapFactory.decodeResource(resources, largeIconId); mBuilder.setLargeIcon(largeIconBitmap); Log.d(LOG_TAG, "using resources large-icon from gcm"); } else { Log.d(LOG_TAG, "Not setting large icon"); } } } } private void setNotificationSmallIcon(Context context, Bundle extras, String packageName, Resources resources, NotificationCompat.Builder mBuilder, String localIcon) { int iconId = 0; String icon = extras.getString(ICON); if (icon != null && !"".equals(icon)) { iconId = resources.getIdentifier(icon, DRAWABLE, packageName); Log.d(LOG_TAG, "using icon from plugin options"); } else if (localIcon != null && !"".equals(localIcon)) { iconId = resources.getIdentifier(localIcon, DRAWABLE, packageName); Log.d(LOG_TAG, "using icon from plugin options"); } if (iconId == 0) { Log.d(LOG_TAG, "no icon resource found - using default icon"); iconId = resources.getIdentifier("rocket_chat_notification", DRAWABLE, packageName); } mBuilder.setSmallIcon(iconId); } private void setNotificationIconColor(String color, NotificationCompat.Builder mBuilder, String localIconColor) { int iconColor = 0; if (color != null && !"".equals(color)) { try { iconColor = Color.parseColor(color); } catch (IllegalArgumentException e) { Log.e(LOG_TAG, "couldn't parse color from android options"); } } else if (localIconColor != null && !"".equals(localIconColor)) { try { iconColor = Color.parseColor(localIconColor); } catch (IllegalArgumentException e) { Log.e(LOG_TAG, "couldn't parse color from android options"); } } if (iconColor != 0) { mBuilder.setColor(iconColor); } } private void updateIntent(Intent intent, String callback, Bundle extras, boolean foreground, int notId) { intent.putExtra(CALLBACK, callback); intent.putExtra(PUSH_BUNDLE, extras); intent.putExtra(FOREGROUND, foreground); intent.putExtra(NOT_ID, notId); } public Bitmap getBitmapFromURL(String strURL) { try { URL url = new URL(strURL); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); InputStream input = connection.getInputStream(); return BitmapFactory.decodeStream(input); } catch (IOException e) { e.printStackTrace(); return null; } } public static String getAppName(Context context) { CharSequence appName = context.getPackageManager() .getApplicationLabel(context.getApplicationInfo()); return (String) appName; } private int parseInt(String value, Bundle extras) { int retval = 0; try { retval = Integer.parseInt(extras.getString(value)); } catch (NumberFormatException e) { Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage()); } catch (Exception e) { Log.e(LOG_TAG, "Number format exception - Error parsing " + value + ": " + e.getMessage()); } return retval; } private Spanned fromHtml(String source) { if (source != null) { return Html.fromHtml(source); } else { return null; } } private int extractBadgeCount(Bundle extras) { int count = -1; String msgcnt = extras.getString(COUNT); try { if (msgcnt != null) { count = Integer.parseInt(msgcnt); } } catch (NumberFormatException e) { Log.e(LOG_TAG, e.getLocalizedMessage(), e); } return count; } private String getServerUrl(Bundle extras) { try { JSONObject jsonObject = new JSONObject(extras.getString("ejson", "[]")); if (!jsonObject.has("host")) { return null; } return jsonObject.getString("host"); } catch (Exception e) { return null; } } private String getRoomId(Bundle extras) { try { JSONObject jsonObject = new JSONObject(extras.getString("ejson", "[]")); if (!jsonObject.has("rid")) { return null; } return jsonObject.getString("rid"); } catch (Exception e) { return null; } } }