FacebookService.java

/**
 * 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;
    }
}

Last updated 16 October 2013

Back to top

Was this page helpful?

Your feedback about this content is important. Let us know what you think.

 

Thank you!

We appreciate your feedback.

×