1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
|
/*
* 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;
}
}
|