Home | History | Annotate | Download | only in client
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
      5  * use this file except in compliance with the License. You may obtain a copy of
      6  * the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  * License for the specific language governing permissions and limitations under
     14  * the License.
     15  */
     16 
     17 package com.example.android.samplesync.client;
     18 
     19 import android.accounts.Account;
     20 import android.content.Context;
     21 import android.os.Handler;
     22 import android.util.Log;
     23 
     24 import com.example.android.samplesync.authenticator.AuthenticatorActivity;
     25 
     26 import org.apache.http.HttpEntity;
     27 import org.apache.http.HttpResponse;
     28 import org.apache.http.HttpStatus;
     29 import org.apache.http.NameValuePair;
     30 import org.apache.http.ParseException;
     31 import org.apache.http.auth.AuthenticationException;
     32 import org.apache.http.client.HttpClient;
     33 import org.apache.http.client.entity.UrlEncodedFormEntity;
     34 import org.apache.http.client.methods.HttpPost;
     35 import org.apache.http.conn.params.ConnManagerParams;
     36 import org.apache.http.impl.client.DefaultHttpClient;
     37 import org.apache.http.message.BasicNameValuePair;
     38 import org.apache.http.params.HttpConnectionParams;
     39 import org.apache.http.params.HttpParams;
     40 import org.apache.http.util.EntityUtils;
     41 import org.json.JSONArray;
     42 import org.json.JSONException;
     43 
     44 import java.io.IOException;
     45 import java.io.UnsupportedEncodingException;
     46 import java.text.SimpleDateFormat;
     47 import java.util.ArrayList;
     48 import java.util.Date;
     49 import java.util.List;
     50 import java.util.TimeZone;
     51 
     52 /**
     53  * Provides utility methods for communicating with the server.
     54  */
     55 public class NetworkUtilities {
     56     private static final String TAG = "NetworkUtilities";
     57     public static final String PARAM_USERNAME = "username";
     58     public static final String PARAM_PASSWORD = "password";
     59     public static final String PARAM_UPDATED = "timestamp";
     60     public static final String USER_AGENT = "AuthenticationService/1.0";
     61     public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms
     62     public static final String BASE_URL =
     63         "https://samplesyncadapter.appspot.com";
     64     public static final String AUTH_URI = BASE_URL + "/auth";
     65     public static final String FETCH_FRIEND_UPDATES_URI =
     66         BASE_URL + "/fetch_friend_updates";
     67     public static final String FETCH_STATUS_URI = BASE_URL + "/fetch_status";
     68     private static HttpClient mHttpClient;
     69 
     70     /**
     71      * Configures the httpClient to connect to the URL provided.
     72      */
     73     public static void maybeCreateHttpClient() {
     74         if (mHttpClient == null) {
     75             mHttpClient = new DefaultHttpClient();
     76             final HttpParams params = mHttpClient.getParams();
     77             HttpConnectionParams.setConnectionTimeout(params,
     78                 REGISTRATION_TIMEOUT);
     79             HttpConnectionParams.setSoTimeout(params, REGISTRATION_TIMEOUT);
     80             ConnManagerParams.setTimeout(params, REGISTRATION_TIMEOUT);
     81         }
     82     }
     83 
     84     /**
     85      * Executes the network requests on a separate thread.
     86      *
     87      * @param runnable The runnable instance containing network mOperations to
     88      *        be executed.
     89      */
     90     public static Thread performOnBackgroundThread(final Runnable runnable) {
     91         final Thread t = new Thread() {
     92             @Override
     93             public void run() {
     94                 try {
     95                     runnable.run();
     96                 } finally {
     97 
     98                 }
     99             }
    100         };
    101         t.start();
    102         return t;
    103     }
    104 
    105     /**
    106      * Connects to the Voiper server, authenticates the provided username and
    107      * password.
    108      *
    109      * @param username The user's username
    110      * @param password The user's password
    111      * @param handler The hander instance from the calling UI thread.
    112      * @param context The context of the calling Activity.
    113      * @return boolean The boolean result indicating whether the user was
    114      *         successfully authenticated.
    115      */
    116     public static boolean authenticate(String username, String password,
    117         Handler handler, final Context context) {
    118         final HttpResponse resp;
    119 
    120         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
    121         params.add(new BasicNameValuePair(PARAM_USERNAME, username));
    122         params.add(new BasicNameValuePair(PARAM_PASSWORD, password));
    123         HttpEntity entity = null;
    124         try {
    125             entity = new UrlEncodedFormEntity(params);
    126         } catch (final UnsupportedEncodingException e) {
    127             // this should never happen.
    128             throw new AssertionError(e);
    129         }
    130         final HttpPost post = new HttpPost(AUTH_URI);
    131         post.addHeader(entity.getContentType());
    132         post.setEntity(entity);
    133         maybeCreateHttpClient();
    134 
    135         try {
    136             resp = mHttpClient.execute(post);
    137             if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
    138                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    139                     Log.v(TAG, "Successful authentication");
    140                 }
    141                 sendResult(true, handler, context);
    142                 return true;
    143             } else {
    144                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    145                     Log.v(TAG, "Error authenticating" + resp.getStatusLine());
    146                 }
    147                 sendResult(false, handler, context);
    148                 return false;
    149             }
    150         } catch (final IOException e) {
    151             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    152                 Log.v(TAG, "IOException when getting authtoken", e);
    153             }
    154             sendResult(false, handler, context);
    155             return false;
    156         } finally {
    157             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    158                 Log.v(TAG, "getAuthtoken completing");
    159             }
    160         }
    161     }
    162 
    163     /**
    164      * Sends the authentication response from server back to the caller main UI
    165      * thread through its handler.
    166      *
    167      * @param result The boolean holding authentication result
    168      * @param handler The main UI thread's handler instance.
    169      * @param context The caller Activity's context.
    170      */
    171     private static void sendResult(final Boolean result, final Handler handler,
    172         final Context context) {
    173         if (handler == null || context == null) {
    174             return;
    175         }
    176         handler.post(new Runnable() {
    177             public void run() {
    178                 ((AuthenticatorActivity) context).onAuthenticationResult(result);
    179             }
    180         });
    181     }
    182 
    183     /**
    184      * Attempts to authenticate the user credentials on the server.
    185      *
    186      * @param username The user's username
    187      * @param password The user's password to be authenticated
    188      * @param handler The main UI thread's handler instance.
    189      * @param context The caller Activity's context
    190      * @return Thread The thread on which the network mOperations are executed.
    191      */
    192     public static Thread attemptAuth(final String username,
    193         final String password, final Handler handler, final Context context) {
    194         final Runnable runnable = new Runnable() {
    195             public void run() {
    196                 authenticate(username, password, handler, context);
    197             }
    198         };
    199         // run on background thread.
    200         return NetworkUtilities.performOnBackgroundThread(runnable);
    201     }
    202 
    203     /**
    204      * Fetches the list of friend data updates from the server
    205      *
    206      * @param account The account being synced.
    207      * @param authtoken The authtoken stored in AccountManager for this account
    208      * @param lastUpdated The last time that sync was performed
    209      * @return list The list of updates received from the server.
    210      */
    211     public static List<User> fetchFriendUpdates(Account account,
    212         String authtoken, Date lastUpdated) throws JSONException,
    213         ParseException, IOException, AuthenticationException {
    214         final ArrayList<User> friendList = new ArrayList<User>();
    215         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
    216         params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
    217         params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
    218         if (lastUpdated != null) {
    219             final SimpleDateFormat formatter =
    220                 new SimpleDateFormat("yyyy/MM/dd HH:mm");
    221             formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
    222             params.add(new BasicNameValuePair(PARAM_UPDATED, formatter
    223                 .format(lastUpdated)));
    224         }
    225         Log.i(TAG, params.toString());
    226 
    227         HttpEntity entity = null;
    228         entity = new UrlEncodedFormEntity(params);
    229         final HttpPost post = new HttpPost(FETCH_FRIEND_UPDATES_URI);
    230         post.addHeader(entity.getContentType());
    231         post.setEntity(entity);
    232         maybeCreateHttpClient();
    233 
    234         final HttpResponse resp = mHttpClient.execute(post);
    235         final String response = EntityUtils.toString(resp.getEntity());
    236 
    237         if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
    238             // Succesfully connected to the samplesyncadapter server and
    239             // authenticated.
    240             // Extract friends data in json format.
    241             final JSONArray friends = new JSONArray(response);
    242             Log.d(TAG, response);
    243             for (int i = 0; i < friends.length(); i++) {
    244                 friendList.add(User.valueOf(friends.getJSONObject(i)));
    245             }
    246         } else {
    247             if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
    248                 Log.e(TAG,
    249                     "Authentication exception in fetching remote contacts");
    250                 throw new AuthenticationException();
    251             } else {
    252                 Log.e(TAG, "Server error in fetching remote contacts: "
    253                     + resp.getStatusLine());
    254                 throw new IOException();
    255             }
    256         }
    257         return friendList;
    258     }
    259 
    260     /**
    261      * Fetches status messages for the user's friends from the server
    262      *
    263      * @param account The account being synced.
    264      * @param authtoken The authtoken stored in the AccountManager for the
    265      *        account
    266      * @return list The list of status messages received from the server.
    267      */
    268     public static List<User.Status> fetchFriendStatuses(Account account,
    269         String authtoken) throws JSONException, ParseException, IOException,
    270         AuthenticationException {
    271         final ArrayList<User.Status> statusList = new ArrayList<User.Status>();
    272         final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
    273         params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
    274         params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
    275 
    276         HttpEntity entity = null;
    277         entity = new UrlEncodedFormEntity(params);
    278         final HttpPost post = new HttpPost(FETCH_STATUS_URI);
    279         post.addHeader(entity.getContentType());
    280         post.setEntity(entity);
    281         maybeCreateHttpClient();
    282 
    283         final HttpResponse resp = mHttpClient.execute(post);
    284         final String response = EntityUtils.toString(resp.getEntity());
    285 
    286         if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
    287             // Succesfully connected to the samplesyncadapter server and
    288             // authenticated.
    289             // Extract friends data in json format.
    290             final JSONArray statuses = new JSONArray(response);
    291             for (int i = 0; i < statuses.length(); i++) {
    292                 statusList.add(User.Status.valueOf(statuses.getJSONObject(i)));
    293             }
    294         } else {
    295             if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
    296                 Log.e(TAG,
    297                     "Authentication exception in fetching friend status list");
    298                 throw new AuthenticationException();
    299             } else {
    300                 Log.e(TAG, "Server error in fetching friend status list");
    301                 throw new IOException();
    302             }
    303         }
    304         return statusList;
    305     }
    306 
    307 }
    308