3f05b8cd75bd9db6d82dcd789ee51dca3ca172df
[public/netxms.git] / android / src / console / src / org / netxms / ui / android / service / ClientConnectorService.java
1 /**
2 *
3 */
4 package org.netxms.ui.android.service;
5
6 import java.text.DateFormat;
7 import java.util.ArrayList;
8 import java.util.Calendar;
9 import java.util.List;
10 import java.util.Map;
11 import java.util.Set;
12
13 import org.netxms.api.client.SessionListener;
14 import org.netxms.api.client.SessionNotification;
15 import org.netxms.base.Logger;
16 import org.netxms.client.NXCNotification;
17 import org.netxms.client.NXCSession;
18 import org.netxms.client.constants.Severity;
19 import org.netxms.client.datacollection.DciValue;
20 import org.netxms.client.events.Alarm;
21 import org.netxms.client.objects.AbstractObject;
22 import org.netxms.client.objecttools.ObjectTool;
23 import org.netxms.ui.android.NXApplication;
24 import org.netxms.ui.android.R;
25 import org.netxms.ui.android.helpers.SafeParser;
26 import org.netxms.ui.android.main.activities.DashboardBrowser;
27 import org.netxms.ui.android.main.activities.GraphBrowser;
28 import org.netxms.ui.android.main.activities.HomeScreen;
29 import org.netxms.ui.android.main.activities.NodeBrowser;
30 import org.netxms.ui.android.main.fragments.AlarmBrowserFragment;
31 import org.netxms.ui.android.receivers.AlarmIntentReceiver;
32 import org.netxms.ui.android.service.helpers.AndroidLoggingFacility;
33 import org.netxms.ui.android.service.tasks.ConnectTask;
34 import org.netxms.ui.android.service.tasks.ExecActionTask;
35
36 import android.app.AlarmManager;
37 import android.app.Notification;
38 import android.app.NotificationManager;
39 import android.app.PendingIntent;
40 import android.app.Service;
41 import android.content.BroadcastReceiver;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.IntentFilter;
45 import android.content.SharedPreferences;
46 import android.content.SharedPreferences.Editor;
47 import android.content.res.Resources;
48 import android.media.Ringtone;
49 import android.media.RingtoneManager;
50 import android.net.Uri;
51 import android.os.Binder;
52 import android.os.Handler;
53 import android.os.IBinder;
54 import android.preference.PreferenceManager;
55 import android.support.v4.app.NotificationCompat;
56 import android.support.v4.content.Loader;
57 import android.util.Log;
58 import android.widget.Toast;
59
60 /**
61 * Background communication service for NetXMS client.
62 *
63 * @author Victor Kirhenshtein
64 * @author Marco Incalcaterra (marco.incalcaterra@thinksoft.it)
65 *
66 */
67
68 public class ClientConnectorService extends Service implements SessionListener
69 {
70 public enum ConnectionStatus
71 {
72 CS_NOCONNECTION, CS_INPROGRESS, CS_ALREADYCONNECTED, CS_CONNECTED, CS_DISCONNECTED, CS_ERROR
73 };
74
75 public static final String ACTION_CONNECT = "org.netxms.ui.android.ACTION_CONNECT";
76 public static final String ACTION_FORCE_CONNECT = "org.netxms.ui.android.ACTION_FORCE_CONNECT";
77 public static final String ACTION_DISCONNECT = "org.netxms.ui.android.ACTION_DISCONNECT";
78 public static final String ACTION_FORCE_DISCONNECT = "org.netxms.ui.android.ACTION_FORCE_DISCONNECT";
79 public static final String ACTION_RESCHEDULE = "org.netxms.ui.android.ACTION_RESCHEDULE";
80 public static final String ACTION_CONFIGURE = "org.netxms.ui.android.ACTION_CONFIGURE";
81 public static final String ACTION_ALARM_ACKNOWLEDGE = "org.netxms.ui.android.ACTION_ALARM_ACKNOWLEDGE";
82 public static final String ACTION_ALARM_RESOLVE = "org.netxms.ui.android.ACTION_ALARM_RESOLVE";
83 public static final String ACTION_ALARM_TERMINATE = "org.netxms.ui.android.ACTION_ALARM_TERMINATE";
84 private static final String TAG = "nxclient/ClientConnectorService";
85 private static final String LASTALARM_KEY = "LastALarmIdNotified";
86
87 private static final int NOTIFY_ALARM = 1;
88 private static final int NOTIFY_STATUS = 2;
89 private static final int NOTIFY_STATUS_NEVER = 0;
90 private static final int NOTIFY_STATUS_ON_CONNECT = 1;
91 private static final int NOTIFY_STATUS_ON_DISCONNECT = 2;
92 private static final int NOTIFY_STATUS_ALWAYS = 3;
93 private static final int ONE_DAY_MINUTES = 24 * 60;
94 private static final int NETXMS_REQUEST_CODE = 123456;
95
96 private final Object mutex = new Object();
97 private final Binder binder = new ClientConnectorBinder();
98 private Handler uiThreadHandler;
99 private NotificationManager notificationManager;
100 private NXCSession session = null;
101 private ConnectionStatus connectionStatus = ConnectionStatus.CS_DISCONNECTED;
102 private String connectionStatusText = "";
103 private int connectionStatusColor = 0;
104 private Map<Long, Alarm> alarms = null;
105 private HomeScreen homeScreen = null;
106 private NodeBrowser nodeBrowser = null;
107 private GraphBrowser graphBrowser = null;
108 private Alarm unknownAlarm = null;
109 private long lastAlarmIdNotified;
110 private List<ObjectTool> objectTools = null;
111 private BroadcastReceiver receiver = null;
112 private DashboardBrowser dashboardBrowser;
113 private SharedPreferences sp;
114 private final List<Loader<Alarm[]>> alarmLoaders = new ArrayList<Loader<Alarm[]>>(0);
115 private final List<Loader<DciValue[]>> dciValueLoaders = new ArrayList<Loader<DciValue[]>>(0);
116 private final List<Loader<AbstractObject>> genericObjectLoaders = new ArrayList<Loader<AbstractObject>>(0);
117 private final List<Loader<Set<AbstractObject>>> genericObjectChildrenLoaders = new ArrayList<Loader<Set<AbstractObject>>>(0);
118 private String server = "";
119 private int port = 4701;
120 private String login = "";
121 private String password = "";
122 private boolean encrypt = false;
123 private boolean enabled = false;
124 private boolean notifyAlarm = false;
125 private int notificationType = NOTIFY_STATUS_NEVER;
126 private boolean notifyIcon = false;
127 private boolean notifyToast = false;
128 private int schedulerPostpone = 0;
129 private int schedulerDuration = 0;
130 private int schedulerInterval = 0;
131 private boolean schedulerDailyEnabled = false;
132 private int schedulerDailyOn = 0;
133 private int schedulerDailyOff = 0;
134
135 /**
136 * Class for clients to access. Because we know this service always runs in
137 * the same process as its clients, we don't need to deal with IPC.
138 */
139 public class ClientConnectorBinder extends Binder
140 {
141 public ClientConnectorService getService()
142 {
143 return ClientConnectorService.this;
144 }
145 }
146
147 /*
148 * (non-Javadoc)
149 *
150 * @see android.app.Service#onCreate()
151 */
152 @Override
153 public void onCreate()
154 {
155 super.onCreate();
156
157 Logger.setLoggingFacility(new AndroidLoggingFacility());
158 uiThreadHandler = new Handler(getMainLooper());
159 notificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
160
161 showToast(getString(R.string.notify_started));
162
163 sp = PreferenceManager.getDefaultSharedPreferences(this);
164 lastAlarmIdNotified = sp.getInt(LASTALARM_KEY, 0);
165 configure();
166
167 receiver = new BroadcastReceiver()
168 {
169 @Override
170 public void onReceive(Context context, Intent intent)
171 {
172 Intent i = new Intent(context, ClientConnectorService.class);
173 i.setAction(ACTION_RESCHEDULE);
174 context.startService(i);
175 }
176 };
177 registerReceiver(receiver, new IntentFilter(Intent.ACTION_TIME_TICK));
178
179 if (NXApplication.isActivityVisible())
180 reconnect(false);
181 }
182
183 /*
184 * (non-Javadoc)
185 *
186 * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
187 */
188 @Override
189 public int onStartCommand(Intent intent, int flags, int startId)
190 {
191 if ((intent != null) && (intent.getAction() != null))
192 if (intent.getAction().equals(ACTION_CONNECT))
193 reconnect(false);
194 else if (intent.getAction().equals(ACTION_FORCE_CONNECT))
195 reconnect(true);
196 else if (intent.getAction().equals(ACTION_DISCONNECT))
197 disconnect(false);
198 else if (intent.getAction().equals(ACTION_FORCE_DISCONNECT))
199 disconnect(true);
200 else if (intent.getAction().equals(ACTION_RESCHEDULE))
201 if (NXApplication.isActivityVisible())
202 reconnect(false);
203 else
204 disconnect(false);
205 else if (intent.getAction().equals(ACTION_CONFIGURE))
206 reconnect(configure());
207 else if (intent.getAction().equals(ACTION_ALARM_ACKNOWLEDGE))
208 {
209 acknowledgeAlarm(intent.getLongExtra("alarmId", 0), false);
210 hideNotification(NOTIFY_ALARM);
211 }
212 else if (intent.getAction().equals(ACTION_ALARM_RESOLVE))
213 {
214 resolveAlarm(intent.getLongExtra("alarmId", 0));
215 hideNotification(NOTIFY_ALARM);
216 }
217 else if (intent.getAction().equals(ACTION_ALARM_TERMINATE))
218 {
219 terminateAlarm(intent.getLongExtra("alarmId", 0));
220 hideNotification(NOTIFY_ALARM);
221 }
222 return super.onStartCommand(intent, flags, startId);
223 }
224
225 /*
226 * (non-Javadoc)
227 *
228 * @see android.app.Service#onBind(android.content.Intent)
229 */
230 @Override
231 public IBinder onBind(Intent intent)
232 {
233 return binder;
234 }
235
236 /*
237 * (non-Javadoc)
238 *
239 * @see android.app.Service#onDestroy()
240 */
241 @Override
242 public void onDestroy()
243 {
244 super.onDestroy();
245 }
246
247 /**
248 * Shutdown background service
249 */
250 public void shutdown()
251 {
252 cancelSchedule();
253 clearNotifications();
254 savePreferences();
255 unregisterReceiver(receiver);
256 stopSelf();
257 }
258
259 /**
260 *
261 */
262 public void savePreferences()
263 {
264 SharedPreferences.Editor editor = sp.edit();
265 editor.putInt(LASTALARM_KEY, (int)lastAlarmIdNotified);
266 editor.commit();
267 }
268
269 /**
270 * Show alarm notification
271 *
272 * @param alarm alarm object
273 * @param text notification text
274 */
275 public void alarmNotification(Alarm alarm, String text)
276 {
277 Severity severity = alarm.getCurrentSeverity();
278 if (notifyAlarm)
279 {
280 Intent notifyIntent = new Intent(getApplicationContext(), AlarmBrowserFragment.class);
281 PendingIntent intent = PendingIntent.getActivity(getApplicationContext(), 0, notifyIntent, Intent.FLAG_ACTIVITY_NEW_TASK);
282 NotificationCompat.Builder nb = new NotificationCompat.Builder(getApplicationContext())
283 .setSmallIcon(getAlarmIcon(severity))
284 .setWhen(System.currentTimeMillis())
285 .setDefaults(Notification.DEFAULT_LIGHTS)
286 .setContentText(text)
287 .setContentTitle(getString(R.string.notification_title))
288 .setContentIntent(intent);
289 final String sound = GetAlarmSound(severity);
290 if ((sound != null) && (sound.length() > 0))
291 nb.setSound(Uri.parse(sound));
292 nb.addAction(R.drawable.alarm_acknowledged, getString(R.string.alarm_acknowledge), createPendingIntent(ACTION_ALARM_ACKNOWLEDGE, alarm.getId()));
293 nb.addAction(R.drawable.alarm_resolved, getString(R.string.alarm_resolve), createPendingIntent(ACTION_ALARM_RESOLVE, alarm.getId()));
294 nb.addAction(R.drawable.alarm_terminated, getString(R.string.alarm_terminate), createPendingIntent(ACTION_ALARM_TERMINATE, alarm.getId()));
295 notificationManager.notify(NOTIFY_ALARM,
296 new NotificationCompat.BigTextStyle(nb)
297 .bigText(text)
298 .build());
299 }
300 else
301 {
302 Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), Uri.parse(GetAlarmSound(severity)));
303 if (r != null)
304 r.play();
305 }
306 }
307
308 /**
309 * Creates pending intent for notification area buttons
310 *
311 * @param action intent action
312 * @param id alarm id on which to execute the action
313 */
314 private PendingIntent createPendingIntent(String action, long id)
315 {
316 return PendingIntent.getService(
317 getApplicationContext(),
318 0,
319 new Intent(getApplicationContext(), ClientConnectorService.class).setAction(action).putExtra("alarmId", id),
320 PendingIntent.FLAG_UPDATE_CURRENT);
321 }
322
323 /**
324 * Show status notification
325 *
326 * @param status connection status
327 * @param extra extra text to add at the end of the toast
328 */
329 public void statusNotification(ConnectionStatus status, String extra)
330 {
331 int icon = -1;
332 String text = "";
333 switch (status)
334 {
335 case CS_CONNECTED:
336 if (notificationType == NOTIFY_STATUS_ON_CONNECT || notificationType == NOTIFY_STATUS_ALWAYS)
337 {
338 icon = R.drawable.ic_stat_connected;
339 }
340 text = getString(R.string.notify_connected, extra);
341 break;
342 case CS_DISCONNECTED:
343 if (notificationType == NOTIFY_STATUS_ON_DISCONNECT || notificationType == NOTIFY_STATUS_ALWAYS)
344 {
345 icon = R.drawable.ic_stat_disconnected;
346 }
347 text = getString(R.string.notify_disconnected) + getNextConnectionRetry();
348 break;
349 case CS_ERROR:
350 if (notificationType == NOTIFY_STATUS_ON_DISCONNECT || notificationType == NOTIFY_STATUS_ALWAYS)
351 {
352 icon = R.drawable.ic_stat_disconnected;
353 }
354 text = getString(R.string.notify_connection_failed, extra);
355 break;
356 case CS_NOCONNECTION:
357 case CS_INPROGRESS:
358 case CS_ALREADYCONNECTED:
359 default:
360 return;
361 }
362 if (icon == -1)
363 hideNotification(NOTIFY_STATUS);
364 else
365 {
366 if (notifyToast)
367 showToast(text);
368 if (notifyIcon)
369 {
370 Intent notifyIntent = new Intent(getApplicationContext(), HomeScreen.class);
371 PendingIntent intent = PendingIntent.getActivity(getApplicationContext(), 0, notifyIntent, Intent.FLAG_ACTIVITY_NEW_TASK);
372 NotificationCompat.Builder nb = new NotificationCompat.Builder(getApplicationContext())
373 .setSmallIcon(icon)
374 .setWhen(System.currentTimeMillis())
375 .setAutoCancel(false)
376 .setOnlyAlertOnce(true)
377 .setOngoing(true)
378 .setContentText(text)
379 .setContentTitle(getString(R.string.notification_title))
380 .setContentIntent(intent);
381 notificationManager.notify(NOTIFY_STATUS, nb.build());
382 }
383 }
384 }
385
386 /**
387 * Hide notification
388 *
389 * @param id
390 * notification id
391 */
392 private void hideNotification(int id)
393 {
394 notificationManager.cancel(id);
395 }
396
397 /**
398 * Clear all notifications
399 */
400 public void clearNotifications()
401 {
402 notificationManager.cancelAll();
403 }
404
405 /**
406 * Configure service, notify callers if it is necessary to reconnect
407 * depending if a particular subset of parameters have been changed.
408 *
409 * @return true If it is necessary to force a reconnection
410 */
411 private boolean configure()
412 {
413 boolean needsToReconnect = enabled != sp.getBoolean("global.scheduler.enable", false) ||
414 !server.equalsIgnoreCase(sp.getString("connection.server", "")) ||
415 port != SafeParser.parseInt(sp.getString("connection.port", "4701"), 4701) ||
416 !login.equals(sp.getString("connection.login", "")) ||
417 !password.equals(sp.getString("connection.password", "")) ||
418 encrypt != sp.getBoolean("connection.encrypt", false) ||
419 notifyAlarm != sp.getBoolean("global.notification.alarm", true) ||
420 notificationType != Integer.parseInt(sp.getString("global.notification.status", "0")) ||
421 notifyIcon != sp.getBoolean("global.notification.icon", false) ||
422 notifyToast != sp.getBoolean("global.notification.toast", true) ||
423 schedulerPostpone != Integer.parseInt(sp.getString("global.scheduler.postpone", "1")) ||
424 schedulerDuration != Integer.parseInt(sp.getString("global.scheduler.duration", "1")) ||
425 schedulerInterval != Integer.parseInt(sp.getString("global.scheduler.interval", "15")) ||
426 schedulerDailyEnabled != sp.getBoolean("global.scheduler.daily.enable", false) ||
427 schedulerDailyOn != getMinutes("global.scheduler.daily.on") ||
428 schedulerDailyOff != getMinutes("global.scheduler.daily.off");
429
430 enabled = sp.getBoolean("global.scheduler.enable", false);
431 server = sp.getString("connection.server", "");
432 port = SafeParser.parseInt(sp.getString("connection.port", "4701"), 4701);
433 login = sp.getString("connection.login", "");
434 password = sp.getString("connection.password", "");
435 encrypt = sp.getBoolean("connection.encrypt", false);
436 notifyAlarm = sp.getBoolean("global.notification.alarm", true);
437 notificationType = Integer.parseInt(sp.getString("global.notification.status", "0"));
438 notifyIcon = sp.getBoolean("global.notification.icon", false);
439 notifyToast = sp.getBoolean("global.notification.toast", true);
440 schedulerPostpone = Integer.parseInt(sp.getString("global.scheduler.postpone", "1"));
441 schedulerDuration = Integer.parseInt(sp.getString("global.scheduler.duration", "1"));
442 schedulerInterval = Integer.parseInt(sp.getString("global.scheduler.interval", "15"));
443 schedulerDailyEnabled = sp.getBoolean("global.scheduler.daily.enable", false);
444 schedulerDailyOn = getMinutes("global.scheduler.daily.on");
445 schedulerDailyOff = getMinutes("global.scheduler.daily.off");
446
447 return needsToReconnect;
448 }
449
450 /**
451 * Reconnect to server.
452 *
453 * @param forceReconnect if set to true forces disconnection before connecting
454 */
455 public void reconnect(boolean force)
456 {
457 if (force || (isScheduleExpired() || NXApplication.isActivityVisible()) &&
458 connectionStatus != ConnectionStatus.CS_CONNECTED &&
459 connectionStatus != ConnectionStatus.CS_ALREADYCONNECTED)
460 {
461 Log.i(TAG, "Reconnecting...");
462 synchronized (mutex)
463 {
464 if (connectionStatus != ConnectionStatus.CS_INPROGRESS)
465 {
466 setConnectionStatus(ConnectionStatus.CS_INPROGRESS, "");
467 statusNotification(ConnectionStatus.CS_INPROGRESS, "");
468 new ConnectTask(this).execute(server, port, login, password, encrypt, force);
469 }
470 }
471 }
472 }
473
474 /**
475 * Disconnect from server. Only when scheduler is enabled and connected.
476 *
477 */
478 public void disconnect(boolean force)
479 {
480 if (force || enabled && !NXApplication.isActivityVisible() &&
481 (connectionStatus == ConnectionStatus.CS_CONNECTED ||
482 connectionStatus == ConnectionStatus.CS_ALREADYCONNECTED))
483 {
484 Log.i(TAG, "Disconnecting...");
485 nullifySession();
486 setConnectionStatus(ConnectionStatus.CS_DISCONNECTED, "");
487 statusNotification(ConnectionStatus.CS_DISCONNECTED, "");
488 }
489 }
490
491 /**
492 * Called by connect task after successful connection
493 *
494 * @param session
495 * new session object
496 */
497 public void onConnect(NXCSession session, Map<Long, Alarm> alarms)
498 {
499 synchronized (mutex)
500 {
501 if (session != null)
502 {
503 schedule(ACTION_DISCONNECT);
504 this.session = session;
505 this.alarms = alarms;
506 session.addListener(this);
507 setConnectionStatus(ConnectionStatus.CS_CONNECTED, session.getServerAddress());
508 statusNotification(ConnectionStatus.CS_CONNECTED, session.getServerAddress());
509 if (alarms != null)
510 {
511 long id = -1;
512 Alarm alarm = null;
513 // Find the newest alarm received when we were offline
514 for (Alarm itAlarm : alarms.values())
515 if (itAlarm.getId() > id)
516 {
517 alarm = itAlarm;
518 id = itAlarm.getId();
519 }
520 if (alarm != null && alarm.getId() > lastAlarmIdNotified)
521 processAlarmChange(alarm);
522 }
523 }
524 }
525 }
526
527 /**
528 * Called by connect task or session notification listener after unsuccessful
529 * connection or disconnect
530 */
531 public void onDisconnect()
532 {
533 schedule(ACTION_CONNECT);
534 nullifySession();
535 setConnectionStatus(ConnectionStatus.CS_DISCONNECTED, "");
536 statusNotification(ConnectionStatus.CS_DISCONNECTED, "");
537 }
538
539 /**
540 * Called by connect task on error during connection
541 */
542 public void onError(String error)
543 {
544 nullifySession();
545 setConnectionStatus(ConnectionStatus.CS_ERROR, error);
546 statusNotification(ConnectionStatus.CS_ERROR, error);
547 }
548
549 /**
550 * Check for expired pending connection schedule
551 */
552 private boolean isScheduleExpired()
553 {
554 if (enabled)
555 {
556 Calendar cal = Calendar.getInstance(); // get a Calendar object with current time
557 return cal.getTimeInMillis() > sp.getLong("global.scheduler.next_activation", 0);
558 }
559 return true;
560 }
561
562 /**
563 * Gets stored time settings in minutes
564 */
565 private int getMinutes(String time)
566 {
567 String[] vals = sp.getString(time, "00:00").split(":");
568 return Integer.parseInt(vals[0]) * 60 + Integer.parseInt(vals[1]);
569 }
570
571 /**
572 * Sets the offset used to compute the next schedule
573 */
574 private void setDayOffset(Calendar cal, int minutes)
575 {
576 cal.set(Calendar.HOUR_OF_DAY, 0);
577 cal.set(Calendar.MINUTE, 0);
578 cal.set(Calendar.SECOND, 0);
579 cal.set(Calendar.MILLISECOND, 0);
580 cal.add(Calendar.MINUTE, minutes);
581 }
582
583 /**
584 * Schedule a new connection/disconnection
585 */
586 public void schedule(String action)
587 {
588 Log.i(TAG, "Schedule: " + action);
589 if (!enabled)
590 cancelSchedule();
591 else
592 {
593 Calendar cal = Calendar.getInstance(); // get a Calendar object with current time
594 if (action.equals(ACTION_RESCHEDULE))
595 cal.add(Calendar.MINUTE, schedulerPostpone);
596 if (action.equals(ACTION_DISCONNECT))
597 cal.add(Calendar.MINUTE, schedulerDuration);
598 else if (!schedulerDailyEnabled)
599 cal.add(Calendar.MINUTE, schedulerInterval);
600 else
601 {
602 int on = schedulerDailyOn;
603 int off = schedulerDailyOff;
604 if (off < on)
605 off += ONE_DAY_MINUTES; // Next day!
606 Calendar calOn = (Calendar)cal.clone();
607 setDayOffset(calOn, on);
608 Calendar calOff = (Calendar)cal.clone();
609 setDayOffset(calOff, off);
610 cal.add(Calendar.MINUTE, schedulerInterval);
611 if (cal.before(calOn))
612 {
613 cal = (Calendar)calOn.clone();
614 Log.i(TAG, "schedule (before): rescheduled for daily interval");
615 }
616 else if (cal.after(calOff))
617 {
618 cal = (Calendar)calOn.clone();
619 setDayOffset(cal, on + ONE_DAY_MINUTES); // Move to the next activation of the excluded range
620 Log.i(TAG, "schedule (after): rescheduled for daily interval");
621 }
622 }
623 setSchedule(cal.getTimeInMillis(), action);
624 }
625 }
626
627 /**
628 * Set a connection schedule
629 */
630 private void setSchedule(long milliseconds, String action)
631 {
632 Intent intent = new Intent(this, AlarmIntentReceiver.class);
633 intent.putExtra("action", action);
634 PendingIntent sender = PendingIntent.getBroadcast(this, NETXMS_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
635 ((AlarmManager)getSystemService(ALARM_SERVICE)).set(AlarmManager.RTC_WAKEUP, milliseconds, sender);
636 // Update status
637 long last = sp.getLong("global.scheduler.next_activation", 0);
638 Editor e = sp.edit();
639 e.putLong("global.scheduler.last_activation", last);
640 e.putLong("global.scheduler.next_activation", milliseconds);
641 e.commit();
642 }
643
644 /**
645 * Cancel a pending connection schedule (if any)
646 */
647 public void cancelSchedule()
648 {
649 Intent intent = new Intent(this, AlarmIntentReceiver.class);
650 PendingIntent sender = PendingIntent.getBroadcast(this, NETXMS_REQUEST_CODE, intent, PendingIntent.FLAG_UPDATE_CURRENT);
651 ((AlarmManager)getSystemService(ALARM_SERVICE)).cancel(sender);
652 if (sp.getLong("global.scheduler.next_activation", 0) != 0)
653 {
654 Editor e = sp.edit();
655 e.putLong("global.scheduler.next_activation", 0);
656 e.commit();
657 }
658 }
659
660 /**
661 * Release internal resources nullifying current session (if any)
662 */
663 private void nullifySession()
664 {
665 synchronized (mutex)
666 {
667 if (session != null)
668 {
669 session.disconnect();
670 session.removeListener(this);
671 session = null;
672 }
673 alarms = null;
674 unknownAlarm = null;
675 }
676 }
677
678 /**
679 * Process alarm change
680 *
681 * @param alarm
682 */
683 private void processAlarmChange(Alarm alarm)
684 {
685 AbstractObject object = findObjectById(alarm.getSourceObjectId());
686 synchronized (mutex)
687 {
688 if (alarms != null)
689 {
690 lastAlarmIdNotified = alarm.getId();
691 alarms.put(lastAlarmIdNotified, alarm);
692 }
693 unknownAlarm = object == null ? alarm : null;
694 alarmNotification(alarm, ((object != null) ? object.getObjectName() : getString(R.string.node_unknown)) + ": " + alarm.getMessage());
695 refreshAlarmBrowser();
696 }
697 }
698
699 /**
700 * Process alarm change
701 *
702 * @param alarm
703 */
704 private void processAlarmDelete(long id)
705 {
706 synchronized (mutex)
707 {
708 if (alarms != null)
709 alarms.remove(id);
710 if (lastAlarmIdNotified == id)
711 hideNotification(NOTIFY_ALARM);
712 refreshAlarmBrowser();
713 }
714 }
715
716 /**
717 * Synchronize information about specific object in background
718 *
719 * @param objectId object ID
720 */
721 private void doBackgroundObjectSync(final long objectId)
722 {
723 new Thread("Background object sync")
724 {
725 @Override
726 public void run()
727 {
728 try
729 {
730 session.syncObjectSet(new long[] { objectId }, false, NXCSession.OBJECT_SYNC_NOTIFY);
731 }
732 catch (Exception e)
733 {
734 Log.e(TAG, "Exception in doBackgroundObjectSync", e);
735 }
736 }
737 }.start();
738 }
739
740 /**
741 * @param objectId
742 * @return
743 */
744 public AbstractObject findObjectById(long objectId)
745 {
746 return findObjectById(objectId, AbstractObject.class);
747 }
748
749 /**
750 * Find object by ID with class filter
751 *
752 * @param objectId
753 * @param classFilter
754 * @return
755 */
756 public AbstractObject findObjectById(long objectId, Class<? extends AbstractObject> classFilter)
757 {
758 // we can't search without active session
759 if (session == null)
760 return null;
761
762 AbstractObject object = session.findObjectById(objectId, classFilter);
763 // if we don't have object - probably we never synced it
764 // request object synchronization in that case
765 if (object == null)
766 {
767 doBackgroundObjectSync(objectId);
768 }
769 return object;
770 }
771
772 /**
773 * @param object
774 */
775 private void processObjectUpdate(AbstractObject object)
776 {
777 synchronized (mutex)
778 {
779 if (unknownAlarm != null && unknownAlarm.getSourceObjectId() == object.getObjectId()) // Update <Unknown> notification
780 {
781 alarmNotification(unknownAlarm, object.getObjectName() + ": " + unknownAlarm.getMessage());
782 unknownAlarm = null;
783 }
784 refreshHomeScreen();
785 refreshAlarmBrowser();
786 refreshNodeBrowser();
787 refreshDashboardBrowser();
788 }
789 }
790
791 /**
792 * Refresh homescreen activity
793 */
794 private void refreshHomeScreen()
795 {
796 if (homeScreen != null)
797 {
798 homeScreen.runOnUiThread(new Runnable()
799 {
800 @Override
801 public void run()
802 {
803 homeScreen.refreshActivityStatus();
804 }
805 });
806 }
807 }
808
809 /**
810 * Refresh the alarms related activities
811 */
812 private void refreshAlarmBrowser()
813 {
814 if (homeScreen != null)
815 {
816 homeScreen.runOnUiThread(new Runnable()
817 {
818 @Override
819 public void run()
820 {
821 homeScreen.refreshPendingAlarms();
822 }
823 });
824 }
825 for (Loader<Alarm[]> l : alarmLoaders)
826 l.forceLoad();
827 }
828
829 /**
830 * Refresh the node browser activity
831 */
832 private void refreshNodeBrowser()
833 {
834 if (nodeBrowser != null)
835 {
836 nodeBrowser.runOnUiThread(new Runnable()
837 {
838 @Override
839 public void run()
840 {
841 nodeBrowser.updateNodeList();
842 }
843 });
844 }
845 for (Loader<DciValue[]> l : dciValueLoaders)
846 l.forceLoad();
847 for (Loader<AbstractObject> l : genericObjectLoaders)
848 l.forceLoad();
849 for (Loader<Set<AbstractObject>> l : genericObjectChildrenLoaders)
850 l.forceLoad();
851 }
852
853 /**
854 * Refresh dashboard browser activity
855 */
856 private void refreshDashboardBrowser()
857 {
858 if (dashboardBrowser != null)
859 {
860 dashboardBrowser.runOnUiThread(new Runnable()
861 {
862 @Override
863 public void run()
864 {
865 dashboardBrowser.updateDashboardList();
866 }
867 });
868 }
869 }
870
871 /**
872 * Process graph update event
873 */
874 private void processGraphUpdate()
875 {
876 synchronized (mutex)
877 {
878 if (this.graphBrowser != null)
879 {
880 graphBrowser.runOnUiThread(new Runnable()
881 {
882 @Override
883 public void run()
884 {
885 graphBrowser.refreshList();
886 }
887 });
888 }
889 }
890 }
891
892 /**
893 * Get alarm sound based on alarm severity
894 *
895 * @param severity
896 */
897 private String GetAlarmSound(Severity severity)
898 {
899 switch (severity)
900 {
901 case NORMAL: // Normal
902 return sp.getString("alarm.sound.normal", "");
903 case WARNING: // Warning
904 return sp.getString("alarm.sound.warning", "");
905 case MINOR: // Minor
906 return sp.getString("alarm.sound.minor", "");
907 case MAJOR: // Major
908 return sp.getString("alarm.sound.major", "");
909 case CRITICAL: // Critical
910 return sp.getString("alarm.sound.critical", "");
911 case UNKNOWN: // Unknown
912 return sp.getString("alarm.sound.unknown", ""); // TODO: 2014Oct10 Implement corresponding settings section
913 case TERMINATE: // Terminate
914 return sp.getString("alarm.sound.terminate", ""); // TODO: 2014Oct10 Implement corresponding settings section
915 case RESOLVE: // Resolve
916 return sp.getString("alarm.sound.resolve", ""); // TODO: 2014Oct10 Implement corresponding settings section
917 }
918 return "";
919 }
920
921 /**
922 * Get alarm icon based on alarm severity
923 *
924 * @param severity
925 */
926 private int getAlarmIcon(Severity severity)
927 {
928 switch (severity)
929 {
930 case NORMAL: // Normal
931 return R.drawable.status_normal;
932 case WARNING: // Warning
933 return R.drawable.status_warning;
934 case MINOR: // Minor
935 return R.drawable.status_minor;
936 case MAJOR: // Major
937 return R.drawable.status_major;
938 case CRITICAL: // Critical
939 return R.drawable.status_critical;
940 case UNKNOWN: // Unknown
941 case TERMINATE: // Terminate
942 case RESOLVE: // Resolve
943 return android.R.drawable.stat_notify_sdcard; // TODO: 2014Oct10 Implement corresponding icon
944 }
945 return android.R.drawable.stat_notify_sdcard;
946 }
947
948 /*
949 * (non-Javadoc)
950 *
951 * @see
952 * org.netxms.api.client.SessionListener#notificationHandler(org.netxms.api
953 * .client.SessionNotification)
954 */
955 @Override
956 public void notificationHandler(SessionNotification n)
957 {
958 switch (n.getCode())
959 {
960 case SessionNotification.CONNECTION_BROKEN:
961 case SessionNotification.SERVER_SHUTDOWN:
962 onDisconnect();
963 break;
964 case NXCNotification.NEW_ALARM:
965 case NXCNotification.ALARM_CHANGED:
966 processAlarmChange((Alarm)n.getObject());
967 break;
968 case NXCNotification.ALARM_DELETED:
969 case NXCNotification.ALARM_TERMINATED:
970 processAlarmDelete(((Alarm)n.getObject()).getId());
971 break;
972 case NXCNotification.OBJECT_CHANGED:
973 case NXCNotification.OBJECT_SYNC_COMPLETED:
974 processObjectUpdate((AbstractObject)n.getObject());
975 break;
976 case NXCNotification.PREDEFINED_GRAPHS_CHANGED:
977 processGraphUpdate();
978 break;
979 default:
980 break;
981 }
982 }
983
984 /**
985 * Get list of active alarms
986 *
987 * @return list of active alarms
988 */
989 public Alarm[] getAlarms()
990 {
991 Alarm[] a;
992 synchronized (mutex)
993 {
994 if (alarms != null)
995 a = alarms.values().toArray(new Alarm[alarms.size()]);
996 else
997 a = new Alarm[0];
998 }
999 return a;
1000 }
1001
1002 /**
1003 * @param id id of alarm
1004 * @param sticky true for sticky acknowledge
1005 */
1006 public void acknowledgeAlarm(long id, boolean sticky)
1007 {
1008 try
1009 {
1010 session.acknowledgeAlarm(id, sticky, 0);
1011 }
1012 catch (Exception e)
1013 {
1014 Log.e(TAG, "Exception while executing session.acknowledgeAlarm", e);
1015 }
1016 }
1017
1018 /**
1019 * @param ids list of id
1020 */
1021 public void acknowledgeAlarms(ArrayList<Long> ids, boolean sticky)
1022 {
1023 for (int i = 0; i < ids.size(); i++)
1024 acknowledgeAlarm(ids.get(i).longValue(), sticky);
1025 }
1026
1027 /**
1028 * @param id id of alarm
1029 */
1030 public void resolveAlarm(long id)
1031 {
1032 try
1033 {
1034 session.resolveAlarm(id);
1035 }
1036 catch (Exception e)
1037 {
1038 Log.e(TAG, "Exception while executing session.resolveAlarm", e);
1039 }
1040 }
1041
1042 /**
1043 * @param ids list of id
1044 */
1045 public void resolveAlarms(ArrayList<Long> ids)
1046 {
1047 for (int i = 0; i < ids.size(); i++)
1048 resolveAlarm(ids.get(i).longValue());
1049 }
1050
1051 /**
1052 * @param id id of alarm
1053 */
1054 public void terminateAlarm(long id)
1055 {
1056 try
1057 {
1058 session.terminateAlarm(id);
1059 }
1060 catch (Exception e)
1061 {
1062 Log.e(TAG, "Exception while executing session.terminateAlarm", e);
1063 }
1064 }
1065
1066 /**
1067 * @param ids list of id
1068 */
1069 public void terminateAlarms(ArrayList<Long> ids)
1070 {
1071 for (int i = 0; i < ids.size(); i++)
1072 terminateAlarm(ids.get(i).longValue());
1073 }
1074
1075 /**
1076 * @param id
1077 * @param state
1078 */
1079 public void setObjectMgmtState(long id, boolean state)
1080 {
1081 try
1082 {
1083 session.setObjectManaged(id, state);
1084 }
1085 catch (Exception e)
1086 {
1087 Log.e(TAG, "Exception while executing session.setObjectManaged", e);
1088 }
1089 }
1090
1091 /**
1092 *
1093 */
1094 public void loadTools()
1095 {
1096 try
1097 {
1098 this.objectTools = session.getObjectTools();
1099 }
1100 catch (Exception e)
1101 {
1102 this.objectTools = null;
1103 Log.d(TAG, "Exception while executing session.getObjectTools", e);
1104 }
1105 }
1106
1107 /**
1108 * Execute agent action. Communication with server will be done in separate worker thread.
1109 *
1110 * @param objectId
1111 * @param action
1112 */
1113 public void executeAction(long objectId, String action)
1114 {
1115 new ExecActionTask().execute(new Object[] { session, objectId, action, this });
1116 }
1117
1118 /**
1119 * @return
1120 */
1121 public List<ObjectTool> getTools()
1122 {
1123 return this.objectTools;
1124 }
1125
1126 /**
1127 * @param homeScreen
1128 */
1129 public void registerHomeScreen(HomeScreen homeScreen)
1130 {
1131 this.homeScreen = homeScreen;
1132 }
1133
1134 /**
1135 * @param browser
1136 */
1137 public void registerNodeBrowser(NodeBrowser browser)
1138 {
1139 nodeBrowser = browser;
1140 }
1141
1142 /**
1143 * @param browser
1144 */
1145 public void registerGraphBrowser(GraphBrowser browser)
1146 {
1147 graphBrowser = browser;
1148 }
1149
1150 /**
1151 * @param browser
1152 */
1153 public void registerDashboardBrowser(DashboardBrowser browser)
1154 {
1155 dashboardBrowser = browser;
1156 }
1157
1158 public void registerAlarmLoader(Loader<Alarm[]> loader)
1159 {
1160 if (!alarmLoaders.contains(loader))
1161 alarmLoaders.add(loader);
1162 }
1163
1164 public void unregisterAlarmLoader(Loader<Alarm[]> loader)
1165 {
1166 alarmLoaders.remove(loader);
1167 }
1168
1169 public void registerDciValueLoader(Loader<DciValue[]> loader)
1170 {
1171 if (!dciValueLoaders.contains(loader))
1172 dciValueLoaders.add(loader);
1173 }
1174
1175 public void unregisterDciValueLoader(Loader<DciValue[]> loader)
1176 {
1177 dciValueLoaders.remove(loader);
1178 }
1179
1180 public void registerGenericObjectLoader(Loader<AbstractObject> loader)
1181 {
1182 if (!genericObjectLoaders.contains(loader))
1183 genericObjectLoaders.add(loader);
1184 }
1185
1186 public void unregisterGenericObjectLoader(Loader<AbstractObject> loader)
1187 {
1188 genericObjectLoaders.remove(loader);
1189 }
1190
1191 public void registerGenericObjectChildrenLoader(Loader<Set<AbstractObject>> loader)
1192 {
1193 if (!genericObjectChildrenLoaders.contains(loader))
1194 genericObjectChildrenLoaders.add(loader);
1195 }
1196
1197 public void unregisterGenericObjectChildrenLoader(Loader<Set<AbstractObject>> loader)
1198 {
1199 genericObjectChildrenLoaders.remove(loader);
1200 }
1201
1202 /**
1203 * @return the connectionStatus
1204 */
1205 public ConnectionStatus getConnectionStatus()
1206 {
1207 return connectionStatus;
1208 }
1209
1210 /**
1211 * @return the connectionStatusText
1212 */
1213 public String getConnectionStatusText()
1214 {
1215 return connectionStatusText;
1216 }
1217
1218 /**
1219 * @return the connectionStatusColor
1220 */
1221 public int getConnectionStatusColor()
1222 {
1223 return connectionStatusColor;
1224 }
1225
1226 /**
1227 * @param connectionStatus
1228 * the connectionStatus to set
1229 */
1230 public void setConnectionStatus(ConnectionStatus connectionStatus, String extra)
1231 {
1232 Resources r = getResources();
1233 this.connectionStatus = connectionStatus;
1234 switch (connectionStatus)
1235 {
1236 case CS_NOCONNECTION:
1237 connectionStatusText = getString(R.string.notify_no_connection);
1238 connectionStatusColor = r.getColor(R.color.notify_no_connection);
1239 break;
1240 case CS_INPROGRESS:
1241 connectionStatusText = getString(R.string.notify_connecting);
1242 connectionStatusColor = r.getColor(R.color.notify_connecting);
1243 break;
1244 case CS_ALREADYCONNECTED:
1245 connectionStatusText = getString(R.string.notify_connected, extra);
1246 connectionStatusColor = r.getColor(R.color.notify_connected);
1247 break;
1248 case CS_CONNECTED:
1249 connectionStatusText = getString(R.string.notify_connected, extra);
1250 connectionStatusColor = r.getColor(R.color.notify_connected);
1251 break;
1252 case CS_DISCONNECTED:
1253 connectionStatusText = getString(R.string.notify_disconnected) + getNextConnectionRetry();
1254 connectionStatusColor = r.getColor(R.color.notify_disconnected);
1255 break;
1256 case CS_ERROR:
1257 connectionStatusText = getString(R.string.notify_connection_failed, extra);
1258 connectionStatusColor = r.getColor(R.color.notify_connection_failed);
1259 break;
1260 default:
1261 break;
1262 }
1263 if (homeScreen != null)
1264 {
1265 homeScreen.runOnUiThread(new Runnable()
1266 {
1267 @Override
1268 public void run()
1269 {
1270 homeScreen.setStatusText(connectionStatusText, connectionStatusColor);
1271 homeScreen.refreshActivityStatus();
1272 }
1273 });
1274 }
1275 }
1276
1277 public String getNextConnectionRetry()
1278 {
1279 long next = sp.getLong("global.scheduler.next_activation", 0);
1280 if (next != 0)
1281 {
1282 Calendar cal = Calendar.getInstance(); // get a Calendar object with current time
1283 if (cal.getTimeInMillis() < next)
1284 {
1285 cal.setTimeInMillis(next);
1286 return " " + getString(R.string.notify_next_connection_schedule, DateFormat.getDateTimeInstance().format(cal));
1287 }
1288 }
1289 return "";
1290 }
1291
1292 /**
1293 * @return the session
1294 */
1295 public NXCSession getSession()
1296 {
1297 return session;
1298 }
1299
1300 /**
1301 * Show toast with given text
1302 *
1303 * @param text message text
1304 */
1305 public void showToast(final String text)
1306 {
1307 uiThreadHandler.post(new Runnable()
1308 {
1309 @Override
1310 public void run()
1311 {
1312 Toast.makeText(getApplicationContext(), text, Toast.LENGTH_SHORT).show();
1313 }
1314 });
1315 }
1316 }