/*******************************************************************************
* This file is part of Zandy.
*
* Zandy is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zandy 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Zandy. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package com.gimranov.zandy.app;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.app.SearchManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.gimranov.zandy.app.data.Database;
import com.gimranov.zandy.app.data.Item;
import com.gimranov.zandy.app.data.ItemAdapter;
import com.gimranov.zandy.app.data.ItemCollection;
import com.gimranov.zandy.app.task.APIEvent;
import com.gimranov.zandy.app.task.APIRequest;
import com.gimranov.zandy.app.task.ZoteroAPITask;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import com.squareup.otto.Subscribe;
import org.apache.http.util.ByteArrayBuffer;
import org.jetbrains.annotations.Nullable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
public class ItemActivity extends ListActivity {
private static final String TAG = "com.gimranov.zandy.app.ItemActivity";
static final int DIALOG_VIEW = 0;
static final int DIALOG_NEW = 1;
static final int DIALOG_SORT = 2;
static final int DIALOG_IDENTIFIER = 3;
static final int DIALOG_PROGRESS = 6;
/**
* Allowed sort orderings
*/
static final String[] SORTS = {
"item_year, item_title COLLATE NOCASE",
"item_creator COLLATE NOCASE, item_year",
"item_title COLLATE NOCASE, item_year",
"timestamp ASC, item_title COLLATE NOCASE"
};
/**
* Strings providing the names of each ordering, respectively
*/
static final int[] SORT_NAMES = {
R.string.sort_year_title,
R.string.sort_creator_year,
R.string.sort_title_year,
R.string.sort_modified_title
};
private static final String SORT_CHOICE = "sort_choice";
private String collectionKey;
private String query;
private Database db;
private ProgressDialog mProgressDialog;
private ProgressThread progressThread;
public String sortBy = "item_year, item_title";
final Handler syncHandler = new Handler() {
public void handleMessage(Message msg) {
Log.d(TAG, "received message: " + msg.arg1);
refreshView();
if (msg.arg1 == APIRequest.UPDATED_DATA) {
refreshView();
return;
}
if (msg.arg1 == APIRequest.QUEUED_MORE) {
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.sync_queued_more, msg.arg2),
Toast.LENGTH_SHORT).show();
return;
}
if (msg.arg1 == APIRequest.BATCH_DONE) {
Application.getInstance().getBus().post(SyncEvent.COMPLETE);
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.sync_complete),
Toast.LENGTH_SHORT).show();
return;
}
if (msg.arg1 == APIRequest.ERROR_UNKNOWN) {
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.sync_error),
Toast.LENGTH_SHORT).show();
return;
}
}
};
private APIEvent mEvent = new APIEvent() {
private int updates = 0;
@Override
public void onComplete(APIRequest request) {
Message msg = syncHandler.obtainMessage();
msg.arg1 = APIRequest.BATCH_DONE;
syncHandler.sendMessage(msg);
Log.d(TAG, "fired oncomplete");
}
@Override
public void onUpdate(APIRequest request) {
updates++;
if (updates % 10 == 0) {
Message msg = syncHandler.obtainMessage();
msg.arg1 = APIRequest.UPDATED_DATA;
syncHandler.sendMessage(msg);
} else {
// do nothing
}
}
@Override
public void onError(APIRequest request, Exception exception) {
Log.e(TAG, "APIException caught", exception);
Message msg = syncHandler.obtainMessage();
msg.arg1 = APIRequest.ERROR_UNKNOWN;
syncHandler.sendMessage(msg);
}
@Override
public void onError(APIRequest request, int error) {
Log.e(TAG, "API error caught");
Message msg = syncHandler.obtainMessage();
msg.arg1 = APIRequest.ERROR_UNKNOWN;
syncHandler.sendMessage(msg);
}
};
protected Bundle b = new Bundle();
/**
* Refreshes the current list adapter
*/
private void refreshView() {
ItemAdapter adapter = (ItemAdapter) getListAdapter();
if (adapter == null) return;
Cursor newCursor = prepareCursor();
adapter.changeCursor(newCursor);
adapter.notifyDataSetChanged();
Log.d(TAG, "refreshing view on request");
}
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String persistedSort = Persistence.read(SORT_CHOICE);
if (persistedSort != null) sortBy = persistedSort;
db = new Database(this);
setContentView(R.layout.items);
Intent intent = getIntent();
collectionKey = intent.getStringExtra("com.gimranov.zandy.app.collectionKey");
ItemCollection coll = ItemCollection.load(collectionKey, db);
APIRequest req;
if (coll != null) {
req = APIRequest.fetchItems(coll, false,
new ServerCredentials(this));
} else {
req = APIRequest.fetchItems(false,
new ServerCredentials(this));
}
prepareAdapter();
ItemAdapter adapter = (ItemAdapter) getListAdapter();
Cursor cur = adapter.getCursor();
if (intent.getBooleanExtra("com.gimranov.zandy.app.rerequest", false)
|| cur == null
|| cur.getCount() == 0) {
if (!ServerCredentials.check(getBaseContext())) {
Toast.makeText(getBaseContext(), getResources().getString(R.string.sync_log_in_first),
Toast.LENGTH_SHORT).show();
return;
}
Toast.makeText(this,
getResources().getString(R.string.collection_empty),
Toast.LENGTH_SHORT).show();
Log.d(TAG, "Running a request to populate missing items");
ZoteroAPITask task = new ZoteroAPITask(this);
req.setHandler(mEvent);
task.execute(req);
}
ListView lv = getListView();
lv.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// If we have a click on an item, do something...
ItemAdapter adapter = (ItemAdapter) parent.getAdapter();
Cursor cur = adapter.getCursor();
// Place the cursor at the selected item
if (cur.moveToPosition(position)) {
// and load an activity for the item
Item item = Item.load(cur);
Log.d(TAG, "Loading item data with key: " + item.getKey());
// We create and issue a specified intent with the necessary data
Intent i = new Intent(getBaseContext(), ItemDataActivity.class);
i.putExtra("com.gimranov.zandy.app.itemKey", item.getKey());
i.putExtra("com.gimranov.zandy.app.itemDbId", item.dbId);
startActivity(i);
} else {
// failed to move cursor-- show a toast
TextView tvTitle = (TextView) view.findViewById(R.id.item_title);
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.cant_open_item, tvTitle.getText()),
Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
protected void onResume() {
super.onResume();
Application.getInstance().getBus().register(this);
refreshView();
}
@Override
public void onDestroy() {
ItemAdapter adapter = (ItemAdapter) getListAdapter();
Cursor cur = adapter.getCursor();
if (cur != null) cur.close();
if (db != null) db.close();
super.onDestroy();
}
@Override
protected void onPause() {
super.onPause();
Application.getInstance().getBus().unregister(this);
}
private void prepareAdapter() {
ItemAdapter adapter = new ItemAdapter(this, prepareCursor());
setListAdapter(adapter);
}
private Cursor prepareCursor() {
Cursor cursor;
// Be ready for a search
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
query = intent.getStringExtra(SearchManager.QUERY);
cursor = getCursor(query);
this.setTitle(getResources().getString(R.string.search_results, query));
} else if (query != null) {
cursor = getCursor(query);
this.setTitle(getResources().getString(R.string.search_results, query));
} else if (intent.getStringExtra("com.gimranov.zandy.app.tag") != null) {
String tag = intent.getStringExtra("com.gimranov.zandy.app.tag");
Query q = new Query();
q.set("tag", tag);
cursor = getCursor(q);
this.setTitle(getResources().getString(R.string.tag_viewing_items, tag));
} else {
collectionKey = intent.getStringExtra("com.gimranov.zandy.app.collectionKey");
ItemCollection coll;
if (collectionKey != null && (coll = ItemCollection.load(collectionKey, db)) != null) {
cursor = getCursor(coll);
this.setTitle(coll.getTitle());
} else {
cursor = getCursor();
this.setTitle(getResources().getString(R.string.all_items));
}
}
return cursor;
}
protected Dialog onCreateDialog(int id) {
switch (id) {
case DIALOG_NEW:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getResources().getString(R.string.item_type))
// XXX i18n
.setItems(Item.ITEM_TYPES_EN, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int pos) {
Item item = new Item(getBaseContext(), Item.ITEM_TYPES[pos]);
item.dirty = APIRequest.API_DIRTY;
item.save(db);
if (collectionKey != null) {
ItemCollection coll = ItemCollection.load(collectionKey, db);
if (coll != null) {
coll.loadChildren(db);
coll.add(item);
coll.saveChildren(db);
}
}
Log.d(TAG, "Loading item data with key: " + item.getKey());
// We create and issue a specified intent with the necessary data
Intent i = new Intent(getBaseContext(), ItemDataActivity.class);
i.putExtra("com.gimranov.zandy.app.itemKey", item.getKey());
startActivity(i);
}
});
AlertDialog dialog = builder.create();
return dialog;
case DIALOG_SORT:
// We generate the sort name list for our current locale
String[] sorts = new String[SORT_NAMES.length];
for (int j = 0; j < SORT_NAMES.length; j++) {
sorts[j] = getResources().getString(SORT_NAMES[j]);
}
AlertDialog.Builder builder2 = new AlertDialog.Builder(this);
builder2.setTitle(getResources().getString(R.string.set_sort_order))
.setItems(sorts, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int pos) {
Cursor cursor;
setSortBy(SORTS[pos]);
ItemCollection collection;
if (collectionKey != null
&& (collection = ItemCollection.load(collectionKey, db)) != null) {
cursor = getCursor(collection);
} else {
if (query != null) {
cursor = getCursor(query);
} else {
cursor = getCursor();
}
}
ItemAdapter adapter = (ItemAdapter) getListAdapter();
adapter.changeCursor(cursor);
Log.d(TAG, "Re-sorting by: " + SORTS[pos]);
Persistence.write(SORT_CHOICE, SORTS[pos]);
}
});
AlertDialog dialog2 = builder2.create();
return dialog2;
case DIALOG_PROGRESS:
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setIndeterminate(true);
mProgressDialog.setMessage(getResources().getString(R.string.identifier_looking_up));
return mProgressDialog;
case DIALOG_IDENTIFIER:
final EditText input = new EditText(this);
input.setHint(getResources().getString(R.string.identifier_hint));
final ItemActivity current = this;
dialog = new AlertDialog.Builder(this)
.setTitle(getResources().getString(R.string.identifier_message))
.setView(input)
.setPositiveButton(getResources().getString(R.string.menu_search), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
Editable value = input.getText();
// run search
Bundle c = new Bundle();
c.putString("mode", "isbn");
c.putString("identifier", value.toString());
removeDialog(DIALOG_PROGRESS);
ItemActivity.this.b = c;
showDialog(DIALOG_PROGRESS);
}
}).setNeutralButton(getResources().getString(R.string.scan), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// If we're about to download from Google play, cancel that dialog
// and prompt from Amazon if we're on an Amazon device
IntentIntegrator integrator = new IntentIntegrator(current);
@Nullable AlertDialog producedDialog = integrator.initiateScan();
if (producedDialog != null && "amazon".equals(BuildConfig.FLAVOR)) {
producedDialog.dismiss();
AmazonZxingGlue.showDownloadDialog(current);
}
}
}).setNegativeButton(getResources().getString(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int whichButton) {
// do nothing
}
}).create();
return dialog;
default:
return null;
}
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch (id) {
case DIALOG_PROGRESS:
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.zotero_menu, menu);
// Turn on sort item
MenuItem sort = menu.findItem(R.id.do_sort);
sort.setEnabled(true);
sort.setVisible(true);
// Turn on search item
MenuItem search = menu.findItem(R.id.do_search);
search.setEnabled(true);
search.setVisible(true);
// Turn on identifier item
MenuItem identifier = menu.findItem(R.id.do_identifier);
identifier.setEnabled(true);
identifier.setVisible(true);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.do_sync:
if (!ServerCredentials.check(getBaseContext())) {
Toast.makeText(getBaseContext(), getResources().getString(R.string.sync_log_in_first),
Toast.LENGTH_SHORT).show();
return true;
}
// Get credentials
ServerCredentials cred = new ServerCredentials(getBaseContext());
// Make this a collection-specific sync, preceding by de-dirtying
Item.queue(db);
ArrayList<APIRequest> list = new ArrayList<APIRequest>();
APIRequest[] templ = {};
for (Item i : Item.queue) {
Log.d(TAG, "Adding dirty item to sync: " + i.getTitle());
list.add(cred.prep(APIRequest.update(i)));
}
if (collectionKey == null) {
Log.d(TAG, "Adding sync request for all items");
APIRequest req = APIRequest.fetchItems(false, cred);
req.setHandler(mEvent);
list.add(req);
} else {
Log.d(TAG, "Adding sync request for collection: " + collectionKey);
APIRequest req = APIRequest.fetchItems(collectionKey, true, cred);
req.setHandler(mEvent);
list.add(req);
}
APIRequest[] reqs = list.toArray(templ);
ZoteroAPITask task = new ZoteroAPITask(getBaseContext());
task.setHandler(syncHandler);
task.execute(reqs);
Toast.makeText(getApplicationContext(), getResources().getString(R.string.sync_started),
Toast.LENGTH_SHORT).show();
return true;
case R.id.do_new:
removeDialog(DIALOG_NEW);
showDialog(DIALOG_NEW);
return true;
case R.id.do_identifier:
removeDialog(DIALOG_IDENTIFIER);
showDialog(DIALOG_IDENTIFIER);
return true;
case R.id.do_search:
onSearchRequested();
return true;
case R.id.do_prefs:
Intent i = new Intent(getBaseContext(), SettingsActivity.class);
startActivity(i);
return true;
case R.id.do_sort:
removeDialog(DIALOG_SORT);
showDialog(DIALOG_SORT);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/* Sorting */
public void setSortBy(String sort) {
this.sortBy = sort;
}
/* Handling the ListView and keeping it up to date */
public Cursor getCursor() {
Cursor cursor = db.query("items", Database.ITEMCOLS, null, null, null, null, this.sortBy, null);
if (cursor == null) {
Log.e(TAG, "cursor is null");
}
return cursor;
}
public Cursor getCursor(ItemCollection parent) {
String[] args = {parent.dbId};
Cursor cursor = db.rawQuery("SELECT item_title, item_type, item_content, etag, dirty, " +
"items._id, item_key, item_year, item_creator, timestamp, item_children " +
" FROM items, itemtocollections WHERE items._id = item_id AND collection_id=? ORDER BY " + this.sortBy,
args);
if (cursor == null) {
Log.e(TAG, "cursor is null");
}
return cursor;
}
public Cursor getCursor(String query) {
String[] args = {"%" + query + "%", "%" + query + "%"};
Cursor cursor = db.rawQuery("SELECT item_title, item_type, item_content, etag, dirty, " +
"_id, item_key, item_year, item_creator, timestamp, item_children " +
" FROM items WHERE item_title LIKE ? OR item_creator LIKE ?" +
" ORDER BY " + this.sortBy,
args);
if (cursor == null) {
Log.e(TAG, "cursor is null");
}
return cursor;
}
public Cursor getCursor(Query query) {
return query.query(db);
}
@Subscribe
public void syncComplete(SyncEvent event) {
if (event.getStatus() == SyncEvent.COMPLETE_CODE) refreshView();
}
/* Thread and helper to run lookups */
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
Log.d(TAG, "_____________________on_activity_result");
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
if (scanResult != null) {
// handle scan result
Bundle b = new Bundle();
b.putString("mode", "isbn");
b.putString("identifier", scanResult.getContents());
if (scanResult != null
&& scanResult.getContents() != null) {
Log.d(TAG, b.getString("identifier"));
progressThread = new ProgressThread(handler, b);
progressThread.start();
this.b = b;
removeDialog(DIALOG_PROGRESS);
showDialog(DIALOG_PROGRESS);
} else {
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.identifier_scan_failed),
Toast.LENGTH_SHORT).show();
}
} else {
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.identifier_scan_failed),
Toast.LENGTH_SHORT).show();
}
}
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
Log.d(TAG, "______________________handle_message");
if (ProgressThread.STATE_DONE == msg.arg2) {
Bundle data = msg.getData();
String itemKey = data.getString("itemKey");
if (itemKey != null) {
if (collectionKey != null) {
Item item = Item.load(itemKey, db);
ItemCollection coll = ItemCollection.load(collectionKey, db);
coll.add(item);
coll.saveChildren(db);
}
mProgressDialog.dismiss();
mProgressDialog = null;
Log.d(TAG, "Loading new item data with key: " + itemKey);
// We create and issue a specified intent with the necessary data
Intent i = new Intent(getBaseContext(), ItemDataActivity.class);
i.putExtra("com.gimranov.zandy.app.itemKey", itemKey);
startActivity(i);
}
return;
}
if (ProgressThread.STATE_PARSING == msg.arg2) {
mProgressDialog.setMessage(getResources().getString(R.string.identifier_processing));
return;
}
if (ProgressThread.STATE_ERROR == msg.arg2) {
dismissDialog(DIALOG_PROGRESS);
Toast.makeText(getApplicationContext(),
getResources().getString(R.string.identifier_lookup_failed),
Toast.LENGTH_SHORT).show();
progressThread.setState(ProgressThread.STATE_DONE);
return;
}
}
};
private class ProgressThread extends Thread {
Handler mHandler;
Bundle arguments;
final static int STATE_DONE = 5;
final static int STATE_FETCHING = 1;
final static int STATE_PARSING = 6;
final static int STATE_ERROR = 7;
int mState;
ProgressThread(Handler h, Bundle b) {
mHandler = h;
arguments = b;
Log.d(TAG, "_____________________thread_constructor");
}
public void run() {
Log.d(TAG, "_____________________thread_run");
mState = STATE_FETCHING;
// Setup
String identifier = arguments.getString("identifier");
String mode = arguments.getString("mode");
URL url;
String urlstring;
String response = "";
if ("isbn".equals(mode)) {
urlstring = "http://xisbn.worldcat.org/webservices/xid/isbn/"
+ identifier
+ "?method=getMetadata&fl=*&format=json&count=1";
} else {
urlstring = "";
}
try {
Log.d(TAG, "Fetching from: " + urlstring);
url = new URL(urlstring);
/* Open a connection to that URL. */
URLConnection ucon = url.openConnection();
/*
* Define InputStreams to read from the URLConnection.
*/
InputStream is = ucon.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is, 16000);
ByteArrayBuffer baf = new ByteArrayBuffer(50);
int current = 0;
/*
* Read bytes to the Buffer until there is nothing more to read(-1).
*/
while (mState == STATE_FETCHING
&& (current = bis.read()) != -1) {
baf.append((byte) current);
}
response = new String(baf.toByteArray());
Log.d(TAG, response);
} catch (IOException e) {
Log.e(TAG, "Error: ", e);
}
Message msg = mHandler.obtainMessage();
msg.arg2 = STATE_PARSING;
mHandler.sendMessage(msg);
/*
* {
"stat":"ok",
"list":[{
"url":["http://www.worldcat.org/oclc/177669176?referer=xid"],
"publisher":"O'Reilly",
"form":["BA"],
"lccn":["2004273129"],
"lang":"eng",
"city":"Sebastopol, CA",
"author":"by Mark Lutz and David Ascher.",
"ed":"2nd ed.",
"year":"2003",
"isbn":["0596002815"],
"title":"Learning Python",
"oclcnum":["177669176",
..
"748093898"]}]}
*/
// This is OCLC-specific logic
try {
JSONObject result = new JSONObject(response);
if (!result.getString("stat").equals("ok")) {
Log.e(TAG, "Error response received");
msg = mHandler.obtainMessage();
msg.arg2 = STATE_ERROR;
mHandler.sendMessage(msg);
return;
}
result = result.getJSONArray("list").getJSONObject(0);
String form = result.getJSONArray("form").getString(0);
String type;
if ("AA".equals(form)) type = "audioRecording";
else if ("VA".equals(form)) type = "videoRecording";
else if ("FA".equals(form)) type = "film";
else type = "book";
// TODO Fix this
type = "book";
Item item = new Item(getBaseContext(), type);
JSONObject content = item.getContent();
if (result.has("lccn")) {
String lccn = "LCCN: " + result.getJSONArray("lccn").getString(0);
content.put("extra", lccn);
}
if (result.has("isbn")) {
content.put("ISBN", result.getJSONArray("isbn").getString(0));
}
content.put("title", result.optString("title", ""));
content.put("place", result.optString("city", ""));
content.put("edition", result.optString("ed", ""));
content.put("language", result.optString("lang", ""));
content.put("publisher", result.optString("publisher", ""));
content.put("date", result.optString("year", ""));
item.setTitle(result.optString("title", ""));
item.setYear(result.optString("year", ""));
String author = result.optString("author", "");
item.setCreatorSummary(author);
JSONArray array = new JSONArray();
JSONObject member = new JSONObject();
member.accumulate("creatorType", "author");
member.accumulate("name", author);
array.put(member);
content.put("creators", array);
item.setContent(content);
item.save(db);
msg = mHandler.obtainMessage();
Bundle data = new Bundle();
data.putString("itemKey", item.getKey());
msg.setData(data);
msg.arg2 = STATE_DONE;
mHandler.sendMessage(msg);
return;
} catch (JSONException e) {
Log.e(TAG, "exception parsing response", e);
msg = mHandler.obtainMessage();
msg.arg2 = STATE_ERROR;
mHandler.sendMessage(msg);
return;
}
}
public void setState(int state) {
mState = state;
}
}
}