Got amazon search working

pull/14/head
Tim Su 13 years ago
parent 6546170ba1
commit be714da510

@ -115,23 +115,27 @@ public class HttpRestClient implements RestClient {
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode >= HTTP_UNAVAILABLE_START && statusCode <= HTTP_UNAVAILABLE_END) {
throw new HttpUnavailableException();
} else if(statusCode != HTTP_OK) {
throw new HttpErrorException(response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase());
}
HttpEntity entity = response.getEntity();
String body = null;
if (entity != null) {
InputStream contentStream = entity.getContent();
try {
return convertStreamToString(contentStream);
body = convertStreamToString(contentStream);
} finally {
contentStream.close();
}
}
return null;
if(statusCode != HTTP_OK) {
System.out.println(body);
throw new HttpErrorException(response.getStatusLine().getStatusCode(),
response.getStatusLine().getReasonPhrase());
}
return body;
}
/**

@ -2,22 +2,18 @@
<!-- See the file "LICENSE" for the full license governing this code. -->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="100dip"
android:background="#9ee5ff"
android:paddingTop="5dip"
android:paddingBottom="5dip"
android:paddingLeft="5dip"
android:paddingRight="5dip"
android:layout_marginLeft="10dip"
android:layout_marginRight="10dip">
android:layout_height="wrap_content"
android:background="#9ee5ff">
<!-- title -->
<TextView android:id="@+id/title"
android:layout_width="fill_parent"
android:layout_height="70dip"
android:layout_marginLeft="5dip"
android:paddingRight="30dp"
android:lines="2"
android:textSize="24sp"
android:singleLine="false"
android:textSize="20sp"
android:textStyle="bold"
android:gravity="center_vertical" />
@ -25,10 +21,11 @@
<TextView android:id="@+id/url"
android:layout_width="fill_parent"
android:layout_height="30dip"
android:layout_below="@id/title"
android:layout_alignParentBottom="true"
android:layout_marginLeft="5dip"
android:paddingRight="30dp"
android:singleLine="true"
android:textSize="18sp"
android:textSize="16sp"
android:gravity="center_vertical" />
<!-- arrow -->
@ -41,6 +38,6 @@
android:layout_marginTop="15dip"
android:paddingLeft="7dip"
android:paddingRight="7dip"
android:gravity="center" />
android:scaleType="center" />
</RelativeLayout>

@ -4,7 +4,6 @@ import android.app.Activity;
import android.os.Bundle;
import android.view.ViewGroup.LayoutParams;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.service.AstridDependencyInjector;
@ -26,10 +25,8 @@ public class AdTestActivity extends Activity {
webServicesView.setLayoutParams(new FrameLayout.LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
webServicesView.setPadding(10, 10, 10, 10);
ScrollView scrollView = new ScrollView(this);
scrollView.addView(webServicesView);
setContentView(scrollView);
setContentView(webServicesView);
Task task = new Task();
task.setValue(Task.TITLE, "America (The Book)"); //$NON-NLS-1$

@ -0,0 +1,294 @@
/**********************************************************************************************
* Copyright 2009 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file
* except in compliance with the License. A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "LICENSE.txt" file accompanying this file. This file 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.
*
* ********************************************************************************************
*
* Amazon Product Advertising API
* Signed Requests Sample Code
*
* API Version: 2009-03-31
*
*/
package com.todoroo.astrid.helper;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TimeZone;
import java.util.TreeMap;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
/**
* This class contains all the logic for signing requests
* to the Amazon Product Advertising API.
*/
@SuppressWarnings("nls")
public class AmazonRequestsHelper {
/**
* All strings are handled as UTF-8
*/
private static final String UTF8_CHARSET = "UTF-8";
/**
* The HMAC algorithm required by Amazon
*/
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
/**
* This is the URI for the service, don't change unless you really know
* what you're doing.
*/
private static final String REQUEST_URI = "/onca/xml";
/**
* The sample uses HTTP GET to fetch the response. If you changed the sample
* to use HTTP POST instead, change the value below to POST.
*/
private static final String REQUEST_METHOD = "GET";
private String endpoint = null;
private String awsAccessKeyId = null;
private String awsSecretKey = null;
private SecretKeySpec secretKeySpec = null;
private Mac mac = null;
/**
* You must provide the three values below to initialize the helper.
*
* @param endpoint Destination for the requests.
* @param awsAccessKeyId Your AWS Access Key ID
* @param awsSecretKey Your AWS Secret Key
*/
public static AmazonRequestsHelper getInstance(
String endpoint,
String awsAccessKeyId,
String awsSecretKey
) throws IllegalArgumentException, UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException
{
if (null == endpoint || endpoint.length() == 0)
{ throw new IllegalArgumentException("endpoint is null or empty"); }
if (null == awsAccessKeyId || awsAccessKeyId.length() == 0)
{ throw new IllegalArgumentException("awsAccessKeyId is null or empty"); }
if (null == awsSecretKey || awsSecretKey.length() == 0)
{ throw new IllegalArgumentException("awsSecretKey is null or empty"); }
AmazonRequestsHelper instance = new AmazonRequestsHelper();
instance.endpoint = endpoint.toLowerCase();
instance.awsAccessKeyId = awsAccessKeyId;
instance.awsSecretKey = awsSecretKey;
byte[] secretyKeyBytes = instance.awsSecretKey.getBytes(UTF8_CHARSET);
instance.secretKeySpec = new SecretKeySpec(secretyKeyBytes, HMAC_SHA256_ALGORITHM);
instance.mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
instance.mac.init(instance.secretKeySpec);
return instance;
}
/**
* The construct is private since we'd rather use getInstance()
*/
private AmazonRequestsHelper() {
//
}
/**
* This method signs requests in hashmap form. It returns a URL that should
* be used to fetch the response. The URL returned should not be modified in
* any way, doing so will invalidate the signature and Amazon will reject
* the request.
*/
public String sign(Map<String, String> params) {
// Let's add the AWSAccessKeyId and Timestamp parameters to the request.
params.put("AWSAccessKeyId", this.awsAccessKeyId);
params.put("Timestamp", this.timestamp());
// The parameters need to be processed in lexicographical order, so we'll
// use a TreeMap implementation for that.
SortedMap<String, String> sortedParamMap = new TreeMap<String, String>(params);
// get the canonical form the query string
String canonicalQS = this.canonicalize(sortedParamMap);
// create the string upon which the signature is calculated
String toSign =
REQUEST_METHOD + "\n"
+ this.endpoint + "\n"
+ REQUEST_URI + "\n"
+ canonicalQS;
// get the signature
String hmac = this.hmac(toSign);
String sig = this.percentEncodeRfc3986(hmac);
// construct the URL
String url =
"http://" + this.endpoint + REQUEST_URI + "?" + canonicalQS + "&Signature=" + sig;
return url;
}
/**
* This method signs requests in query-string form. It returns a URL that
* should be used to fetch the response. The URL returned should not be
* modified in any way, doing so will invalidate the signature and Amazon
* will reject the request.
*/
public String sign(String queryString) {
// let's break the query string into it's constituent name-value pairs
Map<String, String> params = this.createParameterMap(queryString);
// then we can sign the request as before
return this.sign(params);
}
/**
* Compute the HMAC.
*
* @param stringToSign String to compute the HMAC over.
* @return base64-encoded hmac value.
*/
private String hmac(String stringToSign) {
String signature = null;
byte[] data;
byte[] rawHmac;
try {
data = stringToSign.getBytes(UTF8_CHARSET);
rawHmac = mac.doFinal(data);
Base64 encoder = new Base64();
signature = new String(encoder.encode(rawHmac));
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(UTF8_CHARSET + " is unsupported!", e);
}
return signature;
}
/**
* Generate a ISO-8601 format timestamp as required by Amazon.
*
* @return ISO-8601 format timestamp.
*/
private String timestamp() {
String timestamp = null;
Calendar cal = Calendar.getInstance();
DateFormat dfm = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
dfm.setTimeZone(TimeZone.getTimeZone("GMT"));
timestamp = dfm.format(cal.getTime());
return timestamp;
}
/**
* Canonicalize the query string as required by Amazon.
*
* @param sortedParamMap Parameter name-value pairs in lexicographical order.
* @return Canonical form of query string.
*/
private String canonicalize(SortedMap<String, String> sortedParamMap) {
if (sortedParamMap.isEmpty()) {
return "";
}
StringBuffer buffer = new StringBuffer();
Iterator<Map.Entry<String, String>> iter = sortedParamMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, String> kvpair = iter.next();
buffer.append(percentEncodeRfc3986(kvpair.getKey()));
buffer.append("=");
buffer.append(percentEncodeRfc3986(kvpair.getValue()));
if (iter.hasNext()) {
buffer.append("&");
}
}
String cannoical = buffer.toString();
return cannoical;
}
/**
* Percent-encode values according the RFC 3986. The built-in Java
* URLEncoder does not encode according to the RFC, so we make the
* extra replacements.
*
* @param s decoded string
* @return encoded string per RFC 3986
*/
private String percentEncodeRfc3986(String s) {
String out;
try {
out = URLEncoder.encode(s, UTF8_CHARSET)
.replace("+", "%20")
.replace("*", "%2A")
.replace("%7E", "~");
} catch (UnsupportedEncodingException e) {
out = s;
}
return out;
}
/**
* Takes a query string, separates the constituent name-value pairs
* and stores them in a hashmap.
*
* @param queryString
* @return
*/
private Map<String, String> createParameterMap(String queryString) {
Map<String, String> map = new HashMap<String, String>();
String[] pairs = queryString.split("&");
for (String pair: pairs) {
if (pair.length() < 1) {
continue;
}
String[] tokens = pair.split("=",2);
for(int j=0; j<tokens.length; j++)
{
try {
tokens[j] = URLDecoder.decode(tokens[j], UTF8_CHARSET);
} catch (UnsupportedEncodingException e) {
// unpossible
}
}
switch (tokens.length) {
case 1: {
if (pair.charAt(0) == '=') {
map.put("", tokens[0]);
} else {
map.put(tokens[0], "");
}
break;
}
case 2: {
map.put(tokens[0], tokens[1]);
break;
}
}
}
return map;
}
}

@ -1,23 +1,32 @@
package com.todoroo.astrid.ui;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import greendroid.widget.AsyncImageView;
import java.io.StringReader;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserFactory;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
@ -26,12 +35,20 @@ import com.timsu.astrid.R;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.service.DependencyInjectionService;
import com.todoroo.andlib.service.ExceptionService;
import com.todoroo.andlib.service.HttpErrorException;
import com.todoroo.andlib.service.RestClient;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.helper.AmazonRequestsHelper;
import com.todoroo.astrid.producteev.api.StringEscapeUtils;
import com.todoroo.astrid.utility.Constants;
@SuppressWarnings("nls")
public class WebServicesView extends LinearLayout {
private static final String ASSOCIATE_TAG = "wwwtodoroocom-20";
private static final int ROW_HEIGHT = 100;
private static final String GOOGLE_SEARCH_URL = "https://ajax.googleapis.com/ajax/services/search/web?v=1.0&q=";
private Task task;
@ -83,14 +100,118 @@ public class WebServicesView extends LinearLayout {
final LinearLayout body = addHorizontalScroller();
for(int i = 0; i < 10; i++) {
ImageView aiv = new ImageView(getContext());
aiv.setImageResource(R.drawable.icon);
aiv.setLayoutParams(new LinearLayout.LayoutParams(200, 200));
body.addView(aiv);
new Thread() {
@Override
public void run() {
try {
AmazonRequestsHelper helper = AmazonRequestsHelper.getInstance(
Constants.AWS_ENDPOINT, Constants.AWS_ACCESS_KEY_ID,
Constants.AWS_SECRET_KEY_ID);
Map<String, String> params = new HashMap<String, String>();
params.put("Service", "AWSECommerceService");
params.put("Version", "2011-08-01");
params.put("Operation", "ItemSearch");
params.put("Availability", "Available");
params.put("ResponseGroup", "Images");
params.put("Keywords",
URLEncoder.encode(task.getValue(Task.TITLE), "UTF-8"));
params.put("SearchIndex", "All");
params.put("AssociateTag", ASSOCIATE_TAG);
String requestUrl = helper.sign(params);
String result = restClient.get(requestUrl);
activity.runOnUiThread(new AmazonSearchResultsProcessor(body,
result));
} catch (Exception e) {
displayError(e, body);
}
}
}.start();
}
private class AmazonSearchResultsProcessor implements Runnable {
private final LinearLayout body;
private final String searchResults;
private final MarginLayoutParams params;
public AmazonSearchResultsProcessor(LinearLayout body,
String searchResults) {
this.body = body;
this.searchResults = searchResults;
params = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.FILL_PARENT);
params.rightMargin = Math.round(15 * metrics.density);
}
@Override
public void run() {
try {
body.removeAllViews();
XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(new StringReader(searchResults));
int eventType = xpp.getEventType();
String asin = null, image = null, totalResults = null;
while (eventType != XmlPullParser.END_DOCUMENT) {
if(eventType == XmlPullParser.START_TAG) {
if("ASIN".equals(xpp.getName()))
asin = xpp.nextText();
else if("MediumImage".equals(xpp.getName())) {
xpp.next();
image = xpp.nextText();
} else if("Error".equals(xpp.getName())) {
while(!"Message".equals(xpp.getName()))
xpp.next();
throw new HttpErrorException(0, xpp.getText());
} else if("TotalResults".equals(xpp.getText()))
totalResults = xpp.nextText();
} else if(eventType == XmlPullParser.END_TAG) {
if("Item".equals(xpp.getName()))
renderItem(asin, image);
}
eventType = xpp.next();
}
if(totalResults != null) {
String moreLabel = String.format("Show all %s results",
totalResults);
String url = String.format("http://www.amazon.com/s/?field-keywords=%s&tag=%s",
URLEncoder.encode(task.getValue(Task.TITLE), "UTF-8"), ASSOCIATE_TAG);
View view = inflateTextRow(body, moreLabel, "", url);
view.setLayoutParams(params);
view.setBackgroundColor(Color.rgb(200, 200, 200));
}
} catch (Exception e) {
displayError(e, body);
}
}
private void renderItem(String asin, String image) {
AsyncImageView imageView = new AsyncImageView(activity);
imageView.setDefaultImageResource(R.drawable.ic_contact_picture_2);
imageView.setUrl(image);
imageView.setLayoutParams(params);
imageView.setScaleType(ScaleType.FIT_CENTER);
imageView.setTag(String.format("http://www.amazon.com/dp/%s/?tag=%s", asin, ASSOCIATE_TAG));
imageView.setOnClickListener(linkClickListener);
body.addView(imageView);
}
}
/**
* Initialize Google search results
*/
@ -99,88 +220,106 @@ public class WebServicesView extends LinearLayout {
final LinearLayout body = addHorizontalScroller();
ProgressBar progressBar = new ProgressBar(getContext());
progressBar.setIndeterminate(true);
progressBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT));
body.addView(progressBar);
new Thread() {
@Override
public void run() {
Exception exception = null;
JSONObject searchResults = null;
try {
String url = GOOGLE_SEARCH_URL +
URLEncoder.encode(task.getValue(Task.TITLE), "UTF-8");
String result = restClient.get(url);
searchResults = new JSONObject(result);
} catch (UnsupportedEncodingException e) {
exception = e;
} catch (IOException e) {
exception = e;
} catch (JSONException e) {
exception = e;
}
final JSONObject searchResults = new JSONObject(result);
final Exception finalException = exception;
final JSONObject finalResults = searchResults;
activity.runOnUiThread(new Runnable() {
public void run() {
body.removeAllViews();
if(finalException != null)
displayError(finalException, body);
else {
try {
processGoogleSearchResults(body,
finalResults.getJSONObject("responseData"));
} catch (JSONException e) {
displayError(e, body);
}
}
}
});
activity.runOnUiThread(new GoogleSearchResultsProcessor(body,
searchResults.getJSONObject("responseData")));
} catch (Exception e) {
displayError(e, body);
}
}
}.start();
}
protected void processGoogleSearchResults(LinearLayout body,
JSONObject searchResults) throws JSONException {
private class GoogleSearchResultsProcessor implements Runnable {
JSONArray results = searchResults.getJSONArray("results");
private final LinearLayout body;
private final JSONObject searchResults;
for(int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i);
View view = inflater.inflate(R.layout.web_service_text_row, body, false);
((TextView)view.findViewById(R.id.title)).setText(result.getString("titleNoFormatting"));
((TextView)view.findViewById(R.id.url)).setText(result.getString("visibleUrl"));
body.addView(view);
String url = result.getString("url");
view.setTag(url);
public GoogleSearchResultsProcessor(LinearLayout body,
JSONObject searchResults) {
this.body = body;
this.searchResults = searchResults;
}
JSONObject cursor = searchResults.getJSONObject("cursor");
String moreLabel = String.format("Show all %s results",
cursor.getString("estimatedResultCount"));
String url = cursor.getString("moreResultsUrl");
public void run() {
body.removeAllViews();
try {
JSONArray results = searchResults.getJSONArray("results");
LayoutParams params = new LinearLayout.LayoutParams(
Math.round(metrics.widthPixels * 0.9f),
Math.round(ROW_HEIGHT * metrics.density));
params.setMargins(10, 0, 10, 0);
for(int i = 0; i < results.length(); i++) {
JSONObject result = results.getJSONObject(i);
String title = StringEscapeUtils.unescapeHtml(result.getString("titleNoFormatting"));
View view = inflateTextRow(body, title,
result.getString("visibleUrl"), result.getString("url"));
view.setLayoutParams(params);
}
JSONObject cursor = searchResults.getJSONObject("cursor");
String moreLabel = String.format("Show all %s results",
cursor.getString("estimatedResultCount"));
String url = cursor.getString("moreResultsUrl");
View view = inflateTextRow(body, moreLabel, "", url);
view.setLayoutParams(params);
view.setBackgroundColor(Color.rgb(200, 200, 200));
} catch (JSONException e) {
displayError(e, body);
}
}
}
protected View inflateTextRow(ViewGroup body, String title, String subtitle,
String tag) {
View view = inflater.inflate(R.layout.web_service_text_row, body, false);
((TextView)view.findViewById(R.id.title)).setText(moreLabel);
view.setBackgroundColor(Color.rgb(200, 200, 200));
view.setTag(url);
((TextView)view.findViewById(R.id.title)).setText(title);
((TextView)view.findViewById(R.id.url)).setText(subtitle);
view.setOnClickListener(linkClickListener);
view.setTag(tag);
body.addView(view);
return view;
}
protected void displayError(Exception exception, LinearLayout body) {
public OnClickListener linkClickListener = new OnClickListener() {
@Override
public void onClick(View v) {
if(v.getTag() instanceof String) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse((String) v.getTag()));
activity.startActivity(intent);
}
}
};
protected void displayError(final Exception exception, final LinearLayout body) {
exceptionService.reportError("google-error", exception);
TextView textView = new TextView(getContext());
textView.setTextAppearance(getContext(), R.style.TextAppearance_Medium);
textView.setText(exception.toString());
body.addView(textView);
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
body.removeAllViews();
TextView textView = new TextView(getContext());
textView.setTextAppearance(getContext(), R.style.TextAppearance_Medium);
textView.setText(exception.getClass().getSimpleName() + ": " +
exception.getLocalizedMessage());
body.addView(textView);
}
});
}
protected LinearLayout addHorizontalScroller() {
@ -191,16 +330,24 @@ public class WebServicesView extends LinearLayout {
LinearLayout body = new LinearLayout(getContext());
body.setLayoutParams(new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT,
Math.round(100 * metrics.density)));
Math.round(ROW_HEIGHT * metrics.density)));
scroll.addView(body);
ProgressBar progressBar = new ProgressBar(getContext());
progressBar.setIndeterminate(true);
LayoutParams layoutParams = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT,
LayoutParams.FILL_PARENT);
layoutParams.gravity = Gravity.CENTER;
progressBar.setLayoutParams(layoutParams);
body.addView(progressBar);
return body;
}
private void addSectionDivider() {
View view = new View(getContext());
MarginLayoutParams mlp = new MarginLayoutParams(LayoutParams.FILL_PARENT, 1);
mlp.setMargins(10, 5, 10, 5);
mlp.setMargins(10, 20, 10, 20);
view.setLayoutParams(mlp);
view.setBackgroundResource(R.drawable.black_white_gradient);
addView(view);

Loading…
Cancel
Save