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 @@ @@ -17,12 +17,6 @@
*/
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.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
@ -31,110 +25,69 @@ import org.json.JSONException; @@ -31,110 +25,69 @@ import org.json.JSONException;
import org.json.JSONObject;
import org.transdroid.daemon.util.HttpHelper;
import java.io.InputStream;
import java.util.Locale;
public class GoogleWebSearchBarcodeResolver {
public static final String apiUrl = "http://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=%s";
public static String resolveBarcode(String barcode) {
try {
// We use the Google AJAX Search API to get a JSON-formatted list of web search results
String callUrl = apiUrl.replace("%s", barcode);
DefaultHttpClient httpclient = new DefaultHttpClient();
HttpGet httpget = new HttpGet(callUrl);
HttpResponse response = httpclient.execute(httpget);
InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream);
InputStream instream = response.getEntity().getContent();
String result = HttpHelper.convertStreamToString(instream);
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) {
return null;
}
return stripGarbage(results, barcode);
return stripGarbage(results.getJSONObject(0), barcode);
} catch (Exception e) {
return null;
}
}
private static String stripGarbage(JSONArray results, 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);
}
private static String stripGarbage(JSONObject item, String barcode) throws JSONException {
// Only retain the words that are missing in at most one of the search result titles
List<String> allWords = new ArrayList<String>();
for (String title : titles) {
for (String word : Arrays.asList(title.split(" "))) {
if (!allWords.contains(word)) {
allWords.add(word);
}
}
String good = " abcdefghijklmnopqrstuvwxyz1234567890";
// Find the unformatted title
String title = item.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, "");
}
List<String> remainingWords = new ArrayList<String>();
int allowMissing = Math.min(MAX_MISSING, Math.max(titles.size() - MIN_TITLE_CONSIDER, 0));
for (String word : allWords) {
int missing = 0;
for (String title : titles) {
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);
// 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);
}
}
// Now the query is the concatenation of the words remaining; with spaces in between
String query = "";
for (String word : remainingWords) {
query += " " + word;
// Remove double spaces
while (result.contains(" ")) {
result = result.replace(" ", " ");
}
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; @@ -111,6 +111,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MenuItem.OnActionExpandListener;
@ -135,7 +136,8 @@ public class TorrentsActivity extends Activity implements OnNavigationListener, @@ -135,7 +136,8 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
RefreshableActivity {
private static final int RESULT_DETAILS = 0;
// Fragment uses this to pause the refresh across restarts
public boolean stopRefresh = false;
// Navigation components
@Bean
protected Log log;
@ -150,19 +152,12 @@ public class TorrentsActivity extends Activity implements OnNavigationListener, @@ -150,19 +152,12 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
protected ServerStatusView serverStatusView;
@SystemService
protected SearchManager searchManager;
private MenuItem searchMenu = null;
private PullToRefreshAttacher pullToRefreshAttacher = null;
// Settings
@Bean
protected ApplicationSettings applicationSettings;
@Bean
protected SystemSettings systemSettings;
@InstanceState
boolean firstStart = true;
int skipNextOnNavigationItemSelectedCalls = 2;
private IDaemonAdapter currentConnection = null;
@InstanceState
protected NavigationFilter currentFilter = null;
@InstanceState
protected String preselectNavigationFilter = null;
@ -170,17 +165,29 @@ public class TorrentsActivity extends Activity implements OnNavigationListener, @@ -170,17 +165,29 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
protected boolean turleModeEnabled = false;
@InstanceState
protected ArrayList<Label> lastNavigationLabels;
// Contained torrent and details fragments
@FragmentById(resName = "torrents_fragment")
protected TorrentsFragment fragmentTorrents;
@FragmentById(resName = "torrentdetails_fragment")
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
private AsyncTask<Void, Void, Void> autoRefreshTask;
// Fragment uses this to pause the refresh across restarts
public boolean stopRefresh = 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);
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
@ -466,17 +473,6 @@ public class TorrentsActivity extends Activity implements OnNavigationListener, @@ -466,17 +473,6 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
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
* @param item The touched filter item
@ -719,11 +715,20 @@ public class TorrentsActivity extends Activity implements OnNavigationListener, @@ -719,11 +715,20 @@ public class TorrentsActivity extends Activity implements OnNavigationListener,
@OnActivityResult(BarcodeHelper.ACTIVITY_BARCODE_ADDTORRENT)
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
String query = BarcodeHelper.handleScanResult(resultCode, data);
if (query.startsWith("http") || query.startsWith("https"))
addTorrentByUrl(query, "QR code result"); // No torrent title known
else
startSearch(query, false, null, false);
String query = BarcodeHelper.handleScanResult(resultCode, data, navigationHelper.enableSearchUi());
onBarcodeScanHandled(data.getStringExtra("SCAN_RESULT"), query);
}
@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 { @@ -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
* #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}
* scans.
* android.content.Intent, boolean)} can be called to parse the result into a search query, in case of {@link
* #ACTIVITY_BARCODE_ADDTORRENT} scans.
* @param activity The calling activity, to which the result is returned or a dialog is bound that asks to install
* the bar code scanner
* @param requestCode {@link #ACTIVITY_BARCODE_ADDTORRENT} or {@link #ACTIVITY_BARCODE_QRSETTINGS
@ -100,17 +100,19 @@ public class BarcodeHelper { @@ -100,17 +100,19 @@ public class BarcodeHelper {
* 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 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
* 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 formatName = data.getStringExtra("SCAN_RESULT_FORMAT");
if (formatName != null && formatName.equals("QR_CODE")) {
// Scanned barcode was a QR code: return the contents directly
return contents;
} else {
if (TextUtils.isEmpty(contents)) {
if (TextUtils.isEmpty(contents) || !supportsSearch) {
return null;
}
// Get a meaningful search query based on a Google Search product lookup

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

@ -436,6 +436,7 @@ @@ -436,6 +436,7 @@
<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_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_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>

Loading…
Cancel
Save