package it.angelic.soulissclient.net; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; import android.graphics.BitmapFactory; import android.media.Ringtone; import android.media.RingtoneManager; import android.net.Uri; import android.support.v4.app.NotificationCompat; import android.util.Log; import android.widget.Toast; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import it.angelic.soulissclient.Constants; import it.angelic.soulissclient.R; import it.angelic.soulissclient.SoulissApp; import it.angelic.soulissclient.SoulissDataService; import it.angelic.soulissclient.SoulissWidget; import it.angelic.soulissclient.T4nFragWrapper; import it.angelic.soulissclient.helpers.SoulissGlobalPreferenceHelper; import it.angelic.soulissclient.helpers.SoulissPreferenceHelper; import it.angelic.soulissclient.model.SoulissNode; import it.angelic.soulissclient.model.SoulissTrigger; import it.angelic.soulissclient.model.SoulissTypical; import it.angelic.soulissclient.model.db.SoulissDBHelper; import it.angelic.soulissclient.model.db.SoulissDBLowHelper; import it.angelic.soulissclient.model.db.SoulissTypicalDTO; import static it.angelic.soulissclient.Constants.Typicals.Souliss_T41_Antitheft_Main; import static it.angelic.soulissclient.Constants.Typicals.Souliss_T4n_InAlarm; import static junit.framework.Assert.assertEquals; /** * Classe per il decode dei pacchetti nativi souliss * <p/> * This class decodes incoming Souliss packets, starting from decodevNet * * @author Ale */ public class UDPSoulissDecoder { SoulissPreferenceHelper opzioni; private Context context; private SoulissDBLowHelper database; private InetAddress localHost; private SharedPreferences soulissSharedPreference; public UDPSoulissDecoder(SoulissPreferenceHelper opts, Context ctx) { this.opzioni = opts; this.context = ctx; database = new SoulissDBLowHelper(ctx); //soulissSharedPreference = opts.getContx().getSharedPreferences("SoulissPrefs", Activity.MODE_PRIVATE); soulissSharedPreference = opzioni.getCustomPref(); SoulissDBHelper.open(); try { localHost = NetUtils.getInetLocalIpAddress(); } catch (SocketException e) { Log.e(Constants.Net.TAG, "CANT GET LOCALADDR"); } } /** * TODO Should be moved. Produces Android notification * * @param ctx * @param desc * @param longdesc * @param icon * @param ty */ public static void sendAntiTheftNotification(Context ctx, String desc, String longdesc, int icon, SoulissTypical ty) { Intent notificationIntent = new Intent(ctx, T4nFragWrapper.class); notificationIntent.putExtra("TIPICO", ty); 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_warning) .setLargeIcon(BitmapFactory.decodeResource(res, icon)).setTicker(desc) .setWhen(System.currentTimeMillis()).setAutoCancel(true).setContentTitle(desc).setContentText(longdesc); Notification n = builder.build(); nm.notify(664, n); try { Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); Ringtone r = RingtoneManager.getRingtone(ctx, notification); r.play(); } catch (Exception e) { Log.e(Constants.Net.TAG, "Unable to play sounds:" + e.getMessage()); } } /** * Sovrascrive la struttura I nodi e la struttura dei tipici e richiama * UDPHelper.typicalRequest(opzioni, nodes, 0); * * @param mac */ private void decodeDBStructRequest(ArrayList<Short> mac) { // Threee static bytes assertEquals(4, (short) mac.get(4)); final int nodes = mac.get(5); int maxnodes = mac.get(6); int maxTypicalXnode = mac.get(7); int maxrequests = mac.get(8); Log.i(Constants.Net.TAG, ">--decodeDBStructRequest, nodes: " + nodes + " maxnodes: " + maxnodes + " maxrequests: " + maxrequests + " maxTypicalXnode: " + maxTypicalXnode); SoulissDBHelper.open(); database.createOrUpdateStructure(nodes); // Log.w(Constants.TAG, "Drop DB requested, response: " + mac); SharedPreferences.Editor editor = opzioni.getCustomPref().edit(); // SharedPreferences.Editor editor = soulissSharedPreference.edit(); // sistema configurato if (soulissSharedPreference.contains("numNodi")) editor.remove("numNodi"); if (soulissSharedPreference.contains("TipiciXNodo")) editor.remove("TipiciXNodo"); editor.putInt("numNodi", nodes); editor.putInt("TipiciXNodo", maxTypicalXnode); editor.apply(); // FIXME centralizzare sta roba new Thread(new Runnable() { @Override public void run() { //ask for all typicals UDPHelper.typicalRequest(opzioni, nodes, 0); //first health req //UDPHelper.healthRequest(opzioni, nodes, 0); } }).start(); } /** * Decodes a souliss nodes health request * * @param mac packet */ private void decodeHealthRequest(ArrayList<Short> mac) { // Threee static bytes int tgtnode = mac.get(3); int numberOf = mac.get(4); ArrayList<Short> healths = new ArrayList<>(); // build an array containing healths for (int i = 5; i < 5 + numberOf; i++) { healths.add(mac.get(i)); } Log.i(Constants.Net.TAG, ">--decodeHealthRequest OFFSET:" + tgtnode + " NUMOF:" + numberOf); try { numberOf = database.refreshNodeHealths(healths, tgtnode); Log.i(Constants.Net.TAG, "Health request refreshed " + numberOf + " nodes' health"); } catch (IllegalStateException e) { Log.e(Constants.Net.TAG, "DB connection closed! Can't update healths"); return; } } /** * Decodes lower level MaCaCo packet * * @param macacoPck */ private void decodeMacaco(ArrayList<Short> macacoPck) { int functionalCode = macacoPck.get(0); // int putIn = mac.get(1); // PUTIN :) 2 byte // STARTOFFSET 1 byte // NUMBEROF 1 byte int startOffset = macacoPck.get(3); int numberOf = macacoPck.get(4); Log.d(Constants.Net.TAG, ">- Macaco IN: Start Offset:" + startOffset + ", Number of " + numberOf); switch (functionalCode) { case Constants.Net.Souliss_UDP_function_subscribe_data: Log.d(Constants.Net.TAG, ">- Subscription answer"); decodeStateRequest(macacoPck); break; case Constants.Net.Souliss_UDP_function_poll_resp: Log.d(Constants.Net.TAG, ">- Poll answer"); decodeStateRequest(macacoPck); processTriggers(); processWidgets(); break; case Constants.Net.Souliss_UDP_function_ping_resp: // assertEquals(mac.size(), 8); Log.d(Constants.Net.TAG, ">- Ping response bytes " + macacoPck.size()); decodePing(macacoPck); break; case Constants.Net.Souliss_UDP_function_ping_bcast_resp: // assertEquals(mac.size(), 8); Log.d(Constants.Net.TAG, ">- Ping BROADCAST response bytes " + macacoPck.size()); decodePing(macacoPck); break; case Constants.Net.Souliss_UDP_function_subscribe_resp: Log.d(Constants.Net.TAG, ">- State request answer"); decodeStateRequest(macacoPck); processTriggers(); processWidgets(); break; case Constants.Net.Souliss_UDP_function_typreq_resp:// Answer for assigned // typical logic Log.d(Constants.Net.TAG, ">- TypReq answer"); decodeTypRequest(macacoPck); break; case Constants.Net.Souliss_UDP_function_health_resp:// Answer nodes healty Log.d(Constants.Net.TAG, ">- Health answer"); decodeHealthRequest(macacoPck); break; case Constants.Net.Souliss_UDP_function_db_struct_resp:// Answer nodes assertEquals(macacoPck.size(), 9); // healty Log.w(Constants.Net.TAG, ">- DB Structure answer"); decodeDBStructRequest(macacoPck); break; case 0x83: Log.e(Constants.Net.TAG, "!!! (Functional code not supported)"); Toast.makeText(context, "Functional code not supported", Toast.LENGTH_SHORT).show(); break; case 0x84: Log.e(Constants.Net.TAG, "** (Data out of range)"); Toast.makeText(context, "Data out of range", Toast.LENGTH_SHORT).show(); break; case 0x85: Log.e(Constants.Net.TAG, "** (Subscription refused)"); Toast.makeText(context, "Subscription refused", Toast.LENGTH_SHORT).show(); break; default: Log.e(Constants.Net.TAG, "** Unknown functional code: " + functionalCode); break; } } /** * Alla ricezione di una risposta ping, aggiorna il cached address F e` * locale, se trovo B e` Remoto * * @param mac */ private void decodePing(ArrayList<Short> mac) { // int nodes = mac.get(5); int putIn = mac.get(1); // se trovo F e` locale, se trovo B e` Remoto SharedPreferences.Editor editor = soulissSharedPreference.edit(); // se ho gia` indirizzo privato che funziona (!= 0) boolean alreadyPrivate = (soulissSharedPreference.getString("cachedAddress", "").compareTo( opzioni.getPrefIPAddress()) == 0 && "".compareTo(opzioni.getPrefIPAddress()) != 0); if (putIn == 0xB && !alreadyPrivate) {// PUBBLICO opzioni.setCachedAddr(opzioni.getIPPreferencePublic()); editor.putString("cachedAddress", opzioni.getIPPreferencePublic()); Log.w(Constants.Net.TAG, ">--decodePing Set cached address (PUBLIC): " + opzioni.getIPPreferencePublic()); } else if (putIn == 0xF) {// PRIVATO opzioni.setCachedAddr(opzioni.getPrefIPAddress()); editor.putString("cachedAddress", opzioni.getPrefIPAddress()); Log.w(Constants.Net.TAG, ">--decodePing Set cached address: " + opzioni.getPrefIPAddress()); } else if (putIn == 0x5) {// BROADCAST VA, USO QUELLA try {// sanity check final InetAddress toverify = NetUtils.extractTargetAddress(mac); Log.i(Constants.Net.TAG, "decodePing Parsed private IP: " + toverify.getHostAddress()); /** * deve essere determinato se l'indirizzo appartiene ad un nodo * e se è all'interno della propria subnet. Se entrambe le * verifiche hanno esito positivo, si può utilizzare tale * indirizzo, altrimenti si continua ad usare broadcast. */ Log.d(Constants.Net.TAG, "BROADCAST detected, IP to verify: " + toverify); Log.d(Constants.Net.TAG, "BROADCAST, subnet: " + NetUtils.getDeviceSubnetMask(context)); Log.d(Constants.Net.TAG, "BROADCAST, me: " + localHost); Log.d(Constants.Net.TAG, "BROADCAST, belongsToNode: " + NetUtils.belongsToNode(toverify, NetUtils.getDeviceSubnetMask(context))); Log.d(Constants.Net.TAG, "BROADCAST, belongsToSameSubnet: " + NetUtils.belongsToSameSubnet(toverify, NetUtils.getDeviceSubnetMask(context), localHost)); if (NetUtils.belongsToNode(toverify, NetUtils.getDeviceSubnetMask(context)) && NetUtils.belongsToSameSubnet(toverify, NetUtils.getDeviceSubnetMask(context), localHost)) { opzioni.setCachedAddr(toverify.getHostAddress()); editor.putString("cachedAddress", toverify.getHostAddress()); if (!opzioni.isSoulissIpConfigured()) {// forse e` da // togliere Log.w(Constants.Net.TAG, "Auto-setting private IP: " + opzioni.getCachedAddress()); opzioni.setIPPreference(opzioni.getCachedAddress()); //lo aggiungo a dizionario SoulissGlobalPreferenceHelper gbPref = new SoulissGlobalPreferenceHelper(SoulissApp.getAppContext()); gbPref.addWordToIpDictionary(opzioni.getCachedAddress()); } } else { throw new UnknownHostException("belongsToNode or belongsToSameSubnet = FALSE"); } } catch (final Exception e) { Log.e(Constants.Net.TAG, "Error in address parsing, using BCAST address: " + e.getMessage(), e); opzioni.setCachedAddr(Constants.Net.BROADCASTADDR); editor.putString("cachedAddress", Constants.Net.BROADCASTADDR); } } else if (alreadyPrivate) { Log.w(Constants.Net.TAG, "Local address already set. I'll NOT overwrite it: " + soulissSharedPreference.getString("cachedAddress", "")); } else Log.e(Constants.Net.TAG, "Unknown putIn code: " + putIn); editor.commit(); } /** * puo giungere in seguito a state request oppure come subscription data * della publish. Semantica = a typical request. Aggiorna il DB solo se il * tipico esiste * * @param mac */ private void decodeStateRequest(ArrayList<Short> mac) { try { List<SoulissNode> nodes = database.getAllNodes(); Log.i(Constants.Net.TAG, Thread.currentThread().getId() + ">--decodeStateRequest received: " + mac.size()); int tgtnode = mac.get(3); int numberOf = mac.get(4); int typXnodo = soulissSharedPreference.getInt("TipiciXNodo", 8); Log.d(Constants.Net.TAG, Thread.currentThread().getId() + ">--decodeStateRequest MACACO OFFSET:" + tgtnode + " NUMOF:" + numberOf); // SoulissTypicalDTO dto = new SoulissTypicalDTO(); // refresh typicals for (short j = 0; j < numberOf; j++) { try { SoulissNode it = nodes.get(((int) j / typXnodo) + tgtnode); SoulissTypical temp = it.getTypical((short) (j % typXnodo)); SoulissTypicalDTO dto = temp.getTypicalDTO(); dto.setOutput(mac.get(5 + j)); dto.setSlot(((short) (j % typXnodo))); dto.setNodeId((short) (j / typXnodo + tgtnode)); // sufficiente una refresh dto.refresh(temp); Log.d(Constants.Net.TAG, Thread.currentThread().getId() + " --REFRESHED TYP NODE:" + (j / typXnodo + tgtnode) + " SLOT:" + (j % typXnodo) + " OUT:" + (mac.get(5 + j))); } catch (NotFoundException e) { // skipping unexistent typical"); //OK, può succedere } catch (Exception e) { Log.w(Constants.Net.TAG, "Errore di calcolo??" + e.getMessage()); // FIXME nodes.get(((int) j / typXnodo) + tgtnode) è SBAGLIATO l'ha detto Dario } } // Log.d(Constants.TAG, "Refreshed " + refreshed + // " typicals STATUS for node " + tgtnode); } catch (IllegalStateException e) { Log.e(Constants.Net.TAG, "DB connection was closed, impossible to finish"); return; } catch (Exception uy) { Log.e(Constants.Net.TAG, "decodeStateRequest ERROR", uy); } } /** * Definizione dei tipici * * @param mac */ private void decodeTypRequest(ArrayList<Short> mac) { try { assertEquals(Constants.Net.Souliss_UDP_function_typreq_resp, (short) mac.get(0)); SharedPreferences.Editor editor = soulissSharedPreference.edit(); short tgtnode = mac.get(3); int numberOf = mac.get(4); int done = 0; // SoulissNode node = database.getSoulissNode(tgtnode); int typXnodo = soulissSharedPreference.getInt("TipiciXNodo", 1); Log.i(Constants.Net.TAG, ">--decodeTypRequest:" + tgtnode + " NUMOF:" + numberOf + " TYPICALSXNODE: " + typXnodo); // creates Souliss nodes for (int j = 0; j < numberOf; j++) { if (mac.get(5 + j) != 0) {// create only not-empty typicals SoulissDBHelper db = new SoulissDBHelper(SoulissApp.getAppContext()); short slot = ((short) (j % typXnodo)); short node = (short) (j / typXnodo + tgtnode); SoulissTypicalDTO dto; try { dto = db.getTypical(node, slot).getTypicalDTO(); } catch (Exception e) { dto = new SoulissTypicalDTO(); } dto.setTypical(mac.get(5 + j)); dto.setSlot(slot);// magia dto.setNodeId(node); try { dto.persist(); // conta solo i master if (mac.get(5 + j) != it.angelic.soulissclient.Constants.Typicals.Souliss_T_related) { done++; Log.d(Constants.Net.TAG, "---PERSISTED TYPICAL ON NODE:" + ((short) (j / typXnodo + tgtnode)) + " SLOT:" + ((short) (j % typXnodo)) + " TYP:" + (mac.get(5 + j))); } } catch (Exception ie) { Log.e(Constants.Net.TAG, "---PERSIST ERROR:" + ie.getMessage() + " - " + ((short) (j / typXnodo + tgtnode)) + " SLOT:" + ((short) (j % typXnodo)) + " TYP:" + (mac.get(5 + j))); } } } if (soulissSharedPreference.contains("numTipici")) editor.remove("numTipici");// unused editor.putInt("numTipici", database.countTypicals()); editor.apply(); Log.i(Constants.Net.TAG, "Refreshed " + numberOf + " typicals for node " + tgtnode); } catch (Exception uy) { Log.e(Constants.Net.TAG, "decodeTypRequest ERROR", uy); } } /** * processa il pacchetto UDP ricevuto e agisce di condeguenza * * @param packet incoming datagram */ public void decodeVNetDatagram(DatagramPacket packet) { int checklen = packet.getLength(); // Log.d(Constants.TAG, "** Packet received"); ArrayList<Short> mac = new ArrayList<>(); for (int ig = 7; ig < checklen; ig++) { mac.add((short) (packet.getData()[ig] & 0xFF)); } // 0xf 0xe 0x17 0x64 0x1 0x11 0x0 0x18 0x0 0x0 0x0 0x3 0xa 0x8 0xa // il primo byte dev essere la dimensione if (checklen != packet.getData()[0]) { StringBuilder dump = new StringBuilder(); for (int ig = 0; ig < checklen; ig++) { // 0xFF & buf[index] dump.append("0x").append(Long.toHexString(0xFF & packet.getData()[ig])).append(" "); // dump.append(":"+packet.getData()[ig]); } Log.e(Constants.Net.TAG, "**WRONG PACKET SIZE: " + packet.getData()[0] + "bytes\n" + "Actual size: " + checklen + "\n" + dump.toString()); } else { decodeMacaco(mac); } /* * DEBUG PACCHETTO StringBuilder dump = new StringBuilder(); for (int ig * = 0; ig < checklen; ig++) { // 0xFF & buf[index] dump.append("0x" + * Long.toHexString(0xFF & packet.getData()[ig]) + " "); // * dump.append(":"+packet.getData()[ig]); } Log.d(Constants.TAG, "***" + * dump.toString()); */ // Qualcosa ho ricevuto, invia broadcast Intent i = new Intent(); i.putExtra("MACACO", mac); i.setAction(Constants.CUSTOM_INTENT_SOULISS_RAWDATA); opzioni.getContx().sendBroadcast(i); //Segnala a Tasker if (SoulissApp.getOpzioni().isTaskerEnabled()) { Intent it = new Intent(); it.setAction(com.twofortyfouram.locale.Intent.ACTION_REQUEST_QUERY); opzioni.getContx().sendBroadcast(it); } // resetta backoff irraggiungibilit� opzioni.resetBackOff(); //se era irraggiungibile, pinga if (!opzioni.isSoulissReachable()) opzioni.setBestAddress(); } private void processTriggers() { try { List<SoulissNode> ref = database.getAllNodes(); List<SoulissTrigger> triggers = database.getAllTriggers(); Log.i(Constants.Net.TAG, "processTriggers triggersize: " + triggers.size()); // logThings(refreshedNodes); Map<Short, SoulissNode> refreshedNodes = new HashMap<>(); /* Check antifurto */ for (SoulissNode soulissNode : ref) { refreshedNodes.put(soulissNode.getNodeId(), soulissNode); if (opzioni.isAntitheftPresent() && opzioni.isAntitheftNotify()) { for (SoulissTypical ty : soulissNode.getTypicals()) { // check Antitheft if (ty.getTypicalDTO().getTypical() == Souliss_T41_Antitheft_Main && ty.getTypicalDTO().getOutput() == Souliss_T4n_InAlarm) { sendAntiTheftNotification(context, context.getString(R.string.antitheft_notify), context.getString(R.string.antitheft_notify_desc), R.drawable.shield1, ty); break; } } } } for (SoulissTrigger soulissTrigger : triggers) { SoulissTypical source = refreshedNodes.get(soulissTrigger.getInputNodeId()).getTypical(soulissTrigger.getInputSlot()); //SoulissCommand command = new SoulissCommand(soulissTrigger.getCommandDTO(),source); // SoulissTriggerDTO src = soulissTrigger.getTriggerDto(); // SoulissTypical target = // refreshedNodes.get(command.getNodeId()).getTypical(command.getSlot()); Calendar now = Calendar.getInstance(); if (!soulissTrigger.getTriggerDto().isActivated()) { // Descrizione programma StringBuilder info = new StringBuilder(soulissTrigger.toString()); info.append(" slot ").append(soulissTrigger.getSlot()); if ("".compareTo(source.getNiceName()) != 0) info.append(" (") .append(source.getNiceName()) .append(")"); info.append(" on ").append(source.getParentNode().getNiceName()); String op = soulissTrigger.getOp(); if (">".compareTo(op) == 0 && source.getTypicalDTO().getOutput() > soulissTrigger.getThreshVal()) { Log.w(Constants.Net.TAG, "TRIGGERING COMMAND " + soulissTrigger.toString()); soulissTrigger.getTriggerDto().setActive(true); soulissTrigger.execute(); soulissTrigger.setExecutedTime(now); soulissTrigger.persist(database); SoulissDataService.sendProgramNotification(context, SoulissApp.getAppContext().getResources().getString(R.string.programs_trigger_executed), info.toString(), R.drawable.lighthouse1, soulissTrigger); } else if ("<".compareTo(op) == 0 && source.getTypicalDTO().getOutput() < soulissTrigger.getThreshVal()) { Log.w(Constants.Net.TAG, "TRIGGERING COMMAND " + soulissTrigger.toString()); soulissTrigger.getTriggerDto().setActive(true); soulissTrigger.execute(); soulissTrigger.setExecutedTime(now); soulissTrigger.persist(database); SoulissDataService.sendProgramNotification(context, SoulissApp.getAppContext().getResources().getString(R.string.programs_trigger_executed), info.toString(), R.drawable.lighthouse1, soulissTrigger); } else if ("=".compareTo(op) == 0 && source.getTypicalDTO().getOutput() == soulissTrigger.getThreshVal()) { Log.w(Constants.Net.TAG, "TRIGGERING COMMAND " + soulissTrigger.toString()); soulissTrigger.execute(); soulissTrigger.getTriggerDto().setActive(true); soulissTrigger.setExecutedTime(now); soulissTrigger.persist(database); SoulissDataService.sendProgramNotification(context, SoulissApp.getAppContext().getResources().getString(R.string.programs_trigger_executed), info.toString(), R.drawable.lighthouse1, soulissTrigger); } } // vedi se bisogna disattivare else { String op = soulissTrigger.getOp(); if (">".compareTo(op) == 0 && source.getTypicalDTO().getOutput() <= soulissTrigger.getThreshVal()) { Log.w(Constants.Net.TAG, "DEACTIVATE TRIGGER " + soulissTrigger.toString()); soulissTrigger.getTriggerDto().setActive(false); soulissTrigger.persist(database); } else if ("<".compareTo(op) == 0 && source.getTypicalDTO().getOutput() >= soulissTrigger.getThreshVal()) { Log.w(Constants.Net.TAG, "DEACTIVATE TRIGGER " + soulissTrigger.toString()); soulissTrigger.getTriggerDto().setActive(false); soulissTrigger.persist(database); } else if ("=".compareTo(op) == 0 && source.getTypicalDTO().getOutput() != soulissTrigger.getThreshVal()) { Log.w(Constants.Net.TAG, "DEACTIVATE TRIGGER " + soulissTrigger.toString()); soulissTrigger.getTriggerDto().setActive(false); soulissTrigger.persist(database); } } } } catch (IllegalStateException e) { Log.e(Constants.Net.TAG, "DB connection was closed, check trigger impossible"); } catch (Exception e) { Log.e(Constants.Net.TAG, "check trigger impossible", e); } } /** * Si fa dare gli ID di eventuali widgets e li aggiorna * tramite sendBroadcast() */ private void processWidgets() { try { int ids[] = AppWidgetManager.getInstance(SoulissApp.getAppContext()).getAppWidgetIds(new ComponentName(SoulissApp.getAppContext(), SoulissWidget.class)); if (ids.length > 0) { Intent intent = new Intent(SoulissApp.getAppContext(), SoulissWidget.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // Use an array and EXTRA_APPWIDGET_IDS instead of AppWidgetManager.EXTRA_APPWIDGET_ID, // since it seems the onUpdate() is only fired on that: // int[] ids = {widgetId}; intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, ids); SoulissApp.getAppContext().sendBroadcast(intent); } } catch (Exception we) { Log.e(Constants.Net.TAG, "can't update widgets: " + we); } } }