Project: android-tether
/*
 * Copyright 2010 Facebook, Inc. 
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License 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. 
 */
 
package com.facebook.android; 
 
import java.io.BufferedOutputStream; 
import java.io.BufferedReader; 
import java.io.FileNotFoundException; 
import java.io.IOException; 
import java.io.InputStream; 
import java.io.InputStreamReader; 
import java.io.OutputStream; 
import java.net.HttpURLConnection; 
import java.net.MalformedURLException; 
import java.net.URL; 
import java.net.URLDecoder; 
import java.net.URLEncoder; 
 
import org.json.JSONException; 
import org.json.JSONObject; 
 
import android.app.AlertDialog.Builder; 
import android.content.Context; 
import android.os.Bundle; 
import android.util.Log; 
import android.webkit.CookieManager; 
import android.webkit.CookieSyncManager; 
 
/**
 * Utility class supporting the Facebook Object. 
 * 
 * @author [email protected] 
 * 
 */
 
public final class Util { 
 
    /**
     * Generate the multi-part post body providing the parameters and boundary 
     * string 
     *  
     * @param parameters the parameters need to be posted 
     * @param boundary the random string as boundary 
     * @return a string of the post body 
     */
 
    public static String encodePostBody(Bundle parameters, String boundary) { 
        if (parameters == nullreturn ""
        StringBuilder sb = new StringBuilder(); 
 
        for (String key : parameters.keySet()) { 
            if (parameters.getByteArray(key) != null) { 
                continue
            } 
 
            sb.append("Content-Disposition: form-data; name=\"" + key + 
                    "\"\r\n\r\n" + parameters.getString(key)); 
            sb.append("\r\n" + "--" + boundary + "\r\n"); 
        } 
 
        return sb.toString(); 
    } 
 
    public static String encodeUrl(Bundle parameters) { 
        if (parameters == null) { 
            return ""
        } 
 
        StringBuilder sb = new StringBuilder(); 
        boolean first = true
        for (String key : parameters.keySet()) { 
            if (first) first = falseelse sb.append("&"); 
            sb.append(URLEncoder.encode(key) + "=" + 
                      URLEncoder.encode(parameters.getString(key))); 
        } 
        return sb.toString(); 
    } 
 
    public static Bundle decodeUrl(String s) { 
        Bundle params = new Bundle(); 
        if (s != null) { 
            String array[] = s.split("&"); 
            for (String parameter : array) { 
                String v[] = parameter.split("="); 
                params.putString(URLDecoder.decode(v[0]), 
                                 URLDecoder.decode(v[1])); 
            } 
        } 
        return params; 
    } 
 
    /**
     * Parse a URL query and fragment parameters into a key-value bundle. 
     * 
     * @param url the URL to parse 
     * @return a dictionary bundle of keys and values 
     */
 
    public static Bundle parseUrl(String url) { 
        // hack to prevent MalformedURLException 
        url = url.replace("fbconnect""http"); 
        try { 
            URL u = new URL(url); 
            Bundle b = decodeUrl(u.getQuery()); 
            b.putAll(decodeUrl(u.getRef())); 
            return b; 
        } catch (MalformedURLException e) { 
            return new Bundle(); 
        } 
    } 
 
     
    /**
     * Connect to an HTTP URL and return the response as a string. 
     * 
     * Note that the HTTP method override is used on non-GET requests. (i.e. 
     * requests are made as "POST" with method specified in the body). 
     * 
     * @param url - the resource to open: must be a welformed URL 
     * @param method - the HTTP method to use ("GET", "POST", etc.) 
     * @param params - the query parameter for the URL (e.g. access_token=foo) 
     * @return the URL contents as a String 
     * @throws MalformedURLException - if the URL format is invalid 
     * @throws IOException - if a network problem occurs 
     */
 
    public static String openUrl(String url, String method, Bundle params) 
          throws MalformedURLException, IOException { 
        // random string as boundary for multi-part http post 
        String strBoundary = "3i2ndDfv2rTHiSisAbouNdArYfORhtTPEefj3q2f"
        String endLine = "\r\n"
 
        OutputStream os; 
 
        if (method.equals("GET")) { 
            url = url + "?" + encodeUrl(params); 
        } 
        Log.d("Facebook-Util", method + " URL: " + url); 
        HttpURLConnection conn = 
            (HttpURLConnection) new URL(url).openConnection(); 
        conn.setRequestProperty("User-Agent", System.getProperties(). 
                getProperty("http.agent") + " FacebookAndroidSDK"); 
        if (!method.equals("GET")) { 
            Bundle dataparams = new Bundle(); 
            for (String key : params.keySet()) { 
                if (params.getByteArray(key) != null) { 
                        dataparams.putByteArray(key, params.getByteArray(key)); 
                } 
            } 
 
            // use method override 
            if (!params.containsKey("method")) { 
                params.putString("method", method); 
            } 
 
            if (params.containsKey("access_token")) { 
                String decoded_token = 
                    URLDecoder.decode(params.getString("access_token")); 
                params.putString("access_token", decoded_token); 
            } 
 
            conn.setRequestMethod("POST"); 
            conn.setRequestProperty( 
                    "Content-Type"
                    "multipart/form-data;boundary="+strBoundary); 
            conn.setDoOutput(true); 
            conn.setDoInput(true); 
            conn.setRequestProperty("Connection""Keep-Alive"); 
            conn.connect(); 
            os = new BufferedOutputStream(conn.getOutputStream()); 
 
            os.write(("--" + strBoundary +endLine).getBytes()); 
            os.write((encodePostBody(params, strBoundary)).getBytes()); 
            os.write((endLine + "--" + strBoundary + endLine).getBytes()); 
 
            if (!dataparams.isEmpty()) { 
 
                for (String key: dataparams.keySet()){ 
                    os.write(("Content-Disposition: form-data; filename=\"" + key + "\"" + endLine).getBytes()); 
                    os.write(("Content-Type: content/unknown" + endLine + endLine).getBytes()); 
                    os.write(dataparams.getByteArray(key)); 
                    os.write((endLine + "--" + strBoundary + endLine).getBytes()); 
 
                } 
            } 
            os.flush(); 
        } 
 
        String response = ""
        try { 
            response = read(conn.getInputStream()); 
        } catch (FileNotFoundException e) { 
            // Error Stream contains JSON that we can parse to a FB error 
            response = read(conn.getErrorStream()); 
        } 
        return response; 
    } 
 
    private static String read(InputStream in) throws IOException { 
        StringBuilder sb = new StringBuilder(); 
        BufferedReader r = new BufferedReader(new InputStreamReader(in), 1000); 
        for (String line = r.readLine(); line != null; line = r.readLine()) { 
            sb.append(line); 
        } 
        in.close(); 
        return sb.toString(); 
    } 
 
    public static void clearCookies(Context context) { 
        // Edge case: an illegal state exception is thrown if an instance of 
        // CookieSyncManager has not be created.  CookieSyncManager is normally 
        // created by a WebKit view, but this might happen if you start the 
        // app, restore saved state, and click logout before running a UI 
        // dialog in a WebView -- in which case the app crashes 
        @SuppressWarnings("unused"
        CookieSyncManager cookieSyncMngr = 
            CookieSyncManager.createInstance(context); 
        CookieManager cookieManager = CookieManager.getInstance(); 
        cookieManager.removeAllCookie(); 
    } 
 
    /**
     * Parse a server response into a JSON Object. This is a basic 
     * implementation using org.json.JSONObject representation. More 
     * sophisticated applications may wish to do their own parsing. 
     * 
     * The parsed JSON is checked for a variety of error fields and 
     * a FacebookException is thrown if an error condition is set, 
     * populated with the error message and error type or code if 
     * available. 
     * 
     * @param response - string representation of the response 
     * @return the response as a JSON Object 
     * @throws JSONException - if the response is not valid JSON 
     * @throws FacebookError - if an error condition is set 
     */
 
    public static JSONObject parseJson(String response) 
          throws JSONException, FacebookError { 
        // Edge case: when sending a POST request to /[post_id]/likes 
        // the return value is 'true' or 'false'. Unfortunately 
        // these values cause the JSONObject constructor to throw 
        // an exception. 
        if (response.equals("false")) { 
            throw new FacebookError("request failed"); 
        } 
        if (response.equals("true")) { 
            response = "{value : true}"
        } 
        JSONObject json = new JSONObject(response); 
 
        // errors set by the server are not consistent 
        // they depend on the method and endpoint 
        if (json.has("error")) { 
            JSONObject error = json.getJSONObject("error"); 
            throw new FacebookError( 
                    error.getString("message"), error.getString("type"), 0); 
        } 
        if (json.has("error_code") && json.has("error_msg")) { 
            throw new FacebookError(json.getString("error_msg"), ""
                    Integer.parseInt(json.getString("error_code"))); 
        } 
        if (json.has("error_code")) { 
            throw new FacebookError("request failed"""
                    Integer.parseInt(json.getString("error_code"))); 
        } 
        if (json.has("error_msg")) { 
            throw new FacebookError(json.getString("error_msg")); 
        } 
        if (json.has("error_reason")) { 
            throw new FacebookError(json.getString("error_reason")); 
        } 
        return json; 
    } 
 
    /**
     * Display a simple alert dialog with the given text and title. 
     * 
     * @param context 
     *          Android context in which the dialog should be displayed 
     * @param title 
     *          Alert dialog title 
     * @param text 
     *          Alert dialog message 
     */
 
    public static void showAlert(Context context, String title, String text) { 
        Builder alertBuilder = new Builder(context); 
        alertBuilder.setTitle(title); 
        alertBuilder.setMessage(text); 
        alertBuilder.create().show(); 
    } 
 
}