diff --git a/android/src/com/google/zxing/client/android/HttpHelper.java b/android/src/com/google/zxing/client/android/HttpHelper.java index ba2b0792a..0eeecaa97 100644 --- a/android/src/com/google/zxing/client/android/HttpHelper.java +++ b/android/src/com/google/zxing/client/android/HttpHelper.java @@ -16,6 +16,8 @@ package com.google.zxing.client.android; +import android.util.Log; + import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -35,6 +37,8 @@ import java.util.HashSet; */ public final class HttpHelper { + private static final String TAG = HttpHelper.class.getSimpleName(); + private static final Collection REDIRECTOR_DOMAINS = new HashSet(Arrays.asList( "amzn.to", "bit.ly", "bitly.com", "fb.me", "goo.gl", "is.gd", "j.mp", "lnkd.in", "ow.ly", "R.BEETAGG.COM", "r.beetagg.com", "SCN.BY", "su.pr", "t.co", "tinyurl.com", "tr.im" @@ -75,6 +79,7 @@ public final class HttpHelper { } private static String downloadViaHttp(String uri, String contentTypes) throws IOException { + Log.i(TAG, "Downloading " + uri); URL url = new URL(uri); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestProperty("Accept", contentTypes); diff --git a/android/src/com/google/zxing/client/android/book/NetworkWorker.java b/android/src/com/google/zxing/client/android/book/NetworkWorker.java deleted file mode 100644 index 429000070..000000000 --- a/android/src/com/google/zxing/client/android/book/NetworkWorker.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2010 ZXing authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.zxing.client.android.book; - -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import com.google.zxing.client.android.HttpHelper; -import com.google.zxing.client.android.LocaleManager; -import com.google.zxing.client.android.R; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.IOException; - -final class NetworkWorker implements Runnable { - - private static final String TAG = NetworkWorker.class.getSimpleName(); - - private final String isbn; - private final String query; - private final Handler handler; - - NetworkWorker(String isbn, String query, Handler handler) { - this.isbn = isbn; - this.query = query; - this.handler = handler; - } - - @Override - public void run() { - try { - // These return a JSON result which describes if and where the query was found. This API may - // break or disappear at any time in the future. Since this is an API call rather than a - // website, we don't use LocaleManager to change the TLD. - String uri; - if (LocaleManager.isBookSearchUrl(isbn)) { - int equals = isbn.indexOf('='); - String volumeId = isbn.substring(equals + 1); - uri = "http://www.google.com/books?id=" + volumeId + "&jscmd=SearchWithinVolume2&q=" + query; - } else { - uri = "http://www.google.com/books?vid=isbn" + isbn + "&jscmd=SearchWithinVolume2&q=" + query; - } - - try { - String content = HttpHelper.downloadViaHttp(uri, HttpHelper.ContentType.JSON); - JSONObject json = new JSONObject(content); - Message message = Message.obtain(handler, R.id.search_book_contents_succeeded); - message.obj = json; - message.sendToTarget(); - } catch (IOException ioe) { - Message message = Message.obtain(handler, R.id.search_book_contents_failed); - message.sendToTarget(); - } - } catch (JSONException je) { - Log.w(TAG, "Error accessing book search", je); - Message message = Message.obtain(handler, R.id.search_book_contents_failed); - message.sendToTarget(); - } - } - -} diff --git a/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java b/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java index 5340109f4..adb53b6d1 100644 --- a/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java +++ b/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java @@ -18,9 +18,8 @@ package com.google.zxing.client.android.book; import android.app.Activity; import android.content.Intent; +import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.util.Log; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -31,10 +30,12 @@ import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.TextView; +import com.google.zxing.client.android.HttpHelper; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @@ -58,29 +59,12 @@ public final class SearchBookContentsActivity extends Activity { private static final Pattern QUOTE_ENTITY_PATTERN = Pattern.compile("'"); private static final Pattern QUOT_ENTITY_PATTERN = Pattern.compile("""); - private Thread networkThread; private String isbn; private EditText queryTextView; private Button queryButton; private ListView resultListView; private TextView headerView; - private final Handler handler = new Handler() { - @Override - public void handleMessage(Message message) { - switch (message.what) { - case R.id.search_book_contents_succeeded: - handleSearchResults((JSONObject) message.obj); - resetForNewQuery(); - break; - case R.id.search_book_contents_failed: - resetForNewQuery(); - headerView.setText(R.string.msg_sbc_failed); - break; - } - } - }; - private final Button.OnClickListener buttonListener = new Button.OnClickListener() { @Override public void onClick(View view) { @@ -150,86 +134,121 @@ public final class SearchBookContentsActivity extends Activity { queryTextView.selectAll(); } - private void resetForNewQuery() { - networkThread = null; - queryTextView.setEnabled(true); - queryTextView.selectAll(); - queryButton.setEnabled(true); - } - private void launchSearch() { - if (networkThread == null) { - String query = queryTextView.getText().toString(); - if (query != null && query.length() > 0) { - networkThread = new Thread(new NetworkWorker(isbn, query, handler)); - networkThread.start(); - headerView.setText(R.string.msg_sbc_searching_book); - resultListView.setAdapter(null); - queryTextView.setEnabled(false); - queryButton.setEnabled(false); - } - } - } - - // Currently there is no way to distinguish between a query which had no results and a book - // which is not searchable - both return zero results. - private void handleSearchResults(JSONObject json) { - try { - int count = json.getInt("number_of_results"); - headerView.setText(getString(R.string.msg_sbc_results) + " : " + count); - if (count > 0) { - JSONArray results = json.getJSONArray("search_results"); - SearchBookContentsResult.setQuery(queryTextView.getText().toString()); - List items = new ArrayList(count); - for (int x = 0; x < count; x++) { - items.add(parseResult(results.getJSONObject(x))); - } - resultListView.setOnItemClickListener(new BrowseBookListener(this, items)); - resultListView.setAdapter(new SearchBookContentsAdapter(this, items)); - } else { - String searchable = json.optString("searchable"); - if ("false".equals(searchable)) { - headerView.setText(R.string.msg_sbc_book_not_searchable); - } - resultListView.setAdapter(null); - } - } catch (JSONException e) { - Log.w(TAG, "Bad JSON from book search", e); + String query = queryTextView.getText().toString(); + if (query != null && query.length() > 0) { + NetworkTask networkTask = new NetworkTask(); + networkTask.execute(query, isbn); + headerView.setText(R.string.msg_sbc_searching_book); resultListView.setAdapter(null); - headerView.setText(R.string.msg_sbc_failed); + queryTextView.setEnabled(false); + queryButton.setEnabled(false); } } - // Available fields: page_id, page_number, page_url, snippet_text - private SearchBookContentsResult parseResult(JSONObject json) { - try { - String pageId = json.getString("page_id"); - String pageNumber = json.getString("page_number"); - if (pageNumber.length() > 0) { - pageNumber = getString(R.string.msg_sbc_page) + ' ' + pageNumber; - } else { - // This can happen for text on the jacket, and possibly other reasons. - pageNumber = getString(R.string.msg_sbc_unknown_page); - } + private final class NetworkTask extends AsyncTask { - // Remove all HTML tags and encoded characters. Ideally the server would do this. - String snippet = json.optString("snippet_text"); - boolean valid = true; - if (snippet.length() > 0) { - snippet = TAG_PATTERN.matcher(snippet).replaceAll(""); - snippet = LT_ENTITY_PATTERN.matcher(snippet).replaceAll("<"); - snippet = GT_ENTITY_PATTERN.matcher(snippet).replaceAll(">"); - snippet = QUOTE_ENTITY_PATTERN.matcher(snippet).replaceAll("'"); - snippet = QUOT_ENTITY_PATTERN.matcher(snippet).replaceAll("\""); - } else { - snippet = '(' + getString(R.string.msg_sbc_snippet_unavailable) + ')'; - valid = false; + @Override + protected JSONObject doInBackground(String... args) { + try { + // These return a JSON result which describes if and where the query was found. This API may + // break or disappear at any time in the future. Since this is an API call rather than a + // website, we don't use LocaleManager to change the TLD. + String theQuery = args[0]; + String theIsbn = args[1]; + String uri; + if (LocaleManager.isBookSearchUrl(theIsbn)) { + int equals = theIsbn.indexOf('='); + String volumeId = theIsbn.substring(equals + 1); + uri = "http://www.google.com/books?id=" + volumeId + "&jscmd=SearchWithinVolume2&q=" + theQuery; + } else { + uri = "http://www.google.com/books?vid=isbn" + theIsbn + "&jscmd=SearchWithinVolume2&q=" + theQuery; + } + String content = HttpHelper.downloadViaHttp(uri, HttpHelper.ContentType.JSON); + return new JSONObject(content); + } catch (IOException ioe) { + Log.w(TAG, "Error accessing book search", ioe); + return null; + } catch (JSONException je) { + Log.w(TAG, "Error accessing book search", je); + return null; } - return new SearchBookContentsResult(pageId, pageNumber, snippet, valid); - } catch (JSONException e) { - // Never seen in the wild, just being complete. - return new SearchBookContentsResult(getString(R.string.msg_sbc_no_page_returned), "", "", false); } + + @Override + protected void onPostExecute(JSONObject result) { + if (result == null) { + headerView.setText(R.string.msg_sbc_failed); + } else { + handleSearchResults(result); + } + queryTextView.setEnabled(true); + queryTextView.selectAll(); + queryButton.setEnabled(true); + } + + // Currently there is no way to distinguish between a query which had no results and a book + // which is not searchable - both return zero results. + private void handleSearchResults(JSONObject json) { + try { + int count = json.getInt("number_of_results"); + headerView.setText(getString(R.string.msg_sbc_results) + " : " + count); + if (count > 0) { + JSONArray results = json.getJSONArray("search_results"); + SearchBookContentsResult.setQuery(queryTextView.getText().toString()); + List items = new ArrayList(count); + for (int x = 0; x < count; x++) { + items.add(parseResult(results.getJSONObject(x))); + } + resultListView.setOnItemClickListener(new BrowseBookListener(SearchBookContentsActivity.this, items)); + resultListView.setAdapter(new SearchBookContentsAdapter(SearchBookContentsActivity.this, items)); + } else { + String searchable = json.optString("searchable"); + if ("false".equals(searchable)) { + headerView.setText(R.string.msg_sbc_book_not_searchable); + } + resultListView.setAdapter(null); + } + } catch (JSONException e) { + Log.w(TAG, "Bad JSON from book search", e); + resultListView.setAdapter(null); + headerView.setText(R.string.msg_sbc_failed); + } + } + + // Available fields: page_id, page_number, page_url, snippet_text + private SearchBookContentsResult parseResult(JSONObject json) { + try { + String pageId = json.getString("page_id"); + String pageNumber = json.getString("page_number"); + if (pageNumber.length() > 0) { + pageNumber = getString(R.string.msg_sbc_page) + ' ' + pageNumber; + } else { + // This can happen for text on the jacket, and possibly other reasons. + pageNumber = getString(R.string.msg_sbc_unknown_page); + } + + // Remove all HTML tags and encoded characters. Ideally the server would do this. + String snippet = json.optString("snippet_text"); + boolean valid = true; + if (snippet.length() > 0) { + snippet = TAG_PATTERN.matcher(snippet).replaceAll(""); + snippet = LT_ENTITY_PATTERN.matcher(snippet).replaceAll("<"); + snippet = GT_ENTITY_PATTERN.matcher(snippet).replaceAll(">"); + snippet = QUOTE_ENTITY_PATTERN.matcher(snippet).replaceAll("'"); + snippet = QUOT_ENTITY_PATTERN.matcher(snippet).replaceAll("\""); + } else { + snippet = '(' + getString(R.string.msg_sbc_snippet_unavailable) + ')'; + valid = false; + } + return new SearchBookContentsResult(pageId, pageNumber, snippet, valid); + } catch (JSONException e) { + // Never seen in the wild, just being complete. + return new SearchBookContentsResult(getString(R.string.msg_sbc_no_page_returned), "", "", false); + } + } + + } }