blob: 7ec35e413573c72dea1e2cbe4dd04314af5439db [file] [log] [blame]
Alexandre Lision1edb1472013-10-24 14:18:05 -04001/**
2 * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr)
3 * Copyright (C) 2004-2013 Savoir-Faire Linux Inc.
4 *
5 * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com>
6 * Adrien BĂ©raud <adrien.beraud@gmail.com>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 *
22 * Additional permission under GNU GPL version 3 section 7:
23 *
24 * If you modify this program, or any covered work, by linking or
25 * combining it with the OpenSSL project's OpenSSL library (or a
26 * modified version of that library), containing parts covered by the
27 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
28 * grants you additional permission to convey the resulting work.
29 * Corresponding Source for a non-source form of such a combination
30 * shall include the source code for the parts of OpenSSL used as well
31 * as that of the covered work.
32 */
33
34package org.sflphone.utils;
35
Alexandre Lision1edb1472013-10-24 14:18:05 -040036import java.lang.reflect.InvocationTargetException;
37import java.lang.reflect.Method;
38
Alexandre Lision81ef7502013-12-11 17:29:49 -050039import org.sflphone.R;
40import org.sflphone.client.HomeActivity;
Alexandre Lision1edb1472013-10-24 14:18:05 -040041import org.sflphone.model.SipCall;
42
43import android.app.Notification;
44import android.app.NotificationManager;
Alexandre Lision81ef7502013-12-11 17:29:49 -050045import android.app.PendingIntent;
Alexandre Lision1edb1472013-10-24 14:18:05 -040046import android.content.ContentValues;
47import android.content.Context;
Alexandre Lision81ef7502013-12-11 17:29:49 -050048import android.content.Intent;
49import android.graphics.BitmapFactory;
Alexandre Lision1edb1472013-10-24 14:18:05 -040050import android.graphics.Typeface;
51import android.net.sip.SipProfile;
Alexandre Lision81ef7502013-12-11 17:29:49 -050052import android.support.v4.app.NotificationCompat;
Alexandre Lision1edb1472013-10-24 14:18:05 -040053import android.support.v4.app.NotificationCompat.Builder;
54import android.text.Spannable;
55import android.text.SpannableString;
56import android.text.TextUtils;
57import android.text.style.StyleSpan;
58import android.util.Log;
Alexandre Lision1edb1472013-10-24 14:18:05 -040059
60public class SipNotifications {
61
62 private final NotificationManager notificationManager;
63 private final Context context;
64 private Builder inCallNotification;
65 private Builder missedCallNotification;
66 private Builder messageNotification;
67 private Builder messageVoicemail;
68 private boolean resolveContacts = true;
Alexandre Lision81ef7502013-12-11 17:29:49 -050069
Alexandre Lision1edb1472013-10-24 14:18:05 -040070 public static final String NOTIF_CREATION = "notif_creation";
71 public static final String NOTIF_DELETION = "notif_deletion";
72
73 public static final int REGISTER_NOTIF_ID = 1;
74 public static final int CALL_NOTIF_ID = REGISTER_NOTIF_ID + 1;
75 public static final int CALLLOG_NOTIF_ID = REGISTER_NOTIF_ID + 2;
76 public static final int MESSAGE_NOTIF_ID = REGISTER_NOTIF_ID + 3;
77 public static final int VOICEMAIL_NOTIF_ID = REGISTER_NOTIF_ID + 4;
78
79 private static boolean isInit = false;
80
81 public SipNotifications(Context aContext) {
82 context = aContext;
83 notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
84
85 if (!isInit) {
86 cancelAll();
87 cancelCalls();
88 isInit = true;
89 }
Alexandre Lision1edb1472013-10-24 14:18:05 -040090
Alexandre Lision1edb1472013-10-24 14:18:05 -040091 }
Alexandre Lision1edb1472013-10-24 14:18:05 -040092
93 // Foreground api
94
95 private static final Class<?>[] SET_FG_SIG = new Class[] { boolean.class };
96 private static final Class<?>[] START_FG_SIG = new Class[] { int.class, Notification.class };
97 private static final Class<?>[] STOP_FG_SIG = new Class[] { boolean.class };
98 private static final String THIS_FILE = "Notifications";
99
100 private Method mSetForeground;
101 private Method mStartForeground;
102 private Method mStopForeground;
103 private Object[] mSetForegroundArgs = new Object[1];
104 private Object[] mStartForegroundArgs = new Object[2];
105 private Object[] mStopForegroundArgs = new Object[1];
106
107 private void invokeMethod(Method method, Object[] args) {
108 try {
109 method.invoke(context, args);
110 } catch (InvocationTargetException e) {
111 // Should not happen.
112 Log.w(THIS_FILE, "Unable to invoke method", e);
113 } catch (IllegalAccessException e) {
114 // Should not happen.
115 Log.w(THIS_FILE, "Unable to invoke method", e);
116 }
117 }
118
119 /**
Alexandre Lision81ef7502013-12-11 17:29:49 -0500120 * This is a wrapper around the new startForeground method, using the older APIs if it is not available.
Alexandre Lision1edb1472013-10-24 14:18:05 -0400121 */
122 private void startForegroundCompat(int id, Notification notification) {
123 // If we have the new startForeground API, then use it.
124 if (mStartForeground != null) {
125 mStartForegroundArgs[0] = Integer.valueOf(id);
126 mStartForegroundArgs[1] = notification;
127 invokeMethod(mStartForeground, mStartForegroundArgs);
128 return;
129 }
130
131 // Fall back on the old API.
132 mSetForegroundArgs[0] = Boolean.TRUE;
133 invokeMethod(mSetForeground, mSetForegroundArgs);
134 notificationManager.notify(id, notification);
135 }
136
137 /**
Alexandre Lision81ef7502013-12-11 17:29:49 -0500138 * This is a wrapper around the new stopForeground method, using the older APIs if it is not available.
Alexandre Lision1edb1472013-10-24 14:18:05 -0400139 */
140 private void stopForegroundCompat(int id) {
141 // If we have the new stopForeground API, then use it.
142 if (mStopForeground != null) {
143 mStopForegroundArgs[0] = Boolean.TRUE;
144 invokeMethod(mStopForeground, mStopForegroundArgs);
145 return;
146 }
147
148 // Fall back on the old API. Note to cancel BEFORE changing the
149 // foreground state, since we could be killed at that point.
150 notificationManager.cancel(id);
151 mSetForegroundArgs[0] = Boolean.FALSE;
152 invokeMethod(mSetForeground, mSetForegroundArgs);
153 }
154
155 private boolean isServiceWrapper = false;
156
157 public void onServiceCreate() {
158 try {
159 mStartForeground = context.getClass().getMethod("startForeground", START_FG_SIG);
160 mStopForeground = context.getClass().getMethod("stopForeground", STOP_FG_SIG);
161 isServiceWrapper = true;
162 return;
163 } catch (NoSuchMethodException e) {
164 // Running on an older platform.
165 mStartForeground = mStopForeground = null;
166 }
167 try {
168 mSetForeground = context.getClass().getMethod("setForeground", SET_FG_SIG);
169 } catch (NoSuchMethodException e) {
170 throw new IllegalStateException("OS doesn't have Service.startForeground OR Service.setForeground!");
171 }
172 isServiceWrapper = true;
173 }
174
175 public void onServiceDestroy() {
176 // Make sure our notification is gone.
177 cancelAll();
178 cancelCalls();
179 }
180
181 // Announces
182
Alexandre Lision81ef7502013-12-11 17:29:49 -0500183 // // Register
184 // public synchronized void notifyRegisteredAccounts(ArrayList<SipProfileState> activeAccountsInfos, boolean showNumbers) {
185 // if (!isServiceWrapper) {
186 // Log.e(THIS_FILE, "Trying to create a service notification from outside the service");
187 // return;
188 // }
189 // int icon = R.drawable.ic_stat_sipok;
190 // CharSequence tickerText = context.getString(R.string.service_ticker_registered_text);
191 // long when = System.currentTimeMillis();
192 //
193 //
194 // Builder nb = new NotificationCompat.Builder(context);
195 // nb.setSmallIcon(icon);
196 // nb.setTicker(tickerText);
197 // nb.setWhen(when);
198 // Intent notificationIntent = new Intent(SipManager.ACTION_SIP_DIALER);
199 // notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
200 // PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
201 //
202 // RegistrationNotification contentView = new RegistrationNotification(context.getPackageName());
203 // contentView.clearRegistrations();
204 // if(!Compatibility.isCompatible(9)) {
205 // contentView.setTextsColor(notificationPrimaryTextColor);
206 // }
207 // contentView.addAccountInfos(context, activeAccountsInfos);
208 //
209 // // notification.setLatestEventInfo(context, contentTitle,
210 // // contentText, contentIntent);
211 // nb.setOngoing(true);
212 // nb.setOnlyAlertOnce(true);
213 // nb.setContentIntent(contentIntent);
214 // nb.setContent(contentView);
215 //
216 // Notification notification = nb.build();
217 // notification.flags |= Notification.FLAG_NO_CLEAR;
218 // // We have to re-write content view because getNotification setLatestEventInfo implicitly
219 // notification.contentView = contentView;
220 // if (showNumbers) {
221 // // This only affects android 2.3 and lower
222 // notification.number = activeAccountsInfos.size();
223 // }
224 // startForegroundCompat(REGISTER_NOTIF_ID, notification);
225 // }
226
Alexandre Lision1edb1472013-10-24 14:18:05 -0400227 /**
228 * Format the remote contact name for the call info
Alexandre Lision81ef7502013-12-11 17:29:49 -0500229 *
230 * @param callInfo
231 * the callinfo to format
Alexandre Lision1edb1472013-10-24 14:18:05 -0400232 * @return the name to display for the contact
233 */
234 private String formatRemoteContactString(String remoteContact) {
235 String formattedRemoteContact = remoteContact;
Alexandre Lision81ef7502013-12-11 17:29:49 -0500236 // TODO
Alexandre Lision1edb1472013-10-24 14:18:05 -0400237 return formattedRemoteContact;
238 }
Alexandre Lision81ef7502013-12-11 17:29:49 -0500239
Alexandre Lision1edb1472013-10-24 14:18:05 -0400240 /**
241 * Format the notification title for a call info
Alexandre Lision81ef7502013-12-11 17:29:49 -0500242 *
Alexandre Lision1edb1472013-10-24 14:18:05 -0400243 * @param title
244 * @param callInfo
245 * @return
246 */
247 private String formatNotificationTitle(int title, long accId) {
Alexandre Lision81ef7502013-12-11 17:29:49 -0500248 // TODO
Alexandre Lision1edb1472013-10-24 14:18:05 -0400249 return null;
250 }
251
252 // Calls
253 public void showNotificationForCall(SipCall callInfo) {
Alexandre Lision81ef7502013-12-11 17:29:49 -0500254 // TODO
Alexandre Lision1edb1472013-10-24 14:18:05 -0400255 }
256
257 public void showNotificationForVoiceMail(SipProfile acc, int numberOfMessages) {
Alexandre Lision81ef7502013-12-11 17:29:49 -0500258 // TODO
Alexandre Lision1edb1472013-10-24 14:18:05 -0400259 }
260
261 private static String viewingRemoteFrom = null;
262
263 public void setViewingMessageFrom(String remoteFrom) {
264 viewingRemoteFrom = remoteFrom;
265 }
266
267 protected static CharSequence buildTickerMessage(Context context, String address, String body) {
268 String displayAddress = address;
269
270 StringBuilder buf = new StringBuilder(displayAddress == null ? "" : displayAddress.replace('\n', ' ').replace('\r', ' '));
271 buf.append(':').append(' ');
272
273 int offset = buf.length();
274
275 if (!TextUtils.isEmpty(body)) {
276 body = body.replace('\n', ' ').replace('\r', ' ');
277 buf.append(body);
278 }
279
280 SpannableString spanText = new SpannableString(buf.toString());
281 spanText.setSpan(new StyleSpan(Typeface.BOLD), 0, offset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
282
283 return spanText;
284 }
285
286 // Cancels
287 public final void cancelRegisters() {
288 if (!isServiceWrapper) {
289 Log.e(THIS_FILE, "Trying to cancel a service notification from outside the service");
290 return;
291 }
292 stopForegroundCompat(REGISTER_NOTIF_ID);
293 }
294
295 public final void cancelCalls() {
296 notificationManager.cancel(CALL_NOTIF_ID);
297 }
298
299 public final void cancelMissedCalls() {
300 notificationManager.cancel(CALLLOG_NOTIF_ID);
301 }
302
303 public final void cancelMessages() {
304 notificationManager.cancel(MESSAGE_NOTIF_ID);
305 }
306
307 public final void cancelVoicemails() {
308 notificationManager.cancel(VOICEMAIL_NOTIF_ID);
309 }
310
311 public final void cancelAll() {
312 // Do not cancel calls notification since it's possible that there is
313 // still an ongoing call.
314 if (isServiceWrapper) {
315 cancelRegisters();
316 }
317 cancelMessages();
318 cancelMissedCalls();
319 cancelVoicemails();
320 }
321
Alexandre Lision81ef7502013-12-11 17:29:49 -0500322 public void publishMissedCallNotification(SipCall sipCall) {
323
324 CharSequence tickerText = context.getString(R.string.notif_missed_call_title);
325 long when = System.currentTimeMillis();
326
327 Builder nb = new NotificationCompat.Builder(context);
328 nb.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.ic_launcher));
329 nb.setSmallIcon(R.drawable.ic_action_call);
330
331 nb.setTicker(tickerText);
332 nb.setWhen(when);
333 nb.setContentTitle(context.getString(R.string.notif_missed_call_title));
334 nb.setContentText(context.getString(R.string.notif_missed_call_content, sipCall.getContact().getmDisplayName()));
335 Intent notificationIntent = new Intent(context, HomeActivity.class);
336 notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
337 PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
338
339 // notification.setLatestEventInfo(context, contentTitle,
340 // contentText, contentIntent);
341 nb.setOnlyAlertOnce(true);
342 nb.setContentIntent(contentIntent);
343
344 Notification notification = nb.build();
345 // We have to re-write content view because getNotification setLatestEventInfo implicitly
346 // notification.contentView = contentView;
347
348 // startForegroundCompat(CALL_NOTIF_ID, notification);
349 notificationManager.notify(CALL_NOTIF_ID, notification);
350 }
Alexandre Lision1edb1472013-10-24 14:18:05 -0400351}