I am working on an android application that needs to print to a printer. I decided on using the Google Cloud Print, as it seemed easy to set up. Initially, I followed the steps found here for integrating into Android. This works, as in it will print to my desired printer. However, this process is a bit involved for the user. In my case, the process is as follows:
The user selects the print button that I have displayed next to some information.
A Dialog is shown with a preview of what will be printed. There is a button in the ActionBar that says "Print". This begins the process.
A new Activity is displayed showing a list of printers that are connected to that users Google Account. The user must select one.
A new page is shown giving a description of the print job.
The user has to select "Print" in the upper right hand corner.
The print job is started and the printer prints out the picture.
Unfortunately, my client does not want this process. They want the user to click "Print" in step two, and then have the picture printed (steps 1, 2 and 6). Thus, I cannot use Intent provided by Google, I must use the actual API. This requires me to get a Google Auth token, get the desired printer, and submit a print job that way. I do the following:
Use the Google Play Services to retrieve an OAuth token for the users Gmail account.
Get a list of printers using the /search API call.
Submit a print job using the /submit API call.
I have the first two finished. I am just having trouble with the actual printing of the picture. Instead of printing the picture, the byte data of the picture is being printed (Base64 encoded). Here is some code as to how I am sending up the request:
ContentResolver contentResolver = context.getContentResolver();
try {
InputStream is = contentResolver.openInputStream(uri);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int n = is.read(buffer);
while (n >= 0) {
baos.write(buffer, 0, n);
n = is.read(buffer);
}
is.close();
baos.flush();
content = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT);
} catch (FileNotFoundException e) {
Log.d(TAG, "File not found: " + uri.toString(), e);
} catch (IOException e) {
e.printStackTrace();
}
This code retrieves the picture (the variable "uri" is the URI of that file), and turns it into a Base64 encoded string. This is the same method used in the PrintDialogActivity that is provided on the Google Cloud Print page (linked to above). The following is how I send that up:
URL: http://www.google.com/cloudprint/submit?access_token=[AUTH_TOKEN_GOES_HERE]&cookies=false&printerid=[PRINTER_ID_HERE]
HTTP Method: POST
POST Parameters: [printerId=PRINTER_ID_HERE, title=TestPrint, contentType=image/jpeg, capabilities={"capabilities":[{}]}, content=[Base64 Encoded data string is placed here]]
As far as I can tell, this is how it is supposed to be. I am getting a response of {"success":true} when printing. But, as I said above, it prints out the actual Base64 data string. Any help would be appreciated.
EDIT: Using what powerje said below, I managed to fix this. Rather than using the code above, I used the following:
public void submitPrintJobWithFile(String printerId, String title, String token, String filePath, String contentType){
File file = new File(filePath);
// Method that gets the correct headers
List<Header> headers = getHeaders(contentType, token);
// Method that gets the correct post parameters
String url = CLOUDPRINT_URL + PATH_SUBMIT;
List<NameValuePair> postParams = getParams(title, contentType);
String params = "access_token=" + token + "&cookies=false" + "&printerid=" + printerId;
url += params;
response = sendMultipartData(url, file, postParams, headers);
}
private String sendMultipartData(String url, File file, List<NameValuePair> fields, List<Header> headers){
HttpPost post = new HttpPost(url);
MultipartEntity entity = new MultipartEntity();
for(NameValuePair pair : fields){
String name = pair.getName();
String value = pair.getValue();
try{
entity.addPart(name, new StringBody(value));
}catch (UnsupportedEncodingException e){
Log.d(TAG, "Error turning pair (name=" + name + ", value=" + value + ") into StringBody.");
}
entity.addPart("content", new FileBody(file));
post.setEntity(entity);
// Finish HttpClient request here...
}
It looks like you need to use multipart encoding, example here:
http://blog.tacticalnuclearstrike.com/2010/01/using-multipartentity-in-android-applications/
FTA:
The files needed are apache-mime4j, httpclient, httpcore and httpmime. All are opensource projects built by the Apache foundation.
Download the 4 files and add them to your project then you should be able to use the following code to post strings and files to pages.
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost("http://www.tumblr.com/api/write");
try {
MultipartEntity entity = new MultipartEntity();
entity.addPart("type", new StringBody("photo"));
entity.addPart("data", new FileBody(image));
httppost.setEntity(entity);
HttpResponse response = httpclient.execute(httppost);
} catch (ClientProtocolException e) {
} catch (IOException e) {
}
The image variable in this case is a File that contains an image captured by the camera on the phone.
Looking at the Python Sample Code SubmitJob method it seems that only the PDF typs needs to be encoded in Base64.
Answering the question with a bit of an update. As of October 2013, in 4.4 and the support library, there are built in methods to handle printing. See the following documentation for how to do it properly:
PrintHelper - The support Library class to help with printing Bitmaps.
DevBytes: Android 4.4 Printing API - An Android Developers video detailing the APIs
Printing Content - An Android Training guide on how to use these APIs.
Related
Currently, I'm building a Android mobile app & Python restful server services.
I found that, it makes no different, whether or not I'm using
self.response.headers['Content-Type'] = "application/json"
The following code (which doesn't specific Content-Type explicitly) works fine for me. I was wondering, in what situation, I should specific Content-Type explicitly?
Python restful server services code
class DebugHandler(webapp2.RequestHandler):
def get(self):
response = {}
response["key"] = "value"
self.response.out.write(json.dumps(response))
application = webapp2.WSGIApplication([
('/debug', DebugHandler),
], debug = True)
Android mobile app client code
public static String getResponseBodyAsString(String request) {
BufferedReader bufferedReader = null;
try {
URL url = new URL(request);
HttpURLConnection httpURLConnection = (HttpURLConnection)url.openConnection();
initHttpURLConnection(httpURLConnection);
InputStream inputStream = httpURLConnection.getInputStream();
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
int charRead = 0;
char[] buffer = new char[8*1024];
// Use StringBuilder instead of StringBuffer. We do not concern
// on thread safety. stringBuffer = new StringBuffer();
StringBuilder stringBuilder = new StringBuilder();
while ((charRead = bufferedReader.read(buffer)) > 0) {
stringBuilder.append(buffer, 0, charRead);
}
return stringBuilder.toString();
} catch (MalformedURLException e) {
Log.e(TAG, "", e);
} catch (IOException e) {
Log.e(TAG, "", e);
} finally {
close(bufferedReader);
}
return null;
}
Content-Type specifies what's inside the response (i.e. how to interpret the body of the response). Is it JSON, a HTML document, a JPEG, etc? It is useful when you have different representations of your resources and together with Accept it's a header involved in doing content negotiation between client and server.
Different clients might need different formats. A C# client might prefer XML, a Javascript client might prefer JSON, another client could work with multiple representations but try to request the most efficient one first and then settle for others if the server can't serve the preferred one, etc.
Content-Type is very important in the browser so that the user agent knows how to display the response. If you don't specify one the browser will try to guess, usually based on the extension and maybe fallback to some Save as... dialog if that fails also. In a browser, the lack of a Content-Type might cause some HTML to open a Save as... dialog, or a PDF file to be rendered as gibberish in the page.
In an application client, not having a Content-Type might cause a parsing error or might be ignored. If you server only serves JSON and your client only expects JSON then you can ignore the Content-Type, the client will just assume it's JSON because that's how it was built.
But what if at some point you want to add XML as a representation, or YAML or whatever? Then you have a problem because the client assumed it's always JSON and ignored the Content-Type. Now when it receives XML it will try to parse as JSON and fail. If instead the client was built with content types in mind and you always specify a Content-Type then your client will then take it into account and select an appropriate parser instead of blindly making assumptions.
I am trying to create a image upload module Using Imgur API
I have just got Client ID and Client Secret after registration. When it comes to the implementation and testing, it fails and gives the following response in the logcat
The Logcat response
{"data":{"error":"We're really sorry, but
anonymous uploading in your country has
been disabled. Please <a href=\"\/register\">register
for an account<\/a> and try uploading again.","request":"\/3\/upload.json","method":"POST"}
,"success":false,"status":400}
The below is my code
public String uploadToImgur(File uploadFile) {
DefaultHttpClient defaulthttpclient;
HttpPost httppost;
MultipartEntity multipartentity;
String path = uploadFile.getAbsolutePath().toString();
String s;
defaulthttpclient = new DefaultHttpClient();
String targetURL = "https://api.imgur.com/3/upload.json";
String apikey = "client_secret";
httppost = new HttpPost(targetURL);
httppost.setHeader("User-Agent", USER_AGENT);
httppost.addHeader("Authorization", "Client-ID {client)_id}");
multipartentity = new MultipartEntity();
s = path.substring(1 + path.lastIndexOf("."));
if (s.lastIndexOf("jpg") >= 0)
{
s = "jpeg";
}
try
{
multipartentity.addPart("image", new FileBody(new File(path), (new StringBuilder("image/")).append(s).toString()));
multipartentity.addPart("key", new StringBody(apikey));
httppost.setEntity(multipartentity);
String s1 = EntityUtils.toString(defaulthttpclient.execute(httppost).getEntity());
Log.d("outpur" , s1);
if (s1.lastIndexOf("<original>") >= 0 && s1.indexOf("</original>") >= 0)
{
return (new StringBuilder("[img]")).append(s1.substring(10 + s1.lastIndexOf("<original>"), s1.indexOf("</original>"))).append("[/img]").toString();
}
}
catch (Exception exception)
{
return "ERRor";
}
return "Error";
}
Would you please tell me what is the better way to enhance the upload module ?
Registration and sending client id is not good enough for non anonymous uploads. The documentation tells you to use oAuth and get a token that needs to be passed for such requests.
Authentication
The API requires each client to use OAuth 2 authentication. This means you'll have to register your application, and generate an access_code if you'd like to log in as a user.
For public read-only and anonymous resources, such as getting image info, looking up user comments, etc. all you need to do is send an authorization header with your client_id in your requests. This also works if you'd like to upload images anonymously (without the image being tied to an account), or if you'd like to create an anonymous album. This lets us know which application is accessing the API.
Authorization: Client-ID YOUR_CLIENT_ID
For accessing a user's account, please visit the OAuth2 section of the docs
I'm currently working on an app that requires images to be passed to and from an App engine back-end. Originally I planned on sending the images (they are only small - max 100kb - average 20kb) directly through the endpoint however when sending the data as a byte array through the endpoint I receive a JSON error (from the rest API) stating that the data has an invalid character. Is there a way around this?
My second attempt was to use use the BlobService and return an upload URL to the client using the below code:
BlobstoreService blobstoreService = BlobstoreServiceFactory.getBlobstoreService();
UploadOptions uploadOptions = UploadOptions.Builder.withGoogleStorageBucketName("bucketname").maxUploadSizeBytes(1048576);
String url = blobstoreService.createUploadUrl("/uploaded", uploadOptions);
Then using a HTTP post on the android device to upload the image:
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
MultipartEntity entity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE);
entity.addPart("data", new ByteArrayBody(data,"image/png","img.png"));
httppost.setEntity(entity);
String res = EntityUtils.toString( httpclient.execute(httppost).getEntity(), "UTF-8");
This seems to work and the image is uploaded. However, I have no idea how to get the blobkey of this uploaded image. Does anyone know? Also, the result from the HTTP post is a 404 error - because the "/uploaded" page does not exist I'm guessing?
Thirdly, when manually typing in the blobkey and using it to return and image serving url with this code:
private String getImage(){
return getThumbUrl(new BlobKey("encoded_gs_key:ZGNpbWcxMy93czZwZ2lUeXdpY0xvZ2xtZGpHZ2dn"));
}
private String getThumbUrl(BlobKey blobkey){
ServingUrlOptions options = ServingUrlOptions.Builder.withBlobKey(blobkey);
try {
return ImagesServiceFactory.getImagesService().getServingUrl(options);
} catch(IllegalArgumentException e) {
e.printStackTrace();
return null;
} catch(ImagesServiceFailureException e) {
e.printStackTrace();
return null;
}
}
I receive the URL of the image however the image colors are all messed up. I am uploading indexed pngs... I'm not sure if the ImageService can handle them correctly. If it cant, how do I go about serving the image directly i.e. not through the ImageService but through BlobstoreService.serve()?
Here is an image of the resultant picture from the ImageService URL: http://i.imgur.com/EhfkJ9j.png
Cheers,
Ben
About the blob key, when you create the upload url you pass a paramenter with the name of your appengine page that gets called after upload. You need to implement that page ("uploaded") https://developers.google.com/appengine/docs/java/blobstore/#Java_Uploading_a_blob
I am new to Facebook API. Trying the FQL Query from the Graph API for the first time using this link.
I am trying to get photos from the album with the album id. When I request using Facebook object with https://graph.facebook.com/10150146071791729/photos&access_token=ACCESS_TOKEN URL, I am getting the following response (before parsing to JSON object). {"id":"https://graph.facebook.com/10150146071791729/photos","shares":2}. And I confirmed it by printing the length of the JSON object after parsing, which is 2. When I copy and paste the same URL in the web browser, I am getting the expected response (the response in FQL Query I got). Here is my code.
public void onComplete(Bundle values) {
String token = facebook.getAccessToken();
System.out.println("Token: " + token);
try {
String response = facebook.request("https://graph.facebook.com/10150146071791729/photos&access_token=ACCESS_TOKEN");
System.out.println("response :"+response);
JSONObject obj = Util.parseJson(response);
System.out.println("obj length : " + obj.length());
Iterator iterator = obj.keys();
while(iterator.hasNext()){
String s = (String)iterator.next();
System.out.println(""+s+" : "+obj.getString(s));
}
} catch (Throwable e) {
e.printStackTrace();
}
}
Note: I got access token from the FQL Query which is used in the URL. And I did not wrote any session (login/logout) logic as it is a test project.
Your request is wrong. It should be
"https://graph.facebook.com/10150146071791729/photos?access_token=ACCESS_TOKEN"
Replace the '&' after the photos with a '?'.
Two more things, you're making a Graph API query, not an FQL one.
Second, NEVER post your access tokens publicly. If I wanted to, I can now use your access token to edit your facebook information.
EDIT: When you use the Android Facebook SDK, you do not need to use the full graph path. Instead, use
facebook.request("10150146071791729/photos")
You do not need to add the access token as the Facebook object already has it. Hope this helps.
Because not much code has been provided except for the most relevant one, let me give you a couple of ways you can access Photos from an Album
FIRST METHOD (IF your wish to use the complete URL to make the request)
String URL = "https://graph.facebook.com/" + YOUR_ALBUM_ID
+ "/photos&access_token="
+ Utility.mFacebook.getAccessToken() + "?limit=10";
try {
HttpClient hc = new DefaultHttpClient();
HttpGet get = new HttpGet(URL);
HttpResponse rp = hc.execute(get);
if (rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
String queryPhotos = EntityUtils.toString(rp.getEntity());
Log.e("PHOTOS RESULT", queryPhotos);
}
} catch (Exception e) {
e.printStackTrace();
}
SECOND METHOD (Without using the complete URL as #Vinay Shenoy mentioned earlier)
try {
Bundle paramUserInfo = new Bundle();
paramUserInfo.putString(Facebook.TOKEN, Utility.mFacebook.getAccessToken());
String resultPhotos = Utility.mFacebook.request("YOUR_ALBUM_ID/photos", paramUserInfo, "GET");
Log.e("PHOTOS", resultPhotos);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
On a personal note, I follow the first method almost entirely through my application. It lets me using the Paging for endless ListViews
That being said, when I need some quick data in between somewhere, I do rely on the second method. Both of them work and I hope either (or both) of them helps you.
I'm currently using Scribe to both authenticate and post non-media messages to Twitter successfully. This was very easy, my first test message posted with no issues. However, I can't seem to post photos at all. I have reviewed Twitter's instructions for posting with media, both here and here.
All of the Scribe/Twitter examples at Github are for non-media posts. It would be great if someone could provide a solid example of how to post photos to Twitter via Scribe!
I'm have two issues in particular:
1) My posts will not pass authorization. I've tried mimicking the examples I posted above, but nothing seems to work.
2) When converting the image from byte[] to a string, I only seem to get 4113 characters before it stops. From my understanding, this is well under the number of characters a String can hold.
Here is how I'm extracting the photo:
// GET PHOTO FILE AND FILE LENGTH
// INSTANTIATE UPLOAD VARIABLE WITH FILE LENGTH
File file = new File(photo); // ("photo" is a string path to the photo file)
int fileLength = (int) file.length();
uploadFile = new byte[fileLength];
// CREATE BUFFER INPUT STREAM OF FILE
BufferedInputStream inputStream;
try {inputStream = new BufferedInputStream(new FileInputStream(file));}
catch (FileNotFoundException e)
{
inputStream = null;
Toast.makeText(this.getApplicationContext(), "Buffer input stream error!", Toast.LENGTH_LONG).show();
}
// READ DATA FROM FILE INTO UPLOAD VARIABLE
// CLOSE INPUT STREAM
try {inputStream.read(uploadFile);}
catch (IOException e) {Toast.makeText(this.getApplicationContext(), "Read input stream to upload variable error!", Toast.LENGTH_LONG).show();}
try {inputStream.close();}
catch (IOException e) {Toast.makeText(this.getApplicationContext(), "Close input stream error!", Toast.LENGTH_LONG).show();}
After a LOT of research and piece milling code from various places I finally figured out what I was doing wrong. Here is an example of how to post photos to Twitter via Scribe OAuth:
NOTE: This assumes a few things...
1) You have already saved the photo and have the file path
2) You have already authenticated the user at some point and have a valid access Token
3) You MUST add apache-mime4j-0.6.jar & httpmime-4.0.1.jar to you libs folder and include them in your build path!!!
I really hope this helps someone! It's very easy to implement, but took a few days of troubleshooting to get it working correctly!
// BUILD OAUTH SERVICE
OAuthService oAuth = new ServiceBuilder()
.provider(TwitterApi.class)
.apiKey(YOUR_TWITTER_API_KEY) // REPLACE WITH YOUR OWN!!!
.apiSecret(YOUR_TWITTER_API_SECRET) // REPLACE WITH YOUR OWN!!!
.callback(YOUR_CALLBACK) // REPLACE WITH YOUR OWN!!!
.build();
// BUILD OAUTH REQUEST & SIGN IT RIGHT AWAY (OTHERWISE MULTIPART FORM MAY PREVENT SIGNING)
OAuthRequest request = new OAuthRequest(Verb.POST, "https://upload.twitter.com/1.1/statuses/update_with_media.json");
oAuth.signRequest(USER_ACCESS_TOKEN, request); // ENTER USER'S ACCESS TOKEN
// ADD MULTIPART FORM
try
{
MultipartEntity entity = new MultipartEntity();
entity.addPart("status", new StringBody(message)); // THIS IS THE TWITTER MESSAGE
entity.addPart("media", new FileBody(new File(photo))); // THIS IS THE PHOTO TO UPLOAD
ByteArrayOutputStream out = new ByteArrayOutputStream();
entity.writeTo(out);
request.addPayload(out.toByteArray());
request.addHeader(entity.getContentType().getName(), entity.getContentType().getValue());
}
catch (UnsupportedEncodingException e) {e.printStackTrace();}
catch (IOException e) {e.printStackTrace();}
// SEND REQUEST
try {response = new JSONObject (request.send().getBody());}
catch (JSONException e) {Log.e("YOUR_APP_TAG", "JSONException Thrown: " + e.getMessage());}