im.neon.util.NotificationUtils.java Source code

Java tutorial

Introduction

Here is the source code for im.neon.util.NotificationUtils.java

Source

/*
 * Copyright 2016 OpenMarket 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 im.neon.util;

import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
import org.matrix.androidsdk.util.Log;

import im.neon.R;
import im.neon.activity.CommonActivityUtils;
import im.neon.activity.JoinScreenActivity;
import im.neon.activity.LockScreenActivity;
import im.neon.activity.VectorFakeRoomPreviewActivity;
import im.neon.activity.VectorHomeActivity;
import im.neon.activity.VectorRoomActivity;

import java.lang.reflect.Method;
import java.util.Random;

/**
 * Util class for creating notifications.
 */
public class NotificationUtils {
    private static final String LOG_TAG = "NotificationUtils";

    public static final String QUICK_LAUNCH_ACTION = "EventStreamService.QUICK_LAUNCH_ACTION";
    public static final String TAP_TO_VIEW_ACTION = "EventStreamService.TAP_TO_VIEW_ACTION";
    public static final String CAR_VOICE_REPLY_KEY = "EventStreamService.CAR_VOICE_REPLY_KEY";
    public static final String ACTION_MESSAGE_HEARD = "ACTION_MESSAGE_HEARD";
    public static final String ACTION_MESSAGE_REPLY = "ACTION_MESSAGE_REPLY";
    public static final String EXTRA_ROOM_ID = "EXTRA_ROOM_ID";

    // the bubble radius is computed for 99
    static private int mUnreadBubbleWidth = -1;

    /**
     * Build an incoming call notification.
     * This notification starts the VectorHomeActivity which is in charge of centralizing the incoming call flow.
     * @param context the context.
     * @param roomName the room name in which the call is pending.
     * @param matrixId the matrix id
     * @param callId the call id.
     * @return the call notification.
     */
    public static Notification buildIncomingCallNotification(Context context, String roomName, String matrixId,
            String callId) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setWhen(System.currentTimeMillis());

        builder.setContentTitle(roomName);
        builder.setContentText(context.getString(R.string.incoming_call));
        builder.setSmallIcon(R.drawable.incoming_call_notification_transparent);

        // clear the activity stack to home activity
        Intent intent = new Intent(context, VectorHomeActivity.class);
        intent.setFlags(
                Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra(VectorHomeActivity.EXTRA_CALL_SESSION_ID, matrixId);
        intent.putExtra(VectorHomeActivity.EXTRA_CALL_ID, callId);

        // Recreate the back stack
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context).addParentStack(VectorHomeActivity.class)
                .addNextIntent(intent);

        // android 4.3 issue
        // use a generator for the private requestCode.
        // When using 0, the intent is not created/launched when the user taps on the notification.
        //
        PendingIntent pendingIntent = stackBuilder.getPendingIntent((new Random()).nextInt(1000),
                PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);

        Notification n = builder.build();
        n.flags |= Notification.FLAG_SHOW_LIGHTS;
        n.defaults |= Notification.DEFAULT_LIGHTS;

        return n;
    }

    /**
     * Build a pending call notification
     * @param context the context.
     * @param roomName the room name in which the call is pending.
     * @param roomId the room Id
     * @param matrixId the matrix id
     * @param callId the call id.
     * @return the call notification.
     */
    public static Notification buildPendingCallNotification(Context context, String roomName, String roomId,
            String matrixId, String callId) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setWhen(System.currentTimeMillis());

        builder.setContentTitle(roomName);
        builder.setContentText(context.getString(R.string.call_in_progress));
        builder.setSmallIcon(R.drawable.incoming_call_notification_transparent);

        // Build the pending intent for when the notification is clicked
        Intent roomIntent = new Intent(context, VectorRoomActivity.class);
        roomIntent.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId);
        roomIntent.putExtra(VectorRoomActivity.EXTRA_MATRIX_ID, matrixId);
        roomIntent.putExtra(VectorRoomActivity.EXTRA_START_CALL_ID, callId);

        // Recreate the back stack
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context).addParentStack(VectorRoomActivity.class)
                .addNextIntent(roomIntent);

        // android 4.3 issue
        // use a generator for the private requestCode.
        // When using 0, the intent is not created/launched when the user taps on the notification.
        //
        PendingIntent pendingIntent = stackBuilder.getPendingIntent((new Random()).nextInt(1000),
                PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);

        Notification n = builder.build();
        n.flags |= Notification.FLAG_SHOW_LIGHTS;
        n.defaults |= Notification.DEFAULT_LIGHTS;

        return n;
    }

    /**
     * Create a square bitmap from another one.
     * It is centered.
     * @param bitmap the bitmap to "square"
     * @return the squared bitmap
     */
    private static Bitmap createSquareBitmap(Bitmap bitmap) {
        Bitmap resizedBitmap = null;

        if (null != bitmap) {
            // convert the bitmap to a square bitmap
            int width = bitmap.getWidth();
            int height = bitmap.getHeight();

            if (width == height) {
                resizedBitmap = bitmap;
            }
            // larger than high
            else if (width > height) {
                resizedBitmap = Bitmap.createBitmap(bitmap, (width - height) / 2, 0, height, height);

            }
            // higher than large
            else {
                resizedBitmap = Bitmap.createBitmap(bitmap, 0, (height - width) / 2, width, width);
            }
        }

        return resizedBitmap;
    }

    /**
     * Build a message notification.
     * @param context the context
     * @param from the sender
     * @param matrixId the user account id;
     * @param displayMatrixId true to display the matrix id
     * @param largeIcon the notification icon
     * @param unseenNotifiedRoomsCount the number of notified rooms
     * @param body the message body
     * @param roomId the room id
     * @param roomName the room name
     * @param shouldPlaySound true when the notification as sound.
     * @param isInvitationEvent true if it is an invitation notification
     * @return the notification
     */
    public static Notification buildMessageNotification(Context context, String from, String matrixId,
            boolean displayMatrixId, Bitmap largeIcon, int unseenNotifiedRoomsCount, String body, String roomId,
            String roomName, boolean shouldPlaySound, boolean isInvitationEvent) {

        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setWhen(System.currentTimeMillis());

        if (!TextUtils.isEmpty(from)) {
            // don't display the room name for 1:1 room notifications.
            if (!TextUtils.isEmpty(roomName) && !roomName.equals(from)) {
                builder.setContentTitle(from + " (" + roomName + ")");
            } else {
                builder.setContentTitle(from);
            }
        } else {
            builder.setContentTitle(roomName);
        }

        builder.setContentText(body);
        builder.setAutoCancel(true);
        builder.setSmallIcon(R.drawable.message_notification_transparent);

        if (null != largeIcon) {
            largeIcon = createSquareBitmap(largeIcon);

            // add a bubble in the top right
            if (0 != unseenNotifiedRoomsCount) {
                try {
                    android.graphics.Bitmap.Config bitmapConfig = largeIcon.getConfig();

                    // set default bitmap config if none
                    if (bitmapConfig == null) {
                        bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
                    }

                    // setLargeIcon must used a 64 * 64 pixels bitmap
                    // rescale to have the same text UI.
                    float densityScale = context.getResources().getDisplayMetrics().density;
                    int side = (int) (64 * densityScale);

                    Bitmap bitmapCopy = Bitmap.createBitmap(side, side, bitmapConfig);
                    Canvas canvas = new Canvas(bitmapCopy);

                    // resize the bitmap to fill in size
                    int bitmapWidth = largeIcon.getWidth();
                    int bitmapHeight = largeIcon.getHeight();

                    float scale = Math.min((float) canvas.getWidth() / (float) bitmapWidth,
                            (float) canvas.getHeight() / (float) bitmapHeight);

                    int scaledWidth = (int) (bitmapWidth * scale);
                    int scaledHeight = (int) (bitmapHeight * scale);

                    Bitmap rescaledBitmap = Bitmap.createScaledBitmap(largeIcon, scaledWidth, scaledHeight, true);
                    canvas.drawBitmap(rescaledBitmap, (side - scaledWidth) / 2, (side - scaledHeight) / 2, null);

                    String text = "" + unseenNotifiedRoomsCount;

                    // prepare the text drawing
                    Paint textPaint = new Paint();
                    textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
                    textPaint.setColor(Color.WHITE);
                    textPaint.setTextSize(10 * densityScale);

                    // get its size
                    Rect textBounds = new Rect();

                    if (-1 == mUnreadBubbleWidth) {
                        textPaint.getTextBounds("99", 0, 2, textBounds);
                        mUnreadBubbleWidth = textBounds.width();
                    }

                    textPaint.getTextBounds(text, 0, text.length(), textBounds);

                    // draw a red circle
                    int radius = mUnreadBubbleWidth;
                    Paint paint = new Paint();
                    paint.setStyle(Paint.Style.FILL);
                    paint.setColor(Color.RED);
                    canvas.drawCircle(canvas.getWidth() - radius, radius, radius, paint);

                    // draw the text
                    canvas.drawText(text,
                            canvas.getWidth() - textBounds.width() - (radius - (textBounds.width() / 2)),
                            -textBounds.top + (radius - (-textBounds.top / 2)), textPaint);

                    // get the new bitmap
                    largeIcon = bitmapCopy;
                } catch (Exception e) {
                    Log.e(LOG_TAG, "## buildMessageNotification(): Exception Msg=" + e.getMessage());
                }
            }

            builder.setLargeIcon(largeIcon);
        }

        String name = ": ";
        if (!TextUtils.isEmpty(roomName)) {
            name = " (" + roomName + "): ";
        }

        if (displayMatrixId) {
            from = "[" + matrixId + "]\n" + from;
        }

        builder.setTicker(from + name + body);

        TaskStackBuilder stackBuilder;
        Intent intent;

        intent = new Intent(context, VectorRoomActivity.class);
        intent.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId);

        if (null != matrixId) {
            intent.putExtra(VectorRoomActivity.EXTRA_MATRIX_ID, matrixId);
        }

        stackBuilder = TaskStackBuilder.create(context).addParentStack(VectorRoomActivity.class)
                .addNextIntent(intent);

        // android 4.3 issue
        // use a generator for the private requestCode.
        // When using 0, the intent is not created/launched when the user taps on the notification.
        //
        PendingIntent pendingIntent = stackBuilder.getPendingIntent((new Random()).nextInt(1000),
                PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);

        // display the message with more than 1 lines when the device supports it
        NotificationCompat.BigTextStyle textStyle = new NotificationCompat.BigTextStyle();
        textStyle.bigText(from + ":" + body);
        builder.setStyle(textStyle);

        // do not offer to quick respond if the user did not dismiss the previous one
        if (!LockScreenActivity.isDisplayingALockScreenActivity()) {
            if (!isInvitationEvent) {
                // offer to type a quick answer (i.e. without launching the application)
                Intent quickReplyIntent = new Intent(context, LockScreenActivity.class);
                quickReplyIntent.putExtra(LockScreenActivity.EXTRA_ROOM_ID, roomId);
                quickReplyIntent.putExtra(LockScreenActivity.EXTRA_SENDER_NAME, from);
                quickReplyIntent.putExtra(LockScreenActivity.EXTRA_MESSAGE_BODY, body);

                if (null != matrixId) {
                    quickReplyIntent.putExtra(LockScreenActivity.EXTRA_MATRIX_ID, matrixId);
                }

                // the action must be unique else the parameters are ignored
                quickReplyIntent.setAction(QUICK_LAUNCH_ACTION + ((int) (System.currentTimeMillis())));
                PendingIntent pIntent = PendingIntent.getActivity(context, 0, quickReplyIntent, 0);
                builder.addAction(R.drawable.vector_notification_quick_reply,
                        context.getString(R.string.action_quick_reply), pIntent);
            } else {
                {
                    // offer to type a quick reject button
                    Intent leaveIntent = new Intent(context, JoinScreenActivity.class);
                    leaveIntent.putExtra(JoinScreenActivity.EXTRA_ROOM_ID, roomId);
                    leaveIntent.putExtra(JoinScreenActivity.EXTRA_MATRIX_ID, matrixId);
                    leaveIntent.putExtra(JoinScreenActivity.EXTRA_REJECT, true);

                    // the action must be unique else the parameters are ignored
                    leaveIntent.setAction(QUICK_LAUNCH_ACTION + ((int) (System.currentTimeMillis())));
                    PendingIntent pIntent = PendingIntent.getActivity(context, 0, leaveIntent, 0);
                    builder.addAction(R.drawable.vector_notification_reject_invitation,
                            context.getString(R.string.reject), pIntent);
                }

                {
                    // offer to type a quick accept button
                    Intent acceptIntent = new Intent(context, JoinScreenActivity.class);
                    acceptIntent.putExtra(JoinScreenActivity.EXTRA_ROOM_ID, roomId);
                    acceptIntent.putExtra(JoinScreenActivity.EXTRA_MATRIX_ID, matrixId);
                    acceptIntent.putExtra(JoinScreenActivity.EXTRA_JOIN, true);

                    // the action must be unique else the parameters are ignored
                    acceptIntent.setAction(QUICK_LAUNCH_ACTION + ((int) (System.currentTimeMillis())));
                    PendingIntent pIntent = PendingIntent.getActivity(context, 0, acceptIntent, 0);
                    builder.addAction(R.drawable.vector_notification_accept_invitation,
                            context.getString(R.string.join), pIntent);
                }
            }

            // Build the pending intent for when the notification is clicked
            Intent roomIntentTap;
            if (isInvitationEvent) {
                // for invitation the room preview must be displayed
                roomIntentTap = CommonActivityUtils.buildIntentPreviewRoom(matrixId, roomId, context,
                        VectorFakeRoomPreviewActivity.class);
            } else {
                roomIntentTap = new Intent(context, VectorRoomActivity.class);
                roomIntentTap.putExtra(VectorRoomActivity.EXTRA_ROOM_ID, roomId);
            }
            // the action must be unique else the parameters are ignored
            roomIntentTap.setAction(TAP_TO_VIEW_ACTION + ((int) (System.currentTimeMillis())));

            // Recreate the back stack
            TaskStackBuilder stackBuilderTap = TaskStackBuilder.create(context)
                    .addParentStack(VectorRoomActivity.class).addNextIntent(roomIntentTap);

            builder.addAction(R.drawable.vector_notification_open, context.getString(R.string.action_open),
                    stackBuilderTap.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
        }

        //extendForCar(context, builder, roomId, roomName, from, body);

        Notification n = builder.build();
        n.flags |= Notification.FLAG_SHOW_LIGHTS;
        n.defaults |= Notification.DEFAULT_LIGHTS;

        if (shouldPlaySound) {
            n.defaults |= Notification.DEFAULT_SOUND;
        }

        if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            // some devices crash if this field is not set
            // even if it is deprecated

            // setLatestEventInfo() is deprecated on Android M, so we try to use
            // reflection at runtime, to avoid compiler error: "Cannot resolve method.."
            try {
                Method deprecatedMethod = n.getClass().getMethod("setLatestEventInfo", Context.class,
                        CharSequence.class, CharSequence.class, PendingIntent.class);
                deprecatedMethod.invoke(n, context, from, body, pendingIntent);
            } catch (Exception ex) {
                Log.e(LOG_TAG,
                        "## buildMessageNotification(): Exception - setLatestEventInfo() Msg=" + ex.getMessage());
            }
        }

        return n;
    }

    /*
    private static void extendForCar(Context context, NotificationCompat.Builder builder, String roomId, String roomName, String from, String body) {
    int carConversationId = roomId.hashCode();
    Intent msgHeardIntent = new Intent()
            .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
            .setAction(ACTION_MESSAGE_HEARD)
            .putExtra(EXTRA_ROOM_ID, roomId);
        
    PendingIntent msgHeardPendingIntent =
            PendingIntent.getBroadcast(context,
                    carConversationId,
                    msgHeardIntent,
                    PendingIntent.FLAG_UPDATE_CURRENT);
        
    Intent msgReplyIntent = new Intent()
            .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
            .setAction(ACTION_MESSAGE_REPLY)
            .putExtra(EXTRA_ROOM_ID, roomId);
        
    PendingIntent msgReplyPendingIntent = PendingIntent.getBroadcast(
            context,
            carConversationId,
            msgReplyIntent,
            PendingIntent.FLAG_UPDATE_CURRENT);
        
    // Build a RemoteInput for receiving voice input in a Car Notification
    RemoteInput remoteInput = new RemoteInput.Builder(CAR_VOICE_REPLY_KEY)
            .setLabel(context.getString(R.string.action_quick_reply))
            .build();
        
    // Create an unread conversation object to organize a group of messages
    // from a room.
    NotificationCompat.CarExtender.UnreadConversation.Builder unreadConvBuilder =
            new NotificationCompat.CarExtender.UnreadConversation.Builder(roomName)
                    .setReadPendingIntent(msgHeardPendingIntent)
                    .setReplyAction(msgReplyPendingIntent, remoteInput);
        
    unreadConvBuilder.addMessage(context.getString(R.string.user_says_body, from, body))
            .setLatestTimestamp(System.currentTimeMillis());
    builder.extend(new NotificationCompat.CarExtender()
            .setUnreadConversation(unreadConvBuilder.build()));
        
    }*/

    private NotificationUtils() {
    }
}