| /* |
| * Copyright (C) 2010 The Android Open Source Project |
| * |
| * 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.example.android.samplesync.client; |
| |
| import org.apache.http.HttpEntity; |
| import org.apache.http.HttpResponse; |
| import org.apache.http.HttpStatus; |
| import org.apache.http.NameValuePair; |
| import org.apache.http.ParseException; |
| import org.apache.http.auth.AuthenticationException; |
| import org.apache.http.client.HttpClient; |
| import org.apache.http.client.entity.UrlEncodedFormEntity; |
| import org.apache.http.client.methods.HttpPost; |
| import org.apache.http.conn.params.ConnManagerParams; |
| import org.apache.http.impl.client.DefaultHttpClient; |
| import org.apache.http.message.BasicNameValuePair; |
| import org.apache.http.params.HttpConnectionParams; |
| import org.apache.http.params.HttpParams; |
| import org.apache.http.util.EntityUtils; |
| import org.json.JSONArray; |
| import org.json.JSONException; |
| import org.json.JSONObject; |
| |
| import android.accounts.Account; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.text.TextUtils; |
| import android.util.Log; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.UnsupportedEncodingException; |
| import java.net.HttpURLConnection; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Provides utility methods for communicating with the server. |
| */ |
| final public class NetworkUtilities { |
| /** The tag used to log to adb console. */ |
| private static final String TAG = "NetworkUtilities"; |
| /** POST parameter name for the user's account name */ |
| public static final String PARAM_USERNAME = "username"; |
| /** POST parameter name for the user's password */ |
| public static final String PARAM_PASSWORD = "password"; |
| /** POST parameter name for the user's authentication token */ |
| public static final String PARAM_AUTH_TOKEN = "authtoken"; |
| /** POST parameter name for the client's last-known sync state */ |
| public static final String PARAM_SYNC_STATE = "syncstate"; |
| /** POST parameter name for the sending client-edited contact info */ |
| public static final String PARAM_CONTACTS_DATA = "contacts"; |
| /** Timeout (in ms) we specify for each http request */ |
| public static final int HTTP_REQUEST_TIMEOUT_MS = 30 * 1000; |
| /** Base URL for the v2 Sample Sync Service */ |
| public static final String BASE_URL = "https://samplesyncadapter2.appspot.com"; |
| /** URI for authentication service */ |
| public static final String AUTH_URI = BASE_URL + "/auth"; |
| /** URI for sync service */ |
| public static final String SYNC_CONTACTS_URI = BASE_URL + "/sync"; |
| |
| private NetworkUtilities() { |
| } |
| |
| /** |
| * Configures the httpClient to connect to the URL provided. |
| */ |
| public static HttpClient getHttpClient() { |
| HttpClient httpClient = new DefaultHttpClient(); |
| final HttpParams params = httpClient.getParams(); |
| HttpConnectionParams.setConnectionTimeout(params, HTTP_REQUEST_TIMEOUT_MS); |
| HttpConnectionParams.setSoTimeout(params, HTTP_REQUEST_TIMEOUT_MS); |
| ConnManagerParams.setTimeout(params, HTTP_REQUEST_TIMEOUT_MS); |
| return httpClient; |
| } |
| |
| /** |
| * Connects to the SampleSync test server, authenticates the provided |
| * username and password. |
| * |
| * @param username The server account username |
| * @param password The server account password |
| * @return String The authentication token returned by the server (or null) |
| */ |
| public static String authenticate(String username, String password) { |
| |
| final HttpResponse resp; |
| final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(); |
| params.add(new BasicNameValuePair(PARAM_USERNAME, username)); |
| params.add(new BasicNameValuePair(PARAM_PASSWORD, password)); |
| final HttpEntity entity; |
| try { |
| entity = new UrlEncodedFormEntity(params); |
| } catch (final UnsupportedEncodingException e) { |
| // this should never happen. |
| throw new IllegalStateException(e); |
| } |
| Log.i(TAG, "Authenticating to: " + AUTH_URI); |
| final HttpPost post = new HttpPost(AUTH_URI); |
| post.addHeader(entity.getContentType()); |
| post.setEntity(entity); |
| try { |
| resp = getHttpClient().execute(post); |
| String authToken = null; |
| if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { |
| InputStream istream = (resp.getEntity() != null) ? resp.getEntity().getContent() |
| : null; |
| if (istream != null) { |
| BufferedReader ireader = new BufferedReader(new InputStreamReader(istream)); |
| authToken = ireader.readLine().trim(); |
| } |
| } |
| if ((authToken != null) && (authToken.length() > 0)) { |
| Log.v(TAG, "Successful authentication"); |
| return authToken; |
| } else { |
| Log.e(TAG, "Error authenticating" + resp.getStatusLine()); |
| return null; |
| } |
| } catch (final IOException e) { |
| Log.e(TAG, "IOException when getting authtoken", e); |
| return null; |
| } finally { |
| Log.v(TAG, "getAuthtoken completing"); |
| } |
| } |
| |
| /** |
| * Perform 2-way sync with the server-side contacts. We send a request that |
| * includes all the locally-dirty contacts so that the server can process |
| * those changes, and we receive (and return) a list of contacts that were |
| * updated on the server-side that need to be updated locally. |
| * |
| * @param account The account being synced |
| * @param authtoken The authtoken stored in the AccountManager for this |
| * account |
| * @param serverSyncState A token returned from the server on the last sync |
| * @param dirtyContacts A list of the contacts to send to the server |
| * @return A list of contacts that we need to update locally |
| */ |
| public static List<RawContact> syncContacts( |
| Account account, String authtoken, long serverSyncState, List<RawContact> dirtyContacts) |
| throws JSONException, ParseException, IOException, AuthenticationException { |
| // Convert our list of User objects into a list of JSONObject |
| List<JSONObject> jsonContacts = new ArrayList<JSONObject>(); |
| for (RawContact rawContact : dirtyContacts) { |
| jsonContacts.add(rawContact.toJSONObject()); |
| } |
| |
| // Create a special JSONArray of our JSON contacts |
| JSONArray buffer = new JSONArray(jsonContacts); |
| |
| // Create an array that will hold the server-side contacts |
| // that have been changed (returned by the server). |
| final ArrayList<RawContact> serverDirtyList = new ArrayList<RawContact>(); |
| |
| // Prepare our POST data |
| final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(); |
| params.add(new BasicNameValuePair(PARAM_USERNAME, account.name)); |
| params.add(new BasicNameValuePair(PARAM_AUTH_TOKEN, authtoken)); |
| params.add(new BasicNameValuePair(PARAM_CONTACTS_DATA, buffer.toString())); |
| if (serverSyncState > 0) { |
| params.add(new BasicNameValuePair(PARAM_SYNC_STATE, Long.toString(serverSyncState))); |
| } |
| Log.i(TAG, params.toString()); |
| HttpEntity entity = new UrlEncodedFormEntity(params); |
| |
| // Send the updated friends data to the server |
| Log.i(TAG, "Syncing to: " + SYNC_CONTACTS_URI); |
| final HttpPost post = new HttpPost(SYNC_CONTACTS_URI); |
| post.addHeader(entity.getContentType()); |
| post.setEntity(entity); |
| final HttpResponse resp = getHttpClient().execute(post); |
| final String response = EntityUtils.toString(resp.getEntity()); |
| if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { |
| // Our request to the server was successful - so we assume |
| // that they accepted all the changes we sent up, and |
| // that the response includes the contacts that we need |
| // to update on our side... |
| final JSONArray serverContacts = new JSONArray(response); |
| Log.d(TAG, response); |
| for (int i = 0; i < serverContacts.length(); i++) { |
| RawContact rawContact = RawContact.valueOf(serverContacts.getJSONObject(i)); |
| if (rawContact != null) { |
| serverDirtyList.add(rawContact); |
| } |
| } |
| } else { |
| if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { |
| Log.e(TAG, "Authentication exception in sending dirty contacts"); |
| throw new AuthenticationException(); |
| } else { |
| Log.e(TAG, "Server error in sending dirty contacts: " + resp.getStatusLine()); |
| throw new IOException(); |
| } |
| } |
| |
| return serverDirtyList; |
| } |
| |
| /** |
| * Download the avatar image from the server. |
| * |
| * @param avatarUrl the URL pointing to the avatar image |
| * @return a byte array with the raw JPEG avatar image |
| */ |
| public static byte[] downloadAvatar(final String avatarUrl) { |
| // If there is no avatar, we're done |
| if (TextUtils.isEmpty(avatarUrl)) { |
| return null; |
| } |
| |
| try { |
| Log.i(TAG, "Downloading avatar: " + avatarUrl); |
| // Request the avatar image from the server, and create a bitmap |
| // object from the stream we get back. |
| URL url = new URL(avatarUrl); |
| HttpURLConnection connection = (HttpURLConnection) url.openConnection(); |
| connection.connect(); |
| try { |
| final BitmapFactory.Options options = new BitmapFactory.Options(); |
| final Bitmap avatar = BitmapFactory.decodeStream(connection.getInputStream(), |
| null, options); |
| |
| // Take the image we received from the server, whatever format it |
| // happens to be in, and convert it to a JPEG image. Note: we're |
| // not resizing the avatar - we assume that the image we get from |
| // the server is a reasonable size... |
| Log.i(TAG, "Converting avatar to JPEG"); |
| ByteArrayOutputStream convertStream = new ByteArrayOutputStream( |
| avatar.getWidth() * avatar.getHeight() * 4); |
| avatar.compress(Bitmap.CompressFormat.JPEG, 95, convertStream); |
| convertStream.flush(); |
| convertStream.close(); |
| // On pre-Honeycomb systems, it's important to call recycle on bitmaps |
| avatar.recycle(); |
| return convertStream.toByteArray(); |
| } finally { |
| connection.disconnect(); |
| } |
| } catch (MalformedURLException muex) { |
| // A bad URL - nothing we can really do about it here... |
| Log.e(TAG, "Malformed avatar URL: " + avatarUrl); |
| } catch (IOException ioex) { |
| // If we're unable to download the avatar, it's a bummer but not the |
| // end of the world. We'll try to get it next time we sync. |
| Log.e(TAG, "Failed to download user avatar: " + avatarUrl); |
| } |
| return null; |
| } |
| |
| } |