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