package it.quadrata.android.quad_prox_mob; import android.annotation.SuppressLint; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.widget.RemoteViews; import java.io.IOException; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.ResponseHandler; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class WidgetProvider extends AppWidgetProvider { public static final String URI_SCHEME = "quadprox_mobile_widget"; private static final String TAG = "it.quadrata.android.quad_prox_mob.widget_provider"; @SuppressLint("UseSparseArrays") private static HashMap<Integer, Uri> uris = new HashMap<Integer, Uri>(); @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { Log.i(TAG, "onUpdate method called"); for (int widgetId : appWidgetIds) { new UpdateTask(context, appWidgetManager).execute(widgetId); } } @Override public void onDeleted(Context context, int[] appWidgetIds) { Log.i(TAG, "onDeleted method called"); for (int i : appWidgetIds) { SharedPreferences widgetPrefs = context.getSharedPreferences( "WidgetPrefs_" + i, Context.MODE_PRIVATE); Log.i(TAG, "Removing preferences for widget instanse [" + i + "]"); SharedPreferences.Editor widgetPrefsEditor = widgetPrefs.edit(); widgetPrefsEditor.clear(); widgetPrefsEditor.commit(); cancelAlarmManager(context, i); } super.onDeleted(context, appWidgetIds); } @Override public void onReceive(Context context, Intent intent) { // Protect against rogue update broadcasts (not really a security issue, // just filter bad broacasts out so subclasses are less likely to // crash). String action = intent.getAction(); Log.i(TAG, "onReive method called with action '" + action + "'"); if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { // Check if there is a single widget ID. int widgetID = intent.getIntExtra( AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); Log.i(TAG, "EXTRA_APPWIDGET_ID: " + widgetID); // If there is a single ID, call our unUpdate implementation. if (widgetID != AppWidgetManager.INVALID_APPWIDGET_ID) { onUpdate(context, AppWidgetManager.getInstance(context), new int[] { widgetID }); } else { super.onReceive(context, intent); } } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) { final int appWidgetId = extras .getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); onDeleted(context, new int[] { appWidgetId }); } } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { super.onEnabled(context); } else if (AppWidgetManager.ACTION_APPWIDGET_DISABLED.equals(action)) { super.onDisabled(context); } } protected void cancelAlarmManager(Context context, int widgetId) { AlarmManager alarm = (AlarmManager) context .getSystemService(Context.ALARM_SERVICE); Intent intentUpdate = new Intent(context, WidgetProvider.class); intentUpdate.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); // Don't put the uri to cancel all the AlarmManager with action // UPDATE_ONE. intentUpdate.setData(uris.get(widgetId)); intentUpdate.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); PendingIntent pendingIntentAlarm = PendingIntent.getBroadcast(context, 0, intentUpdate, PendingIntent.FLAG_UPDATE_CURRENT); alarm.cancel(pendingIntentAlarm); Log.d("cancelAlarmManager", "Cancelled Alarm. Action URI = " + uris.get(widgetId)); uris.remove(widgetId); } public static void addUri(int id, Uri uri) { uris.put(Integer.valueOf(id), uri); } public static void updateUri(int id, Uri uri) { if (!uris.containsKey(Integer.valueOf(id))) uris.put(Integer.valueOf(id), uri); } public static Intent get_ACTION_APPWIDGET_UPDATE_Intent(Context context, int widgetId) { ComponentName thisAppWidget = new ComponentName( context.getPackageName(), WidgetProvider.class.getName()); int[] appWidgetIds = AppWidgetManager.getInstance(context) .getAppWidgetIds(thisAppWidget); Intent intent = new Intent(context, WidgetProvider.class); intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); Uri uriData = Uri.withAppendedPath( Uri.parse(WidgetProvider.URI_SCHEME + "://widget/id/"), String.valueOf(widgetId)); intent.setData(uriData); Log.i(TAG, "Setting appWidget scheme [" + uriData.toString() + "]"); return intent; } private class UpdateTask extends AsyncTask<Integer, Void, Void> { // Authentication credentials private String server; private String username; private String realm; private String password; private String ticket; private String token; // Host info private String version; private String release; // Node info private double node_cpu_usage_double; private String node_cpu_usage; private int node_max_cpu; private double node_mem_double; private double node_max_mem_double; private String node_mem; private String node_max_mem; final DecimalFormat cpu_dec_form = new DecimalFormat("0.0"); private Context context; private AppWidgetManager appWidgetManager; private int widgetId; public UpdateTask(Context context, AppWidgetManager appWidgetManager) { this.context = context; this.appWidgetManager = appWidgetManager; } @Override protected Void doInBackground(Integer... params) { String cluster; RemoteViews updateViews = new RemoteViews(context.getPackageName(), R.layout.widget_layout); widgetId = params[0]; updateViews.removeAllViews(R.id.nodeList); Log.i(TAG, "Called with appWidgetId = " + widgetId); // Retrieving of widget preferences Map<String, String> prefs = WidgetPrefsActivity.getPrefs(context, widgetId); // Retrieving of login preferences SharedPreferences authPref = context.getSharedPreferences( "AuthPref", Context.MODE_PRIVATE); server = authPref.getString("server", ""); username = authPref.getString("username", ""); realm = authPref.getString("realm", ""); password = authPref.getString("password", ""); try { if (isOnline() == false) { throw new IOException(); } ProxmoxCustomApp httpApp = (ProxmoxCustomApp) context .getApplicationContext(); HttpClient serverHttpClient = httpApp.getHttpClient(); // Proxmox ticket request HttpPost authRequest = new HttpPost(server + "/api2/json/access/ticket"); List<NameValuePair> authParameters = new ArrayList<NameValuePair>(); authParameters.add(new BasicNameValuePair("username", username + "@" + realm)); authParameters .add(new BasicNameValuePair("password", password)); HttpEntity authEntity = new UrlEncodedFormEntity(authParameters); authRequest.setEntity(authEntity); String authResponse = serverHttpClient.execute(authRequest, serverResponseHandler); // Ticket and token extraction from authentication // json string JSONObject authObject = new JSONObject(authResponse); JSONObject data = authObject.getJSONObject("data"); ticket = data.getString("ticket"); token = data.getString("CSRFPreventionToken"); // Cluster info request HttpGet clusterRequest = new HttpGet(server + "/api2/json/cluster/status"); clusterRequest.addHeader("Cookie", "PVEAuthCookie=" + ticket); String clusterResponse = serverHttpClient.execute( clusterRequest, serverResponseHandler); JSONObject clusterObject = new JSONObject(clusterResponse); JSONArray clusterDataArray = clusterObject.getJSONArray("data"); JSONObject clusterInfo = (JSONObject) clusterDataArray.get(0); cluster = clusterInfo.optString("name"); updateViews.setTextViewText(R.id.hostInfo, cluster); HttpGet versionRequest = new HttpGet(server + "/api2/json/version"); versionRequest.addHeader("Cookie", "PVEAuthCookie=" + ticket); String versionResponse = serverHttpClient.execute( versionRequest, serverResponseHandler); JSONObject versionObject = new JSONObject(versionResponse); JSONObject versionDataObject = versionObject .getJSONObject("data"); version = versionDataObject.getString("version"); release = versionDataObject.getString("release"); updateViews.setTextViewText(R.id.hostVers, "Proxmox VE " + version + "-" + release); // Nodes list request HttpGet nodesRequest = new HttpGet(server + "/api2/json/nodes"); nodesRequest.addHeader("Cookie", "PVEAuthCookie=" + ticket); String nodesResponse = serverHttpClient.execute(nodesRequest, serverResponseHandler); JSONObject nodesObject = new JSONObject(nodesResponse); JSONArray nodesArray = nodesObject.getJSONArray("data"); int nodesArrayLength = nodesArray.length(); // Nodes list items creation JSONObject singleNodeObject = new JSONObject(); for (int i = 0; i < nodesArrayLength; i++) { singleNodeObject = nodesArray.getJSONObject(i); final NodeItem item = new NodeItem(); item.node = singleNodeObject.optString("node"); item.cluster = cluster; // Status request HttpGet qemuRequest = new HttpGet(server + "/api2/json/nodes/" + item.node + "/qemu"); qemuRequest.addHeader("Cookie", "PVEAuthCookie=" + ticket); String qemuResponse = serverHttpClient.execute(qemuRequest, serverResponseHandler); JSONObject qemuObject = new JSONObject(qemuResponse); JSONArray qemuArray = qemuObject.getJSONArray("data"); int qemuArrayLength = qemuArray.length(); int running = 0; for (int q = 0; q < qemuArrayLength; q++) { JSONObject qemu = qemuArray.getJSONObject(q); String status = qemu.optString("status"); if (status.equals("running")) running++; } HttpGet vzRequest = new HttpGet(server + "/api2/json/nodes/" + item.node + "/openvz"); vzRequest.addHeader("Cookie", "PVEAuthCookie=" + ticket); String vzResponse = serverHttpClient.execute(vzRequest, serverResponseHandler); JSONObject vzObject = new JSONObject(vzResponse); JSONArray vzArray = vzObject.getJSONArray("data"); int vzArrayLength = vzArray.length(); for (int v = 0; v < vzArrayLength; v++) { JSONObject vz = vzArray.getJSONObject(v); String status = vz.optString("status"); if (status.equals("running")) running++; } item.index = i; item.status = running + "/" + (qemuArrayLength + vzArrayLength) + " up"; node_cpu_usage_double = singleNodeObject .optDouble("cpu", 0) * 100; node_cpu_usage = cpu_dec_form.format(node_cpu_usage_double); node_max_cpu = singleNodeObject.optInt("maxcpu", 0); node_mem_double = singleNodeObject.optDouble("mem", 0); node_max_mem_double = singleNodeObject.optDouble("maxmem", 0); node_mem = cpu_dec_form .format(node_mem_double / 1073741824); node_max_mem = cpu_dec_form .format(node_max_mem_double / 1073741824); item.cpu = node_cpu_usage + "% (" + node_max_cpu + " cpu)"; item.memory = node_mem + "GB of " + node_max_mem + "GB"; item.cpuUsage = cpu_dec_form.parse(node_cpu_usage) .doubleValue(); item.memUsage = (cpu_dec_form.parse(node_mem).doubleValue() * 100) / cpu_dec_form.parse(node_max_mem).doubleValue(); HttpGet nodesVersRequest = new HttpGet(server + "/api2/json/nodes/" + item.node + "/version"); nodesVersRequest.addHeader("Cookie", "PVEAuthCookie=" + ticket); String nodesVersResponse = serverHttpClient.execute( nodesVersRequest, serverResponseHandler); JSONObject nodesVersObject = new JSONObject( nodesVersResponse); JSONObject nodesVersDataObject = nodesVersObject .getJSONObject("data"); item.node_vers = (nodesVersDataObject.getString("version") + "-" + nodesVersDataObject.getString("release")); // Create remote view for the object to add to linear layout Log.i(TAG, item.toString()); RemoteViews v = new RemoteViews(context.getPackageName(), R.layout.widget_row_layout); // ... Set Text on text views in "v" for the current object float fontSize = Integer.parseInt(prefs.get("fontSize")); Log.i(TAG, "Setting FontSize = " + fontSize + "sp"); v.setTextViewText(R.id.node, item.node); v.setFloat(R.id.node, "setTextSize", fontSize); v.setTextViewText(R.id.cpu, item.cpu); v.setFloat(R.id.cpu, "setTextSize", fontSize); v.setTextViewText(R.id.memory, item.memory); v.setFloat(R.id.memory, "setTextSize", fontSize); v.setTextViewText(R.id.status, item.status); v.setFloat(R.id.status, "setTextSize", fontSize); Intent vmListIntent = new Intent(context, VMListActivity.class); // Putting VM data into the intent for VM stats activity long id = WidgetPrefsActivity.getNextId(context, widgetId); Uri uri = Uri .withAppendedPath( Uri.parse(WidgetProvider.URI_SCHEME + "://widget/id/"), String.valueOf(widgetId) + "/" + String.valueOf(id)); Log.i(TAG, "Setting URI: " + uri.toString()); vmListIntent.setData(uri); vmListIntent.putExtra("server", server); vmListIntent.putExtra("ticket", ticket); vmListIntent.putExtra("token", token); vmListIntent.putExtra("node", item.node); vmListIntent.putExtra("node_index", i); vmListIntent.putExtra("node_vers", item.node_vers); PendingIntent pendingIntent = PendingIntent.getActivity( context, 0, vmListIntent, 0);// Intent.FLAG_ACTIVITY_NEW_TASK); v.setOnClickPendingIntent(R.id.widgetNodeRow, pendingIntent); // Add new view to the linear layout in the widget updateViews.addView(R.id.nodeList, v); checkNotifications(item, prefs); } Uri uriData = Uri.withAppendedPath( Uri.parse(WidgetProvider.URI_SCHEME + "://widget/id/"), String.valueOf(widgetId)); // Add onClickListener for cluster Intent nodeListIntent = new Intent(context, NodeListActivity.class); nodeListIntent.setAction(Intent.ACTION_VIEW); nodeListIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); nodeListIntent.setData(uriData); Log.i(TAG, "Adding onClickListener for NodeListActivity. Setting appWidgetId = " + widgetId); PendingIntent pendingIntentCluster = PendingIntent.getActivity( context, 0, nodeListIntent, 0);// Intent.FLAG_ACTIVITY_NEW_TASK); updateViews.setOnClickPendingIntent(R.id.hostInfo, pendingIntentCluster); // Add onClickListener for refresh Intent refreshIntent = WidgetProvider .get_ACTION_APPWIDGET_UPDATE_Intent(context, widgetId); Log.i(TAG, "Setting up pending broadcast event for widget " + widgetId); PendingIntent pendingIntentUpdate = PendingIntent.getBroadcast( context, 0, refreshIntent, 0/* * Intent. * FLAG_RECEIVER_REPLACE_PENDING */); updateViews.setOnClickPendingIntent(R.id.widgetRefresh, pendingIntentUpdate); // Add onClickListener for preferences Intent prefsIntent = new Intent(context, WidgetPrefsActivity.class); prefsIntent.setAction(Intent.ACTION_VIEW); prefsIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); prefsIntent.setData(uriData); Log.i(TAG, "Adding onClickListener for WidgetPrefsActivity. Setting appWidgetId = " + widgetId); PendingIntent pendingIntentPrefs = PendingIntent.getActivity( context, 0, prefsIntent, 0);// Intent.FLAG_ACTIVITY_NEW_TASK); updateViews.setOnClickPendingIntent(R.id.widgetConfig, pendingIntentPrefs); updateUri(widgetId, uriData); // updateViews appWidgetManager.updateAppWidget(widgetId, updateViews); } catch (JSONException e) { if (e.getMessage() != null) { Log.e(e.getClass().getName(), e.getMessage()); } else { Log.e(e.getClass().getName(), "null"); } } catch (IOException e) { if (e.getMessage() != null) { Log.e(e.getClass().getName(), e.getMessage()); } else { Log.e(e.getClass().getName(), "null"); } } catch (RuntimeException e) { if (e.getMessage() != null) { Log.e(e.getClass().getName(), e.getMessage()); } else { Log.e(e.getClass().getName(), "null"); } } catch (Exception e) { if (e.getMessage() != null) { Log.e(e.getClass().getName(), e.getMessage()); } else { Log.e(e.getClass().getName(), "null"); } } return null; } private void checkNotifications(NodeItem item, Map<String, String> prefs) { String notifyCpu = prefs.get("notifyCpu"); String notifyMem = prefs.get("notifyMem"); String cpuLimit = prefs.get("cpuLimit"); String memLimit = prefs.get("memLimit"); String notifyChange = prefs.get("notifyChange"); if (notifyCpu.equals("true") && cpuLimit != null) { if (item.cpuUsage > Integer.valueOf(cpuLimit)) { createNotification(item, "CPU usage [" + cpu_dec_form.format(item.cpuUsage) + "%] exceeds" + " [" + cpuLimit + "%]", (item.index * 10) + 1); Log.i(TAG, "CPU Limit: " + item.node + " -> " + item.cpuUsage + " > " + cpuLimit); } } if (notifyMem.equals("true") && memLimit != null) { if (item.memUsage > Integer.valueOf(memLimit)) { createNotification( item, "Memory usage [" + cpu_dec_form.format(item.memUsage) + "%] exceeds" + " [" + memLimit + "%]", (item.index * 10) + 2); Log.i(TAG, "Mem Limit: " + item.node + " -> " + item.memUsage + " > " + memLimit); } } if (notifyChange.equals("true")) { int last = 0, now = 0; String status = WidgetPrefsActivity.getStatus(context, widgetId, item.node); String current = item.status.substring(0, item.status.indexOf("/")); if (status != null) { try { last = Integer.parseInt(status); now = Integer.parseInt(current); if (last != now) { createNotification(item, "Status change: last [" + last + "] -> now [" + now + "]", (item.index * 10) + 3); Log.i(TAG, "Status change: Running last check [" + last + "] -> now [" + now + "]"); } } catch (NumberFormatException e) { Log.e(TAG, e.getMessage()); } } if (status == null || last != now) WidgetPrefsActivity.setStatus(context, widgetId, item.node, current); } } /* * We support API level < 11 so use backwards compatible notification * interface */ @SuppressWarnings("deprecation") public void createNotification(NodeItem item, String text, int id) { NotificationManager notificationManager = (NotificationManager) context .getSystemService(Context.NOTIFICATION_SERVICE); Notification notification = new Notification( R.drawable.qpm_launcher, item.cluster + ":" + item.node + ": Alarm", System.currentTimeMillis()); notification.flags |= Notification.FLAG_AUTO_CANCEL; Intent intent = new Intent(context, VMListActivity.class); // Putting VM data into the intent for VM stats activity intent.setData(Uri.withAppendedPath( Uri.parse(WidgetProvider.URI_SCHEME + "://widget/id/"), String.valueOf(widgetId) + "/" + item.index)); intent.putExtra("server", server); intent.putExtra("ticket", ticket); intent.putExtra("token", token); intent.putExtra("node", item.node); intent.putExtra("node_index", item.index); intent.putExtra("node_vers", item.node_vers); PendingIntent activity = PendingIntent.getActivity(context, 0, intent, 0); notification.setLatestEventInfo(context, item.cluster + ":" + item.node + ": Alarm", text, activity); notification.number += 1; notificationManager.notify(id, notification); } private ResponseHandler<String> serverResponseHandler = new ResponseHandler<String>() { @Override public String handleResponse(HttpResponse response) throws ClientProtocolException, IOException { HttpEntity entity = response.getEntity(); String result = EntityUtils.toString(entity); return result; } }; public boolean isOnline() { ConnectivityManager connMgr = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); } private class NodeItem { public String node; public String cpu; public String memory; public String status; public String node_vers; public Double cpuUsage; public Double memUsage; public int index; public String cluster; @Override public String toString() { return node + ":" + cpu + ":" + memory + ":" + status; } } } }