com.irccloud.android.data.collection.NotificationsList.java Source code

Java tutorial

Introduction

Here is the source code for com.irccloud.android.data.collection.NotificationsList.java

Source

/*
 * Copyright (c) 2015 IRCCloud, Ltd.
 *
 * 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 com.irccloud.android.data.collection;

import android.annotation.SuppressLint;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.database.SQLException;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationCompat.WearableExtender;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.app.RemoteInput;
import android.text.Html;
import android.text.Spanned;
import android.text.TextUtils;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.widget.RemoteViews;

import com.crashlytics.android.Crashlytics;
import com.irccloud.android.ColorFormatter;
import com.irccloud.android.DashClock;
import com.irccloud.android.IRCCloudApplication;
import com.irccloud.android.NetworkConnection;
import com.irccloud.android.R;
import com.irccloud.android.RemoteInputService;
import com.irccloud.android.SonyExtensionService;
import com.irccloud.android.activity.MainActivity;
import com.irccloud.android.activity.QuickReplyActivity;
import com.irccloud.android.data.IRCCloudDatabase;
import com.irccloud.android.data.model.Buffer;
import com.irccloud.android.data.model.Notification;
import com.irccloud.android.data.model.Notification$Table;
import com.irccloud.android.data.model.Notification_LastSeenEID;
import com.irccloud.android.data.model.Notification_LastSeenEID$Table;
import com.irccloud.android.data.model.Notification_ServerNick;
import com.irccloud.android.data.model.Notification_ServerNick$Table;
import com.irccloud.android.data.model.Server;
import com.raizlabs.android.dbflow.runtime.TransactionManager;
import com.raizlabs.android.dbflow.sql.builder.Condition;
import com.raizlabs.android.dbflow.sql.language.Delete;
import com.raizlabs.android.dbflow.sql.language.Select;
import com.sonyericsson.extras.liveware.extension.util.notification.NotificationUtil;

import org.json.JSONArray;
import org.json.JSONObject;

import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class NotificationsList {
    private static NotificationsList instance = null;
    private int excludeBid = -1;
    private static final Timer mNotificationTimer = new Timer("notification-timer");
    private TimerTask mNotificationTimerTask = null;
    private final Object dbLock = new Object();

    public interface NotificationAddedListener {
        void onNotificationAdded(Notification notification);
    }

    public NotificationAddedListener notificationAddedListener = null;

    public static NotificationsList getInstance() {
        if (instance == null)
            instance = new NotificationsList();
        return instance;
    }

    public List<Notification> getNotifications() {
        synchronized (dbLock) {
            return new Select().all().from(Notification.class).where()
                    .orderBy(Notification$Table.BID + ", " + Notification$Table.EID).queryList();
        }
    }

    public void clear() {
        try {
            for (Notification n : getNotifications()) {
                NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                        .cancel((int) (n.eid / 1000));
                NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                        .cancel(n.bid);
            }
            IRCCloudApplication.getInstance().getApplicationContext()
                    .sendBroadcast(new Intent(DashClock.REFRESH_INTENT));
            try {
                if (PreferenceManager
                        .getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext())
                        .getBoolean("notify_sony", false))
                    NotificationUtil.deleteAllEvents(IRCCloudApplication.getInstance().getApplicationContext());
            } catch (Exception e) {
                //Sony LiveWare was probably removed
            }
        } catch (Exception e) {
            NetworkConnection.printStackTraceToCrashlytics(e);
        }
        updateTeslaUnreadCount();
        synchronized (dbLock) {
            Delete.table(Notification.class);
        }
    }

    public void clearLastSeenEIDs() {
        synchronized (dbLock) {
            Delete.table(Notification_LastSeenEID.class);
        }
    }

    public long getLastSeenEid(int bid) {
        Buffer b = BuffersList.getInstance().getBuffer(bid);
        if (b != null)
            return b.getLast_seen_eid();
        synchronized (dbLock) {
            Notification_LastSeenEID eid = new Select().from(Notification_LastSeenEID.class)
                    .where(Condition.column(Notification_LastSeenEID$Table.BID).is(bid)).querySingle();
            if (eid != null)
                return eid.eid;
            else
                return -1;
        }
    }

    public synchronized void updateLastSeenEid(int bid, long eid) {
        Notification_LastSeenEID n = new Notification_LastSeenEID();
        n.bid = bid;
        n.eid = eid;
        Buffer b = BuffersList.getInstance().getBuffer(bid);
        if (b != null) {
            TransactionManager.getInstance().saveOnSaveQueue(n);
        } else {
            synchronized (dbLock) {
                n.save();
            }
        }
    }

    public String getServerNick(int cid) {
        Server s = ServersList.getInstance().getServer(cid);
        if (s != null && s.getNick() != null && s.getNick().length() > 0)
            return s.getNick();
        synchronized (dbLock) {
            Notification_ServerNick nick = new Select().from(Notification_ServerNick.class)
                    .where(Condition.column(Notification_ServerNick$Table.CID).is(cid)).querySingle();
            if (nick != null)
                return nick.nick;
            else
                return null;
        }
    }

    public synchronized void updateServerNick(int cid, String nick) {
        Notification_ServerNick n = new Notification_ServerNick();
        n.cid = cid;
        n.nick = nick;
        Server s = ServersList.getInstance().getServer(cid);
        if (s != null) {
            TransactionManager.getInstance().saveOnSaveQueue(n);
        } else {
            synchronized (dbLock) {
                n.save();
            }
        }
    }

    public synchronized void dismiss(int bid, long eid) {
        synchronized (dbLock) {
            Log.d("IRCCloud", "Dismiss bid" + bid + " eid" + eid);
            Notification n = getNotification(eid);
            if (n != null)
                n.delete();
        }
        if (IRCCloudApplication.getInstance() != null)
            IRCCloudApplication.getInstance().getApplicationContext()
                    .sendBroadcast(new Intent(DashClock.REFRESH_INTENT));
        updateTeslaUnreadCount();
    }

    public synchronized void addNotification(int cid, int bid, long eid, String from, String message, String chan,
            String buffer_type, String message_type, String network) {
        long last_eid = getLastSeenEid(bid);
        if (eid <= last_eid) {
            Crashlytics.log("Refusing to add notification for seen eid: " + eid);
            return;
        }

        Notification n = new Notification();
        n.bid = bid;
        n.cid = cid;
        n.eid = eid;
        n.nick = from;
        n.message = TextUtils.htmlEncode(ColorFormatter.strip(message).toString());
        n.chan = chan;
        n.buffer_type = buffer_type;
        n.message_type = message_type;
        n.network = network;

        synchronized (dbLock) {
            n.save();
        }

        if (notificationAddedListener != null)
            notificationAddedListener.onNotificationAdded(n);
    }

    public void deleteOldNotifications() {
        boolean changed = false, pending = false;
        if (mNotificationTimerTask != null) {
            mNotificationTimerTask.cancel();
            pending = true;
        }

        List<Notification> notifications = getNotifications();

        for (Notification n : notifications) {
            long last_seen_eid = getLastSeenEid(n.bid);
            Buffer b = BuffersList.getInstance().getBuffer(n.bid);
            if (b != null)
                last_seen_eid = b.getLast_seen_eid();
            if (last_seen_eid == -1 || n.eid <= last_seen_eid) {
                synchronized (dbLock) {
                    n.delete();
                }
                NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                        .cancel(n.bid);
                NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                        .cancel((int) (n.eid / 1000));
                changed = true;
                try {
                    if (PreferenceManager
                            .getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext())
                            .getBoolean("notify_sony", false))
                        NotificationUtil.deleteEvents(IRCCloudApplication.getInstance().getApplicationContext(),
                                com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.FRIEND_KEY
                                        + " = ?",
                                new String[] { String.valueOf(n.bid) });
                } catch (Exception e) {
                }
            }
        }
        if (changed) {
            IRCCloudApplication.getInstance().getApplicationContext()
                    .sendBroadcast(new Intent(DashClock.REFRESH_INTENT));
            updateTeslaUnreadCount();
        }

        if (pending)
            showNotifications(mTicker);
    }

    public void deleteNotificationsForBid(int bid) {
        Log.d("IRCCloud", "Removing all notifications for bid" + bid);
        List<Notification> notifications = getOtherNotifications();

        if (notifications.size() > 0) {
            for (Notification n : notifications) {
                NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                        .cancel((int) (n.eid / 1000));
            }
        }
        NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext()).cancel(bid);

        synchronized (dbLock) {
            notifications = getNotifications();
            for (Notification n : notifications) {
                if (n.bid == bid) {
                    n.delete();
                }
            }
            new Delete().from(Notification_LastSeenEID.class)
                    .where(Condition.column(Notification_LastSeenEID$Table.BID).is(bid)).queryClose();
        }
        IRCCloudApplication.getInstance().getApplicationContext()
                .sendBroadcast(new Intent(DashClock.REFRESH_INTENT));
        try {
            if (PreferenceManager
                    .getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext())
                    .getBoolean("notify_sony", false))
                NotificationUtil.deleteEvents(IRCCloudApplication.getInstance().getApplicationContext(),
                        com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.FRIEND_KEY
                                + " = ?",
                        new String[] { String.valueOf(bid) });
        } catch (Exception e) {
            //User has probably uninstalled Sony Liveware
        }
        updateTeslaUnreadCount();
    }

    public long count() {
        synchronized (dbLock) {
            return new Select().count().from(Notification.class).count();
        }
    }

    public List<Notification> getMessageNotifications() {
        synchronized (dbLock) {
            return new Select().from(Notification.class)
                    .where(Condition.column(Notification$Table.BID).isNot(excludeBid))
                    .and(Condition.column(Notification$Table.MESSAGE_TYPE).isNot("callerid"))
                    .and(Condition.column(Notification$Table.MESSAGE_TYPE).isNot("callerid_success"))
                    .and(Condition.column(Notification$Table.MESSAGE_TYPE).isNot("channel_invite"))
                    .orderBy(Notification$Table.BID + ", " + Notification$Table.EID).queryList();
        }
    }

    public List<Notification> getOtherNotifications() {
        synchronized (dbLock) {
            return new Select().from(Notification.class)
                    .where(Condition.CombinedCondition
                            .begin(Condition.column(Notification$Table.BID).isNot(excludeBid))
                            .and(Condition.CombinedCondition
                                    .begin(Condition.column(Notification$Table.MESSAGE_TYPE).is("callerid"))
                                    .or(Condition.column(Notification$Table.MESSAGE_TYPE).is("callerid_success"))
                                    .or(Condition.column(Notification$Table.MESSAGE_TYPE).is("channel_invite"))))
                    .orderBy(Notification$Table.BID + ", " + Notification$Table.EID).queryList();
        }
    }

    public Notification getNotification(long eid) {
        synchronized (dbLock) {
            return new Select().from(Notification.class).where(Condition.column(Notification$Table.EID).is(eid))
                    .querySingle();
        }
    }

    public synchronized void excludeBid(int bid) {
        synchronized (dbLock) {
            excludeBid = -1;
            List<Notification> notifications = getOtherNotifications();

            if (notifications.size() > 0) {
                for (Notification n : notifications) {
                    NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                            .cancel((int) (n.eid / 1000));
                }
            }
            NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext()).cancel(bid);
            excludeBid = bid;
        }
    }

    private String mTicker = null;

    public void showNotificationsNow() {
        if (mNotificationTimerTask != null)
            mNotificationTimerTask.cancel();
        mNotificationTimerTask = null;
        showMessageNotifications(mTicker);
        showOtherNotifications();
        mTicker = null;
        IRCCloudApplication.getInstance().getApplicationContext()
                .sendBroadcast(new Intent(DashClock.REFRESH_INTENT));
        updateTeslaUnreadCount();
    }

    public synchronized void showNotifications(String ticker) {
        if (ticker != null)
            mTicker = ColorFormatter.emojify(ticker);

        if (mNotificationTimerTask == null) {
            try {
                TimerTask task = new TimerTask() {
                    @Override
                    public void run() {
                        showNotificationsNow();
                    }

                    @Override
                    public boolean cancel() {
                        mNotificationTimerTask = null;
                        return super.cancel();
                    }
                };
                mNotificationTimer.schedule(task, 5000);
                mNotificationTimerTask = task;
            } catch (Exception e) {
                NetworkConnection.printStackTraceToCrashlytics(e);
            }
        }
    }

    private void showOtherNotifications() {
        String title = "";
        String text = "";
        String ticker;
        NotificationCompat.Action action = null;
        SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext());
        final List<Notification> notifications = getOtherNotifications();

        int notify_type = Integer.parseInt(prefs.getString("notify_type", "1"));
        boolean notify = false;
        if (notify_type == 1 || (notify_type == 2 && NetworkConnection.getInstance().isVisible()))
            notify = true;

        if (notifications.size() > 0 && notify) {
            for (Notification n : notifications) {
                if (!n.shown) {
                    Crashlytics.log(Log.DEBUG, "IRCCloud", "Posting notification for type " + n.message_type);
                    if (n.message_type.equals("callerid")) {
                        title = n.network;
                        text = n.nick + " is trying to contact you";
                        ticker = n.nick + " is trying to contact you on " + n.network;

                        Intent i = new Intent(RemoteInputService.ACTION_REPLY);
                        i.setComponent(new ComponentName(
                                IRCCloudApplication.getInstance().getApplicationContext().getPackageName(),
                                RemoteInputService.class.getName()));
                        i.putExtra("cid", n.cid);
                        i.putExtra("eid", n.eid);
                        i.putExtra("chan", n.chan);
                        i.putExtra("buffer_type", n.buffer_type);
                        i.putExtra("network", n.network);
                        i.putExtra("to", n.nick);
                        i.putExtra("reply", "/accept " + n.nick);
                        action = new NotificationCompat.Action(R.drawable.ic_wearable_add, "Accept",
                                PendingIntent.getService(IRCCloudApplication.getInstance().getApplicationContext(),
                                        (int) (n.eid / 1000), i, PendingIntent.FLAG_UPDATE_CURRENT));
                    } else if (n.message_type.equals("callerid_success")) {
                        title = n.network;
                        text = n.nick + " has been added to your accept list";
                        ticker = n.nick + " has been added to your accept list on " + n.network;
                        Intent i = new Intent(RemoteInputService.ACTION_REPLY);
                        i.setComponent(new ComponentName(
                                IRCCloudApplication.getInstance().getApplicationContext().getPackageName(),
                                RemoteInputService.class.getName()));
                        i.putExtra("cid", n.cid);
                        i.putExtra("eid", n.eid);
                        i.putExtra("chan", n.chan);
                        i.putExtra("buffer_type", n.buffer_type);
                        i.putExtra("network", n.network);
                        i.putExtra("to", n.nick);
                        action = new NotificationCompat.Action.Builder(R.drawable.ic_wearable_reply, "Message",
                                PendingIntent.getService(IRCCloudApplication.getInstance().getApplicationContext(),
                                        (int) (n.eid / 1000), i, PendingIntent.FLAG_UPDATE_CURRENT))
                                                .setAllowGeneratedReplies(true)
                                                .addRemoteInput(new RemoteInput.Builder("extra_reply")
                                                        .setLabel("Message to " + n.nick).build())
                                                .build();
                    } else if (n.message_type.equals("channel_invite")) {
                        title = n.network;
                        text = n.nick + " invited you to join " + n.chan;
                        ticker = text;
                        try {
                            Intent i = new Intent();
                            i.setComponent(new ComponentName(
                                    IRCCloudApplication.getInstance().getApplicationContext().getPackageName(),
                                    "com.irccloud.android.MainActivity"));
                            i.setData(Uri.parse(IRCCloudApplication.getInstance().getResources()
                                    .getString(R.string.IRCCLOUD_SCHEME) + "://cid/" + n.cid + "/"
                                    + URLEncoder.encode(n.chan, "UTF-8")));
                            i.putExtra("eid", n.eid);
                            action = new NotificationCompat.Action(R.drawable.ic_wearable_add, "Join",
                                    PendingIntent.getActivity(
                                            IRCCloudApplication.getInstance().getApplicationContext(),
                                            (int) (n.eid / 1000), i, PendingIntent.FLAG_UPDATE_CURRENT));
                        } catch (Exception e) {
                            action = null;
                        }
                    } else {
                        title = n.nick;
                        text = n.message;
                        ticker = n.message;
                        action = null;
                    }
                    if (title != null && text != null)
                        NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                                .notify((int) (n.eid / 1000), buildNotification(ticker, n.cid, n.bid,
                                        new long[] { n.eid }, title, text, 1, null, n.network, null, action,
                                        AvatarsList.getInstance().getAvatar(n.cid, n.nick).getBitmap(false,
                                                (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 64,
                                                        IRCCloudApplication.getInstance().getApplicationContext()
                                                                .getResources().getDisplayMetrics()),
                                                false, Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP),
                                        AvatarsList.getInstance().getAvatar(n.cid, n.nick).getBitmap(false, 400,
                                                false, false)));
                }
            }
            TransactionManager.transact(IRCCloudDatabase.NAME, new Runnable() {
                @Override
                public void run() {
                    for (Notification n : notifications) {
                        n.delete();
                    }
                }
            });
        }
    }

    private boolean hasTouchWiz() {
        try {
            IRCCloudApplication.getInstance().getApplicationContext().getPackageManager()
                    .getPackageInfo("com.sec.android.app.launcher", 0);
            return true;
        } catch (Exception e) {
        }
        return false;
    }

    @SuppressLint("NewApi")
    private android.app.Notification buildNotification(String ticker, int cid, int bid, long[] eids, String title,
            String text, int count, Intent replyIntent, String network, ArrayList<Notification> messages,
            NotificationCompat.Action otherAction, Bitmap largeIcon, Bitmap wearBackground) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel c = new NotificationChannel(String.valueOf(bid), title,
                    NotificationManager.IMPORTANCE_HIGH);
            c.setGroup(String.valueOf(cid));
            ((NotificationManager) IRCCloudApplication.getInstance().getApplicationContext()
                    .getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(c);
        }
        SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext());
        int defaults = 0;
        NotificationCompat.Builder builder = new NotificationCompat.Builder(
                IRCCloudApplication.getInstance().getApplicationContext(), String.valueOf(bid))
                        .setContentTitle(
                                title + ((network != null && !network.equals(title)) ? (" (" + network + ")") : ""))
                        .setContentText(Html.fromHtml(text)).setAutoCancel(true).setTicker(ticker)
                        .setWhen(eids[0] / 1000).setSmallIcon(R.drawable.ic_stat_notify).setLargeIcon(largeIcon)
                        .setColor(IRCCloudApplication.getInstance().getApplicationContext().getResources()
                                .getColor(R.color.ic_background))
                        .setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
                        .setCategory(NotificationCompat.CATEGORY_MESSAGE)
                        .setPriority(hasTouchWiz() ? NotificationCompat.PRIORITY_DEFAULT
                                : NotificationCompat.PRIORITY_HIGH)
                        .setOnlyAlertOnce(false);

        if (ticker != null && (System.currentTimeMillis() - prefs.getLong("lastNotificationTime", 0)) > 2000) {
            String ringtone = prefs.getString("notify_ringtone",
                    "android.resource://"
                            + IRCCloudApplication.getInstance().getApplicationContext().getPackageName() + "/"
                            + R.raw.digit);
            if (ringtone.length() > 0)
                builder.setSound(Uri.parse(ringtone));
        }

        int led_color = Integer.parseInt(prefs.getString("notify_led_color", "1"));
        if (led_color == 1) {
            defaults = android.app.Notification.DEFAULT_LIGHTS;
        } else if (led_color == 2) {
            builder.setLights(0xFF0000FF, 500, 500);
        }

        if (prefs.getBoolean("notify_vibrate", true) && ticker != null
                && (System.currentTimeMillis() - prefs.getLong("lastNotificationTime", 0)) > 2000)
            defaults |= android.app.Notification.DEFAULT_VIBRATE;
        else
            builder.setVibrate(new long[] { 0L });

        builder.setDefaults(defaults);
        SharedPreferences.Editor editor = prefs.edit();
        editor.putLong("lastNotificationTime", System.currentTimeMillis());
        editor.commit();

        Intent i = new Intent();
        i.setComponent(new ComponentName(IRCCloudApplication.getInstance().getApplicationContext().getPackageName(),
                "com.irccloud.android.MainActivity"));
        i.putExtra("bid", bid);
        i.setData(Uri.parse("bid://" + bid));
        Intent dismiss = new Intent(IRCCloudApplication.getInstance().getApplicationContext().getResources()
                .getString(R.string.DISMISS_NOTIFICATION));
        dismiss.setData(Uri.parse("irccloud-dismiss://" + bid));
        dismiss.putExtra("bid", bid);
        dismiss.putExtra("eids", eids);

        PendingIntent dismissPendingIntent = PendingIntent.getBroadcast(
                IRCCloudApplication.getInstance().getApplicationContext(), 0, dismiss,
                PendingIntent.FLAG_UPDATE_CURRENT);

        builder.setContentIntent(
                PendingIntent.getActivity(IRCCloudApplication.getInstance().getApplicationContext(), 0, i,
                        PendingIntent.FLAG_UPDATE_CURRENT));
        builder.setDeleteIntent(dismissPendingIntent);

        WearableExtender wearableExtender = new WearableExtender();
        wearableExtender.setBackground(wearBackground);
        if (messages != null && messages.size() > 0) {
            StringBuilder weartext = new StringBuilder();
            String servernick = getServerNick(messages.get(0).cid);
            NotificationCompat.MessagingStyle style = new NotificationCompat.MessagingStyle(servernick);
            style.setConversationTitle(title + ((network != null) ? (" (" + network + ")") : ""));
            for (Notification n : messages) {
                if (n != null && n.message != null && n.message.length() > 0) {
                    if (weartext.length() > 0)
                        weartext.append("<br/>");
                    if (n.message_type.equals("buffer_me_msg")) {
                        style.addMessage(Html.fromHtml(n.message).toString(), n.eid / 1000,
                                " " + ((n.nick == null) ? servernick : n.nick));
                        weartext.append("<b> ").append((n.nick == null) ? servernick : n.nick).append("</b> ")
                                .append(n.message);
                    } else {
                        style.addMessage(Html.fromHtml(n.message).toString(), n.eid / 1000, n.nick);
                        weartext.append("<b>&lt;").append((n.nick == null) ? servernick : n.nick)
                                .append("&gt;</b> ").append(n.message);
                    }
                }
            }

            ArrayList<String> history = new ArrayList<>(messages.size());
            for (int j = messages.size() - 1; j >= 0; j--) {
                Notification n = messages.get(j);
                if (n != null) {
                    if (n.nick == null)
                        history.add(Html.fromHtml(n.message).toString());
                    else
                        break;
                }
            }
            builder.setRemoteInputHistory(history.toArray(new String[history.size()]));
            builder.setStyle(style);

            if (messages.size() > 1) {
                wearableExtender.addPage(
                        new NotificationCompat.Builder(IRCCloudApplication.getInstance().getApplicationContext())
                                .setContentText(Html.fromHtml(weartext.toString()))
                                .extend(new WearableExtender().setStartScrollBottom(true)).build());
            }

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN
                    && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                weartext.setLength(0);
                int j = 0;
                for (Notification n : messages) {
                    if (messages.size() - ++j < 3) {
                        if (n != null && n.message != null && n.message.length() > 0) {
                            if (weartext.length() > 0)
                                weartext.append("<br/>");
                            if (n.message_type.equals("buffer_me_msg")) {
                                weartext.append("<b> ").append((n.nick == null) ? servernick : n.nick)
                                        .append("</b> ").append(n.message);
                            } else {
                                weartext.append("<b>&lt;").append((n.nick == null) ? servernick : n.nick)
                                        .append("&gt;</b> ").append(n.message);
                            }
                        }
                    }
                }

                RemoteViews bigContentView = new RemoteViews(
                        IRCCloudApplication.getInstance().getApplicationContext().getPackageName(),
                        R.layout.notification_expanded);
                bigContentView.setTextViewText(R.id.title,
                        title + (!title.equals(network) ? (" (" + network + ")") : ""));
                bigContentView.setTextViewText(R.id.text, Html.fromHtml(weartext.toString()));
                bigContentView.setImageViewBitmap(R.id.image, largeIcon);
                bigContentView.setLong(R.id.time, "setTime", eids[0] / 1000);
                if (count > 3) {
                    bigContentView.setViewVisibility(R.id.more, View.VISIBLE);
                    bigContentView.setTextViewText(R.id.more, "+" + (count - 3) + " more");
                } else {
                    bigContentView.setViewVisibility(R.id.more, View.GONE);
                }
                if (replyIntent != null && prefs.getBoolean("notify_quickreply", true)) {
                    bigContentView.setViewVisibility(R.id.actions, View.VISIBLE);
                    bigContentView.setViewVisibility(R.id.action_divider, View.VISIBLE);
                    i = new Intent(IRCCloudApplication.getInstance().getApplicationContext(),
                            QuickReplyActivity.class);
                    i.setData(Uri.parse("irccloud-bid://" + bid));
                    i.putExtras(replyIntent);
                    i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    PendingIntent quickReplyIntent = PendingIntent.getActivity(
                            IRCCloudApplication.getInstance().getApplicationContext(), 0, i,
                            PendingIntent.FLAG_UPDATE_CURRENT);
                    bigContentView.setOnClickPendingIntent(R.id.action_reply, quickReplyIntent);
                }
                builder.setCustomBigContentView(bigContentView);
            }
        }

        if (replyIntent != null) {
            PendingIntent replyPendingIntent = PendingIntent.getService(
                    IRCCloudApplication.getInstance().getApplicationContext(), bid + 1, replyIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                builder.addAction(new NotificationCompat.Action.Builder(0, "Reply", replyPendingIntent)
                        .setAllowGeneratedReplies(true)
                        .addRemoteInput(
                                new RemoteInput.Builder("extra_reply").setLabel("Reply to " + title).build())
                        .build());
            }

            NotificationCompat.Action.Builder actionBuilder = new NotificationCompat.Action.Builder(
                    R.drawable.ic_wearable_reply, "Reply", replyPendingIntent).setAllowGeneratedReplies(true)
                            .addRemoteInput(
                                    new RemoteInput.Builder("extra_reply").setLabel("Reply to " + title).build());

            NotificationCompat.Action.WearableExtender actionExtender = new NotificationCompat.Action.WearableExtender()
                    .setHintLaunchesActivity(true).setHintDisplayActionInline(true);

            wearableExtender.addAction(actionBuilder.extend(actionExtender).build());

            NotificationCompat.CarExtender.UnreadConversation.Builder unreadConvBuilder = new NotificationCompat.CarExtender.UnreadConversation.Builder(
                    title + ((network != null) ? (" (" + network + ")") : ""))
                            .setReadPendingIntent(dismissPendingIntent).setReplyAction(replyPendingIntent,
                                    new RemoteInput.Builder("extra_reply").setLabel("Reply to " + title).build());

            if (messages != null) {
                for (Notification n : messages) {
                    if (n != null && n.nick != null && n.message != null && n.message.length() > 0) {
                        if (n.buffer_type.equals("conversation")) {
                            if (n.message_type.equals("buffer_me_msg"))
                                unreadConvBuilder
                                        .addMessage(" " + n.nick + " " + Html.fromHtml(n.message).toString());
                            else
                                unreadConvBuilder.addMessage(Html.fromHtml(n.message).toString());
                        } else {
                            if (n.message_type.equals("buffer_me_msg"))
                                unreadConvBuilder
                                        .addMessage(" " + n.nick + " " + Html.fromHtml(n.message).toString());
                            else
                                unreadConvBuilder
                                        .addMessage(n.nick + " said: " + Html.fromHtml(n.message).toString());
                        }
                    }
                }
            } else {
                unreadConvBuilder.addMessage(text);
            }
            unreadConvBuilder.setLatestTimestamp(eids[count - 1] / 1000);

            builder.extend(new NotificationCompat.CarExtender().setUnreadConversation(unreadConvBuilder.build()));
        }

        if (replyIntent != null && prefs.getBoolean("notify_quickreply", true)
                && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            i = new Intent(IRCCloudApplication.getInstance().getApplicationContext(), QuickReplyActivity.class);
            i.setData(Uri.parse("irccloud-bid://" + bid));
            i.putExtras(replyIntent);
            i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            PendingIntent quickReplyIntent = PendingIntent.getActivity(
                    IRCCloudApplication.getInstance().getApplicationContext(), 0, i,
                    PendingIntent.FLAG_UPDATE_CURRENT);
            builder.addAction(R.drawable.ic_action_reply, "Quick Reply", quickReplyIntent);
        }

        if (otherAction != null) {
            int drawable = 0;
            if (otherAction.getIcon() == R.drawable.ic_wearable_add)
                drawable = R.drawable.ic_action_add;
            else if (otherAction.getIcon() == R.drawable.ic_wearable_reply)
                drawable = R.drawable.ic_action_reply;
            builder.addAction(
                    new NotificationCompat.Action(drawable, otherAction.getTitle(), otherAction.getActionIntent()));
            wearableExtender.addAction(otherAction);
        }

        builder.extend(wearableExtender);

        return builder.build();
    }

    private void notifyPebble(String title, String body) {
        JSONObject jsonData = new JSONObject();
        try {
            final Intent i = new Intent("com.getpebble.action.SEND_NOTIFICATION");
            jsonData.put("title", title);
            jsonData.put("body", body);
            final String notificationData = new JSONArray().put(jsonData).toString();

            i.putExtra("messageType", "PEBBLE_ALERT");
            i.putExtra("sender", "IRCCloud");
            i.putExtra("notificationData", notificationData);
            IRCCloudApplication.getInstance().getApplicationContext().sendBroadcast(i);
        } catch (Exception e) {
            NetworkConnection.printStackTraceToCrashlytics(e);
        }
    }

    private void showMessageNotifications(String ticker) {
        SharedPreferences prefs = PreferenceManager
                .getDefaultSharedPreferences(IRCCloudApplication.getInstance().getApplicationContext());
        String text = "";
        final List<Notification> notifications = getMessageNotifications();

        int notify_type = Integer.parseInt(prefs.getString("notify_type", "1"));
        boolean notify = false;
        if (notify_type == 1 || (notify_type == 2 && NetworkConnection.getInstance().isVisible()))
            notify = true;

        if (notifications.size() > 0 && notify) {
            int lastbid = notifications.get(0).bid;
            int count = 0;
            long[] eids = new long[notifications.size()];
            ArrayList<Notification> messages = new ArrayList<>(notifications.size());
            Notification last = notifications.get(0);
            boolean show = false;
            for (Notification n : notifications) {
                if (n.bid != lastbid) {
                    if (show) {
                        String title = last.chan;
                        if (title == null || title.length() == 0)
                            title = last.nick;
                        if (title == null || title.length() == 0)
                            title = last.network;

                        Intent replyIntent = new Intent(RemoteInputService.ACTION_REPLY);
                        replyIntent.putExtra("bid", last.bid);
                        replyIntent.putExtra("cid", last.cid);
                        replyIntent.putExtra("eids", eids);
                        replyIntent.putExtra("network", last.network);
                        replyIntent.putExtra("chan", last.chan);
                        replyIntent.putExtra("buffer_type", last.buffer_type);
                        replyIntent.putExtra("to", last.chan);

                        String body;
                        if (last.buffer_type.equals("channel")) {
                            if (last.message_type.equals("buffer_me_msg"))
                                body = "<b> " + ((last.nick != null) ? last.nick : getServerNick(last.cid))
                                        + "</b> " + last.message;
                            else
                                body = "<b>&lt;" + ((last.nick != null) ? last.nick : getServerNick(last.cid))
                                        + "&gt;</b> " + last.message;
                        } else {
                            if (last.message_type.equals("buffer_me_msg"))
                                body = " " + ((last.nick != null) ? last.nick : getServerNick(last.cid)) + " "
                                        + last.message;
                            else
                                body = last.message;
                        }

                        ArrayList<String> lines = new ArrayList<>(Arrays.asList(text.split("<br/>")));
                        while (lines.size() > 3)
                            lines.remove(0);

                        try {
                            Crashlytics.log(Log.DEBUG, "IRCCloud",
                                    "Posting notification for type " + last.message_type);
                            NotificationManagerCompat
                                    .from(IRCCloudApplication.getInstance().getApplicationContext())
                                    .notify(lastbid, buildNotification(ticker, last.cid, lastbid, eids, title, body,
                                            count, replyIntent, last.network, messages, null,
                                            AvatarsList.getInstance().getAvatar(last.cid, last.nick).getBitmap(
                                                    false,
                                                    (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 64,
                                                            IRCCloudApplication.getInstance()
                                                                    .getApplicationContext().getResources()
                                                                    .getDisplayMetrics()),
                                                    false, Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP),
                                            AvatarsList.getInstance().getAvatar(last.cid, last.nick)
                                                    .getBitmap(false, 400, false, false)));
                        } catch (Exception e) {
                            Crashlytics.logException(e);
                        }
                    }
                    lastbid = n.bid;
                    text = "";
                    count = 0;
                    eids = new long[notifications.size()];
                    show = false;
                    messages.clear();
                }

                if (text.length() > 0)
                    text += "<br/>";
                if (n.buffer_type.equals("conversation") && n.message_type.equals("buffer_me_msg"))
                    text += " " + n.message;
                else if (n.buffer_type.equals("conversation"))
                    text += n.message;
                else if (n.message_type.equals("buffer_me_msg"))
                    text += "<b> " + ((n.nick != null) ? n.nick : getServerNick(n.cid)) + "</b> " + n.message;
                else
                    text += "<b>" + ((n.nick != null) ? n.nick : getServerNick(n.cid)) + "</b> " + n.message;

                if (!n.shown) {
                    n.shown = true;
                    show = true;

                    if (n.nick != null && prefs.getBoolean("notify_sony", false)) {
                        long time = System.currentTimeMillis();
                        long sourceId = NotificationUtil.getSourceId(
                                IRCCloudApplication.getInstance().getApplicationContext(),
                                SonyExtensionService.EXTENSION_SPECIFIC_ID);
                        if (sourceId == NotificationUtil.INVALID_ID) {
                            Crashlytics.log(Log.ERROR, "IRCCloud",
                                    "Sony LiveWare Manager not configured, disabling Sony notifications");
                            SharedPreferences.Editor editor = prefs.edit();
                            editor.putBoolean("notify_sony", false);
                            editor.commit();
                        } else {
                            ContentValues eventValues = new ContentValues();
                            eventValues.put(
                                    com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.EVENT_READ_STATUS,
                                    false);
                            eventValues.put(
                                    com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.DISPLAY_NAME,
                                    n.nick);

                            if (n.buffer_type.equals("channel") && n.chan != null && n.chan.length() > 0)
                                eventValues.put(
                                        com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.TITLE,
                                        n.chan);
                            else
                                eventValues.put(
                                        com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.TITLE,
                                        n.network);

                            if (n.message_type.equals("buffer_me_msg"))
                                eventValues.put(
                                        com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.MESSAGE,
                                        " " + Html.fromHtml(n.message).toString());
                            else
                                eventValues.put(
                                        com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.MESSAGE,
                                        Html.fromHtml(n.message).toString());

                            eventValues.put(
                                    com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.PERSONAL,
                                    1);
                            eventValues.put(
                                    com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.PUBLISHED_TIME,
                                    time);
                            eventValues.put(
                                    com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.SOURCE_ID,
                                    sourceId);
                            eventValues.put(
                                    com.sonyericsson.extras.liveware.aef.notification.Notification.EventColumns.FRIEND_KEY,
                                    String.valueOf(n.bid));

                            try {
                                IRCCloudApplication.getInstance().getApplicationContext().getContentResolver()
                                        .insert(com.sonyericsson.extras.liveware.aef.notification.Notification.Event.URI,
                                                eventValues);
                            } catch (IllegalArgumentException e) {
                                Log.e("IRCCloud", "Failed to insert event", e);
                            } catch (SecurityException e) {
                                Log.e("IRCCloud", "Failed to insert event, is Live Ware Manager installed?", e);
                            } catch (SQLException e) {
                                Log.e("IRCCloud", "Failed to insert event", e);
                            }
                        }
                    }

                    if (prefs.getBoolean("notify_pebble", false) && n.nick != null) {
                        String pebbleTitle = n.network + ":\n";
                        String pebbleBody = "";
                        if (n.buffer_type.equals("channel") && n.chan != null && n.chan.length() > 0)
                            pebbleTitle = n.chan + ":\n";

                        if (n.message_type.equals("buffer_me_msg"))
                            pebbleBody = " " + n.message;
                        else
                            pebbleBody = n.message;

                        if (n.chan != null && n.nick != null && n.nick.length() > 0)
                            notifyPebble(n.nick, pebbleTitle + Html.fromHtml(pebbleBody).toString());
                        else
                            notifyPebble(n.network, pebbleTitle + Html.fromHtml(pebbleBody).toString());
                    }
                }
                messages.add(n);
                eids[count++] = n.eid;
                if (n.nick != null)
                    last = n;
            }

            if (show) {
                String title = last.chan;
                if (title == null || title.length() == 0)
                    title = last.network;

                Intent replyIntent = new Intent(RemoteInputService.ACTION_REPLY);
                replyIntent.putExtra("bid", last.bid);
                replyIntent.putExtra("cid", last.cid);
                replyIntent.putExtra("network", last.network);
                replyIntent.putExtra("eids", eids);
                replyIntent.putExtra("chan", last.chan);
                replyIntent.putExtra("buffer_type", last.buffer_type);
                replyIntent.putExtra("to", last.chan);

                String body = "";
                if (last.buffer_type.equals("channel")) {
                    if (last.message_type.equals("buffer_me_msg"))
                        body = "<b> " + ((last.nick != null) ? last.nick : getServerNick(last.cid)) + "</b> "
                                + last.message;
                    else
                        body = "<b>&lt;" + ((last.nick != null) ? last.nick : getServerNick(last.cid)) + "&gt;</b> "
                                + last.message;
                } else {
                    if (last.message_type.equals("buffer_me_msg"))
                        body = " " + ((last.nick != null) ? last.nick : getServerNick(last.cid)) + " "
                                + last.message;
                    else
                        body = last.message;
                }

                ArrayList<String> lines = new ArrayList<>(Arrays.asList(text.split("<br/>")));
                while (lines.size() > 3)
                    lines.remove(0);

                try {
                    Crashlytics.log(Log.DEBUG, "IRCCloud", "Posting notification for type " + last.message_type);
                    NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext())
                            .notify(last.bid, buildNotification(ticker, last.cid, last.bid, eids, title, body,
                                    count, replyIntent, last.network, messages, null,
                                    AvatarsList.getInstance().getAvatar(last.cid, last.nick).getBitmap(false,
                                            (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 64,
                                                    IRCCloudApplication.getInstance().getApplicationContext()
                                                            .getResources().getDisplayMetrics()),
                                            false, Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP),
                                    AvatarsList.getInstance().getAvatar(last.cid, last.nick).getBitmap(false, 400,
                                            false, false)));
                } catch (Exception e) {
                    Crashlytics.logException(e);
                }
            }

            TransactionManager.transact(IRCCloudDatabase.NAME, new Runnable() {
                @Override
                public void run() {
                    for (Notification n : notifications) {
                        n.save();
                    }
                }
            });
        }
    }

    public NotificationCompat.Builder alert(int bid, String title, String body) {
        Crashlytics.log(Log.DEBUG, "IRCCloud", "Posting alert notification");
        NotificationCompat.Builder builder = new NotificationCompat.Builder(
                IRCCloudApplication.getInstance().getApplicationContext()).setContentTitle(title)
                        .setContentText(body).setTicker(body).setAutoCancel(true)
                        .setColor(IRCCloudApplication.getInstance().getApplicationContext().getResources()
                                .getColor(R.color.ic_background))
                        .setSmallIcon(R.drawable.ic_stat_notify);

        Intent i = new Intent();
        i.setComponent(new ComponentName(IRCCloudApplication.getInstance().getApplicationContext().getPackageName(),
                "com.irccloud.android.MainActivity"));
        i.putExtra("bid", bid);
        i.setData(Uri.parse("bid://" + bid));
        builder.setContentIntent(
                PendingIntent.getActivity(IRCCloudApplication.getInstance().getApplicationContext(), 0, i,
                        PendingIntent.FLAG_UPDATE_CURRENT));

        NotificationManagerCompat.from(IRCCloudApplication.getInstance().getApplicationContext()).notify(bid,
                builder.build());

        return builder;
    }

    public void updateTeslaUnreadCount() {
        try {
            IRCCloudApplication.getInstance().getApplicationContext().getPackageManager()
                    .getPackageInfo("com.teslacoilsw.notifier", PackageManager.GET_META_DATA);
        } catch (PackageManager.NameNotFoundException e) {
            return;
        }

        try {
            ContentValues cv = new ContentValues();
            cv.put("tag",
                    IRCCloudApplication.getInstance().getApplicationContext().getPackageManager()
                            .getLaunchIntentForPackage(
                                    IRCCloudApplication.getInstance().getApplicationContext().getPackageName())
                            .getComponent().flattenToString());
            synchronized (dbLock) {
                cv.put("count", new Select().count().from(Notification.class)
                        .where(Condition.column(Notification$Table.NICK).isNotNull()).count());
            }
            IRCCloudApplication.getInstance().getApplicationContext().getContentResolver()
                    .insert(Uri.parse("content://com.teslacoilsw.notifier/unread_count"), cv);
        } catch (IllegalArgumentException ex) {
        } catch (Exception ex) {
            NetworkConnection.printStackTraceToCrashlytics(ex);
        }
    }

    public void addNotificationGroup(int id, String name) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            ((NotificationManager) IRCCloudApplication.getInstance().getApplicationContext()
                    .getSystemService(Context.NOTIFICATION_SERVICE))
                            .createNotificationChannelGroup(new NotificationChannelGroup(String.valueOf(id), name));
        }
    }

    public void pruneNotificationChannels() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationManager nm = ((NotificationManager) IRCCloudApplication.getInstance()
                    .getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE));

            for (NotificationChannelGroup c : nm.getNotificationChannelGroups()) {
                try {
                    if (ServersList.getInstance().getServer(Integer.valueOf(c.getId())) == null)
                        nm.deleteNotificationChannelGroup(c.getId());
                } catch (NumberFormatException e) {
                }
            }

            for (NotificationChannel c : nm.getNotificationChannels()) {
                try {
                    if (BuffersList.getInstance().getBuffer(Integer.valueOf(c.getId())) == null)
                        nm.deleteNotificationChannel(c.getId());
                } catch (NumberFormatException e) {
                }
            }
        }
    }
}