/**
* openHAB, the open Home Automation Bus.
* Copyright (C) 2010-2012, openHAB.org <admin@openhab.org>
*
* See the contributors.txt file in the distribution for a
* full listing of individual contributors.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or
* combining it with Eclipse (or a modified version of that library),
* containing parts covered by the terms of the Eclipse Public License
* (EPL), the licensors of this Program grant you additional permission
* to convey the resulting work.
*/
package org.openhab.habdroid.ui;
import android.Manifest.permission;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.app.PendingIntent;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManager.BadTokenException;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.Toast;
import com.google.analytics.tracking.android.EasyTracker;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.image.WebImageCache;
import org.openhab.domain.IOpenHABWidgetProvider;
import org.openhab.domain.model.OpenHABItem;
import org.openhab.domain.model.OpenHABItemType;
import org.openhab.domain.model.OpenHABNFCActionList;
import org.openhab.domain.model.OpenHABSitemap;
import org.openhab.domain.model.OpenHABWidget;
import org.openhab.domain.model.OpenHABWidgetDataSource;
import org.openhab.habclient.dagger.DaggerWidgetListActivityComponent;
import org.openhab.habdroid.util.AndroidLogger;
import org.openhab.habclient.ColorParser;
import org.openhab.habclient.HABApplication;
import org.openhab.habclient.IOpenHABSetting;
import org.openhab.habdroid.R;
import org.openhab.habdroid.util.AsyncServiceResolver;
import org.openhab.habdroid.util.AsyncServiceResolverListener;
import org.openhab.habdroid.util.MyAsyncHttpClient;
import org.openhab.habdroid.util.Util;
import org.openhab.domain.util.IColorParser;
import org.openhab.domain.util.ILogger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.jmdns.ServiceInfo;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
/**
* This class is apps' main activity which runs startup sequence and displays list of openHAB
* widgets from sitemap page with further navigation through sitemap and everything else!
*
* @author Victor Belov
*
*/
public class OpenHABWidgetListActivity extends ListActivity implements AsyncServiceResolverListener {
// Datasource, providing list of openHAB widgets
private OpenHABWidgetDataSource openHABWidgetDataSource;
// List adapter for list view of openHAB widgets
private OpenHABWidgetArrayAdapter openHABWidgetAdapter;
// Url of current sitemap page displayed
private String displayPageUrl ="";
// sitemap root url
private String sitemapRootUrl = "";
// async http client
private static AsyncHttpClient mPageAsyncHttpClient;
// openHAB base url
private String openHABBaseUrl = "https://demo.openhab.org:8443/";
// List of widgets to display
private ArrayList<OpenHABWidget> widgetList = new ArrayList<OpenHABWidget>();
// Username/password for authentication
private String openHABUsername;
private String openHABPassword;
// openHAB Bonjour service name
private String openHABServiceType;
// openHAB page url from NFC tag
private String nfcTagData = "";
// Progress dialog
private ProgressDialog progressDialog;
// selected openhab widget
private OpenHABWidget selectedOpenHABWidget;
// widget Id which we got from nfc tag
private String nfcWidgetId;
// widget command which we got from nfc tag
private String nfcCommand;
// auto close app after nfc action is complete
private boolean nfcAutoClose = false;
// Service resolver for Bonjour
private AsyncServiceResolver serviceResolver;
// Enable/disable openHAB discovery
private boolean serviceDiscoveryEnabled = true;
@Inject IOpenHABSetting mOpenHABSetting;
@Inject IOpenHABWidgetProvider mWidgetProvider;
/**
* Overriding onStart to enable Google Analytics stats collection
*/
@Override
public void onStart() {
super.onStart();
// Start activity tracking via Google Analytics
EasyTracker.getInstance().activityStart(this);
}
/**
* Overriding onStop to enable Google Analytics stats collection
*/
@Override
public void onStop() {
super.onStop();
// Stop activity tracking via Google Analytics
EasyTracker.getInstance().activityStop(this);
}
/**
* This is called when activity is created. Initializes the state, performs network
* state based selection for app initialization and starts the widget list
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
DaggerWidgetListActivityComponent.builder()
.appComponent(((HABApplication)getApplication()).appComponent())
.build()
.inject(this);
Log.d("OpenHABWidgetListActivity", "onCreate");
// Set default values, false means do it one time during the very first launch
PreferenceManager.setDefaultValues(this, R.xml.preferences, false);
// Set non-persistant HABDroid version preference to current version from application package
try {
PreferenceManager.getDefaultSharedPreferences(this).edit().putString("default_openhab_appversion",
getPackageManager().getPackageInfo(getPackageName(), 0).versionName).commit();
} catch (NameNotFoundException e1) {
if (e1 != null)
Log.d(HABApplication.getLogTag(), e1.getMessage());
}
// Set the theme to one from preferences
Util.setActivityTheme(this);
// Fetch openHAB service type name from strings.xml
openHABServiceType = getString(R.string.openhab_service_type);
// Enable progress ring bar
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
requestWindowFeature(Window.FEATURE_PROGRESS);
setProgressBarIndeterminateVisibility(true);
// // Initialize crittercism reporting
// JSONObject crittercismConfig = new JSONObject();
// try {
// crittercismConfig.put("shouldCollectLogcat", true);
// } catch (JSONException e) {
// if (e.getMessage() != null)
// Log.e(HABApplication.getLogTag(), e.getMessage());
// else
// Log.e(HABApplication.getLogTag(), "Crittercism JSON exception");
// }
// Crittercism.init(getApplicationContext(), "5117659f59e1bd4ba9000004", crittercismConfig);
// Initialize activity view
setContentView(R.layout.openhabwidgetlist);
// Disable screen timeout if set in preferences
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (settings.getBoolean("default_openhab_screentimeroff", false)) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
// Check if we got all needed permissions
PackageManager pm = getPackageManager();
if (!(pm.checkPermission(permission.CHANGE_WIFI_MULTICAST_STATE, getPackageName()) == PackageManager.PERMISSION_GRANTED)) {
showAlertDialog(getString(R.string.erorr_no_wifi_mcast_permission));
serviceDiscoveryEnabled = false;
}
if (!(pm.checkPermission(permission.ACCESS_WIFI_STATE, getPackageName()) == PackageManager.PERMISSION_GRANTED)) {
showAlertDialog(getString(R.string.erorr_no_wifi_state_permission));
serviceDiscoveryEnabled = false;
}
// Get username/password from preferences
openHABUsername = settings.getString("default_openhab_username", null);
openHABPassword = settings.getString("default_openhab_password", null);
mOpenHABSetting.setUsername(openHABUsername);
mOpenHABSetting.setPassword(openHABPassword);
// Create new data source and adapter and set it to list view
ILogger logger = new AndroidLogger();
IColorParser colorParser = new ColorParser();
IWidgetTypeLayoutProvider widgetTypeLayoutProvider = new WidgetTypeLayoutProvider();
openHABWidgetDataSource = new OpenHABWidgetDataSource(logger, colorParser);
openHABWidgetAdapter = new OpenHABWidgetArrayAdapter(OpenHABWidgetListActivity.this,
R.layout.openhabwidgetlist_genericitem, widgetList, widgetTypeLayoutProvider,
OpenHABWidgetArrayAdapter.WidgetLayoutType.IconTextControlList);
getListView().setAdapter(openHABWidgetAdapter);
// Set adapter parameters
openHABWidgetAdapter.setOpenHABUsername(openHABUsername);
openHABWidgetAdapter.setOpenHABPassword(openHABPassword);
// Enable app logo as home button
this.getActionBar().setHomeButtonEnabled(true);
// Check if we have openHAB page url in saved instance state?
if (savedInstanceState != null) {
displayPageUrl = savedInstanceState.getString("displayPageUrl");
openHABBaseUrl = savedInstanceState.getString("openHABBaseUrl");
sitemapRootUrl = savedInstanceState.getString("sitemapRootUrl");
openHABWidgetAdapter.setOpenHABBaseUrl(openHABBaseUrl);
}
// Check if this is a launch from myself (drill down navigation)
if (getIntent() != null) {
if (getIntent().getAction() != null) {
if (getIntent().getAction().equals("org.openhab.habdroid.ui.OpwnHABWidgetListActivity")) {
Log.d(HABApplication.getLogTag(), String.format("Found intent =>/ndisplayPageUrl = '%s'/nopenHABBaseUrl = '%s'/nsitemapRootUrl = '%s'", getIntent().getExtras().getString("displayPageUrl"), getIntent().getExtras().getString("openHABBaseUrl"), getIntent().getExtras().getString("sitemapRootUrl")));
displayPageUrl = getIntent().getExtras().getString("displayPageUrl");
openHABBaseUrl = getIntent().getExtras().getString("openHABBaseUrl");
sitemapRootUrl = getIntent().getExtras().getString("sitemapRootUrl");
this.setTitle(getIntent().getExtras().getString("pageTitle"));
openHABWidgetAdapter.setOpenHABBaseUrl(openHABBaseUrl);
}
}
}
// If yes, then just go to it (means restore activity from it's saved state)
if (displayPageUrl.length() > 0) {
Log.d(HABApplication.getLogTag(), "onCreate() calling showPage(...) displayPageUrl = " + displayPageUrl);
showPage(displayPageUrl, false);
// If not means it is a clean start
} else {
if (getIntent() != null) {
Log.i(HABApplication.getLogTag(), "Launch intent = " + getIntent().getAction());
// If this is a launch through NFC tag reading
if (getIntent().getAction() != null) {
if (getIntent().getAction().equals("android.nfc.action.NDEF_DISCOVERED")) {
// Save url which we got from NFC tag
nfcTagData = getIntent().getDataString();
}
}
}
// If we are in demo mode, ignore all settings and use demo url from strings
if (settings.getBoolean("default_openhab_demomode", false)) {
mOpenHABSetting.setBaseUrl(getString(R.string.openhab_demo_url));
openHABBaseUrl = mOpenHABSetting.getBaseUrl();
Log.i(HABApplication.getLogTag(), "Demo mode, connecting to " + openHABBaseUrl);
Toast.makeText(getApplicationContext(), getString(R.string.info_demo_mode),
Toast.LENGTH_LONG).show();
showWidgetList();
} else {
mOpenHABSetting.setBaseUrl(normalizeUrl(settings.getString("default_openhab_url", "")));
openHABBaseUrl = mOpenHABSetting.getBaseUrl();
// Check if we have a direct URL in preferences, if yes - use it
if (openHABBaseUrl.length() > 0) {
Log.i(HABApplication.getLogTag(), "Connecting to configured URL = " + openHABBaseUrl);
Toast.makeText(getApplicationContext(), getString(R.string.info_conn_url),
Toast.LENGTH_SHORT).show();
showWidgetList();
} else {
// Get current network information
ConnectivityManager connectivityManager = (ConnectivityManager)getSystemService(
Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo != null) {
Log.i(HABApplication.getLogTag(), "Network is connected");
// If network is mobile, try to use remote URL
if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_MOBILE || !serviceDiscoveryEnabled) {
if (!serviceDiscoveryEnabled) {
Log.i(HABApplication.getLogTag(), "openHAB discovery is disabled");
} else {
Log.i(HABApplication.getLogTag(), "Network is Mobile (" + activeNetworkInfo.getSubtypeName() + ")");
}
mOpenHABSetting.setBaseUrl(normalizeUrl(settings.getString("default_openhab_alturl", "")));
openHABBaseUrl = mOpenHABSetting.getBaseUrl();
// If remote URL is configured
if (openHABBaseUrl.length() > 0) {
Toast.makeText(getApplicationContext(), getString(R.string.info_conn_rem_url),
Toast.LENGTH_SHORT).show();
Log.i(HABApplication.getLogTag(), "Connecting to remote URL " + openHABBaseUrl);
showWidgetList();
} else {
Toast.makeText(getApplicationContext(), getString(R.string.error_no_url),
Toast.LENGTH_LONG).show();
}
// If network is WiFi or Ethernet
} if (activeNetworkInfo.getType() == ConnectivityManager.TYPE_WIFI
|| activeNetworkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) {
Log.i(HABApplication.getLogTag(), "Network is WiFi or Ethernet");
// Start service discovery
this.serviceResolver = new AsyncServiceResolver(this, openHABServiceType);
progressDialog = ProgressDialog.show(this, "",
"Discovering openHAB. Please wait...", true);
this.serviceResolver.start();
// We don't know how to handle this network type
} else {
Log.i(HABApplication.getLogTag(), "Network type (" + activeNetworkInfo.getTypeName() + ") is unsupported");
}
// Network is not available
} else {
Log.i(HABApplication.getLogTag(), "Network is not available");
Toast.makeText(getApplicationContext(), getString(R.string.error_network_not_available),
Toast.LENGTH_LONG).show();
}
}
}
}
}
// Start openHAB browsing!
public void showWidgetList() {
Log.d(HABApplication.getLogTag(), "showWidgetList() - openHABBaseUrl = '" + openHABBaseUrl + "'");
if (openHABBaseUrl != null) {
openHABWidgetAdapter.setOpenHABBaseUrl(openHABBaseUrl);
if (nfcTagData.length() > 0) {
Log.d(HABApplication.getLogTag(), "We have NFC tag data");
onNfcTag(nfcTagData, false);
} else {
selectSitemap(openHABBaseUrl, false);
}
} else {
Log.e(HABApplication.getLogTag(), "No base URL!");
}
}
/**
* This method is called by AsyncServiceResolver in case of successful service discovery
* to start connection with local openHAB instance
*/
public void onServiceResolved(ServiceInfo serviceInfo) {
Log.i(HABApplication.getLogTag(), "[AsyncHttpClient] Service resolved: "
+ serviceInfo.getHostAddresses()[0]
+ " port:" + serviceInfo.getPort());
openHABBaseUrl = "https://" + serviceInfo.getHostAddresses()[0] + ":" +
String.valueOf(serviceInfo.getPort()) + "/";
// progressDialog.hide();
progressDialog.dismiss();
AsyncHttpClient asyncHttpClient = new MyAsyncHttpClient(this);
String restURL = openHABBaseUrl + "static/uuid";
Log.d(HABApplication.getLogTag(), "[AsyncHttpClient] GET Request for: " + restURL);
asyncHttpClient.get(restURL, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String content) {
Log.i(HABApplication.getLogTag(), "[AsyncHttpClient] Got openHAB UUID = " + content);
SharedPreferences settings =
PreferenceManager.getDefaultSharedPreferences(OpenHABWidgetListActivity.this);
if (settings.contains("openhab_uuid")) {
String openHABUUID = settings.getString("openhab_uuid", "");
if (openHABUUID.equals(content)) {
Log.i(HABApplication.getLogTag(), "openHAB UUID does match the saved one");
showWidgetList();
} else {
Log.i(HABApplication.getLogTag(), "openHAB UUID doesn't match the saved one");
// TODO: need to add some user prompt here
/* Toast.makeText(getApplicationContext(),
"openHAB UUID doesn't match the saved one!",
Toast.LENGTH_LONG).show();*/
showWidgetList();
}
} else {
Log.i(HABApplication.getLogTag(), "No recorded openHAB UUID, saving the new one");
Editor preferencesEditor = settings.edit();
preferencesEditor.putString("openhab_uuid", content);
preferencesEditor.commit();
showWidgetList();
}
}
@Override
public void onFailure(Throwable e, String errorResponse) {
Toast.makeText(getApplicationContext(), getString(R.string.error_no_uuid),
Toast.LENGTH_LONG).show();
}
});
}
/**
* This method is called by AsyncServiceResolver in case of discovery failure
* to start alternate connection through remote url (if configured)
*/
public void onServiceResolveFailed() {
if (progressDialog.isShowing())
progressDialog.dismiss();
Log.i(HABApplication.getLogTag(), "Service resolve failed, switching to remote URL");
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
openHABBaseUrl = normalizeUrl(settings.getString("default_openhab_alturl", ""));
// If remote URL is configured
if (openHABBaseUrl.length() > 0) {
Toast.makeText(getApplicationContext(), getString(R.string.info_conn_rem_url),
Toast.LENGTH_SHORT).show();
Log.i(HABApplication.getLogTag(), "Connecting to remote URL " + openHABBaseUrl);
showWidgetList();
} else {
Toast.makeText(getApplicationContext(), getString(R.string.error_no_url),
Toast.LENGTH_LONG).show();
}
}
/**
* This method is called when activity receives a new intent while running
*/
@Override
public void onNewIntent(Intent newIntent) {
Log.d(HABApplication.getLogTag(), "New intent received = " + newIntent.toString());
if (newIntent.getDataString() != null) {
onNfcTag(newIntent.getDataString(), true);
}
}
/**
* This method processes new intents generated by NFC subsystem
* @param nfcData - a data which NFC subsystem got from the NFC tag
* @param pushCurrentToStack
*/
public void onNfcTag(String nfcData, boolean pushCurrentToStack) {
Uri openHABURI = Uri.parse(nfcData);
Log.d(HABApplication.getLogTag(), openHABURI.getScheme());
Log.d(HABApplication.getLogTag(), openHABURI.getHost());
Log.d(HABApplication.getLogTag(), openHABURI.getPath());
if (openHABURI.getHost().equals("sitemaps")) {
Log.d(HABApplication.getLogTag(), "Tag indicates a sitemap link");
String newPageUrl = this.openHABBaseUrl + "rest/sitemaps" + openHABURI.getPath();
String widgetId = openHABURI.getQueryParameter("widget");
String command = openHABURI.getQueryParameter("command");
Log.d(HABApplication.getLogTag(), "widgetId = " + widgetId);
Log.d(HABApplication.getLogTag(), "command = " + command);
if (widgetId != null && command != null) {
this.nfcWidgetId = widgetId;
this.nfcCommand = command;
// If we have widget+command and not pushing current page to stack
// this means we started through NFC read when HABDroid was not running
// so we need to put a flag to automatically close activity after we
// finish nfc action
if (!pushCurrentToStack)
this.nfcAutoClose = true;
}
Log.d(HABApplication.getLogTag(), "Should go to " + newPageUrl);
if (pushCurrentToStack)
navigateToPage(newPageUrl, "");
else
openSitemap(newPageUrl);
}
}
/**
* This method is called when activity is being resumed after onPause()
*/
@Override
public void onResume() {
Log.d(HABApplication.getLogTag(), "onResume()");
Log.d(HABApplication.getLogTag(), "displayPageUrl = " + this.displayPageUrl);
super.onResume();
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, new Intent(this, OpenHABWidgetListActivity.class).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), 0);
if (NfcAdapter.getDefaultAdapter(this) != null)
NfcAdapter.getDefaultAdapter(this).enableForegroundDispatch(this, pendingIntent, null, null);
if (this.displayPageUrl.length() > 0) {
Log.d(HABApplication.getLogTag(), "OnResume() calling showPage(...) displayPageUrl > 0, resuming");
showPage(displayPageUrl, false);
}
}
/**
* This method is called when activity is paused
*/
@Override
public void onPause() {
Log.d(HABApplication.getLogTag(), "onPause()");
super.onPause();
if (NfcAdapter.getDefaultAdapter(this) != null)
NfcAdapter.getDefaultAdapter(this).disableForegroundDispatch(this);
openHABWidgetAdapter.stopVideoWidgets();
openHABWidgetAdapter.stopImageRefresh();
if(NfcAdapter.getDefaultAdapter(this) != null)
NfcAdapter.getDefaultAdapter(this).disableForegroundDispatch(this);
if (mPageAsyncHttpClient != null)
mPageAsyncHttpClient.cancelRequests(this, true);
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
Log.d("OpenHABWidgetListActivity", "onSaveInstanceState");
// Save UI state changes to the savedInstanceState.
// This bundle will be passed to onCreate if the process is
// killed and restarted.
savedInstanceState.putString("displayPageUrl", displayPageUrl);
savedInstanceState.putString("openHABBaseUrl", openHABBaseUrl);
savedInstanceState.putString("sitemapRootUrl", sitemapRootUrl);
super.onSaveInstanceState(savedInstanceState);
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(HABApplication.getLogTag(), "onDestroy() for " + this.displayPageUrl);
if (this.progressDialog != null)
this.progressDialog.dismiss();
if (this.serviceResolver != null)
this.serviceResolver.interrupt();
if (mPageAsyncHttpClient != null)
mPageAsyncHttpClient.cancelRequests(this, true);
}
/**
* This method is called when activity is finished
* We need to intercept it to override transition animation according to application
* settings
*/
@Override
public void finish() {
super.finish();
Util.overridePendingTransition(this, true);
}
/**
* This method is called when a daugther activity is finished
* we need to analyze return code
*/
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.d(HABApplication.getLogTag(), "onActivityResult " + String.valueOf(requestCode) + " " + String.valueOf(resultCode));
if (resultCode == -1) {
// Right now only PreferencesActivity returns -1
// Restart app after preferences
Log.d(HABApplication.getLogTag(), "Restarting");
// Get launch intent for application
Intent restartIntent = getBaseContext().getPackageManager()
.getLaunchIntentForPackage( getBaseContext().getPackageName() );
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
// Finish current activity
finish();
// Start launch activity
startActivity(restartIntent);
}
}
/**
* Loads data from sitemap page URL and passes it to processContent
*
* @param pageUrl an absolute base URL of openHAB sitemap page
* @param longPolling enable long polling when loading page
* @return void
*/
public void showPage(String pageUrl, boolean longPolling) {
Log.i(HABApplication.getLogTag(), "showPage() for " + pageUrl + " longPolling = " + longPolling);
// Cancel any existing http request to openHAB (typically ongoing long poll)
if (!longPolling)
setProgressBarIndeterminateVisibility(true);
if (mPageAsyncHttpClient != null) {
mPageAsyncHttpClient.cancelRequests(this, true);
}
if (!longPolling) {
mPageAsyncHttpClient = null;
mPageAsyncHttpClient = new MyAsyncHttpClient(this);
}
// If authentication is needed
mPageAsyncHttpClient.setBasicAuth(openHABUsername, openHABPassword);
// If long-polling is needed
if (longPolling) {
// Add corresponding fields to header to make openHAB know we need long-polling
mPageAsyncHttpClient.addHeader("X-Atmosphere-Transport", "long-polling");
mPageAsyncHttpClient.addHeader("Accept", "application/xml");
mPageAsyncHttpClient.setTimeout(30000);
}
mPageAsyncHttpClient.get(this, pageUrl, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String content) {
processContent(content);
}
@Override
public void onFailure(Throwable e, String errorResponse) {
Log.e(HABApplication.getLogTag(), "http request failed");
if (e.getMessage() != null) {
Log.e(HABApplication.getLogTag(), e.getMessage());
if (e.getMessage().equals("Unauthorized")) {
showAlertDialog(getString(R.string.error_authentication_failed));
}
}
stopProgressIndicator();
}
});
}
public Node getRootNode(String XMLContent) {
Node rootNode = null;
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
try {
DocumentBuilder builder = factory.newDocumentBuilder();
Document document;
openHABWidgetAdapter.stopVideoWidgets();
openHABWidgetAdapter.stopImageRefresh();
if (XMLContent != null) {
document = builder.parse(new ByteArrayInputStream(XMLContent.getBytes("UTF-8")));
rootNode = document.getFirstChild();
} else {
Log.e(HABApplication.getLogTag(), "getNode: XMLContent == null");
}
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return rootNode;
}
/**
* Parse XML sitemap page and show it
*
* @param content XML as a text
* @return void
*/
public void processContent(String content) {
Node rootNode = getRootNode(content);
openHABWidgetDataSource.setSourceNode(rootNode);
// mWidgetProvider.setOpenHABWidgets(openHABWidgetDataSource);
widgetList.clear();
// As we change the page we need to stop all videos on current page
// before going to the new page. This is quite dirty, but is the only
// way to do that...
for (OpenHABWidget w : openHABWidgetDataSource.getWidgets()) {
widgetList.add(w);//TA: TODO - Let the getOpenHABWidgetProvider provide this class with data instead of the local widgetList.
}
openHABWidgetAdapter.notifyDataSetChanged();
setTitle(openHABWidgetDataSource.getTitle());
setProgressBarIndeterminateVisibility(false);
// Set widget list index to saved or zero position
// This would mean we got widget and command from nfc tag, so we need to do some automatic actions!
if (this.nfcWidgetId != null && this.nfcCommand != null) {
Log.d(HABApplication.getLogTag(), "Have widget and command, NFC action!");
OpenHABWidget nfcWidget = this.openHABWidgetDataSource.getWidgetById(this.nfcWidgetId);
OpenHABItem nfcItem = nfcWidget.getItem();
// Found widget with id from nfc tag and it has an item
if (nfcWidget != null && nfcItem != null) {
// TODO: Perform nfc widget action here
if (this.nfcCommand.equals("TOGGLE")) {
if (nfcItem.getType() == OpenHABItemType.Rollershutter) {
if (nfcItem.getStateAsBoolean())
openHABWidgetAdapter.sendItemCommand(nfcItem, "UP");
else
openHABWidgetAdapter.sendItemCommand(nfcItem, "DOWN");
} else {
if (nfcItem.getStateAsBoolean())
openHABWidgetAdapter.sendItemCommand(nfcItem, "OFF");
else
openHABWidgetAdapter.sendItemCommand(nfcItem, "ON");
}
} else {
openHABWidgetAdapter.sendItemCommand(nfcItem, this.nfcCommand);
}
}
this.nfcWidgetId = null;
this.nfcCommand = null;
if (this.nfcAutoClose) {
finish();
}
}
getListView().setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Log.d(HABApplication.getLogTag(), "Widget clicked " + String.valueOf(position));
OpenHABWidget openHABWidget = openHABWidgetAdapter.getItem(position);
if (openHABWidget.hasLinkedPage()) {
// Widget have a page linked to it
String[] splitString;
splitString = openHABWidget.getLinkedPage().getTitle().split("\\[|\\]");
navigateToPage(openHABWidget.getLinkedPage().getLink(), splitString[0]);
}
}
});
getListView().setOnItemLongClickListener(new OnItemLongClickListener() {
public boolean onItemLongClick(AdapterView<?> parent, View view,
int position, long id) {
Log.d(HABApplication.getLogTag(), "Widget long-clicked " + String.valueOf(position));
OpenHABWidget openHABWidget = openHABWidgetAdapter.getItem(position);
Log.d(HABApplication.getLogTag(), "Widget type = " + openHABWidget.getType());
switch (openHABWidget.getType()) {
case Switch:
case Selection:
case Color:
OpenHABWidgetListActivity.this.selectedOpenHABWidget = openHABWidget;
AlertDialog.Builder builder = new AlertDialog.Builder(OpenHABWidgetListActivity.this);
builder.setTitle(R.string.nfc_dialog_title);
OpenHABNFCActionList nfcActionList = new OpenHABNFCActionList(OpenHABWidgetListActivity.this.selectedOpenHABWidget);
builder.setItems(nfcActionList.getNames(), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
Intent writeTagIntent = new Intent(OpenHABWidgetListActivity.this.getApplicationContext(),
OpenHABWriteTagActivity.class);
writeTagIntent.putExtra("sitemapPage", OpenHABWidgetListActivity.this.displayPageUrl);
writeTagIntent.putExtra("widget", OpenHABWidgetListActivity.this.selectedOpenHABWidget.getId());
OpenHABNFCActionList nfcActionList =
new OpenHABNFCActionList(OpenHABWidgetListActivity.this.selectedOpenHABWidget);
writeTagIntent.putExtra("command", nfcActionList.getCommands()[which]);
startActivityForResult(writeTagIntent, 0);
Util.overridePendingTransition(OpenHABWidgetListActivity.this, false);
OpenHABWidgetListActivity.this.selectedOpenHABWidget = null;
}
});
builder.show();
}
return true;
}
});
Log.d(HABApplication.getLogTag(), "processContent() - Calling showPage() displayPageUrl = " + displayPageUrl);
showPage(displayPageUrl, true);
}
/*
* Put current page and current widget list position into the stack and go to new page
*/
private void navigateToPage(String pageLink, String pageTitle) {
Log.d(HABApplication.getLogTag(), "navigateToPage(): pageTitle = '" + pageTitle + "' pageLink = '" + pageLink + "'");
// We don't want to put current page to stack if navigateToPage is trying to go to the same page
if (!pageLink.equals(displayPageUrl)) {
Intent drillDownIntent = new Intent(OpenHABWidgetListActivity.this.getApplicationContext(),
OpenHABWidgetListActivity.class);
drillDownIntent.setAction("org.openhab.habdroid.ui.OpwnHABWidgetListActivity");
drillDownIntent.putExtra("displayPageUrl", pageLink);
drillDownIntent.putExtra("openHABBaseUrl", openHABBaseUrl);
drillDownIntent.putExtra("sitemapRootUrl", sitemapRootUrl);
drillDownIntent.putExtra("pageTitle", pageTitle);
startActivityForResult(drillDownIntent, 0);
Util.overridePendingTransition(this, false);
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
Log.d(HABApplication.getLogTag(), event.toString());
Log.d(HABApplication.getLogTag(), String.format("event action = %d", event.getAction()));
return super.dispatchTouchEvent(event);
}
@Override
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
Log.d(HABApplication.getLogTag(), "keyCode = " + String.format("%d", keyCode));
Log.d(HABApplication.getLogTag(), "event = " + event.toString());
if (keyCode == 4) {
return true;
} else {
return super.onKeyLongPress(keyCode, event);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.mainmenu_openhab_preferences:
Intent settingsIntent = new Intent(this.getApplicationContext(), OpenHABPreferencesActivity.class);
Util.overridePendingTransition(this, false);
return true;
case R.id.mainmenu_openhab_selectsitemap:
SharedPreferences settings =
PreferenceManager.getDefaultSharedPreferences(OpenHABWidgetListActivity.this);
Editor preferencesEditor = settings.edit();
preferencesEditor.putString("default_openhab_sitemap", "");
preferencesEditor.commit();
selectSitemap(openHABBaseUrl, true);
return true;
case android.R.id.home:
Log.d(HABApplication.getLogTag(), "Home selected - " + sitemapRootUrl);
// Get launch intent for application
Intent homeIntent = getBaseContext().getPackageManager()
.getLaunchIntentForPackage( getBaseContext().getPackageName() );
homeIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
homeIntent.setAction("org.openhab.habdroid.ui.OpwnHABWidgetListActivity");
homeIntent.putExtra("displayPageUrl", sitemapRootUrl);
homeIntent.putExtra("openHABBaseUrl", openHABBaseUrl);
homeIntent.putExtra("sitemapRootUrl", sitemapRootUrl);
Log.d(HABApplication.getLogTag(), String.format("openPage intent =>/ndisplayPageUrl = '%s'/nopenHABBaseUrl = '%s'/nsitemapRootUrl = '%s'", sitemapRootUrl, openHABBaseUrl, sitemapRootUrl));
// Finish current activity
finish();
// Start launch activity
startActivity(homeIntent);
Util.overridePendingTransition(this, true);
return true;
case R.id.mainmenu_openhab_clearcache:
Log.d(HABApplication.getLogTag(), "Restarting");
// Get launch intent for application
Intent restartIntent = getBaseContext().getPackageManager()
.getLaunchIntentForPackage( getBaseContext().getPackageName() );
restartIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
// Finish current activity
finish();
WebImageCache cache = new WebImageCache(getBaseContext());
cache.clear();
// Start launch activity
startActivity(restartIntent);
// Start launch activity
return true;
case R.id.mainmenu_openhab_writetag:
Intent writeTagIntent = new Intent(this.getApplicationContext(), OpenHABWriteTagActivity.class);
writeTagIntent.putExtra("sitemapPage", this.displayPageUrl);
startActivityForResult(writeTagIntent, 0);
Util.overridePendingTransition(this, false);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Get sitemaps from openHAB, if user already configured preffered sitemap
* just open it. If no preffered sitemap is configured - let user select one.
*
* @param baseURL an absolute base URL of openHAB to open
* @return void
*/
private void selectSitemap(final String baseURL, final boolean forceSelect) {
Log.d(HABApplication.getLogTag(), "Loding sitemap list from " + baseURL + "rest/sitemaps");
AsyncHttpClient asyncHttpClient = new MyAsyncHttpClient(this);
// If authentication is needed
asyncHttpClient.setBasicAuth(openHABUsername, openHABPassword);
asyncHttpClient.get(baseURL + "rest/sitemaps", new AsyncHttpResponseHandler() {
@Override
public void onSuccess(String content) {
Log.d(HABApplication.getLogTag(), "[AsyncHttpClient] onSuccess()");
List<OpenHABSitemap> sitemapList = parseSitemapList(content);
if (sitemapList.size() == 0) {
// Got an empty sitemap list!
showAlertDialog(getString(R.string.error_empty_sitemap_list));
return;
}
// If we are forced to do selection, just open selection dialog
if (forceSelect) {
showSitemapSelectionDialog(sitemapList);
} else {
// Check if we have a sitemap configured to use
SharedPreferences settings =
PreferenceManager.getDefaultSharedPreferences(OpenHABWidgetListActivity.this);
String configuredSitemap = settings.getString("default_openhab_sitemap", "");
// If we have sitemap configured
if (configuredSitemap.length() > 0) {
// Configured sitemap is on the list we got, open it!
if (sitemapExists(sitemapList, configuredSitemap)) {
Log.d(HABApplication.getLogTag(), "Configured sitemap is on the list");
OpenHABSitemap selectedSitemap = getSitemapByName(sitemapList, configuredSitemap);
openSitemap(selectedSitemap.getHomepageLink());
// Configured sitemap is not on the list we got!
} else {
Log.d(HABApplication.getLogTag(), "Configured sitemap is not on the list");
if (sitemapList.size() == 1) {
Log.d(HABApplication.getLogTag(), "Got only one sitemap");
Editor preferencesEditor = settings.edit();
preferencesEditor.putString("default_openhab_sitemap", sitemapList.get(0).getName());
preferencesEditor.commit();
openSitemap(sitemapList.get(0).getHomepageLink());
} else {
Log.d(HABApplication.getLogTag(), "Got multiply sitemaps, user have to select one");
showSitemapSelectionDialog(sitemapList);
}
}
// No sitemap is configured to use
} else {
// We got only one single sitemap from openHAB, use it
if (sitemapList.size() == 1) {
Log.d(HABApplication.getLogTag(), "Got only one sitemap");
Editor preferencesEditor = settings.edit();
preferencesEditor.putString("default_openhab_sitemap", sitemapList.get(0).getName());
preferencesEditor.commit();
openSitemap(sitemapList.get(0).getHomepageLink());
} else {
Log.d(HABApplication.getLogTag(), "Got multiply sitemaps, user have to select one");
showSitemapSelectionDialog(sitemapList);
}
}
}
}
@Override
public void onFailure(Throwable e, String errorResponse) {
Log.d(HABApplication.getLogTag(), "[AsyncHttpClient] onFailure()");
if (e.getMessage() != null) {
if (e.getMessage().equals("Unauthorized")) {
showAlertDialog(getString(R.string.error_authentication_failed));
} else {
showAlertDialog("ERROR: " + e.getMessage());
}
} else {
// TODO: carefully handle errors without message
// showAlertDialog("ERROR: Http error, no details");
}
}
});
}
private void stopProgressIndicator() {
setProgressBarIndeterminateVisibility(false);
}
private boolean sitemapExists(List<OpenHABSitemap> sitemapList, String sitemapName) {
for (int i=0; i<sitemapList.size(); i++) {
if (sitemapList.get(i).getName().equals(sitemapName))
return true;
}
return false;
}
private OpenHABSitemap getSitemapByName(List<OpenHABSitemap> sitemapList, String sitemapName) {
for (int i=0; i<sitemapList.size(); i++) {
if (sitemapList.get(i).getName().equals(sitemapName))
return sitemapList.get(i);
}
return null;
}
private List<OpenHABSitemap> parseSitemapList(String xmlContent) {
List<OpenHABSitemap> sitemapList = new ArrayList<OpenHABSitemap>();
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder;
// Crittercism.leaveBreadcrumb("parseSitemapList");
try {
builder = factory.newDocumentBuilder();
Document document;
document = builder.parse(new ByteArrayInputStream(xmlContent.getBytes("UTF-8")));
NodeList sitemapNodes = document.getElementsByTagName("sitemap");
if (sitemapNodes.getLength() > 0) {
for (int i=0; i < sitemapNodes.getLength(); i++) {
Node sitemapNode = sitemapNodes.item(i);
OpenHABSitemap openhabSitemap = new OpenHABSitemap(sitemapNode, null, null);
sitemapList.add(openhabSitemap);
}
}
} catch (ParserConfigurationException e) {
Log.e(HABApplication.getLogTag(), e.getMessage());
} catch (UnsupportedEncodingException e) {
Log.e(HABApplication.getLogTag(), e.getMessage());
} catch (SAXException e) {
Log.e(HABApplication.getLogTag(), e.getMessage());
} catch (IOException e) {
Log.e(HABApplication.getLogTag(), e.getMessage());
}
return sitemapList;
}
private void showAlertDialog(String alertMessage) {
AlertDialog.Builder builder = new AlertDialog.Builder(OpenHABWidgetListActivity.this);
builder.setMessage(alertMessage)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
}
});
AlertDialog alert = builder.create();
alert.show();
}
private void showSitemapSelectionDialog(final List<OpenHABSitemap> sitemapList) {
Log.d(HABApplication.getLogTag(), "Opening sitemap selection dialog");
final List<String> sitemapNameList = new ArrayList<String>();;
for (int i=0; i<sitemapList.size(); i++) {
sitemapNameList.add(sitemapList.get(i).getName());
}
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(OpenHABWidgetListActivity.this);
dialogBuilder.setTitle("Select sitemap");
try {
dialogBuilder.setItems(sitemapNameList.toArray(new CharSequence[sitemapNameList.size()]),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int item) {
Log.d(HABApplication.getLogTag(), "Selected sitemap " + sitemapNameList.get(item));
SharedPreferences settings =
PreferenceManager.getDefaultSharedPreferences(OpenHABWidgetListActivity.this);
Editor preferencesEditor = settings.edit();
preferencesEditor.putString("default_openhab_sitemap", sitemapList.get(item).getName());
preferencesEditor.commit();
openSitemap(sitemapList.get(item).getHomepageLink());
}
}).show();
} catch (BadTokenException e) {
Log.e(HABApplication.getLogTag(), "Error while showing sitemap selection dialog.", e);
// Crittercism.logHandledException(e);
}
}
private void openSitemap(String sitemapUrl) {
Log.i(HABApplication.getLogTag(), "openSitemap() - Calling showPage(...) Opening sitemap at " + sitemapUrl);
displayPageUrl = sitemapUrl;
sitemapRootUrl = sitemapUrl;
showPage(displayPageUrl, false);
OpenHABWidgetListActivity.this.getListView().setSelection(0);
}
private String normalizeUrl(String sourceUrl) {
String normalizedUrl = "";
try {
URL url = new URL(sourceUrl);
normalizedUrl = url.toString();
normalizedUrl = normalizedUrl.replace("\n", "");
normalizedUrl = normalizedUrl.replace(" ", "");
if (!normalizedUrl.endsWith("/"))
normalizedUrl = normalizedUrl + "/";
} catch (MalformedURLException e) {
Log.d(HABApplication.getLogTag(), "normalizeUrl: invalid URL");
}
return normalizedUrl;
}
}