Google Cloud Printing from Android

If you’ve worked with Google Cloud Printing then you know that much of the sample code out there that shows how to submit print jobs using Java or C# don’t work … frustrating to say the least.

After more pain then I’d care to admit, I finally got the code working so wanted to share it.

Below is the code to submit a PDF file for Printing (or storage on a Google Drive) using Google Cloud Printing from an Android device.

For a complete Android Studio project that shows how to interrogate the available printers, get the list of print jobs, and submit a print job download this project zip file.

public class GoogleCloudPrint {
 private static final String SERVICE_SOURCE_NAME = "your-apps-name-for-logging-purposes";
 public static final String GCP_BASE_URL_STRING =
  "https://www.google.com/cloudprint";
 private static final String SUBMIT_URL_STRING =
  GCP_BASE_URL_STRING + "/submit";
 private static final int DEFAULT_STREAMING_CHUNK_SIZE = 0;
 String mUsername;
 String mPassword;
 public GoogleCloudPrint(String username, String password) {
  mUsername = username;
  mPassword = password;
 }
 public void print(String printerId, String title, File documentFile, String mimeType) {
  try {
   byte[] documentBytes = getBytes(documentFile);
   String urlString = SUBMIT_URL_STRING +
    "?output=json" +
    "&printerid=" + printerId +
    "&contentType=" + mimeType +
    "&title=" + title ;
   // Open connection and configure for POST with streaming writes
   HttpsURLConnection connection = createConnection(urlString);
   connection.setDoOutput(true);
   connection.setChunkedStreamingMode(DEFAULT_STREAMING_CHUNK_SIZE);
   // Create the multi-part form data prefix and suffix
   // include filename value in prefix
   String queryPrefix = String.format(
    "------CloudPrintFormBoundaryqeq6g6ncj5v7\r\n" +
    "Content-Disposition: form-data; name=\"content\"; filename=\"%s\"\r\n" +
    "\r\n", title);
   String querySuffix = "\r\n------CloudPrintFormBoundaryqeq6g6ncj5v7--";
   // Encoide prefix and suffix and determine total content length
   byte[] queryPrefixBytes = EncodingUtils.getAsciiBytes(queryPrefix);
   byte[] querySuffixBytes = EncodingUtils.getAsciiBytes(querySuffix);
   int contentLength = queryPrefixBytes.length + documentBytes.length + querySuffixBytes.length;
   // Add headers for multipart form data and length
   connection.addRequestProperty("Content-Type", 
    "multipart/form-data; boundary=----CloudPrintFormBoundaryqeq6g6ncj5v7");
   connection.addRequestProperty("Content-Length", Integer.toString(contentLength));
   // Write the document to server wrapped as multipart form data
   writePrintBody(connection, queryPrefixBytes, documentBytes, querySuffixBytes);
   // Write results to log
   InputStream inStream = connection.getInputStream();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }
 private HttpsURLConnection createConnection(String urlString) throws IOException {
  String auth = retrieveAuthToken(mUsername, mPassword);
  URL url = new URL(urlString);
  HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
  connection.addRequestProperty("X-CloudPrint-Proxy", SERVICE_SOURCE_NAME);
  connection.addRequestProperty("Authorization", "GoogleLogin auth=" + auth);
  return connection;
 }
 private void writePrintBody(HttpsURLConnection connection, byte[] prefixBytes,
  byte[] documentByes, byte[] suffixBytes) throws IOException {
  OutputStream outStream = new BufferedOutputStream(connection.getOutputStream());
  outStream.write(prefixBytes, 0, prefixBytes.length);
  outStream.write(documentByes, 0, documentByes.length);
  outStream.write(suffixBytes, 0, suffixBytes.length);
  outStream.flush();
  outStream.close();
 }
private String retrieveAuthToken(String userId, String password) {
  final String AUTHORIZE_URL_FORMAT = "https://www.google.com/accounts/ClientLogin" +
   "?accountType=HOSTED_OR_GOOGLE" +
   "&Email=%s" +
   "&Passwd=%s" +
   "&service=cloudprint" +
   "&source=" + SERVICE_SOURCE_NAME;
  String authorizeURL = String.format(AUTHORIZE_URL_FORMAT, userId, password);
  String auth = "NOT_SET";
  InputStream inStream = null;
  BufferedReader reader = null;
  try {
   URL url = new URL(authorizeURL);
   HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
   inStream = connection.getInputStream();
   reader = new BufferedReader(new InputStreamReader(inStream));
   String buffer;
   StringBuilder stringBuilder = new StringBuilder();
   while ((buffer = reader.readLine()) != null) {
    String[] parts = buffer.split("=");
    if ("Auth".equalsIgnoreCase(parts[0])) {
      auth = parts[1];
      break;
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   try {
    if (reader != null)
     reader.close();
    if (inStream != null)
     inStream.close();
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
  return auth;
 }
 private byte[] getBytes(File documentFile) {
  byte[] bytes = null;
  try {
   FileInputStream inputStream = new FileInputStream(documentFile);
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
   byte[] buffer = new byte[4096];
   int n = inputStream.read(buffer);
   while (n >= 0) {
    baos.write(buffer, 0, n);
    n = inputStream.read(buffer);
   }
   inputStream.close();
   baos.flush();
   bytes = baos.toByteArray();
  } catch (Exception e) {
   e.printStackTrace();
  }
  return bytes;
 }
}

AnDevCon Slides and Code

Thanks to everyone for attending the AnDevCon Tutorial and Class this week. Here’s the info from both.

Understanding Android Studio and Android Developer Toolset

Integrate Google Cloud Printing Into Your Android App

You must set the user id and password constants in the GmailHelper class or the program will not work

Thank you to everyone who attended. Please feel free to contact me with any questions.

ADCLogo_large