Home | History | Annotate | Download | only in transaction
      1 /*
      2  * Copyright (C) 2008 Esmertec AG.
      3  * Copyright (C) 2008 The Android Open Source Project
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.mms.transaction;
     19 
     20 import java.io.DataInputStream;
     21 import java.io.IOException;
     22 import java.net.SocketException;
     23 import java.net.URI;
     24 import java.net.URISyntaxException;
     25 import java.util.Locale;
     26 
     27 import org.apache.http.HttpEntity;
     28 import org.apache.http.HttpHost;
     29 import org.apache.http.HttpRequest;
     30 import org.apache.http.HttpResponse;
     31 import org.apache.http.StatusLine;
     32 import org.apache.http.client.methods.HttpGet;
     33 import org.apache.http.client.methods.HttpPost;
     34 import org.apache.http.conn.params.ConnRouteParams;
     35 import org.apache.http.params.HttpConnectionParams;
     36 import org.apache.http.params.HttpParams;
     37 import org.apache.http.params.HttpProtocolParams;
     38 
     39 import android.content.Context;
     40 import android.net.http.AndroidHttpClient;
     41 import android.telephony.TelephonyManager;
     42 import android.text.TextUtils;
     43 import android.util.Config;
     44 import android.util.Log;
     45 
     46 import com.android.mms.LogTag;
     47 import com.android.mms.MmsConfig;
     48 
     49 public class HttpUtils {
     50     private static final String TAG = LogTag.TRANSACTION;
     51 
     52     private static final boolean DEBUG = false;
     53     private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
     54 
     55     public static final int HTTP_POST_METHOD = 1;
     56     public static final int HTTP_GET_METHOD = 2;
     57 
     58     private static final int MMS_READ_BUFFER = 4096;
     59 
     60     // This is the value to use for the "Accept-Language" header.
     61     // Once it becomes possible for the user to change the locale
     62     // setting, this should no longer be static.  We should call
     63     // getHttpAcceptLanguage instead.
     64     private static final String HDR_VALUE_ACCEPT_LANGUAGE;
     65 
     66     static {
     67         HDR_VALUE_ACCEPT_LANGUAGE = getCurrentAcceptLanguage(Locale.getDefault());
     68     }
     69 
     70     // Definition for necessary HTTP headers.
     71     private static final String HDR_KEY_ACCEPT = "Accept";
     72     private static final String HDR_KEY_ACCEPT_LANGUAGE = "Accept-Language";
     73 
     74     private static final String HDR_VALUE_ACCEPT =
     75         "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic";
     76 
     77     private HttpUtils() {
     78         // To forbidden instantiate this class.
     79     }
     80 
     81     /**
     82      * A helper method to send or retrieve data through HTTP protocol.
     83      *
     84      * @param token The token to identify the sending progress.
     85      * @param url The URL used in a GET request. Null when the method is
     86      *         HTTP_POST_METHOD.
     87      * @param pdu The data to be POST. Null when the method is HTTP_GET_METHOD.
     88      * @param method HTTP_POST_METHOD or HTTP_GET_METHOD.
     89      * @return A byte array which contains the response data.
     90      *         If an HTTP error code is returned, an IOException will be thrown.
     91      * @throws IOException if any error occurred on network interface or
     92      *         an HTTP error code(>=400) returned from the server.
     93      */
     94     protected static byte[] httpConnection(Context context, long token,
     95             String url, byte[] pdu, int method, boolean isProxySet,
     96             String proxyHost, int proxyPort) throws IOException {
     97         if (url == null) {
     98             throw new IllegalArgumentException("URL must not be null.");
     99         }
    100 
    101         if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    102             Log.v(TAG, "httpConnection: params list");
    103             Log.v(TAG, "\ttoken\t\t= " + token);
    104             Log.v(TAG, "\turl\t\t= " + url);
    105             Log.v(TAG, "\tmethod\t\t= "
    106                     + ((method == HTTP_POST_METHOD) ? "POST"
    107                             : ((method == HTTP_GET_METHOD) ? "GET" : "UNKNOWN")));
    108             Log.v(TAG, "\tisProxySet\t= " + isProxySet);
    109             Log.v(TAG, "\tproxyHost\t= " + proxyHost);
    110             Log.v(TAG, "\tproxyPort\t= " + proxyPort);
    111             // TODO Print out binary data more readable.
    112             //Log.v(TAG, "\tpdu\t\t= " + Arrays.toString(pdu));
    113         }
    114 
    115         AndroidHttpClient client = null;
    116 
    117         try {
    118             // Make sure to use a proxy which supports CONNECT.
    119             URI hostUrl = new URI(url);
    120             HttpHost target = new HttpHost(
    121                     hostUrl.getHost(), hostUrl.getPort(),
    122                     HttpHost.DEFAULT_SCHEME_NAME);
    123 
    124             client = createHttpClient(context);
    125             HttpRequest req = null;
    126             switch(method) {
    127                 case HTTP_POST_METHOD:
    128                     ProgressCallbackEntity entity = new ProgressCallbackEntity(
    129                                                         context, token, pdu);
    130                     // Set request content type.
    131                     entity.setContentType("application/vnd.wap.mms-message");
    132 
    133                     HttpPost post = new HttpPost(url);
    134                     post.setEntity(entity);
    135                     req = post;
    136                     break;
    137                 case HTTP_GET_METHOD:
    138                     req = new HttpGet(url);
    139                     break;
    140                 default:
    141                     Log.e(TAG, "Unknown HTTP method: " + method
    142                             + ". Must be one of POST[" + HTTP_POST_METHOD
    143                             + "] or GET[" + HTTP_GET_METHOD + "].");
    144                     return null;
    145             }
    146 
    147             // Set route parameters for the request.
    148             HttpParams params = client.getParams();
    149             if (isProxySet) {
    150                 ConnRouteParams.setDefaultProxy(
    151                         params, new HttpHost(proxyHost, proxyPort));
    152             }
    153             req.setParams(params);
    154 
    155             // Set necessary HTTP headers for MMS transmission.
    156             req.addHeader(HDR_KEY_ACCEPT, HDR_VALUE_ACCEPT);
    157             {
    158                 String xWapProfileTagName = MmsConfig.getUaProfTagName();
    159                 String xWapProfileUrl = MmsConfig.getUaProfUrl();
    160 
    161                 if (xWapProfileUrl != null) {
    162                     if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) {
    163                         Log.d(LogTag.TRANSACTION,
    164                                 "[HttpUtils] httpConn: xWapProfUrl=" + xWapProfileUrl);
    165                     }
    166                     req.addHeader(xWapProfileTagName, xWapProfileUrl);
    167                 }
    168             }
    169 
    170             // Extra http parameters. Split by '|' to get a list of value pairs.
    171             // Separate each pair by the first occurrence of ':' to obtain a name and
    172             // value. Replace the occurrence of the string returned by
    173             // MmsConfig.getHttpParamsLine1Key() with the users telephone number inside
    174             // the value.
    175             String extraHttpParams = MmsConfig.getHttpParams();
    176 
    177             if (extraHttpParams != null) {
    178                 String line1Number = ((TelephonyManager)context
    179                         .getSystemService(Context.TELEPHONY_SERVICE))
    180                         .getLine1Number();
    181                 String line1Key = MmsConfig.getHttpParamsLine1Key();
    182                 String paramList[] = extraHttpParams.split("\\|");
    183 
    184                 for (String paramPair : paramList) {
    185                     String splitPair[] = paramPair.split(":", 2);
    186 
    187                     if (splitPair.length == 2) {
    188                         String name = splitPair[0].trim();
    189                         String value = splitPair[1].trim();
    190 
    191                         if (line1Key != null) {
    192                             value = value.replace(line1Key, line1Number);
    193                         }
    194                         if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) {
    195                             req.addHeader(name, value);
    196                         }
    197                     }
    198                 }
    199             }
    200             req.addHeader(HDR_KEY_ACCEPT_LANGUAGE, HDR_VALUE_ACCEPT_LANGUAGE);
    201 
    202             HttpResponse response = client.execute(target, req);
    203             StatusLine status = response.getStatusLine();
    204             if (status.getStatusCode() != 200) { // HTTP 200 is success.
    205                 throw new IOException("HTTP error: " + status.getReasonPhrase());
    206             }
    207 
    208             HttpEntity entity = response.getEntity();
    209             byte[] body = null;
    210             if (entity != null) {
    211                 try {
    212                     if (entity.getContentLength() > 0) {
    213                         body = new byte[(int) entity.getContentLength()];
    214                         DataInputStream dis = new DataInputStream(entity.getContent());
    215                         try {
    216                             dis.readFully(body);
    217                         } finally {
    218                             try {
    219                                 dis.close();
    220                             } catch (IOException e) {
    221                                 Log.e(TAG, "Error closing input stream: " + e.getMessage());
    222                             }
    223                         }
    224                     }
    225                     if (entity.isChunked()) {
    226                         Log.v(TAG, "httpConnection: transfer encoding is chunked");
    227                         int bytesTobeRead = MmsConfig.getMaxMessageSize();
    228                         byte[] tempBody = new byte[bytesTobeRead];
    229                         DataInputStream dis = new DataInputStream(entity.getContent());
    230                         try {
    231                             int bytesRead = 0;
    232                             int offset = 0;
    233                             boolean readError = false;
    234                             do {
    235                                 try {
    236                                     bytesRead = dis.read(tempBody, offset, bytesTobeRead);
    237                                 } catch (IOException e) {
    238                                     readError = true;
    239                                     Log.e(TAG, "httpConnection: error reading input stream"
    240                                         + e.getMessage());
    241                                     break;
    242                                 }
    243                                 if (bytesRead > 0) {
    244                                     bytesTobeRead -= bytesRead;
    245                                     offset += bytesRead;
    246                                 }
    247                             } while (bytesRead >= 0 && bytesTobeRead > 0);
    248                             if (bytesRead == -1 && offset > 0 && !readError) {
    249                                 // offset is same as total number of bytes read
    250                                 // bytesRead will be -1 if the data was read till the eof
    251                                 body = new byte[offset];
    252                                 System.arraycopy(tempBody, 0, body, 0, offset);
    253                                 Log.v(TAG, "httpConnection: Chunked response length ["
    254                                     + Integer.toString(offset) + "]");
    255                             } else {
    256                                 Log.e(TAG, "httpConnection: Response entity too large or empty");
    257                             }
    258                         } finally {
    259                             try {
    260                                 dis.close();
    261                             } catch (IOException e) {
    262                                 Log.e(TAG, "Error closing input stream: " + e.getMessage());
    263                             }
    264                         }
    265                     }
    266                 } finally {
    267                     if (entity != null) {
    268                         entity.consumeContent();
    269                     }
    270                 }
    271             }
    272             return body;
    273         } catch (URISyntaxException e) {
    274             handleHttpConnectionException(e, url);
    275         } catch (IllegalStateException e) {
    276             handleHttpConnectionException(e, url);
    277         } catch (IllegalArgumentException e) {
    278             handleHttpConnectionException(e, url);
    279         } catch (SocketException e) {
    280             handleHttpConnectionException(e, url);
    281         } catch (Exception e) {
    282             handleHttpConnectionException(e, url);
    283         }
    284         finally {
    285             if (client != null) {
    286                 client.close();
    287             }
    288         }
    289         return null;
    290     }
    291 
    292     private static void handleHttpConnectionException(Exception exception, String url)
    293             throws IOException {
    294         // Inner exception should be logged to make life easier.
    295         Log.e(TAG, "Url: " + url + "\n" + exception.getMessage());
    296         IOException e = new IOException(exception.getMessage());
    297         e.initCause(exception);
    298         throw e;
    299     }
    300 
    301     private static AndroidHttpClient createHttpClient(Context context) {
    302         String userAgent = MmsConfig.getUserAgent();
    303         AndroidHttpClient client = AndroidHttpClient.newInstance(userAgent, context);
    304         HttpParams params = client.getParams();
    305         HttpProtocolParams.setContentCharset(params, "UTF-8");
    306 
    307         // set the socket timeout
    308         int soTimeout = MmsConfig.getHttpSocketTimeout();
    309 
    310         if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) {
    311             Log.d(TAG, "[HttpUtils] createHttpClient w/ socket timeout " + soTimeout + " ms, "
    312                     + ", UA=" + userAgent);
    313         }
    314         HttpConnectionParams.setSoTimeout(params, soTimeout);
    315         return client;
    316     }
    317 
    318     private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US";
    319 
    320     /**
    321      * Return the Accept-Language header.  Use the current locale plus
    322      * US if we are in a different locale than US.
    323      * This code copied from the browser's WebSettings.java
    324      * @return Current AcceptLanguage String.
    325      */
    326     public static String getCurrentAcceptLanguage(Locale locale) {
    327         StringBuilder buffer = new StringBuilder();
    328         addLocaleToHttpAcceptLanguage(buffer, locale);
    329 
    330         if (!Locale.US.equals(locale)) {
    331             if (buffer.length() > 0) {
    332                 buffer.append(", ");
    333             }
    334             buffer.append(ACCEPT_LANG_FOR_US_LOCALE);
    335         }
    336 
    337         return buffer.toString();
    338     }
    339 
    340     /**
    341      * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish,
    342      * to new standard.
    343      */
    344     private static String convertObsoleteLanguageCodeToNew(String langCode) {
    345         if (langCode == null) {
    346             return null;
    347         }
    348         if ("iw".equals(langCode)) {
    349             // Hebrew
    350             return "he";
    351         } else if ("in".equals(langCode)) {
    352             // Indonesian
    353             return "id";
    354         } else if ("ji".equals(langCode)) {
    355             // Yiddish
    356             return "yi";
    357         }
    358         return langCode;
    359     }
    360 
    361     private static void addLocaleToHttpAcceptLanguage(StringBuilder builder,
    362                                                       Locale locale) {
    363         String language = convertObsoleteLanguageCodeToNew(locale.getLanguage());
    364         if (language != null) {
    365             builder.append(language);
    366             String country = locale.getCountry();
    367             if (country != null) {
    368                 builder.append("-");
    369                 builder.append(country);
    370             }
    371         }
    372     }
    373 }
    374