blob: 73f31e2b533d844c9231df1961352015c2c351c2 [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
36
37import java.lang.reflect.InvocationTargetException;
38import java.lang.reflect.Method;
39
40import org.sflphone.model.SipCall;
41
42import android.app.Notification;
43import android.app.NotificationManager;
44import android.content.ContentValues;
45import android.content.Context;
46import android.graphics.Typeface;
47import android.net.sip.SipProfile;
48import android.support.v4.app.NotificationCompat.Builder;
49import android.text.Spannable;
50import android.text.SpannableString;
51import android.text.TextUtils;
52import android.text.style.StyleSpan;
53import android.util.Log;
54import android.view.ViewGroup;
55import android.widget.LinearLayout;
56import android.widget.TextView;
57
58public class SipNotifications {
59
60 private final NotificationManager notificationManager;
61 private final Context context;
62 private Builder inCallNotification;
63 private Builder missedCallNotification;
64 private Builder messageNotification;
65 private Builder messageVoicemail;
66 private boolean resolveContacts = true;
67
68 public static final String NOTIF_CREATION = "notif_creation";
69 public static final String NOTIF_DELETION = "notif_deletion";
70
71 public static final int REGISTER_NOTIF_ID = 1;
72 public static final int CALL_NOTIF_ID = REGISTER_NOTIF_ID + 1;
73 public static final int CALLLOG_NOTIF_ID = REGISTER_NOTIF_ID + 2;
74 public static final int MESSAGE_NOTIF_ID = REGISTER_NOTIF_ID + 3;
75 public static final int VOICEMAIL_NOTIF_ID = REGISTER_NOTIF_ID + 4;
76
77 private static boolean isInit = false;
78
79 public SipNotifications(Context aContext) {
80 context = aContext;
81 notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
82
83 if (!isInit) {
84 cancelAll();
85 cancelCalls();
86 isInit = true;
87 }
88
89 if( ! Compatibility.isCompatible(9) ) {
90 searchNotificationPrimaryText(aContext);
91 }
92 }
93
94 private Integer notificationPrimaryTextColor = null;
95
96 private static String TO_SEARCH = "Search";
97 // Retrieve notification textColor with android < 2.3
98 @SuppressWarnings("deprecation")
99 private void searchNotificationPrimaryText(Context aContext) {
100 try {
101 Notification ntf = new Notification();
102 ntf.setLatestEventInfo(aContext, TO_SEARCH, "", null);
103 LinearLayout group = new LinearLayout(aContext);
104 ViewGroup event = (ViewGroup) ntf.contentView.apply(aContext, group);
105 recurseSearchNotificationPrimaryText(event);
106 group.removeAllViews();
107 } catch (Exception e) {
108 Log.e(THIS_FILE, "Can't retrieve the color", e);
109 }
110 }
111
112 private boolean recurseSearchNotificationPrimaryText(ViewGroup gp) {
113 final int count = gp.getChildCount();
114 for (int i = 0; i < count; ++i) {
115 if (gp.getChildAt(i) instanceof TextView){
116 final TextView text = (TextView) gp.getChildAt(i);
117 final String szText = text.getText().toString();
118 if (TO_SEARCH.equals(szText)) {
119 notificationPrimaryTextColor = text.getTextColors().getDefaultColor();
120 return true;
121 }
122 } else if (gp.getChildAt(i) instanceof ViewGroup) {
123 if(recurseSearchNotificationPrimaryText((ViewGroup) gp.getChildAt(i))) {
124 return true;
125 }
126 }
127 }
128 return false;
129 }
130
131
132 // Foreground api
133
134 private static final Class<?>[] SET_FG_SIG = new Class[] { boolean.class };
135 private static final Class<?>[] START_FG_SIG = new Class[] { int.class, Notification.class };
136 private static final Class<?>[] STOP_FG_SIG = new Class[] { boolean.class };
137 private static final String THIS_FILE = "Notifications";
138
139 private Method mSetForeground;
140 private Method mStartForeground;
141 private Method mStopForeground;
142 private Object[] mSetForegroundArgs = new Object[1];
143 private Object[] mStartForegroundArgs = new Object[2];
144 private Object[] mStopForegroundArgs = new Object[1];
145
146 private void invokeMethod(Method method, Object[] args) {
147 try {
148 method.invoke(context, args);
149 } catch (InvocationTargetException e) {
150 // Should not happen.
151 Log.w(THIS_FILE, "Unable to invoke method", e);
152 } catch (IllegalAccessException e) {
153 // Should not happen.
154 Log.w(THIS_FILE, "Unable to invoke method", e);
155 }
156 }
157
158 /**
159 * This is a wrapper around the new startForeground method, using the older
160 * APIs if it is not available.
161 */
162 private void startForegroundCompat(int id, Notification notification) {
163 // If we have the new startForeground API, then use it.
164 if (mStartForeground != null) {
165 mStartForegroundArgs[0] = Integer.valueOf(id);
166 mStartForegroundArgs[1] = notification;
167 invokeMethod(mStartForeground, mStartForegroundArgs);
168 return;
169 }
170
171 // Fall back on the old API.
172 mSetForegroundArgs[0] = Boolean.TRUE;
173 invokeMethod(mSetForeground, mSetForegroundArgs);
174 notificationManager.notify(id, notification);
175 }
176
177 /**
178 * This is a wrapper around the new stopForeground method, using the older
179 * APIs if it is not available.
180 */
181 private void stopForegroundCompat(int id) {
182 // If we have the new stopForeground API, then use it.
183 if (mStopForeground != null) {
184 mStopForegroundArgs[0] = Boolean.TRUE;
185 invokeMethod(mStopForeground, mStopForegroundArgs);
186 return;
187 }
188
189 // Fall back on the old API. Note to cancel BEFORE changing the
190 // foreground state, since we could be killed at that point.
191 notificationManager.cancel(id);
192 mSetForegroundArgs[0] = Boolean.FALSE;
193 invokeMethod(mSetForeground, mSetForegroundArgs);
194 }
195
196 private boolean isServiceWrapper = false;
197
198 public void onServiceCreate() {
199 try {
200 mStartForeground = context.getClass().getMethod("startForeground", START_FG_SIG);
201 mStopForeground = context.getClass().getMethod("stopForeground", STOP_FG_SIG);
202 isServiceWrapper = true;
203 return;
204 } catch (NoSuchMethodException e) {
205 // Running on an older platform.
206 mStartForeground = mStopForeground = null;
207 }
208 try {
209 mSetForeground = context.getClass().getMethod("setForeground", SET_FG_SIG);
210 } catch (NoSuchMethodException e) {
211 throw new IllegalStateException("OS doesn't have Service.startForeground OR Service.setForeground!");
212 }
213 isServiceWrapper = true;
214 }
215
216 public void onServiceDestroy() {
217 // Make sure our notification is gone.
218 cancelAll();
219 cancelCalls();
220 }
221
222 // Announces
223
224// // Register
225// public synchronized void notifyRegisteredAccounts(ArrayList<SipProfileState> activeAccountsInfos, boolean showNumbers) {
226// if (!isServiceWrapper) {
227// Log.e(THIS_FILE, "Trying to create a service notification from outside the service");
228// return;
229// }
230// int icon = R.drawable.ic_stat_sipok;
231// CharSequence tickerText = context.getString(R.string.service_ticker_registered_text);
232// long when = System.currentTimeMillis();
233//
234//
235// Builder nb = new NotificationCompat.Builder(context);
236// nb.setSmallIcon(icon);
237// nb.setTicker(tickerText);
238// nb.setWhen(when);
239// Intent notificationIntent = new Intent(SipManager.ACTION_SIP_DIALER);
240// notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
241// PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
242//
243// RegistrationNotification contentView = new RegistrationNotification(context.getPackageName());
244// contentView.clearRegistrations();
245// if(!Compatibility.isCompatible(9)) {
246// contentView.setTextsColor(notificationPrimaryTextColor);
247// }
248// contentView.addAccountInfos(context, activeAccountsInfos);
249//
250// // notification.setLatestEventInfo(context, contentTitle,
251// // contentText, contentIntent);
252// nb.setOngoing(true);
253// nb.setOnlyAlertOnce(true);
254// nb.setContentIntent(contentIntent);
255// nb.setContent(contentView);
256//
257// Notification notification = nb.build();
258// notification.flags |= Notification.FLAG_NO_CLEAR;
259// // We have to re-write content view because getNotification setLatestEventInfo implicitly
260// notification.contentView = contentView;
261// if (showNumbers) {
262// // This only affects android 2.3 and lower
263// notification.number = activeAccountsInfos.size();
264// }
265// startForegroundCompat(REGISTER_NOTIF_ID, notification);
266// }
267
268 /**
269 * Format the remote contact name for the call info
270 * @param callInfo the callinfo to format
271 * @return the name to display for the contact
272 */
273 private String formatRemoteContactString(String remoteContact) {
274 String formattedRemoteContact = remoteContact;
275 //TODO
276 return formattedRemoteContact;
277 }
278
279 /**
280 * Format the notification title for a call info
281 * @param title
282 * @param callInfo
283 * @return
284 */
285 private String formatNotificationTitle(int title, long accId) {
286 //TODO
287 return null;
288 }
289
290 // Calls
291 public void showNotificationForCall(SipCall callInfo) {
292 //TODO
293 }
294
295 public void showNotificationForMissedCall(ContentValues callLog) {
296 //TODO
297 }
298
299 public void showNotificationForVoiceMail(SipProfile acc, int numberOfMessages) {
300 //TODO
301 }
302
303 private static String viewingRemoteFrom = null;
304
305 public void setViewingMessageFrom(String remoteFrom) {
306 viewingRemoteFrom = remoteFrom;
307 }
308
309 protected static CharSequence buildTickerMessage(Context context, String address, String body) {
310 String displayAddress = address;
311
312 StringBuilder buf = new StringBuilder(displayAddress == null ? "" : displayAddress.replace('\n', ' ').replace('\r', ' '));
313 buf.append(':').append(' ');
314
315 int offset = buf.length();
316
317 if (!TextUtils.isEmpty(body)) {
318 body = body.replace('\n', ' ').replace('\r', ' ');
319 buf.append(body);
320 }
321
322 SpannableString spanText = new SpannableString(buf.toString());
323 spanText.setSpan(new StyleSpan(Typeface.BOLD), 0, offset, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
324
325 return spanText;
326 }
327
328 // Cancels
329 public final void cancelRegisters() {
330 if (!isServiceWrapper) {
331 Log.e(THIS_FILE, "Trying to cancel a service notification from outside the service");
332 return;
333 }
334 stopForegroundCompat(REGISTER_NOTIF_ID);
335 }
336
337 public final void cancelCalls() {
338 notificationManager.cancel(CALL_NOTIF_ID);
339 }
340
341 public final void cancelMissedCalls() {
342 notificationManager.cancel(CALLLOG_NOTIF_ID);
343 }
344
345 public final void cancelMessages() {
346 notificationManager.cancel(MESSAGE_NOTIF_ID);
347 }
348
349 public final void cancelVoicemails() {
350 notificationManager.cancel(VOICEMAIL_NOTIF_ID);
351 }
352
353 public final void cancelAll() {
354 // Do not cancel calls notification since it's possible that there is
355 // still an ongoing call.
356 if (isServiceWrapper) {
357 cancelRegisters();
358 }
359 cancelMessages();
360 cancelMissedCalls();
361 cancelVoicemails();
362 }
363
364}