/** * Copyright (c) 2013 Nokia Corporation. All rights reserved. * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. * Oracle and Java are trademarks or registered trademarks of Oracle and/or its * affiliates. Other product and company names mentioned herein may be trademarks * or trade names of their respective owners. * See license text file delivered with this project for more information. */ package com.nokia.example.statusshout.engine; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.IOException; import java.io.OutputStream; import javax.microedition.content.Invocation; import javax.microedition.content.Registry; import javax.microedition.io.Connector; import javax.microedition.io.HttpConnection; import javax.microedition.midlet.MIDlet; import com.nokia.example.statusshout.ui.ImageUtils; /** * Provides methods for authentication to Facebook and sharing status updates. */ public class FacebookService extends OAuthService { // Constants private static final String TAG = "FacebookService."; /* * TODO: Get your application ID from developers.facebook.com * * Note that this Facebook implementation will not work without a valid * application ID! */ private static final String APP_ID = ""; // Facebook app client ID /* * See https://developers.facebook.com/docs/facebook-login/login-flow-for-web-no-jssdk/ * * Note that "m" prefix needs to be used instead of "www"! Otherwise, the * app will not be able to intercept the access token! */ private static final String REDIRECT_URL = "https://m.facebook.com/connect/login_success.html"; private static final String OAUTH_URL = "https://facebook.com/dialog/oauth"; private static final String GRAPH_URL = "https://graph.facebook.com/"; private static final String ACCESS_TOKEN_STRING = "access_token="; // The full URL address used for logging in and retrieving the access token. // For more information, see https://developers.facebook.com/docs/reference/dialogs/oauth/ private static final String LOGIN_URL = OAUTH_URL + "?client_id=" + APP_ID + "&redirect_uri=" + REDIRECT_URL + "&display=popup" + "&scope=publish_stream,user_status,user_photos,photo_upload" // Permissions + "&response_type=token"; // See http://www.w3.org/Protocols/rfc1341/7_2_Multipart.html private static final String BOUNDARY = "th15iSmYB0und4RyM3s54G"; // Members private String messageToSend; private String imageToShare; /** * Constructor. */ public FacebookService(MIDlet midlet, ShareListener listener) { super(midlet, listener); } /** * @see javax.microedition.content.ResponseListener#invocationResponseNotify(javax.microedition.content.Registry) */ public void invocationResponseNotify(Registry registry) { Invocation response = registry.getResponse(true); final int responseCode = response.getStatus(); System.out.println(TAG + "invocationResponseNotify(): Response: " + responseCode); if (responseCode == Invocation.OK) { final String token = parseToken(response.getURL()); System.out.println(TAG + "invocationResponseNotify(): Parsed token: " + token); if (token != null && token.length() > 0) { // Parsed token seems valid. Store it. StatusShoutData.getInstance().setFacebookToken(token); if (listener != null) { listener.onAuthenticated(this); } // Check if we had pending content to be shared if ((messageToSend != null && messageToSend.length() > 0) || (imageToShare != null && imageToShare.length() > 0)) { // Send the pending message and/or image doShare(messageToSend, imageToShare); messageToSend = null; imageToShare = null; } } else if (listener != null) { listener.onError("Failed to parse Facebook token!"); } } else if (listener != null) { listener.onError("Failed to authenticate. Server response code was " + responseCode + "."); } } /** * @see com.nokia.example.statusshout.engine.OAuthService#authenticate() */ public void authenticate() { if (APP_ID.length() == 0 && listener != null) { listener.onError("No Facebook application ID defined!"); return; } setAsListener(this); createInvocation(LOGIN_URL, REDIRECT_URL); // Implemented in the base class } /** * @see com.nokia.example.statusshout.engine.OAuthService#isValid(java.lang.String) */ public boolean isValid(String token) { if (token != null && token.length() > 0) { // TODO Do a proper validation return true; } return false; } /** * Shares a message and/or an image. * * @param message The message to share. * @param imageUri The URI of the image to share. */ public void share(final String message, final String imageUri) { System.out.println(TAG + "share(): \"" + message + "\", " + imageUri); if ((message == null || message.length() == 0) && (imageUri == null || imageUri.length() == 0)) { if (listener != null) { listener.onError("Nothing to share! Note that image sharing is not implemented."); } return; } if (!isValid(StatusShoutData.getInstance().getFacebookToken())) { // No valid token. Save the message to be sent after authentication // and authenticate first. messageToSend = message; imageToShare = imageUri; authenticate(); } else { doShare(message, imageUri); } } /** * Shares the given message to Facebook feed. Note that this method does not * validate the message content nor the token. * * See https://developers.facebook.com/docs/reference/api/publishing/ for * more information on publishing in Facebook. * * @param message The message to share. * @param imageUri The image to share. */ private void doShare(String message, final String imageUri) { if (listener != null) { listener.onSending(); } final boolean hasImageToShare = (imageUri != null && imageUri.length() > 0); final String accessToken = StatusShoutData.getInstance().getFacebookToken(); StringBuffer sb = new StringBuffer(); sb.append("access_token=").append(accessToken); if (message != null) { sb.append("&message="); if (hasImageToShare) { // We are putting the message into the URL and thus, we need to // replace any space characters with "%20". for (int i = 0; i < message.length(); ++i) { if (message.charAt(i) == ' ') { sb.append("%20"); } else { sb.append(message.charAt(i)); } } } else { sb.append(message); } } final String content = sb.toString(); final String contentLength = String.valueOf(content.getBytes().length); new Thread() { public void run() { String url = GRAPH_URL; if (hasImageToShare) { url += "me/photos?" + content; System.out.println(TAG + "doShare(): URL: " + url); } else { url += "me/feed"; System.out.println(TAG + "doShare(): URL: " + url); System.out.println(TAG + "doShare(): Content: " + content); } HttpConnection connection = null; ByteArrayOutputStream bos = null; try { connection = (HttpConnection) Connector.open(url, Connector.READ_WRITE); if (connection != null) { connection.setRequestMethod(HttpConnection.POST); if (hasImageToShare) { connection.setRequestProperty("Content-type", "multipart/form-data; boundary=" + BOUNDARY); bos = new ByteArrayOutputStream(); bos.write(getBoundaryMessage(imageUri, "image/jpeg").getBytes()); bos.write(ImageUtils.localImageToByteArray(imageUri)); bos.write(new String("\r\n--" + BOUNDARY + "--\r\n").getBytes()); } else { System.out.println(TAG + "doShare(): Content length: " + contentLength); connection.setRequestProperty("Content-length", contentLength); } } } catch (IOException e) { System.out.println(TAG + "doShare(): " + e.toString()); if (listener != null) { listener.onError("Failed to open connection: " + e.toString()); } } catch (SecurityException e) { // User did not allow accessing network connection = null; } catch (Exception e) { if (listener != null) { listener.onError("Failed to open connection: " + e.toString()); } connection = null; } int responseCode = HttpConnection.HTTP_UNAVAILABLE; if (connection != null) { try { OutputStream outputStream = connection.openDataOutputStream(); if (outputStream != null) { if (hasImageToShare) { outputStream.write(bos.toByteArray()); } else { outputStream.write(content.getBytes()); } outputStream.flush(); if (bos != null) { bos.close(); } outputStream.close(); responseCode = connection.getResponseCode(); System.out.println(TAG + "doShare(): Response code: " + responseCode); } } catch (IOException e) { System.out.println(TAG + "doShare(): " + e.toString()); if (listener != null) { listener.onError("Failed to post content: " + e.toString()); } } catch (SecurityException e) { } catch (NullPointerException e) { // This may happen if permission is denied by user } // Get the response message DataInputStream inputStream = null; String responseMessage = new String(); try { inputStream = new DataInputStream(connection.openInputStream()); int ch; while ((ch = inputStream.read()) != -1) { responseMessage += ((char) ch); } System.out.println(TAG + "doShare(): Response message : " + responseMessage); inputStream.close(); } catch (Exception e) { System.out.println(TAG + "doShare(): " + e.toString()); } if (listener != null) { if (responseCode == HttpConnection.HTTP_OK) { listener.onSuccess("Message posted successfully!"); } else { listener.onError("Failed to share to Facebook. Code " + responseCode + ", message: " + parseResponseMessage(responseMessage)); } } } try { connection.close(); } catch (Exception e) { } } }.start(); } /** * Parses the authentication token from the given string. * * @param input The string containing the token. * @return The authentication token. */ private String parseToken(String input) { System.out.println(TAG + "parseToken(): " + input); if (input == null) { return null; } int start = 0; int end = 0; start = input.indexOf(ACCESS_TOKEN_STRING); if (start == -1) { return null; } else { start += ACCESS_TOKEN_STRING.length(); } end = input.indexOf("&", start); String token = null; try { token = input.substring(start, (end != -1) ? end : input.length()); } catch (StringIndexOutOfBoundsException e) { } return token; } /** * Creates a boundary message (for sharing an image in the Facebook). * * @param imageUri The URI of the image to share. * @param mimeType The MIME type of the image. * @return The boundary message as a String. */ private String getBoundaryMessage(final String imageUri, final String mimeType) { // Crop the filename final int index = imageUri.lastIndexOf('/') + 1; final String filename = imageUri.substring(index); StringBuffer sb = new StringBuffer("--").append(BOUNDARY).append("\r\n"); sb.append("Content-Disposition: form-data; name=\"").append("upload_field") .append("\"; filename=\"").append(filename).append("\"\r\n") .append("Content-Type: ").append(mimeType).append("\r\n\r\n"); return sb.toString(); } /** * Parses and returns the message content from the given response. * @param response The response. * @return The message content. */ private String parseResponseMessage(final String response) { if (response == null) { return null; } final int startIndex = response.indexOf("message\":\"") + 10; final int endIndex = response.indexOf('"', startIndex); String message = null; try { message = response.substring(startIndex, endIndex); } catch (StringIndexOutOfBoundsException e) { message = "(n/a)"; } return message; } }