com.ubergeek42.WeechatAndroid.service.Notificator.java Source code

Java tutorial

Introduction

Here is the source code for com.ubergeek42.WeechatAndroid.service.Notificator.java

Source

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

package com.ubergeek42.WeechatAndroid.service;

import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.AnyThread;
import android.support.annotation.MainThread;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.text.TextUtils;

import com.ubergeek42.WeechatAndroid.BuildConfig;
import com.ubergeek42.WeechatAndroid.R;
import com.ubergeek42.WeechatAndroid.WeechatActivity;
import com.ubergeek42.WeechatAndroid.relay.Hotlist;
import com.ubergeek42.WeechatAndroid.relay.Hotlist.HotMessage;
import com.ubergeek42.cats.Cat;
import com.ubergeek42.cats.Kitty;
import com.ubergeek42.cats.Root;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static android.support.v4.app.NotificationCompat.Builder;
import static android.support.v4.app.NotificationCompat.MessagingStyle;
import static com.ubergeek42.WeechatAndroid.service.RelayService.STATE.AUTHENTICATED;
import static com.ubergeek42.WeechatAndroid.utils.Constants.NOTIFICATION_EXTRA_BUFFER_FULL_NAME;
import static com.ubergeek42.WeechatAndroid.utils.Constants.NOTIFICATION_EXTRA_BUFFER_FULL_NAME_ANY;

public class Notificator {

    final private static @Root Kitty kitty = Kitty.make();

    final private static int NOTIFICATION_MAIN_ID = 42;
    final private static int NOTIFICATION_HOT_ID = 43;
    final private static String NOTIFICATION_CHANNEL_CONNECTION_STATUS = "connection status";
    final private static String NOTIFICATION_CHANNEL_HOTLIST = "notification";
    final private static String GROUP_KEY = "hot messages";

    @SuppressLint("StaticFieldLeak")
    private static Context context;
    private static NotificationManager manager;

    @MainThread
    public static void init(Context c) {
        context = c.getApplicationContext();
        manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
            return;

        NotificationChannel channel = new NotificationChannel(NOTIFICATION_CHANNEL_CONNECTION_STATUS,
                context.getString(R.string.notification_channel_connection_status),
                NotificationManager.IMPORTANCE_MIN);
        channel.setShowBadge(false);
        manager.createNotificationChannel(channel);

        channel = new NotificationChannel(NOTIFICATION_CHANNEL_HOTLIST,
                context.getString(R.string.notification_channel_hotlist), NotificationManager.IMPORTANCE_DEFAULT);
        manager.createNotificationChannel(channel);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////

    @AnyThread
    @Cat
    static void showMain(@NonNull RelayService relay, @NonNull String content) {
        PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
                new Intent(context, WeechatActivity.class), PendingIntent.FLAG_CANCEL_CURRENT);

        boolean authenticated = relay.state.contains(AUTHENTICATED);
        int icon = authenticated ? R.drawable.ic_connected : R.drawable.ic_disconnected;

        Builder builder = new Builder(context, NOTIFICATION_CHANNEL_CONNECTION_STATUS);
        builder.setContentIntent(contentIntent).setSmallIcon(icon)
                .setContentTitle("Weechat-Android " + BuildConfig.VERSION_NAME).setContentText(content)
                .setWhen(System.currentTimeMillis());

        builder.setPriority(Notification.PRIORITY_MIN);

        if (P.notificationTicker)
            builder.setTicker(content);

        String disconnectText = context.getString(authenticated ? R.string.disconnect : R.string.stop_connecting);

        builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, disconnectText, PendingIntent.getService(
                context, 0, new Intent(RelayService.ACTION_STOP, null, context, RelayService.class), 0));

        Notification notification = builder.build();
        notification.flags |= Notification.FLAG_ONGOING_EVENT;

        relay.startForeground(NOTIFICATION_MAIN_ID, notification);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////

    @SuppressWarnings("unused")
    private static final int BUFFER = 0, LINE = 1;

    // display a notification with a hot message. clicking on it will open the buffer & scroll up
    // to the hot line, if needed. mind that SOMETIMES hotCount will be larger than hotList, because
    // it's filled from hotlist data and hotList only contains lines that arrived in real time. so
    // we add (message not available) if there are NO lines to display and add "..." if there are
    // some lines to display, but not all
    @AnyThread
    @Cat
    public static void showHot(boolean connected, int totalHotCount, int hotBufferCount,
            List<Hotlist.HotMessage> allMessages, Hotlist.HotBuffer hotBuffer, boolean newHighlight) {
        if (!P.notificationEnable)
            return;

        // https://developer.android.com/guide/topics/ui/notifiers/notifications.html#back-compat
        boolean canMakeBundledNotifications = Build.VERSION.SDK_INT >= 24;
        Resources res = context.getResources();

        int hotCount = hotBuffer.hotCount;
        List<HotMessage> messages = hotBuffer.messages;
        String fullName = hotBuffer.fullName;
        String shortName = hotBuffer.shortName;

        if (hotCount == 0) {
            manager.cancel(fullName, NOTIFICATION_HOT_ID);
            onNotificationDismissed(fullName);
            return;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////

        // (re)build the parent? summary notification. in practice, it should never be visible on
        // Lollipop and later, except for the SubText part, which appears on the right of app name

        String appName = res.getString(R.string.app_name);
        String nMessagesInNBuffers = res.getQuantityString(R.plurals.messages, totalHotCount, totalHotCount)
                + res.getQuantityString(R.plurals.in_buffers, hotBufferCount, hotBufferCount);
        Builder summary = new Builder(context, NOTIFICATION_CHANNEL_HOTLIST)
                .setContentIntent(getIntentFor(NOTIFICATION_EXTRA_BUFFER_FULL_NAME_ANY))
                .setSmallIcon(R.drawable.ic_hot).setContentTitle(appName).setContentText(nMessagesInNBuffers)
                .setGroup(GROUP_KEY).setGroupSummary(true);

        if (canMakeBundledNotifications) {
            summary.setSubText(nMessagesInNBuffers);
        } else {
            MessagingStyle style = new MessagingStyle("");
            style.setConversationTitle(nMessagesInNBuffers);
            addMissingMessageLine(totalHotCount - allMessages.size(), res, style);
            for (HotMessage message : allMessages)
                style.addMessage(message.forFullList());
            summary.setStyle(style);

            if (newHighlight)
                makeNoise(summary, res, allMessages);
        }

        manager.notify(NOTIFICATION_HOT_ID, summary.build());

        if (!canMakeBundledNotifications)
            return;

        ////////////////////////////////////////////////////////////////////////////////////////////

        String newMessageInB = res.getQuantityString(R.plurals.hot_messages, hotCount, hotCount, shortName);
        Builder builder = new Builder(context, NOTIFICATION_CHANNEL_HOTLIST)
                .setContentIntent(getIntentFor(fullName)).setSmallIcon(R.drawable.ic_hot).setContentTitle(appName)
                .setContentText(newMessageInB).setDeleteIntent(getDismissIntentFor(fullName)).setGroup(GROUP_KEY)
                .setWhen(hotBuffer.lastMessageTimestamp);

        // messages hold the latest messages, don't show the reply button if user can't see any
        if (connected && messages.size() > 0)
            builder.addAction(getAction(context, fullName));

        MessagingStyle style = new MessagingStyle("");
        style.setConversationTitle(hotCount < 2 ? shortName : shortName + " (" + String.valueOf(hotCount) + ")");
        addMissingMessageLine(hotCount - messages.size(), res, style);
        for (HotMessage message : messages)
            style.addMessage(message.forBuffer());
        builder.setStyle(style);

        if (newHighlight)
            makeNoise(builder, res, messages);
        manager.notify(fullName, NOTIFICATION_HOT_ID, builder.build());
        onNotificationFired(fullName);
    }

    // setting action in this way is not quite a proper way, but this ensures that all intents
    // are treated as separate intents
    private static PendingIntent getIntentFor(String fullName) {
        Intent intent = new Intent(context, WeechatActivity.class).putExtra(NOTIFICATION_EXTRA_BUFFER_FULL_NAME,
                fullName);
        intent.setAction(fullName);
        return PendingIntent.getActivity(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private static PendingIntent getDismissIntentFor(String fullName) {
        Intent intent = new Intent(context, NotificationDismissedReceiver.class);
        intent.setAction(fullName);
        return PendingIntent.getBroadcast(context, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private static void addMissingMessageLine(int missingMessages, Resources res, MessagingStyle style) {
        if (missingMessages > 0) {
            String s = res.getQuantityString(R.plurals.hot_messages_missing, missingMessages, missingMessages);
            style.addMessage(s, 0, "");
        }
    }

    private static void makeNoise(Builder builder, Resources res, List<HotMessage> messages) {
        if (P.notificationTicker)
            builder.setTicker(messages.size() == 0 ? res.getQuantityString(R.plurals.hot_messages_missing, 1)
                    : messages.get(messages.size() - 1).forTicker());
        builder.setPriority(Notification.PRIORITY_HIGH);
        if (!TextUtils.isEmpty(P.notificationSound))
            builder.setSound(Uri.parse(P.notificationSound));
        int flags = 0;
        if (P.notificationLight)
            flags |= Notification.DEFAULT_LIGHTS;
        if (P.notificationVibrate)
            flags |= Notification.DEFAULT_VIBRATE;
        builder.setDefaults(flags);
    }

    public static final String KEY_TEXT_REPLY = "key_text_reply";

    private static NotificationCompat.Action getAction(Context ctx, String fullName) {
        String replyLabel = ctx.getResources().getString(R.string.reply_label);
        RemoteInput remoteInput = new RemoteInput.Builder(KEY_TEXT_REPLY).setLabel(replyLabel).build();
        Intent intent = new Intent(ctx, Hotlist.InlineReplyReceiver.class);
        intent.setAction(fullName);
        PendingIntent replyPendingIntent = PendingIntent.getBroadcast(ctx, 1, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
        return new NotificationCompat.Action.Builder(R.drawable.ic_send, replyLabel, replyPendingIntent)
                .addRemoteInput(remoteInput).build();
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////

    final private static Set<String> notifications = new HashSet<>();

    @AnyThread
    private static synchronized void onNotificationFired(String fullName) {
        notifications.add(fullName);
    }

    @AnyThread
    private static synchronized void onNotificationDismissed(String fullName) {
        notifications.remove(fullName);
        if (notifications.isEmpty())
            manager.cancel(NOTIFICATION_HOT_ID);
    }

    public static class NotificationDismissedReceiver extends BroadcastReceiver {
        @MainThread
        @Override
        public void onReceive(Context context, Intent intent) {
            final String fullName = intent.getAction();
            onNotificationDismissed(fullName);
        }
    }
}