Browse Source

Updated barcode scanner feature to better handle EAN code conversion to search query (might need to change from the Google Search AJAX provider in the future).

pull/187/head
Eric Kok 10 years ago
parent
commit
664ef994b3
  1. 125
      app/src/main/java/org/transdroid/core/app/search/GoogleWebSearchBarcodeResolver.java
  2. 61
      app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java
  3. 10
      app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java
  4. 1
      app/src/main/res/values/strings.xml

125
app/src/main/java/org/transdroid/core/app/search/GoogleWebSearchBarcodeResolver.java

@ -17,12 +17,6 @@
*/ */
package org.transdroid.core.app.search; package org.transdroid.core.app.search;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.DefaultHttpClient;
@ -31,110 +25,69 @@ import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import org.transdroid.daemon.util.HttpHelper; import org.transdroid.daemon.util.HttpHelper;
import java.io.InputStream;
import java.util.Locale;
public class GoogleWebSearchBarcodeResolver { public class GoogleWebSearchBarcodeResolver {
public static final String apiUrl = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s"; public static final String apiUrl = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s";
public static String resolveBarcode(String barcode) { public static String resolveBarcode(String barcode) {
try { try {
// We use the Google AJAX Search API to get a JSON-formatted list of web search results // We use the Google AJAX Search API to get a JSON-formatted list of web search results
String callUrl = apiUrl.replace("%s", barcode); String callUrl = apiUrl.replace("%s", barcode);
DefaultHttpClient httpclient = new DefaultHttpClient(); DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(callUrl); HttpGet httpget = new HttpGet(callUrl);
HttpResponse response = httpclient.execute(httpget); HttpResponse response = httpclient.execute(httpget);
InputStream instream = response.getEntity().getContent(); InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream); String result = HttpHelper.convertStreamToString(instream);
JSONArray results = new JSONObject(result).getJSONObject("responseData").getJSONArray("results"); JSONArray results = new JSONObject(result).getJSONObject("responseData").getJSONArray("results");
// We will combine and filter multiple results, if there are any // Use the first result, if any, after cleaning it from special characters
if (results.length() < 1) { if (results.length() < 1) {
return null; return null;
} }
return stripGarbage(results, barcode); return stripGarbage(results.getJSONObject(0), barcode);
} catch (Exception e) { } catch (Exception e) {
return null; return null;
} }
} }
private static String stripGarbage(JSONArray results, String barcode) throws JSONException { private static String stripGarbage(JSONObject item, String barcode) throws JSONException {
String good = " abcdefghijklmnopqrstuvwxyz";
final int MAX_TITLE_CONSIDER = 4;
final int MAX_MISSING = 1;
final int MIN_TITLE_CONSIDER = 2;
// First gather the titles for the first MAX_TITLE_CONSIDER results
List<String> titles = new ArrayList<String>();
for (int i = 0; i < results.length() && i < MAX_TITLE_CONSIDER; i++) {
String title = results.getJSONObject(i).getString("titleNoFormatting");
// Make string lowercase first
title = title.toLowerCase(Locale.US);
// Remove the barcode number if it's there
title = title.replace(barcode, "");
// Remove unwanted words and HTML special chars
for (String rem : new String[] { "dvd", "blu-ray", "bluray", "&amp;", "&quot;", "&apos;", "&lt;", "&gt;" }) {
title = title.replace(rem, "");
}
// Remove all non-alphanumeric (and space) characters
String result = "";
for ( int j = 0; j < title.length(); j++ ) {
if ( good.indexOf(title.charAt(j)) >= 0 )
result += title.charAt(j);
}
// Remove double spaces
while (result.contains(" ")) {
result = result.replace(" ", " ");
}
titles.add(result);
}
// Only retain the words that are missing in at most one of the search result titles String good = " abcdefghijklmnopqrstuvwxyz1234567890";
List<String> allWords = new ArrayList<String>();
for (String title : titles) { // Find the unformatted title
for (String word : Arrays.asList(title.split(" "))) { String title = item.getString("titleNoFormatting");
if (!allWords.contains(word)) {
allWords.add(word); // Make string lowercase first
} title = title.toLowerCase(Locale.US);
}
// Remove the barcode number if it's there
title = title.replace(barcode, "");
// Remove unwanted words and HTML special chars
for (String rem : new String[]{"dvd", "blu-ray", "bluray", "&amp;", "&quot;", "&apos;", "&lt;", "&gt;"}) {
title = title.replace(rem, "");
} }
List<String> remainingWords = new ArrayList<String>();
int allowMissing = Math.min(MAX_MISSING, Math.max(titles.size() - MIN_TITLE_CONSIDER, 0)); // Remove all non-alphanumeric (and space) characters
for (String word : allWords) { String result = "";
for (int j = 0; j < title.length(); j++) {
int missing = 0; if (good.indexOf(title.charAt(j)) >= 0) {
for (String title : titles) { result += title.charAt(j);
if (!title.contains(word)) {
// The word is not contained in this result title
missing++;
if (missing > allowMissing) {
// Already misssing more than once, no need to look further
break;
}
}
}
if (missing <= allowMissing) {
// The word was only missing at most once, so we keep it
remainingWords.add(word);
} }
} }
// Now the query is the concatenation of the words remaining; with spaces in between // Remove double spaces
String query = ""; while (result.contains(" ")) {
for (String word : remainingWords) { result = result.replace(" ", " ");
query += " " + word;
} }
return query.length() > 0? query.substring(1): null;
return result;
} }
} }

61
app/src/main/java/org/transdroid/core/gui/TorrentsActivity.java

@ -111,6 +111,7 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MenuItem.OnActionExpandListener; import android.view.MenuItem.OnActionExpandListener;
@ -135,7 +136,8 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
RefreshableActivity { RefreshableActivity {
private static final int RESULT_DETAILS = 0; private static final int RESULT_DETAILS = 0;
// Fragment uses this to pause the refresh across restarts
public boolean stopRefresh = false;
// Navigation components // Navigation components
@Bean @Bean
protected Log log; protected Log log;
@ -150,19 +152,12 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
protected ServerStatusView serverStatusView; protected ServerStatusView serverStatusView;
@SystemService @SystemService
protected SearchManager searchManager; protected SearchManager searchManager;
private MenuItem searchMenu = null;
private PullToRefreshAttacher pullToRefreshAttacher = null;
// Settings // Settings
@Bean @Bean
protected ApplicationSettings applicationSettings; protected ApplicationSettings applicationSettings;
@Bean @Bean
protected SystemSettings systemSettings; protected SystemSettings systemSettings;
@InstanceState @InstanceState
boolean firstStart = true;
int skipNextOnNavigationItemSelectedCalls = 2;
private IDaemonAdapter currentConnection = null;
@InstanceState
protected NavigationFilter currentFilter = null; protected NavigationFilter currentFilter = null;
@InstanceState @InstanceState
protected String preselectNavigationFilter = null; protected String preselectNavigationFilter = null;
@ -170,17 +165,29 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
protected boolean turleModeEnabled = false; protected boolean turleModeEnabled = false;
@InstanceState @InstanceState
protected ArrayList<Label> lastNavigationLabels; protected ArrayList<Label> lastNavigationLabels;
// Contained torrent and details fragments // Contained torrent and details fragments
@FragmentById(resName = "torrents_fragment") @FragmentById(resName = "torrents_fragment")
protected TorrentsFragment fragmentTorrents; protected TorrentsFragment fragmentTorrents;
@FragmentById(resName = "torrentdetails_fragment") @FragmentById(resName = "torrentdetails_fragment")
protected DetailsFragment fragmentDetails; protected DetailsFragment fragmentDetails;
@InstanceState
boolean firstStart = true;
int skipNextOnNavigationItemSelectedCalls = 2;
private MenuItem searchMenu = null;
private PullToRefreshAttacher pullToRefreshAttacher = null;
private IDaemonAdapter currentConnection = null;
// Auto refresh task // Auto refresh task
private AsyncTask<Void, Void, Void> autoRefreshTask; private AsyncTask<Void, Void, Void> autoRefreshTask;
// Fragment uses this to pause the refresh across restarts // Handles item selections on the dedicated list of filter items
public boolean stopRefresh = false; private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
filtersList.setItemChecked(position, true);
Object item = filtersList.getAdapter().getItem(position);
if (item instanceof SimpleListItem)
filterSelected((SimpleListItem) item, false);
}
};
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
@ -466,17 +473,6 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
return false; return false;
} }
// Handles item selections on the dedicated list of filter items
private OnItemClickListener onFilterListItemClicked = new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
filtersList.setItemChecked(position, true);
Object item = filtersList.getAdapter().getItem(position);
if (item instanceof SimpleListItem)
filterSelected((SimpleListItem) item, false);
}
};
/** /**
* A new filter was selected; update the view over the current data * A new filter was selected; update the view over the current data
* @param item The touched filter item * @param item The touched filter item
@ -719,11 +715,20 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT) @OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT)
public void onBarcodeScanned(int resultCode, Intent data) { public void onBarcodeScanned(int resultCode, Intent data) {
// We receive from the helper either a URL (as string) or a query we can start a search for // We receive from the helper either a URL (as string) or a query we can start a search for
String query = BarcodeHelper.handleScanResult(resultCode, data); String query = BarcodeHelper.handleScanResult(resultCode, data, navigationHelper.enableSearchUi());
if (query.startsWith("http") || query.startsWith("https")) onBarcodeScanHandled(data.getStringExtra("SCAN_RESULT"), query);
addTorrentByUrl(query, "QR code result"); // No torrent title known }
else
startSearch(query, false, null, false); @UiThread
protected void onBarcodeScanHandled(String barcode, String result) {
log.d(this, "Scanned barcode " + barcode + " and got " + result);
if (TextUtils.isEmpty(result)) {
Crouton.showText(this, R.string.error_noproductforcode, NavigationHelper.CROUTON_ERROR_STYLE);
} else if (result.startsWith("http") || result.startsWith("https")) {
addTorrentByUrl(result, "QR code result"); // No torrent title known
} else if (navigationHelper.enableSearchUi()) {
startSearch(result, false, null, false);
}
} }
/** /**

10
app/src/main/java/org/transdroid/core/gui/search/BarcodeHelper.java

@ -40,8 +40,8 @@ public class BarcodeHelper {
/** /**
* Call this to start a bar code scanner intent. The calling activity will receive an Intent result with ID {@link * Call this to start a bar code scanner intent. The calling activity will receive an Intent result with ID {@link
* #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there {@link #handleScanResult(int, * #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS}. From there {@link #handleScanResult(int,
* Intent)} can be called to parse the result into a search query, in case of {@link #ACTIVITY_BARCODE_ADDTORRENT} * android.content.Intent, boolean)} can be called to parse the result into a search query, in case of {@link
* scans. * #ACTIVITY_BARCODE_ADDTORRENT} scans.
* @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install * @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
* the bar code scanner * the bar code scanner
* @param requestCode {@link #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS * @param requestCode {@link #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS
@ -100,17 +100,19 @@ public class BarcodeHelper {
* and return a query search query appropriate to the bar code. * and return a query search query appropriate to the bar code.
* @param resultCode The raw result code as returned by the bar code scanner * @param resultCode The raw result code as returned by the bar code scanner
* @param data The raw data as returned from the bar code scanner * @param data The raw data as returned from the bar code scanner
* @param supportsSearch Whether the application has the search UI enabled, such that it can use the scanned barcode
* to find torrents
* @return A String that can be used as new search query, or null if the bar code could not be scanned or no query * @return A String that can be used as new search query, or null if the bar code could not be scanned or no query
* can be constructed for it * can be constructed for it
*/ */
public static String handleScanResult(int resultCode, Intent data) { public static String handleScanResult(int resultCode, Intent data, boolean supportsSearch) {
String contents = data.getStringExtra("SCAN_RESULT"); String contents = data.getStringExtra("SCAN_RESULT");
String formatName = data.getStringExtra("SCAN_RESULT_FORMAT"); String formatName = data.getStringExtra("SCAN_RESULT_FORMAT");
if (formatName != null && formatName.equals("QR_CODE")) { if (formatName != null && formatName.equals("QR_CODE")) {
// Scanned barcode was a QR code: return the contents directly // Scanned barcode was a QR code: return the contents directly
return contents; return contents;
} else { } else {
if (TextUtils.isEmpty(contents)) { if (TextUtils.isEmpty(contents) || !supportsSearch) {
return null; return null;
} }
// Get a meaningful search query based on a Google Search product lookup // Get a meaningful search query based on a Google Search product lookup

1
app/src/main/res/values/strings.xml

@ -436,6 +436,7 @@
<string name="error_invalid_directory">Directory paths end with a / or \</string> <string name="error_invalid_directory">Directory paths end with a / or \</string>
<string name="error_invalid_timeout">Timeout can not be empty and is a positive number</string> <string name="error_invalid_timeout">Timeout can not be empty and is a positive number</string>
<string name="error_notorrentfile">The search result does not link to a .torrent file</string> <string name="error_notorrentfile">The search result does not link to a .torrent file</string>
<string name="error_noproductforcode">Can\'t find torrent link or product information for this barcode</string>
<string name="error_no_url_enclosure">The RSS feed item didn\'t provide an URL enclosure or link tag pointing to the .torrent file</string> <string name="error_no_url_enclosure">The RSS feed item didn\'t provide an URL enclosure or link tag pointing to the .torrent file</string>
<string name="error_no_link">The RSS feed item does not provide a link to browse to</string> <string name="error_no_link">The RSS feed item does not provide a link to browse to</string>
<string name="error_norssfeed">URL is not a (valid) RSS feed</string> <string name="error_norssfeed">URL is not a (valid) RSS feed</string>

Loading…
Cancel
Save