mirror of
https://github.com/oxen-io/session-android.git
synced 2024-12-23 16:37:30 +00:00
322 lines
12 KiB
Java
322 lines
12 KiB
Java
|
/**
|
||
|
* Copyright (C) 2011 Whisper Systems
|
||
|
*
|
||
|
* This program is free software: you can redistribute it and/or modify
|
||
|
* it under the terms of the GNU General Public License as published by
|
||
|
* the Free Software Foundation, either version 3 of the License, or
|
||
|
* (at your option) any later version.
|
||
|
*
|
||
|
* This program is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
* GNU General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU General Public License
|
||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*/
|
||
|
package org.thoughtcrime.securesms.notifications;
|
||
|
|
||
|
import android.app.Notification;
|
||
|
import android.app.NotificationManager;
|
||
|
import android.app.PendingIntent;
|
||
|
import android.content.Context;
|
||
|
import android.content.Intent;
|
||
|
import android.content.SharedPreferences;
|
||
|
import android.database.Cursor;
|
||
|
import android.graphics.BitmapFactory;
|
||
|
import android.graphics.Color;
|
||
|
import android.media.AudioManager;
|
||
|
import android.media.MediaPlayer;
|
||
|
import android.net.Uri;
|
||
|
import android.preference.PreferenceManager;
|
||
|
import android.support.v4.app.NotificationCompat;
|
||
|
import android.support.v4.app.NotificationCompat.BigTextStyle;
|
||
|
import android.support.v4.app.NotificationCompat.InboxStyle;
|
||
|
import android.text.SpannableStringBuilder;
|
||
|
import android.text.TextUtils;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||
|
import org.thoughtcrime.securesms.ConversationListActivity;
|
||
|
import org.thoughtcrime.securesms.R;
|
||
|
import org.thoughtcrime.securesms.crypto.MasterCipher;
|
||
|
import org.thoughtcrime.securesms.crypto.MasterSecret;
|
||
|
import org.thoughtcrime.securesms.crypto.MessageDisplayHelper;
|
||
|
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||
|
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||
|
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||
|
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||
|
import org.thoughtcrime.securesms.protocol.Prefix;
|
||
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||
|
import org.thoughtcrime.securesms.recipients.RecipientFactory;
|
||
|
import org.thoughtcrime.securesms.recipients.RecipientFormattingException;
|
||
|
import org.thoughtcrime.securesms.recipients.Recipients;
|
||
|
import org.thoughtcrime.securesms.util.InvalidMessageException;
|
||
|
import org.thoughtcrime.securesms.util.Util;
|
||
|
|
||
|
import java.io.IOException;
|
||
|
import java.util.List;
|
||
|
|
||
|
/**
|
||
|
* Handles posting system notifications for new messages.
|
||
|
*
|
||
|
*
|
||
|
* @author Moxie Marlinspike
|
||
|
*/
|
||
|
|
||
|
public class MessageNotifier {
|
||
|
|
||
|
public static final int NOTIFICATION_ID = 1338;
|
||
|
|
||
|
private volatile static long visibleThread = -1;
|
||
|
|
||
|
public static void setVisibleThread(long threadId) {
|
||
|
visibleThread = threadId;
|
||
|
}
|
||
|
|
||
|
public static void updateNotification(Context context, MasterSecret masterSecret) {
|
||
|
updateNotification(context, masterSecret, false);
|
||
|
}
|
||
|
|
||
|
public static void updateNotification(Context context, MasterSecret masterSecret, long threadId) {
|
||
|
if (visibleThread == threadId) {
|
||
|
DatabaseFactory.getThreadDatabase(context).setRead(threadId);
|
||
|
sendInThreadNotification(context);
|
||
|
} else {
|
||
|
updateNotification(context, masterSecret, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void updateNotification(Context context, MasterSecret masterSecret, boolean signal) {
|
||
|
Cursor cursor = null;
|
||
|
|
||
|
try {
|
||
|
cursor = DatabaseFactory.getMmsSmsDatabase(context).getUnread();
|
||
|
|
||
|
if (cursor == null || cursor.isAfterLast()) {
|
||
|
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||
|
.cancel(NOTIFICATION_ID);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
NotificationState notificationState = constructNotificationState(context, masterSecret, cursor);
|
||
|
|
||
|
if (notificationState.hasMultipleThreads()) {
|
||
|
sendMultipleThreadNotification(context, notificationState, signal);
|
||
|
} else {
|
||
|
sendSingleThreadNotification(context, notificationState, signal);
|
||
|
}
|
||
|
} finally {
|
||
|
if (cursor != null)
|
||
|
cursor.close();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void sendSingleThreadNotification(Context context,
|
||
|
NotificationState notificationState,
|
||
|
boolean signal)
|
||
|
{
|
||
|
List<NotificationItem> notifications = notificationState.getNotifications();
|
||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
|
||
|
Recipients recipients = notifications.get(0).getRecipients();
|
||
|
|
||
|
builder.setSmallIcon(R.drawable.icon_notification);
|
||
|
builder.setLargeIcon(recipients.getPrimaryRecipient().getContactPhoto());
|
||
|
builder.setContentTitle(recipients.getPrimaryRecipient().toShortString());
|
||
|
builder.setContentText(notifications.get(0).getText());
|
||
|
builder.setContentIntent(notifications.get(0).getPendingIntent(context));
|
||
|
|
||
|
SpannableStringBuilder content = new SpannableStringBuilder();
|
||
|
|
||
|
for (NotificationItem item : notifications) {
|
||
|
content.append(item.getBigStyleSummary());
|
||
|
content.append('\n');
|
||
|
}
|
||
|
|
||
|
builder.setStyle(new BigTextStyle().bigText(content));
|
||
|
|
||
|
setNotificationAlarms(context, builder, signal);
|
||
|
|
||
|
if (signal) {
|
||
|
builder.setTicker(notifications.get(0).getTickerText());
|
||
|
}
|
||
|
|
||
|
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||
|
.notify(NOTIFICATION_ID, builder.build());
|
||
|
}
|
||
|
|
||
|
private static void sendMultipleThreadNotification(Context context,
|
||
|
NotificationState notificationState,
|
||
|
boolean signal)
|
||
|
{
|
||
|
List<NotificationItem> notifications = notificationState.getNotifications();
|
||
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
|
||
|
|
||
|
builder.setSmallIcon(R.drawable.icon_notification);
|
||
|
builder.setLargeIcon(BitmapFactory.decodeResource(context.getResources(),
|
||
|
R.drawable.icon_notification));
|
||
|
builder.setContentTitle(String.format(context.getString(R.string.MessageNotifier_d_new_messages),
|
||
|
notificationState.getMessageCount()));
|
||
|
builder.setContentText(String.format(context.getString(R.string.MessageNotifier_most_recent_from_s),
|
||
|
notifications.get(0).getRecipientName()));
|
||
|
builder.setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, ConversationListActivity.class), 0));
|
||
|
|
||
|
InboxStyle style = new InboxStyle();
|
||
|
|
||
|
for (NotificationItem item : notifications) {
|
||
|
style.addLine(item.getTickerText());
|
||
|
}
|
||
|
|
||
|
builder.setStyle(style);
|
||
|
|
||
|
setNotificationAlarms(context, builder, signal);
|
||
|
|
||
|
if (signal) {
|
||
|
builder.setTicker(notifications.get(0).getTickerText());
|
||
|
}
|
||
|
|
||
|
((NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE))
|
||
|
.notify(NOTIFICATION_ID, builder.build());
|
||
|
}
|
||
|
|
||
|
private static void sendInThreadNotification(Context context) {
|
||
|
try {
|
||
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
||
|
String ringtone = sp.getString(ApplicationPreferencesActivity.RINGTONE_PREF, null);
|
||
|
|
||
|
if (ringtone == null)
|
||
|
return;
|
||
|
|
||
|
Uri uri = Uri.parse(ringtone);
|
||
|
MediaPlayer player = new MediaPlayer();
|
||
|
player.setAudioStreamType(AudioManager.STREAM_NOTIFICATION);
|
||
|
player.setDataSource(context, uri);
|
||
|
player.setLooping(false);
|
||
|
player.setVolume(0.25f, 0.25f);
|
||
|
player.prepare();
|
||
|
|
||
|
final AudioManager audioManager = ((AudioManager)context.getSystemService(Context.AUDIO_SERVICE));
|
||
|
|
||
|
audioManager.requestAudioFocus(null, AudioManager.STREAM_NOTIFICATION,
|
||
|
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);
|
||
|
|
||
|
player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
|
||
|
@Override
|
||
|
public void onCompletion(MediaPlayer mp) {
|
||
|
audioManager.abandonAudioFocus(null);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
player.start();
|
||
|
} catch (IOException ioe) {
|
||
|
Log.w("MessageNotifier", ioe);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static NotificationState constructNotificationState(Context context,
|
||
|
MasterSecret masterSecret,
|
||
|
Cursor cursor)
|
||
|
{
|
||
|
NotificationState notificationState = new NotificationState();
|
||
|
|
||
|
while (cursor.moveToNext()) {
|
||
|
Recipients recipients = getRecipients(context, cursor);
|
||
|
long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.THREAD_ID));
|
||
|
CharSequence body = getBody(context, masterSecret, cursor);
|
||
|
Uri image = null;
|
||
|
|
||
|
notificationState.addNotification(new NotificationItem(recipients, threadId, body, image));
|
||
|
}
|
||
|
|
||
|
return notificationState;
|
||
|
}
|
||
|
|
||
|
private static CharSequence getBody(Context context, MasterSecret masterSecret, Cursor cursor) {
|
||
|
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||
|
|
||
|
if (body == null) {
|
||
|
return context.getString(R.string.MessageNotifier_no_subject);
|
||
|
}
|
||
|
|
||
|
if (masterSecret != null) {
|
||
|
try {
|
||
|
body = MessageDisplayHelper.getDecryptedMessageBody(new MasterCipher(masterSecret), body);
|
||
|
} catch (InvalidMessageException e) {
|
||
|
Log.w("MessageNotifier", e);
|
||
|
return Util.getItalicizedString(context.getString(R.string.MessageNotifier_corrupted_ciphertext));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (body.startsWith(Prefix.SYMMETRIC_ENCRYPT) ||
|
||
|
body.startsWith(Prefix.ASYMMETRIC_ENCRYPT) ||
|
||
|
body.startsWith(Prefix.ASYMMETRIC_LOCAL_ENCRYPT))
|
||
|
{
|
||
|
return Util.getItalicizedString(context.getString(R.string.MessageNotifier_encrypted_message));
|
||
|
} else if (body.startsWith(Prefix.KEY_EXCHANGE) ||
|
||
|
body.startsWith(Prefix.PROCESSED_KEY_EXCHANGE))
|
||
|
{
|
||
|
return Util.getItalicizedString(context.getString(R.string.MessageNotifier_key_exchange));
|
||
|
}
|
||
|
|
||
|
return body;
|
||
|
}
|
||
|
|
||
|
private static Recipients getSmsRecipient(Context context, Cursor cursor)
|
||
|
throws RecipientFormattingException
|
||
|
{
|
||
|
String address = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.ADDRESS));
|
||
|
return RecipientFactory.getRecipientsFromString(context, address, false);
|
||
|
}
|
||
|
|
||
|
private static Recipients getMmsRecipient(Context context, Cursor cursor)
|
||
|
throws RecipientFormattingException
|
||
|
{
|
||
|
long messageId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsDatabase.ID));
|
||
|
String address = DatabaseFactory.getMmsDatabase(context).getMessageRecipient(messageId);
|
||
|
return RecipientFactory.getRecipientsFromString(context, address, false);
|
||
|
}
|
||
|
|
||
|
private static Recipients getRecipients(Context context, Cursor cursor) {
|
||
|
String type = cursor.getString(cursor.getColumnIndexOrThrow(MmsSmsDatabase.TRANSPORT));
|
||
|
|
||
|
try {
|
||
|
if (type.equals("sms")) {
|
||
|
return getSmsRecipient(context, cursor);
|
||
|
} else {
|
||
|
return getMmsRecipient(context, cursor);
|
||
|
}
|
||
|
} catch (RecipientFormattingException e) {
|
||
|
return new Recipients(new Recipient("Unknown", null, null));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void setNotificationAlarms(Context context,
|
||
|
NotificationCompat.Builder builder,
|
||
|
boolean signal)
|
||
|
{
|
||
|
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
||
|
|
||
|
String ringtone = sp.getString(ApplicationPreferencesActivity.RINGTONE_PREF, null);
|
||
|
boolean vibrate = sp.getBoolean(ApplicationPreferencesActivity.VIBRATE_PREF, true);
|
||
|
String ledColor = sp.getString(ApplicationPreferencesActivity.LED_COLOR_PREF, "green");
|
||
|
String ledBlinkPattern = sp.getString(ApplicationPreferencesActivity.LED_BLINK_PREF, "500,2000");
|
||
|
String ledBlinkPatternCustom = sp.getString(ApplicationPreferencesActivity.LED_BLINK_PREF_CUSTOM, "500,2000");
|
||
|
String[] blinkPatternArray = parseBlinkPattern(ledBlinkPattern, ledBlinkPatternCustom);
|
||
|
|
||
|
builder.setSound(TextUtils.isEmpty(ringtone) || !signal ? null : Uri.parse(ringtone));
|
||
|
|
||
|
if (signal && vibrate)
|
||
|
builder.setDefaults(Notification.DEFAULT_VIBRATE);
|
||
|
|
||
|
builder.setLights(Color.parseColor(ledColor), Integer.parseInt(blinkPatternArray[0]),
|
||
|
Integer.parseInt(blinkPatternArray[1]));
|
||
|
}
|
||
|
|
||
|
private static String[] parseBlinkPattern(String blinkPattern, String blinkPatternCustom) {
|
||
|
if (blinkPattern.equals("custom"))
|
||
|
blinkPattern = blinkPatternCustom;
|
||
|
|
||
|
return blinkPattern.split(",");
|
||
|
}
|
||
|
}
|