/**
* Copyright (c) 2013, Redsolution LTD. All rights reserved.
*
* This file is part of Xabber project; you can redistribute it and/or
* modify it under the terms of the GNU General Public License, Version 3.
*
* Xabber 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 com.xabber.android.data.notification;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Handler;
import android.os.Vibrator;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import com.xabber.android.R;
import com.xabber.android.data.Application;
import com.xabber.android.data.OnCloseListener;
import com.xabber.android.data.OnInitializedListener;
import com.xabber.android.data.OnLoadListener;
import com.xabber.android.data.SettingsManager;
import com.xabber.android.data.account.AccountItem;
import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.account.listeners.OnAccountChangedListener;
import com.xabber.android.data.account.listeners.OnAccountRemovedListener;
import com.xabber.android.data.connection.ConnectionState;
import com.xabber.android.data.database.messagerealm.MessageItem;
import com.xabber.android.data.database.sqlite.NotificationTable;
import com.xabber.android.data.entity.AccountJid;
import com.xabber.android.data.entity.UserJid;
import com.xabber.android.data.log.LogManager;
import com.xabber.android.data.message.MessageManager;
import com.xabber.android.data.message.chat.ChatManager;
import com.xabber.android.data.message.phrase.PhraseManager;
import com.xabber.android.service.XabberService;
import com.xabber.android.ui.activity.ClearNotificationsActivity;
import com.xabber.android.ui.activity.ContactListActivity;
import com.xabber.android.ui.color.ColorManager;
import com.xabber.android.utils.StringUtils;
import org.jxmpp.stringprep.XmppStringprepException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
* Manage notifications about message, subscription and authentication.
*
* @author alexander.ivanov
*/
public class NotificationManager implements OnInitializedListener, OnAccountChangedListener,
OnCloseListener, OnLoadListener, Runnable, OnAccountRemovedListener {
public static final int PERSISTENT_NOTIFICATION_ID = 1;
public static final int MESSAGE_NOTIFICATION_ID = 2;
private static final int BASE_NOTIFICATION_PROVIDER_ID = 0x10;
private static final long VIBRATION_DURATION = 500;
private static final String LOG_TAG = NotificationManager.class.getSimpleName();
private static NotificationManager instance;
private final Application application;
private final android.app.NotificationManager notificationManager;
private final PendingIntent clearNotifications;
private final Handler handler;
/**
* Runnable to start vibration.
*/
private final Runnable startVibration;
/**
* Runnable to force stop vibration.
*/
private final Runnable stopVibration;
/**
* List of providers for notifications.
*/
private final List<NotificationProvider<? extends NotificationItem>> providers;
/**
* List of
*/
private final List<MessageNotification> messageNotifications;
private NotificationCompat.Builder persistentNotificationBuilder;
private MessageNotificationCreator messageNotificationCreator;
private int persistentNotificationColor;
public static NotificationManager getInstance() {
if (instance == null) {
instance = new NotificationManager();
}
return instance;
}
private NotificationManager() {
this.application = Application.getInstance();
notificationManager = (android.app.NotificationManager)
application.getSystemService(Context.NOTIFICATION_SERVICE);
handler = new Handler();
providers = new ArrayList<>();
messageNotifications = new ArrayList<>();
clearNotifications = PendingIntent.getActivity(
application, 0, ClearNotificationsActivity.createIntent(application), 0);
stopVibration = new Runnable() {
@Override
public void run() {
handler.removeCallbacks(startVibration);
handler.removeCallbacks(stopVibration);
((Vibrator) NotificationManager.this.application.
getSystemService(Context.VIBRATOR_SERVICE)).cancel();
}
};
startVibration = new Runnable() {
@Override
public void run() {
handler.removeCallbacks(startVibration);
handler.removeCallbacks(stopVibration);
((Vibrator) NotificationManager.this.application
.getSystemService(Context.VIBRATOR_SERVICE)).cancel();
((Vibrator) NotificationManager.this.application
.getSystemService(Context.VIBRATOR_SERVICE))
.vibrate(VIBRATION_DURATION);
handler.postDelayed(stopVibration, VIBRATION_DURATION);
}
};
persistentNotificationBuilder = new NotificationCompat.Builder(application);
initPersistentNotification();
messageNotificationCreator = new MessageNotificationCreator();
persistentNotificationColor = application.getResources().getColor(R.color.persistent_notification_color);
}
public static void addEffects(NotificationCompat.Builder notificationBuilder, MessageItem messageItem) {
if (messageItem == null) {
return;
}
if (MessageManager.getInstance().getChat(messageItem.getAccount(), messageItem.getUser()).getFirstNotification() || !SettingsManager.eventsFirstOnly()) {
Uri sound = PhraseManager.getInstance().getSound(messageItem.getAccount(),
messageItem.getUser(), messageItem.getText());
boolean makeVibration = ChatManager.getInstance().isMakeVibro(messageItem.getAccount(),
messageItem.getUser());
NotificationManager.getInstance().setNotificationDefaults(notificationBuilder,
makeVibration, sound, AudioManager.STREAM_NOTIFICATION);
}
}
private void initPersistentNotification() {
persistentNotificationBuilder.setContentTitle(application.getString(R.string.application_title_full));
persistentNotificationBuilder.setDeleteIntent(clearNotifications);
persistentNotificationBuilder.setOngoing(true);
persistentNotificationBuilder.setWhen(System.currentTimeMillis());
persistentNotificationBuilder.setCategory(NotificationCompat.CATEGORY_SERVICE);
persistentNotificationBuilder.setPriority(NotificationCompat.PRIORITY_LOW);
persistentNotificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
}
@Override
public void onLoad() {
final Collection<MessageNotification> messageNotifications = new ArrayList<>();
Cursor cursor = NotificationTable.getInstance().list();
try {
if (cursor.moveToFirst()) {
do {
try {
messageNotifications.add(new MessageNotification(
AccountJid.from(NotificationTable.getAccount(cursor)),
UserJid.from(NotificationTable.getUser(cursor)),
NotificationTable.getText(cursor),
NotificationTable.getTimeStamp(cursor),
NotificationTable.getCount(cursor)));
} catch (UserJid.UserJidCreateException | XmppStringprepException e) {
LogManager.exception(this, e);
}
} while (cursor.moveToNext());
}
} finally {
cursor.close();
}
Application.getInstance().runOnUiThread(new Runnable() {
@Override
public void run() {
onLoaded(messageNotifications);
}
});
}
private void onLoaded(Collection<MessageNotification> messageNotifications) {
this.messageNotifications.addAll(messageNotifications);
for (MessageNotification messageNotification : messageNotifications) {
MessageManager.getInstance().openChat(
messageNotification.getAccount(),
messageNotification.getUser());
}
}
@Override
public void onInitialized() {
application.addUIListener(OnAccountChangedListener.class, this);
updateMessageNotification(null);
}
/**
* Register new provider for notifications.
*
* @param provider
*/
public void registerNotificationProvider(NotificationProvider<? extends NotificationItem> provider) {
providers.add(provider);
}
/**
* Update notifications for specified provider.
*
* @param <T>
* @param provider
* @param notify Ticker to be shown. Can be <code>null</code>.
*/
public <T extends NotificationItem> void updateNotifications(
NotificationProvider<T> provider, T notify) {
int id = providers.indexOf(provider);
if (id == -1) {
throw new IllegalStateException(
"registerNotificationProvider() must be called from onLoaded() method.");
}
id += BASE_NOTIFICATION_PROVIDER_ID;
Iterator<? extends NotificationItem> iterator = provider.getNotifications().iterator();
if (!iterator.hasNext()) {
notificationManager.cancel(id);
return;
}
NotificationItem top;
String ticker;
if (notify == null) {
top = iterator.next();
ticker = null;
} else {
top = notify;
ticker = top.getTitle();
}
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(application);
notificationBuilder.setSmallIcon(provider.getIcon());
notificationBuilder.setTicker(ticker);
if (!provider.canClearNotifications()) {
notificationBuilder.setOngoing(true);
}
notificationBuilder.setContentTitle(top.getTitle());
notificationBuilder.setContentText(top.getText());
TaskStackBuilder taskStackBuilder = TaskStackBuilder.create(application);
taskStackBuilder.addNextIntentWithParentStack(top.getIntent());
notificationBuilder.setContentIntent(taskStackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
if (ticker != null) {
setNotificationDefaults(notificationBuilder, SettingsManager.eventsVibro(), provider.getSound(), provider.getStreamType());
}
notificationBuilder.setDeleteIntent(clearNotifications);
notificationBuilder.setColor(ColorManager.getInstance().getAccountPainter().getDefaultMainColor());
notify(id, notificationBuilder.build());
}
/**
* Sound, vibration and lightning flags.
*
* @param notificationBuilder
* @param streamType
*/
public void setNotificationDefaults(NotificationCompat.Builder notificationBuilder, boolean vibration, Uri sound, int streamType) {
notificationBuilder.setSound(sound, streamType);
notificationBuilder.setDefaults(0);
int defaults = 0;
if (vibration) {
if (SettingsManager.eventsIgnoreSystemVibro()) {
startVibration();
} else {
defaults |= Notification.DEFAULT_VIBRATE;
}
}
if (SettingsManager.eventsLightning()) {
defaults |= Notification.DEFAULT_LIGHTS;
}
notificationBuilder.setDefaults(defaults);
}
public void startVibration() {
handler.post(startVibration);
}
/**
* Chat was changed:
* <ul>
* <li>incoming message</li>
* <li>chat was opened</li>
* <li>account was changed</li>
* </ul>
* <p/>
* Update chat and persistent notifications.
*
* @param ticker message to be shown.
* @return
*/
private void updateMessageNotification(MessageItem ticker) {
updatePersistentNotification();
Notification messageNotification = messageNotificationCreator.notifyMessageNotification(messageNotifications, ticker);
if (messageNotification != null) {
notify(MESSAGE_NOTIFICATION_ID, messageNotification);
} else {
notificationManager.cancel(MESSAGE_NOTIFICATION_ID);
}
}
private void updatePersistentNotification() {
if (!SettingsManager.eventsPersistent()) {
return;
}
// we do not want to show persistent notification if there are no enabled accounts
XabberService.getInstance().changeForeground();
Collection<AccountJid> accountList = AccountManager.getInstance().getEnabledAccounts();
if (accountList.isEmpty()) {
return;
}
int waiting = 0;
int connecting = 0;
int connected = 0;
for (AccountJid account : accountList) {
AccountItem accountItem = AccountManager.getInstance().getAccount(account);
if (accountItem == null) {
continue;
}
ConnectionState state = accountItem.getState();
LogManager.i(this, "updatePersistentNotification account " + account + " state " + state );
switch (state) {
case offline:
break;
case waiting:
waiting++;
break;
case connecting:
case registration:
case authentication:
connecting++;
break;
case connected:
connected++;
break;
}
}
final Intent persistentIntent;
persistentIntent = ContactListActivity.createPersistentIntent(application);
if (connected > 0) {
persistentNotificationBuilder.setColor(persistentNotificationColor);
persistentNotificationBuilder.setSmallIcon(R.drawable.ic_stat_online);
} else {
persistentNotificationBuilder.setColor(NotificationCompat.COLOR_DEFAULT);
persistentNotificationBuilder.setSmallIcon(R.drawable.ic_stat_offline);
}
persistentNotificationBuilder.setContentText(getConnectionState(waiting, connecting, connected, accountList.size()));
persistentNotificationBuilder.setContentIntent(PendingIntent.getActivity(application, 0, persistentIntent,
PendingIntent.FLAG_UPDATE_CURRENT));
notify(PERSISTENT_NOTIFICATION_ID, persistentNotificationBuilder.build());
}
private String getConnectionState(int waiting, int connecting, int connected, int accountCount) {
String accountQuantity;
String connectionState;
if (connected > 0) {
accountQuantity = StringUtils.getQuantityString(
application.getResources(), R.array.account_quantity, accountCount);
String connectionFormat = StringUtils.getQuantityString(
application.getResources(), R.array.connection_state_connected, connected);
connectionState = String.format(connectionFormat, connected, accountCount, accountQuantity);
} else if (connecting > 0) {
accountQuantity = StringUtils.getQuantityString(
application.getResources(), R.array.account_quantity, accountCount);
String connectionFormat = StringUtils.getQuantityString(
application.getResources(), R.array.connection_state_connecting, connecting);
connectionState = String.format(connectionFormat, connecting, accountCount, accountQuantity);
} else if (waiting > 0 && application.isInitialized()) {
accountQuantity = StringUtils.getQuantityString(
application.getResources(), R.array.account_quantity, accountCount);
String connectionFormat = StringUtils.getQuantityString(
application.getResources(), R.array.connection_state_waiting, waiting);
connectionState = String.format(connectionFormat, waiting, accountCount, accountQuantity);
} else {
accountQuantity = StringUtils.getQuantityString(
application.getResources(), R.array.account_quantity_offline, accountCount);
connectionState = application.getString(
R.string.connection_state_offline, accountCount, accountQuantity);
}
return connectionState;
}
private void notify(int id, Notification notification) {
LogManager.i(this, "Notification: " + id
+ ", ticker: " + notification.tickerText
+ ", sound: " + notification.sound
+ ", vibro: " + (notification.defaults & Notification.DEFAULT_VIBRATE)
+ ", light: " + (notification.defaults & Notification.DEFAULT_LIGHTS));
try {
notificationManager.notify(id, notification);
} catch (SecurityException e) {
LogManager.exception(this, e);
}
}
private MessageNotification getMessageNotification(AccountJid account, UserJid user) {
for (MessageNotification messageNotification : messageNotifications) {
if (messageNotification.equals(account, user)) {
return messageNotification;
}
}
return null;
}
public void onMessageNotification(MessageItem messageItem) {
MessageNotification messageNotification = getMessageNotification(
messageItem.getAccount(), messageItem.getUser());
if (messageNotification == null) {
messageNotification = new MessageNotification(
messageItem.getAccount(), messageItem.getUser(), null, null, 0);
} else {
messageNotifications.remove(messageNotification);
}
messageNotification.addMessage(messageItem.getText());
messageNotifications.add(messageNotification);
final AccountJid account = messageNotification.getAccount();
final UserJid user = messageNotification.getUser();
final String text = messageNotification.getText();
final Date timestamp = messageNotification.getTimestamp();
final int count = messageNotification.getCount();
Application.getInstance().runInBackgroundUserRequest(new Runnable() {
@Override
public void run() {
NotificationTable.getInstance().write(account.toString(), user.toString(), text, timestamp, count);
}
});
updateMessageNotification(messageItem);
}
/**
* Updates message notification.
*/
public void onMessageNotification() {
updateMessageNotification(null);
}
public int getNotificationMessageCount(AccountJid account, UserJid user) {
MessageNotification messageNotification = getMessageNotification(
account, user);
if (messageNotification == null)
return 0;
return messageNotification.getCount();
}
public void removeMessageNotification(final AccountJid account, final UserJid user) {
MessageNotification messageNotification = getMessageNotification(account, user);
if (messageNotification == null)
return;
messageNotifications.remove(messageNotification);
Application.getInstance().runInBackgroundUserRequest(new Runnable() {
@Override
public void run() {
NotificationTable.getInstance().remove(account.toString(), user.toString());
}
});
updateMessageNotification(null);
}
public void removeMessageNotificationsForAccount(final AccountJid account) {
Iterator<MessageNotification> iterator = messageNotifications.iterator();
while(iterator.hasNext()) {
MessageNotification messageNotification = iterator.next();
if (messageNotification.getAccount().equals(account)) {
iterator.remove();
}
}
Application.getInstance().runInBackgroundUserRequest(new Runnable() {
@Override
public void run() {
NotificationTable.getInstance().remove(account);
}
});
updateMessageNotification(null);
}
/**
* Called when notifications was cleared by user.
*/
public void onClearNotifications() {
for (NotificationProvider<? extends NotificationItem> provider : providers)
if (provider.canClearNotifications())
provider.clearNotifications();
messageNotifications.clear();
Application.getInstance().runInBackgroundUserRequest(new Runnable() {
@Override
public void run() {
NotificationTable.getInstance().clear();
}
});
updateMessageNotification(null);
}
@Override
public void onAccountsChanged(Collection<AccountJid> accounts) {
handler.post(this);
}
@Override
public void onAccountRemoved(AccountItem accountItem) {
LogManager.i(LOG_TAG, "onAccountRemoved " + accountItem.getAccount());
for (NotificationProvider<? extends NotificationItem> notificationProvider : providers) {
if (notificationProvider instanceof AccountNotificationProvider) {
((AccountNotificationProvider) notificationProvider)
.clearAccountNotifications(accountItem.getAccount());
updateNotifications(notificationProvider, null);
}
}
removeMessageNotificationsForAccount(accountItem.getAccount());
}
@Override
public void run() {
handler.removeCallbacks(this);
updateMessageNotification(null);
}
public Notification getPersistentNotification() {
return persistentNotificationBuilder.build();
}
@Override
public void onClose() {
notificationManager.cancelAll();
}
}