/**
*
*/
package org.wordpress.android.ui;
import java.io.IOException;
import java.io.PushbackInputStream;
import java.lang.reflect.Type;
import java.net.URI;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.TranslateAnimation;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuInflater;
import com.actionbarsherlock.view.MenuItem;
import com.google.gson.Gson;
import com.google.gson.internal.StringMap;
import com.google.gson.reflect.TypeToken;
import com.justsystems.hpb.pad.R;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.cookie.Cookie;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlrpc.android.ApiHelper;
import org.xmlrpc.android.ConnectionClient;
import org.wordpress.android.Constants;
import org.wordpress.android.WordPress;
import org.wordpress.android.models.Blog;
/**
* @author Eric
*
*/
public class StatsActivity extends WPActionBarActivity {
static String lastAuthedName = "";
private ConnectionClient client;
private HttpPost postMethod;
private HttpParams httpParams;
protected String errorMsg = "";
private WebView webView;
boolean loginShowing = false;
boolean authed = false;
boolean isRetrying = false;
private AsyncTask<String, Void, List<?>> currentTask = null;
private MenuItem refreshMenuItem;
@SuppressLint("SetJavaScriptEnabled")
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
createMenuDrawer(R.layout.view_web_stats);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(true);
setTitle(getString(R.string.tab_stats));
EditText dotcomUsername = (EditText) findViewById(R.id.dotcomUsername);
EditText dotcomPassword = (EditText) findViewById(R.id.dotcomPassword);
webView = (WebView) findViewById(R.id.webView);
webView.setWebViewClient(new StatsWebViewClient());
WebSettings webSettings = webView.getSettings();
webSettings.setCacheMode(WebSettings.LOAD_NO_CACHE);
webSettings.setBuiltInZoomControls(true);
webSettings.setJavaScriptEnabled(true);
webSettings.setPluginsEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setSavePassword(false);
clearCookies();
webView.clearCache(false);
Button saveStatsLogin = (Button) findViewById(R.id.saveDotcom);
saveStatsLogin.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
EditText dotcomUsername = (EditText) findViewById(R.id.dotcomUsername);
EditText dotcomPassword = (EditText) findViewById(R.id.dotcomPassword);
String dcUsername = dotcomUsername.getText().toString();
String dcPassword = dotcomPassword.getText().toString();
if (dcUsername.equals("") || dcPassword.equals("")) {
showErrorDialog(
getResources().getText(R.string.required_fields)
.toString(),
getResources().getText(
R.string.username_password_required)
.toString());
} else {
WordPress.currentBlog.setDotcom_username(dcUsername);
WordPress.currentBlog.setDotcom_password(dcPassword);
WordPress.currentBlog.save(WordPress.currentBlog
.getUsername());
hideLoginForm();
initStats(); // start over again now that we have the login
}
}
});
TextView wpcomHelp = (TextView) findViewById(R.id.wpcomHelp);
wpcomHelp.setOnClickListener(new TextView.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("http://jetpack.me/about"));
startActivity(intent);
}
});
CookieSyncManager.createInstance(this);
this.initStats();
}
@Override
protected void onResume() {
super.onResume();
CookieSyncManager.getInstance().startSync();
}
@Override
protected void onPause() {
super.onPause();
CookieSyncManager.getInstance().stopSync();
}
@Override
protected void onDestroy() {
super.onDestroy();
webView.clearCache(false);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getSupportMenuInflater();
inflater.inflate(R.menu.basic_menu, menu);
refreshMenuItem = menu.findItem(R.id.menu_refresh);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.menu_refresh) {
reloadStats();
}
return super.onOptionsItemSelected(item);
}
private void initStats() {
this.startAnimatingRefreshButton(refreshMenuItem);
Blog blog = WordPress.getCurrentBlog();
if (blog == null) {
return;
}
if (!blog.isDotcomFlag() && blog.getApi_blogid() == null) {
// first run or was deleted.
this.checkAPIBlogInfo();
} else if (!blog.isDotcomFlag() && blog.getDotcom_username() == null) {
// .org blog with no corresponding .com jetpack login.
this.showLoginForm();
} else {
this.loadStats();
}
}
private void checkAPIBlogInfo() {
String sUsername, sPassword;
Blog blog = WordPress.currentBlog;
if (blog.isDotcomFlag()) {
sUsername = blog.getUsername();
sPassword = blog.getPassword();
} else {
// we have an alternate login, use that instead
sUsername = blog.getDotcom_username();
sPassword = blog.getDotcom_password();
}
this.startAnimatingRefreshButton(refreshMenuItem);
// Start an async task to retrieve the blog's data from the api.
currentTask = new StatsAPIBlogInfoAsyncTask().execute(sUsername,
sPassword);
}
private void clearCookies() {
//get rid of old auth cookie
CookieSyncManager.createInstance(StatsActivity.this);
CookieManager cookieManager = CookieManager.getInstance();
cookieManager.removeAllCookie();
}
protected void authStats() {
String sUsername, sPassword;
Blog blog = WordPress.currentBlog;
if (blog.isDotcomFlag()) {
sUsername = blog.getUsername();
sPassword = blog.getPassword();
} else {
// we have an alternate login, use that instead
sUsername = blog.getDotcom_username();
sPassword = blog.getDotcom_password();
}
if (lastAuthedName.equals(sUsername)) {
// Check for a valid auth cookie for this username.
CookieManager cookieManager = CookieManager.getInstance();
if (cookieManager.hasCookies()) {
String rawCookieString = cookieManager
.getCookie("wordpress.com");
if (rawCookieString != null && rawCookieString.length() > 0) {
rawCookieString = rawCookieString.toLowerCase();
Log.d("cookeis", rawCookieString);
String[] rawCookies = rawCookieString.split(";");
String[] rawCookieNameAndValue = rawCookies[0].split("=");
String val = rawCookieNameAndValue[1].trim();
if (val.indexOf(sUsername.toLowerCase()) != -1) {
authed = true;
this.loadStats();
return;
}
}
}
}
currentTask = new AuthStatsAsyncTask().execute(sUsername, sPassword);
}
protected void loadStats() {
if (!authed) {
this.authStats();
return;
}
Blog blog = WordPress.currentBlog;
String id = "";
if (blog.isDotcomFlag()) {
id = Integer.toString(WordPress.currentBlog.getBlogId());
} else {
id = blog.getApi_blogid();
}
webView.setVisibility(View.VISIBLE);
String path = "http://wordpress.com/?no-chrome#!/my-stats/?unit=1&blog="
+ id;
webView.loadUrl(path);
// Clear the history here so in a case where the user has changed blogs via the titlebar,
// tapping the back button will not try to load the previous blog's stats.
webView.clearHistory();
}
public void reloadStats() {
webView.reload();
}
private void configureClient(URI uri, String username, String password) {
postMethod = new HttpPost(uri);
postMethod.addHeader("charset", "UTF-8");
postMethod.addHeader("User-Agent", "wp-android/"
+ WordPress.versionName);
httpParams = postMethod.getParams();
HttpProtocolParams.setUseExpectContinue(httpParams, false);
UsernamePasswordCredentials creds;
// username & password for basic http auth
if (username != null) {
creds = new UsernamePasswordCredentials(username, password);
} else {
creds = new UsernamePasswordCredentials("", "");
}
// this gets connections working over https
if (uri.getScheme() != null) {
if (uri.getScheme().equals("https")) {
if (uri.getPort() == -1)
try {
client = new ConnectionClient(creds, 443);
} catch (KeyManagementException e) {
client = new ConnectionClient(creds);
} catch (NoSuchAlgorithmException e) {
client = new ConnectionClient(creds);
} catch (KeyStoreException e) {
client = new ConnectionClient(creds);
} catch (UnrecoverableKeyException e) {
client = new ConnectionClient(creds);
}
else
try {
client = new ConnectionClient(creds, uri.getPort());
} catch (KeyManagementException e) {
client = new ConnectionClient(creds);
} catch (NoSuchAlgorithmException e) {
client = new ConnectionClient(creds);
} catch (KeyStoreException e) {
client = new ConnectionClient(creds);
} catch (UnrecoverableKeyException e) {
client = new ConnectionClient(creds);
}
} else {
client = new ConnectionClient(creds);
}
} else {
client = new ConnectionClient(creds);
}
}
public void showLoginForm() {
if (loginShowing)
return;
loginShowing = true;
AnimationSet set = new AnimationSet(true);
Animation animation = new AlphaAnimation(0.0f, 1.0f);
animation.setDuration(500);
set.addAnimation(animation);
animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF,
1.0f, Animation.RELATIVE_TO_SELF, 0.0f);
animation.setDuration(500);
set.addAnimation(animation);
RelativeLayout moderationBar = (RelativeLayout) findViewById(R.id.dotcomLogin);
moderationBar.setVisibility(View.VISIBLE);
moderationBar.startAnimation(set);
}
public void hideLoginForm() {
if (!loginShowing)
return;
loginShowing = false;
AnimationSet set = new AnimationSet(true);
Animation animation = new AlphaAnimation(1.0f, 0.0f);
animation.setDuration(500);
set.addAnimation(animation);
animation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0.0f,
Animation.RELATIVE_TO_SELF, 0.0f, Animation.RELATIVE_TO_SELF,
0.0f, Animation.RELATIVE_TO_SELF, 1.0f);
animation.setDuration(500);
set.addAnimation(animation);
;
RelativeLayout moderationBar = (RelativeLayout) findViewById(R.id.dotcomLogin);
moderationBar.clearAnimation();
moderationBar.startAnimation(set);
moderationBar.setVisibility(View.INVISIBLE);
}
public void showErrorDialog(String title, String msg) {
if (isFinishing())
return;
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
StatsActivity.this);
dialogBuilder.setTitle(title);
dialogBuilder.setMessage(msg);
dialogBuilder.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// Just close the window.
}
});
dialogBuilder.setCancelable(true);
dialogBuilder.create().show();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (webView.canGoBack()) {
webView.goBack();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
@Override
public void onBlogChanged() {
super.onBlogChanged();
initStats();
}
/*
*
*/
protected class StatsWebViewClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
//Log.d("WP", url);
startAnimatingRefreshButton(refreshMenuItem);
}
@Override
public void onPageFinished(WebView view, String url) {
if (authed) {
// The webview loads an empty string during init/auth. We don't want to stop the refresh icon in this case.
stopAnimatingRefreshButton(refreshMenuItem);
}
}
@Override
public void onReceivedError(WebView view, int errorCode,
String description, String failingUrl) {
stopAnimatingRefreshButton(refreshMenuItem);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (!url.equalsIgnoreCase(Constants.readerDetailURL)) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(url));
startActivity(intent);
return true;
}
return false;
}
}
/*
* Call to authenticate so we can display stats. If auth fails prompt
* for updated wp.com credentials.
*/
private class AuthStatsAsyncTask extends AsyncTask<String, Void, List<?>> {
int statusCode = 0;
@Override
protected List<?> doInBackground(String... args) {
List<String> result = null;
String sUsername = args[0];
String sPassword = args[1];
try {
URI uri = URI.create("https://wordpress.com/wp-login.php");
configureClient(uri, sUsername, sPassword);
List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(
3);
nameValuePairs.add(new BasicNameValuePair("log", sUsername));
nameValuePairs.add(new BasicNameValuePair("pwd", sPassword));
nameValuePairs.add(new BasicNameValuePair("rememberme",
"forever"));
nameValuePairs
.add(new BasicNameValuePair("wp-submit", "Log In"));
nameValuePairs.add(new BasicNameValuePair("redirect_to", "/"));
postMethod.setEntity(new UrlEncodedFormEntity(nameValuePairs,
HTTP.UTF_8));
HttpResponse response = client.execute(postMethod);
statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new IOException("HTTP status code: " + statusCode
+ " was returned. "
+ response.getStatusLine().getReasonPhrase());
}
List<Cookie> cookies = client.getCookieStore().getCookies();
if (!cookies.isEmpty()) {
CookieManager cookieManager = CookieManager.getInstance();
for (Cookie cookie : cookies) {
if (cookie.getName().equalsIgnoreCase(
"wordpress_logged_in")) {
// Auth cookie found so mark the parent class authed and set the lastAuthedName
authed = true;
lastAuthedName = sUsername;
}
String cookieString = cookie.getName() + "="
+ cookie.getValue() + "; domain="
+ cookie.getDomain();
cookieManager.setCookie("wordpress.com", cookieString);
CookieSyncManager.getInstance().sync();
}
}
// Uncomment to review the html returned from the auth call.
// HttpEntity entity = response.getEntity();
// InputStream is = entity.getContent();
// InputStreamReader isr = new InputStreamReader(is);
// BufferedReader br = new BufferedReader(isr);
//
// StringBuffer sb = new StringBuffer("");
// String line = "";
// String NL = System.getProperty("line.separator");
// while((line = br.readLine()) != null){
// sb.append(line + NL);
// }
// br.close();
//
// String res = sb.toString();
// Log.d("AuthStatsAsyncTask", res);
} catch (Exception e) {
e.printStackTrace();
errorMsg = e.getMessage();
}
return result;
}
protected void onPostExecute(List<?> result) {
currentTask = null;
stopAnimatingRefreshButton(refreshMenuItem);
if (authed) {
loadStats();
} else {
// if we're here and not authed then there was either a server error
// or the auth cookie was not set.
if (errorMsg == "" || statusCode == 401) {
// Either there wasn't an error and the auth cookie wasn't set, or
// we received a 401 error.
Toast.makeText(StatsActivity.this,
getResources().getText(R.string.invalid_login),
Toast.LENGTH_SHORT).show();
showLoginForm();
} else {
// server error of some kind.
Toast.makeText(
StatsActivity.this,
getResources()
.getText(R.string.stats_service_error),
Toast.LENGTH_SHORT).show();
}
}
}
}
/*
* AsyncTask for retrieving a blog's key and id from the API
*/
private class StatsAPIBlogInfoAsyncTask extends
AsyncTask<String, Void, List<?>> {
int statusCode = 0;
@Override
protected List<?> doInBackground(String... args) {
String username = args[0];
String password = args[1];
String url = WordPress.currentBlog.getUrl();
String homeURL = WordPress.currentBlog.getHomeURL();
String storedBlogID = String.valueOf(WordPress.currentBlog
.getBlogId());
String wwwURL = "";
List<String> apiInfo = null;
if (homeURL.equals("")) {
//get the 'homePageLink' url from RSD to match with the stats api
String homePageLink = ApiHelper.getHomePageLink(url + "?rsd");
if (homePageLink != null) {
url = homePageLink;
//home url was added in 2.2.2, it may need to be set if the user upgraded
WordPress.currentBlog.setHomeURL(url);
WordPress.currentBlog.save("");
} else {
url = url.replace("xmlrpc.php", "");
}
} else {
url = homeURL;
}
url = url.replace("https://", "http://");
if (url.indexOf("http://www.") >= 0) {
wwwURL = url;
url = url.replace("http://www.", "http://");
} else {
wwwURL = url.replace("http://", "http://www.");
}
if (!url.endsWith("/")) {
url += "/";
wwwURL += "/";
}
URI uri = URI
.create("https://public-api.wordpress.com/get-user-blogs/1.0");
configureClient(uri, username, password);
// execute HTTP POST request
HttpResponse response;
try {
response = client.execute(postMethod);
/*
* ByteArrayOutputStream outstream = new ByteArrayOutputStream();
* response.getEntity().writeTo(outstream); String text =
* outstream.toString(); Log.i("WordPress", text);
*/
// check status code
this.statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new IOException("HTTP status code: " + statusCode
+ " was returned. "
+ response.getStatusLine().getReasonPhrase());
}
// setup pull parser
try {
XmlPullParser pullParser = XmlPullParserFactory
.newInstance().newPullParser();
HttpEntity entity = response.getEntity();
// change to pushbackinput stream 1/18/2010 to handle self
// installed wp sites that insert the BOM
PushbackInputStream is = new PushbackInputStream(
entity.getContent());
// get rid of junk characters before xml response. 60 = '<'.
// Added stopper to prevent infinite loop
int bomCheck = is.read();
int stopper = 0;
while (bomCheck != 60 && stopper < 20) {
bomCheck = is.read();
stopper++;
}
is.unread(bomCheck);
pullParser.setInput(is, "UTF-8");
int eventType = pullParser.getEventType();
String apiKey = "";
boolean foundKey = false;
boolean foundID = false;
boolean foundURL = false;
String curBlogID = "";
String curBlogURL = "";
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_DOCUMENT) {
// System.out.println("Start document");
} else if (eventType == XmlPullParser.END_DOCUMENT) {
// System.out.println("End document");
} else if (eventType == XmlPullParser.START_TAG) {
if (pullParser.getName().equals("apikey")) {
foundKey = true;
} else if (pullParser.getName().equals("id")) {
foundID = true;
} else if (pullParser.getName().equals("url")) {
foundURL = true;
}
} else if (eventType == XmlPullParser.END_TAG) {
// System.out.println("End tag "+pullParser.getName());
} else if (eventType == XmlPullParser.TEXT) {
// System.out.println("Text "+pullParser.getText().toString());
if (foundKey) {
apiKey = pullParser.getText();
foundKey = false;
} else if (foundID) {
curBlogID = pullParser.getText();
foundID = false;
} else if (foundURL) {
curBlogURL = pullParser.getText();
foundURL = false;
StringMap<?> jetpackClientIDOption = null;
try {
Gson gson = new Gson();
Type type = new TypeToken<Map<?, ?>>() {
}.getType();
Map<?, ?> blogOptions = gson.fromJson(
WordPress.currentBlog
.getBlogOptions(), type);
jetpackClientIDOption = blogOptions != null ? (StringMap<?>) blogOptions
.get("jetpack_client_id") : null;
} catch (Exception e) {
e.printStackTrace();
}
if (jetpackClientIDOption != null) {
//Try to match the ID first. The WordPress.com ID of the Jetpack blog was introduced in options in Jetpack 1.8.3 or higher
if (jetpackClientIDOption.get("value") instanceof Double) {
double clientID = (Double) jetpackClientIDOption
.get("value");
String jetpackClientIDString = String
.valueOf((long) clientID);
if (jetpackClientIDString
.equals(curBlogID)
&& !curBlogID.equals("1")) {
// yay, found a match
apiInfo = new Vector<String>();
apiInfo.add(apiKey);
apiInfo.add(curBlogID);
}
}
} else {
// make sure we're matching with a '/' at the end of
// the string, the api returns both with and w/o
if (!curBlogURL.endsWith("/"))
curBlogURL += "/";
if (((curBlogURL.equals(url) || (curBlogURL
.equals(wwwURL))) || storedBlogID
.equals(curBlogID))
&& !curBlogID.equals("1")) {
// yay, found a match
apiInfo = new Vector<String>();
apiInfo.add(apiKey);
apiInfo.add(curBlogID);
}
}
}
}
eventType = pullParser.next();
}
} catch (XmlPullParserException e) {
e.printStackTrace();
errorMsg = e.getMessage();
}
} catch (ClientProtocolException e) {
e.printStackTrace();
errorMsg = e.getMessage();
} catch (IOException e) {
e.printStackTrace();
errorMsg = e.getMessage();
}
return apiInfo;
}
protected void onPostExecute(List<?> result) {
currentTask = null;
stopAnimatingRefreshButton(refreshMenuItem);
if (result != null) {
// store the api key and blog id
final String apiKey = result.get(0).toString();
final String apiBlogID = result.get(1).toString();
WordPress.currentBlog.setApi_blogid(apiBlogID);
WordPress.currentBlog.setApi_key(apiKey);
WordPress.currentBlog.save("");
if (!isFinishing())
authStats();
} else {
// Either there was a server error, an auth error,
// or the blog could not be found among the list of blogs
// returned by the API.
if (isRetrying) {
if (errorMsg.equals("") || statusCode == 401) {
Toast.makeText(
StatsActivity.this,
getResources().getText(
R.string.invalid_jp_login),
Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(
StatsActivity.this,
getResources().getText(R.string.invalid_login)
+ " "
+ getResources().getText(
R.string.site_not_found),
Toast.LENGTH_SHORT).show();
errorMsg = "";
showErrorDialog(
getResources().getText(
R.string.connection_error).toString(),
getResources().getText(
R.string.connection_error_occured)
.toString());
}
}
showLoginForm();
isRetrying = true;
}
}
}
}