package it.angelic.soulissclient; import android.Manifest; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.BitmapFactory; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; import android.support.v4.content.ContextCompat; import android.util.Log; import java.util.Calendar; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import it.angelic.soulissclient.helpers.SoulissPreferenceHelper; import it.angelic.soulissclient.model.SoulissCommand; import it.angelic.soulissclient.model.SoulissNode; import it.angelic.soulissclient.model.SoulissTypical; import it.angelic.soulissclient.model.db.SoulissDBHelper; import it.angelic.soulissclient.net.UDPHelper; import it.angelic.soulissclient.net.UDPRunnable; import it.angelic.soulissclient.util.SoulissUtils; public class SoulissDataService extends Service implements LocationListener { // LOGGA a parte private static final String TAG = "SoulissDataService"; // This is the object that receives interactions from clients. See // RemoteService for a more complete example. private final IBinder mBinder = new LocalBinder(); NotificationManager nm; private Intent cIntent; // private long uir; private SoulissDBHelper db; private float homeDist = 0; private Calendar lastupd = Calendar.getInstance(); private LocationManager locationManager; private Handler mHandler = new Handler(); // private Timer timer = new Timer(); private SoulissPreferenceHelper opts; private String provider; private Thread udpThread; private Runnable mUpdateSoulissRunnable = new Runnable() { public void run() { // locationManager.requestSingleUpdate(provider, null); Log.d(TAG, "Service run " + SoulissDataService.this.hashCode() + " backedoffInterval=" + opts.getBackedOffServiceIntervalMsec()); opts = SoulissApp.getOpzioni(); if (!opts.isDbConfigured()) { Log.w(TAG, "Database empty, closing service"); setLastupd(Calendar.getInstance()); // mHandler.removeCallbacks(mUpdateSoulissRunnable); reschedule(false); // SoulissDataService.this.stopSelf(); return; } if (!opts.getCustomPref().contains("numNodi")) { Log.w(TAG, "Souliss didn't answer yet, rescheduling"); setLastupd(Calendar.getInstance()); // mHandler.removeCallbacks(mUpdateSoulissRunnable); reschedule(false); return; } /* * if (uir != opts.getDataServiceInterval()) { uir = * opts.getDataServiceInterval(); Log.w(TAG, "pace changed: " + * opts.getDataServiceInterval()); } */ String cached = opts.getAndSetCachedAddress(); final byte nodesNum = (byte) opts.getCustomPref().getInt("numNodi", 0); if (opts.getCustomPref().contains("connection") && cached != null) { if (cached.compareTo("") == 0 || cached.compareTo(SoulissDataService.this.getResources().getString(R.string.unavailable)) == 0) { Log.e(TAG, "Souliss Unavailable, rescheduling"); //DONT REFRESH last exec. We want a re-schedule soon //setLastupd(Calendar.getInstance()); reschedule(false); // SoulissDataService.this.stopSelf(); return; } // db.open(); float homeDistPrev = opts.getPrevDistance(); Log.i(TAG, "Previous distance " + homeDistPrev + " current: " + homeDist); // PROGRAMMI POSIZIONALI / if (homeDist != homeDistPrev) { processPositionalPrograms(homeDistPrev); } // Timed commands new Thread(new Runnable() { @Override public void run() { SoulissDBHelper.open(); LinkedList<SoulissCommand> unexecuted = db.getUnexecutedCommands(SoulissDataService.this); Log.i(TAG, String.format("checking %d unexecuted TIMED commands ", unexecuted.size())); for (SoulissCommand unexnex : unexecuted) { Calendar now = Calendar.getInstance(); if (unexnex.getType() == Constants.COMMAND_TIMED && now.after(unexnex.getScheduledTime())) { // esegui comando Log.w(TAG, "issuing command: " + unexnex.toString()); unexnex.execute(); unexnex.persistCommand(); // Se ricorsivo, ricrea if (unexnex.getInterval() > 0) { SoulissCommand nc = new SoulissCommand( unexnex.getParentTypical()); nc.setNodeId(unexnex.getNodeId()); nc.setSlot(unexnex.getSlot()); nc.setCommand(unexnex.getCommand()); nc.setInterval(unexnex.getInterval()); Calendar cop = Calendar.getInstance(); cop.add(Calendar.SECOND, unexnex.getInterval()); nc.setScheduledTime(cop); nc.setType(Constants.COMMAND_TIMED); nc.persistCommand(); Log.w(TAG, "recreate recursive command"); } sendProgramNotification(SoulissDataService.this, getString(R.string.timed_program_executed), unexnex.toString() + " " + unexnex.getParentTypical().toString(), R.drawable.clock1, unexnex); } else if (unexnex.getType() != Constants.COMMAND_TIMED) { //this is only a check Log.e(TAG, "WTF? nt TIMED?? " + unexnex.getType()); } } // db.close(); } }).start(); // Check for too long ON status new Thread(new Runnable() { @Override public void run() { Log.d(TAG, "Checking warning for long turned-on typicals"); SoulissDBHelper.open(); int checkd = 0; List<SoulissNode> nodes = db.getAllNodes(); Calendar now = Calendar.getInstance(); for (SoulissNode piter : nodes) { List<SoulissTypical> slots = piter.getActiveTypicals(); for (SoulissTypical tipico : slots) { Date when = tipico.getTypicalDTO().getLastStatusChange(); if (when != null && (tipico.getOutput() != Constants.Typicals.Souliss_T1n_OffCoil || tipico.getOutput() != Constants.Typicals.Souliss_T1n_OffCoil_Auto) && tipico.getTypicalDTO().getWarnDelayMsec() > 0 && now.getTime().getTime() - when.getTime() > tipico.getTypicalDTO().getWarnDelayMsec()) { Log.w(TAG, String.format(getString(R.string.hasbeenturnedontoolong), tipico.getNiceName())); sendTooLongWarnNotification(SoulissDataService.this, getString(R.string.timed_warning), String.format(getString(R.string.hasbeenturnedontoolong), tipico.getNiceName()), tipico); checkd++; } } } Log.i(TAG, "checked timed on warnings: " + checkd); // db.close(); } }).start(); /* SENSORS REFRESH THREAD */ new Thread(new Runnable() { @Override public void run() { /* * finally { db.close(); } */ // spostato per consentire comandi manuali if (!opts.isDataServiceEnabled()) { Log.w(TAG, "Service disabled, is not going to be re-scheduled"); setLastupd(Calendar.getInstance()); mHandler.removeCallbacks(mUpdateSoulissRunnable); // SoulissDataService.this.stopSelf(); return; } else { // refresh della subscription new Thread(new Runnable() { @Override public void run() { Log.i(TAG, "issuing subscribe, numnodes=" + nodesNum); UDPHelper.stateRequest(opts, nodesNum, 0); } }).start(); try { // ritarda il logging Thread.sleep(3000); SoulissDBHelper.open(); Map<Short, SoulissNode> refreshedNodes = new HashMap<>(); List<SoulissNode> ref = db.getAllNodes(); for (SoulissNode soulissNode : ref) { refreshedNodes.put(soulissNode.getNodeId(), soulissNode); } Log.v(TAG, "logging nodes:" + nodesNum); // issueRefreshSensors(ref, refreshedNodes); logThings(refreshedNodes); // try a local reach, just in case .. UDPHelper.checkSoulissUdp(2000, opts, opts.getPrefIPAddress()); } catch (Exception e) { Log.e(TAG, "Service error, scheduling again ", e); } Log.i(TAG, "Service end run" + SoulissDataService.this.hashCode()); setLastupd(Calendar.getInstance()); reschedule(false); } } }).start(); } else {// NO CONNECTION, NO NODES!! Log.w(TAG, "Service end but NOTHING DONE"); Intent i = new Intent(); i.setAction(Constants.CUSTOM_INTENT); setLastupd(Calendar.getInstance()); getApplicationContext().sendBroadcast(i); reschedule(false); } } }; public static void sendTooLongWarnNotification(Context ctx, String desc, String longdesc, @NonNull SoulissTypical ppr) { Intent notificationIntent = new Intent(ctx, TypicalDetailFragWrapper.class); notificationIntent.putExtra("TIPICO", ppr); notificationIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); PendingIntent contentIntent = PendingIntent.getActivity(ctx, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationManager nm = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); Resources res = ctx.getResources(); NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); SoulissCommand shutoff = new SoulissCommand(ppr); shutoff.setCommand(Constants.Typicals.Souliss_T1n_OffCmd); Intent mapIntent = new Intent(ctx, SendCommandActivityNoDisplay.class); mapIntent.putExtra("COMMAND", shutoff); PendingIntent mapPendingIntent = PendingIntent.getActivity(ctx, 0, mapIntent, 0); builder.setContentIntent(contentIntent).setSmallIcon(android.R.drawable.stat_sys_warning) .setLargeIcon(BitmapFactory.decodeResource(res, ppr.getIconResourceId())) .setTicker("Turned on warning") .setWhen(System.currentTimeMillis()) .setAutoCancel(true).setContentTitle(desc) .addAction(R.drawable.ic_cancel_24dp, ctx.getString(R.string.scene_turnoff_lights), mapPendingIntent) .setContentText(longdesc); Notification n = builder.build(); nm.notify(664, n); } public static void sendProgramNotification(Context ctx, String desc, String longdesc, int icon, @Nullable SoulissCommand ppr) { Intent notificationIntent = new Intent(ctx, AddProgramActivity.class); if (ppr != null) notificationIntent.putExtra("PROG", ppr); PendingIntent contentIntent = PendingIntent.getActivity(ctx, 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT); NotificationManager nm = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); Resources res = ctx.getResources(); NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx); builder.setContentIntent(contentIntent).setSmallIcon(android.R.drawable.stat_sys_upload_done) .setLargeIcon(BitmapFactory.decodeResource(res, icon)) .setTicker("Souliss program activated") .setWhen(System.currentTimeMillis()) .setAutoCancel(true).setContentTitle(desc) .setContentText(longdesc); Notification n = builder.build(); nm.notify(665, n); } public Calendar getLastupd() { return lastupd; } public void setLastupd(Calendar lastupd) { this.lastupd = lastupd; opts.setLastServiceRun(lastupd); } private void logThings(Map<Short, SoulissNode> refreshedNodes) { Log.i(Constants.TAG, "logging sensors for " + refreshedNodes.size() + " nodes"); for (SoulissNode pirt : refreshedNodes.values()) { //SoulissNode pirt = refreshedNodes.get(index); List<SoulissTypical> tips = pirt.getTypicals(); for (SoulissTypical soulissTypical : tips) { if (soulissTypical.isSensor()) { soulissTypical.logTypical(); } } } } @Override public IBinder onBind(Intent intent) { return mBinder; } @Override public void onCreate() { super.onCreate(); Log.w(TAG, "service onCreate()"); opts = SoulissApp.getOpzioni(); // subito startUDPListener(); locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); // riporta exec precedenti, non usare ora attuale lastupd.setTimeInMillis(opts.getServiceLastrun()); provider = locationManager.getBestProvider(SoulissUtils.getGeoCriteria(), true); db = new SoulissDBHelper(this); nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); cIntent = new Intent(this, SoulissDataService.class); } @Override public void onDestroy() { super.onDestroy(); Log.w(TAG, "Service Destroy!! " + opts.getBackedOffServiceIntervalMsec()); mHandler.removeCallbacks(mUpdateSoulissRunnable); //db.close(); if (udpThread != null && udpThread.isAlive()) { udpThread.interrupt(); Log.w(TAG, "UDP Interrupt"); } if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // public void requestPermissions(@NonNull String[] permissions, int requestCode) // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for Activity#requestPermissions for more details. return; } locationManager.removeUpdates(SoulissDataService.this); } @Override public void onLocationChanged(Location location) { opts = SoulissApp.getOpzioni(); double lat = (location.getLatitude()); double lng = (location.getLongitude()); float[] res = new float[3]; try { Location.distanceBetween(lat, lng, opts.getHomeLatitude(), opts.getHomeLongitude(), res); Log.d(TAG, "Service received new Position. Home Distance:" + (int) res[0]); homeDist = res[0]; if (opts.getPrevDistance() == 0) { Log.w(TAG, "Resetting prevdistance =>" + homeDist); opts.setPrevDistance(homeDist); } else { float homeDistPrev = opts.getPrevDistance(); processPositionalPrograms(homeDistPrev); } } catch (Exception e) {// home note set homeDist = 0; Log.w(TAG, "can't compute home distance, home position not set"); } } @Override public void onLowMemory() { super.onLowMemory(); Log.w(TAG, "Low memory, schedule a reserve task"); mHandler.postDelayed(mUpdateSoulissRunnable, opts.getDataServiceIntervalMsec() + 1000000); } @Override public void onProviderDisabled(String provider) { Log.i(TAG, "Service received Provider Disabled"); } @Override public void onProviderEnabled(String provider) { Log.i(TAG, "Service received Provider ENABLED"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.i(TAG, "Service onStartCommand()"); opts = SoulissApp.getOpzioni(); opts.initializePrefs();//forse cambiate,ricarica startUDPListener(); requestBackedOffLocationUpdates(); // uir = opts.getDataServiceInterval(); // delle opzioni if (opts.isDataServiceEnabled()) { reschedule(false); } else { Log.i(TAG, "Service disabled"); } return START_STICKY; } @Override public void onStatusChanged(String provider, int status, Bundle extras) { Log.d(TAG, "Service location status Provider changed to: " + status); } /** * Se la distanza attuale rispetto alla precedente supera la soglia, * scattano i programmi posizionali. * <p/> * Se viene rilevato un cambio fascia tra prev e attuale viene anche rischedulato * il locationManager * * @param homeDistPrev */ private void processPositionalPrograms(float homeDistPrev) { float distPrevCache = opts.getPrevDistance(); Log.d(TAG, "process positional programs, homedistanceprev=" + homeDistPrev + " homedist now is=" + homeDist); if (homeDistPrev > (opts.getHomeThresholdDistance() - opts.getHomeThresholdDistance() / 10) && homeDist < (opts.getHomeThresholdDistance() - opts.getHomeThresholdDistance() / 10)) { SoulissDBHelper.open(); final LinkedList<SoulissCommand> unexecuted = db.getPositionalPrograms(); Log.i(TAG, "processing positional programs: " + unexecuted.size()); // tornato a casa for (SoulissCommand soulissCommand : unexecuted) { if (soulissCommand.getType() == Constants.COMMAND_COMEBACK_CODE) { Log.w(TAG, "issuing COMEBACK command: " + soulissCommand.toString()); soulissCommand.execute(); soulissCommand.persistCommand(); sendProgramNotification(SoulissDataService.this, getString(R.string.positional_executed), soulissCommand.toString() + " " + soulissCommand.getParentTypical().getNiceName(), R.drawable.exit1, soulissCommand); } } opts.setPrevDistance(homeDist); } else if (homeDistPrev < (opts.getHomeThresholdDistance() + opts.getHomeThresholdDistance() / 10) && homeDist > (opts.getHomeThresholdDistance() + opts.getHomeThresholdDistance() / 10)) { SoulissDBHelper.open(); final LinkedList<SoulissCommand> unexecuted = db.getPositionalPrograms(); Log.i(TAG, "activating positional programs: " + unexecuted.size()); // uscito di casa for (SoulissCommand soulissCommand : unexecuted) { if (soulissCommand.getType() == Constants.COMMAND_GOAWAY_CODE) { Log.w(TAG, "issuing AWAY command: " + soulissCommand.toString()); soulissCommand.execute(); soulissCommand.persistCommand(); sendProgramNotification(SoulissDataService.this, getString(R.string.positional_executed), soulissCommand.getNiceName(), R.drawable.exit1, soulissCommand); } } } else {// gestione BACKOFF sse e` cambiata la fascia if ((homeDist > 25000 && distPrevCache <= 25000) || (homeDist < 25000 && homeDist > 5000 && distPrevCache >= 25000)) { Log.w(TAG, "FASCIA 25 " + homeDist); requestBackedOffLocationUpdates(); } else if ((homeDist > 5000 && distPrevCache <= 5000) || (homeDist < 5000 && homeDist > 2000 && distPrevCache >= 5000)) { Log.w(TAG, "FASCIA 5 " + homeDist); requestBackedOffLocationUpdates(); } else if ((homeDist > 2000 && distPrevCache <= 2000) || (homeDist < 2000 && distPrevCache >= 2000)) { Log.w(TAG, "FASCIA 2 " + homeDist); requestBackedOffLocationUpdates(); } } //abbiamo processato, e` ora di resettare il prev opts.setPrevDistance(homeDist); } /** * Rischedula le richieste posizionali a intervali basati su fascie distanza */ private void requestBackedOffLocationUpdates() { try { Log.w(TAG, "requesting updates at meters " + homeDist); locationManager.removeUpdates(SoulissDataService.this); if (homeDist > 25000) { locationManager.requestLocationUpdates(provider, Constants.POSITION_UPDATE_INTERVAL * 100, Constants.POSITION_UPDATE_MIN_DIST * 10, SoulissDataService.this); } else if (homeDist > 5000) { locationManager.requestLocationUpdates(provider, Constants.POSITION_UPDATE_INTERVAL * 10, Constants.POSITION_UPDATE_MIN_DIST * 4, SoulissDataService.this); } else if (homeDist > 2000) { locationManager.requestLocationUpdates(provider, Constants.POSITION_UPDATE_INTERVAL * 2, Constants.POSITION_UPDATE_MIN_DIST * 2, SoulissDataService.this); } else { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { Log.e(TAG, SoulissDataService.class.getName() + " misses permission. Killing himself"); //...e amen. MUOIO return; } locationManager.requestLocationUpdates(provider, Constants.POSITION_UPDATE_INTERVAL, Constants.POSITION_UPDATE_MIN_DIST, SoulissDataService.this); } } catch (SecurityException re) { Log.e(TAG, "NOT ALLOWED FROM USER PERMISSION", re); } catch (Exception e) { Log.e(TAG, "location manager updates request FAIL", e); } } /** * Schedule a new execution of the srvice via mHandler.postDelayed */ public void reschedule(boolean immediate) { opts.initializePrefs();// reload interval // the others // reschedule self Calendar calendar = Calendar.getInstance(); if (immediate) { mHandler.removeCallbacks(mUpdateSoulissRunnable);// the first removes Log.i(TAG, "Reschedule immediate"); mHandler.post(mUpdateSoulissRunnable); } else { Log.i(TAG, "Regular mode, rescheduling self every " + opts.getDataServiceIntervalMsec() / 1000 + " seconds"); if (getLastupd().getTime().getTime() + opts.getBackedOffServiceIntervalMsec() < Calendar.getInstance().getTime().getTime()) { Log.i(TAG, "DETECTED LATE SERVICE, LAST RUN: " + getLastupd().getTime()); reschedule(true); return; } // mHandler.postDelayed(mUpdateSoulissRunnable, // opts.getDataServiceIntervalMsec()); /* One of the two should get it */ AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE); PendingIntent secureShot = PendingIntent.getService(this, 0, cIntent, PendingIntent.FLAG_UPDATE_CURRENT); calendar.setTimeInMillis(getLastupd().getTime().getTime()); calendar.add(Calendar.MILLISECOND, opts.getBackedOffServiceIntervalMsec().intValue()); alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), secureShot); //will call onStart(), detect late and schedule immediate Log.i(TAG, "DATASERVICE SCHEDULED ON: " + calendar.getTime()); } startUDPListener(); } private void startUDPListener() { if (udpThread == null || !udpThread.isAlive() || udpThread.isInterrupted()) { // Create the object with the run() method Runnable runnable = new UDPRunnable(opts); // Create the udpThread supplying it with the runnable // object udpThread = new Thread(runnable); // Start the udpThread udpThread.start(); Log.i(TAG, "UDP thread started" + opts.getBackedOffServiceIntervalMsec()); } } /** * Class for clients to access. Because we know this service always runs in * the same process as its clients, we don't need to deal with IPC. */ public class LocalBinder extends Binder { public SoulissDataService getService() { return SoulissDataService.this; } } }