2 * Copyright (C) 2008 ZXing authors
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package cz.cvut.fel.dce.barcodescanner.book;
19 import android.app.Activity;
20 import android.content.Intent;
21 import android.os.AsyncTask;
22 import android.os.Bundle;
23 import android.util.Log;
24 import android.view.KeyEvent;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.webkit.CookieManager;
28 import android.webkit.CookieSyncManager;
29 import android.widget.EditText;
30 import android.widget.ListView;
31 import android.widget.TextView;
33 import cz.cvut.fel.dce.barcodescanner.HttpHelper;
34 import cz.cvut.fel.dce.barcodescanner.Intents;
35 import cz.cvut.fel.dce.barcodescanner.LocaleManager;
36 import cz.cvut.fel.dce.barcodescanner.R;
38 import org.json.JSONArray;
39 import org.json.JSONException;
40 import org.json.JSONObject;
42 import java.io.IOException;
43 import java.util.ArrayList;
44 import java.util.List;
45 import java.util.regex.Pattern;
48 * Uses Google Book Search to find a word or phrase in the requested book.
50 * @author dswitkin@google.com (Daniel Switkin)
52 public final class SearchBookContentsActivity extends Activity {
54 private static final String TAG = SearchBookContentsActivity.class.getSimpleName();
56 private static final Pattern TAG_PATTERN = Pattern.compile("\\<.*?\\>");
57 private static final Pattern LT_ENTITY_PATTERN = Pattern.compile("<");
58 private static final Pattern GT_ENTITY_PATTERN = Pattern.compile(">");
59 private static final Pattern QUOTE_ENTITY_PATTERN = Pattern.compile("'");
60 private static final Pattern QUOT_ENTITY_PATTERN = Pattern.compile(""");
63 private EditText queryTextView;
64 private View queryButton;
65 private ListView resultListView;
66 private TextView headerView;
67 private AsyncTask<String,?,?> networkTask;
69 private final View.OnClickListener buttonListener = new View.OnClickListener() {
71 public void onClick(View view) {
76 private final View.OnKeyListener keyListener = new View.OnKeyListener() {
78 public boolean onKey(View view, int keyCode, KeyEvent event) {
79 if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_DOWN) {
92 public void onCreate(Bundle icicle) {
93 super.onCreate(icicle);
95 // Make sure that expired cookies are removed on launch.
96 CookieSyncManager.createInstance(this);
97 CookieManager.getInstance().removeExpiredCookie();
99 Intent intent = getIntent();
100 if (intent == null || !intent.getAction().equals(Intents.SearchBookContents.ACTION)) {
105 isbn = intent.getStringExtra(Intents.SearchBookContents.ISBN);
106 if (LocaleManager.isBookSearchUrl(isbn)) {
107 setTitle(getString(R.string.sbc_name));
109 setTitle(getString(R.string.sbc_name) + ": ISBN " + isbn);
112 setContentView(R.layout.search_book_contents);
113 queryTextView = (EditText) findViewById(R.id.query_text_view);
115 String initialQuery = intent.getStringExtra(Intents.SearchBookContents.QUERY);
116 if (initialQuery != null && !initialQuery.isEmpty()) {
117 // Populate the search box but don't trigger the search
118 queryTextView.setText(initialQuery);
120 queryTextView.setOnKeyListener(keyListener);
122 queryButton = findViewById(R.id.query_button);
123 queryButton.setOnClickListener(buttonListener);
125 resultListView = (ListView) findViewById(R.id.result_list_view);
126 LayoutInflater factory = LayoutInflater.from(this);
127 headerView = (TextView) factory.inflate(R.layout.search_book_contents_header,
128 resultListView, false);
129 resultListView.addHeaderView(headerView);
133 protected void onResume() {
135 queryTextView.selectAll();
139 protected void onPause() {
140 AsyncTask<?,?,?> oldTask = networkTask;
141 if (oldTask != null) {
142 oldTask.cancel(true);
148 private void launchSearch() {
149 String query = queryTextView.getText().toString();
150 if (query != null && !query.isEmpty()) {
151 AsyncTask<?,?,?> oldTask = networkTask;
152 if (oldTask != null) {
153 oldTask.cancel(true);
155 networkTask = new NetworkTask();
156 networkTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, query, isbn);
157 headerView.setText(R.string.msg_sbc_searching_book);
158 resultListView.setAdapter(null);
159 queryTextView.setEnabled(false);
160 queryButton.setEnabled(false);
164 private final class NetworkTask extends AsyncTask<String,Object,JSONObject> {
167 protected JSONObject doInBackground(String... args) {
169 // These return a JSON result which describes if and where the query was found. This API may
170 // break or disappear at any time in the future. Since this is an API call rather than a
171 // website, we don't use LocaleManager to change the TLD.
172 String theQuery = args[0];
173 String theIsbn = args[1];
175 if (LocaleManager.isBookSearchUrl(theIsbn)) {
176 int equals = theIsbn.indexOf('=');
177 String volumeId = theIsbn.substring(equals + 1);
178 uri = "http://www.google.com/books?id=" + volumeId + "&jscmd=SearchWithinVolume2&q=" + theQuery;
180 uri = "http://www.google.com/books?vid=isbn" + theIsbn + "&jscmd=SearchWithinVolume2&q=" + theQuery;
182 CharSequence content = HttpHelper.downloadViaHttp(uri, HttpHelper.ContentType.JSON);
183 return new JSONObject(content.toString());
184 } catch (IOException ioe) {
185 Log.w(TAG, "Error accessing book search", ioe);
187 } catch (JSONException je) {
188 Log.w(TAG, "Error accessing book search", je);
194 protected void onPostExecute(JSONObject result) {
195 if (result == null) {
196 headerView.setText(R.string.msg_sbc_failed);
198 handleSearchResults(result);
200 queryTextView.setEnabled(true);
201 queryTextView.selectAll();
202 queryButton.setEnabled(true);
205 // Currently there is no way to distinguish between a query which had no results and a book
206 // which is not searchable - both return zero results.
207 private void handleSearchResults(JSONObject json) {
209 int count = json.getInt("number_of_results");
210 headerView.setText(getString(R.string.msg_sbc_results) + " : " + count);
212 JSONArray results = json.getJSONArray("search_results");
213 SearchBookContentsResult.setQuery(queryTextView.getText().toString());
214 List<SearchBookContentsResult> items = new ArrayList<>(count);
215 for (int x = 0; x < count; x++) {
216 items.add(parseResult(results.getJSONObject(x)));
218 resultListView.setOnItemClickListener(new BrowseBookListener(SearchBookContentsActivity.this, items));
219 resultListView.setAdapter(new SearchBookContentsAdapter(SearchBookContentsActivity.this, items));
221 String searchable = json.optString("searchable");
222 if ("false".equals(searchable)) {
223 headerView.setText(R.string.msg_sbc_book_not_searchable);
225 resultListView.setAdapter(null);
227 } catch (JSONException e) {
228 Log.w(TAG, "Bad JSON from book search", e);
229 resultListView.setAdapter(null);
230 headerView.setText(R.string.msg_sbc_failed);
234 // Available fields: page_id, page_number, snippet_text
235 private SearchBookContentsResult parseResult(JSONObject json) {
241 pageId = json.getString("page_id");
242 pageNumber = json.optString("page_number");
243 snippet = json.optString("snippet_text");
244 } catch (JSONException e) {
246 // Never seen in the wild, just being complete.
247 return new SearchBookContentsResult(getString(R.string.msg_sbc_no_page_returned), "", "", false);
250 if (pageNumber == null || pageNumber.isEmpty()) {
251 // This can happen for text on the jacket, and possibly other reasons.
254 pageNumber = getString(R.string.msg_sbc_page) + ' ' + pageNumber;
257 boolean valid = snippet != null && !snippet.isEmpty();
259 // Remove all HTML tags and encoded characters.
260 snippet = TAG_PATTERN.matcher(snippet).replaceAll("");
261 snippet = LT_ENTITY_PATTERN.matcher(snippet).replaceAll("<");
262 snippet = GT_ENTITY_PATTERN.matcher(snippet).replaceAll(">");
263 snippet = QUOTE_ENTITY_PATTERN.matcher(snippet).replaceAll("'");
264 snippet = QUOT_ENTITY_PATTERN.matcher(snippet).replaceAll("\"");
266 snippet = '(' + getString(R.string.msg_sbc_snippet_unavailable) + ')';
269 return new SearchBookContentsResult(pageId, pageNumber, snippet, valid);