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