├── AndroidManifest.xml
├── default.properties
├── res
├── drawable
│ └── icon.png
├── layout
│ └── login_activity.xml
├── values
│ └── strings.xml
└── xml
│ ├── authenticator.xml
│ ├── contacts.xml
│ └── syncadapter.xml
└── src
└── com
├── example
└── android
│ └── samplesync
│ ├── Constants.java
│ ├── authenticator
│ ├── AuthenticationService.java
│ ├── Authenticator.java
│ └── AuthenticatorActivity.java
│ ├── client
│ ├── NetworkUtilities.java
│ └── User.java
│ ├── platform
│ ├── BatchOperation.java
│ ├── ContactManager.java
│ ├── ContactOperations.java
│ └── SampleSyncAdapterColumns.java
│ └── syncadapter
│ ├── SyncAdapter.java
│ └── SyncService.java
└── puny
└── android
└── network
└── util
└── DrupalJSONServerNetworkUtilityBase.java
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
24 |
26 |
28 |
30 |
32 |
34 |
36 |
38 |
40 |
42 |
44 |
46 |
48 |
49 |
52 |
53 |
56 |
57 |
59 |
60 |
63 |
64 |
67 |
68 |
70 |
71 |
74 |
77 |
78 |
84 |
88 |
89 |
90 |
92 |
--------------------------------------------------------------------------------
/default.properties:
--------------------------------------------------------------------------------
1 | # This file is automatically generated by Android Tools.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file must be checked in Version Control Systems.
5 | #
6 | # To customize properties used by the Ant build system use,
7 | # "build.properties", and override values to adapt the script to your
8 | # project structure.
9 |
10 | # Project target.
11 | target=android-7
12 |
--------------------------------------------------------------------------------
/res/drawable/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jbeuckm/Android-SyncAdapter-JSON-Server-Example/81667842a97bffb3f8927a0f38660237ac3115c2/res/drawable/icon.png
--------------------------------------------------------------------------------
/res/layout/login_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
24 |
28 |
37 |
43 |
49 |
59 |
66 |
74 |
85 |
91 |
92 |
93 |
101 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
21 |
22 |
23 | JSON Server Sync Sample
25 |
26 |
27 | access to passwords for Sample SyncAdapter accounts
29 |
30 | Allows applications direct access to the passwords for the
32 | Sample SyncAdapter account(s) you have configured.
33 |
34 |
35 | view configured accounts
37 |
38 | Allows applications to see the usernames (email addresses) of the JSON Server account(s) you have configured.
40 | Touch to sign into your JSON Server account.
42 |
43 |
44 | Sign-in
46 |
47 | Authenticating\u2026
49 |
50 |
51 |
52 |
53 | Username
55 |
56 | Password
58 |
59 | Sign in
61 |
62 |
63 | The username or password isn\'t valid. A JSON Server (Drupal) account is required. Please try again.
65 |
66 | You entered the wrong password or your account has changed.
68 | Please re-enter your password.
69 |
70 | Type the password for this account.
72 |
76 | Sign in to your Sample SyncAdapter account.
78 |
79 |
80 | Sign in
82 |
83 | Back
85 |
86 | Cancel
88 | Sample profile
90 | View Profile
92 |
--------------------------------------------------------------------------------
/res/xml/authenticator.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
29 |
--------------------------------------------------------------------------------
/res/xml/contacts.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/res/xml/syncadapter.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
22 |
23 |
28 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/Constants.java:
--------------------------------------------------------------------------------
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;
18 |
19 | public class Constants {
20 |
21 | /**
22 | * Account type string.
23 | */
24 | public static final String ACCOUNT_TYPE = "com.example.android.samplesync";
25 |
26 | /**
27 | * Authtoken type string.
28 | */
29 | public static final String AUTHTOKEN_TYPE = "com.example.android.samplesync";
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/authenticator/AuthenticationService.java:
--------------------------------------------------------------------------------
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.authenticator;
18 |
19 | import android.app.Service;
20 | import android.content.Intent;
21 | import android.os.IBinder;
22 | import android.util.Log;
23 |
24 | /**
25 | * Service to handle Account authentication. It instantiates the authenticator
26 | * and returns its IBinder.
27 | */
28 | public class AuthenticationService extends Service {
29 | private static final String TAG = "AuthenticationService";
30 | private Authenticator mAuthenticator;
31 |
32 | @Override
33 | public void onCreate() {
34 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
35 | Log.v(TAG, "SampleSyncAdapter Authentication Service started.");
36 | }
37 | mAuthenticator = new Authenticator(this);
38 | }
39 |
40 | @Override
41 | public void onDestroy() {
42 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
43 | Log.v(TAG, "SampleSyncAdapter Authentication Service stopped.");
44 | }
45 | }
46 |
47 | @Override
48 | public IBinder onBind(Intent intent) {
49 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
50 | Log.v(TAG,
51 | "getBinder()... returning the AccountAuthenticator binder for intent "
52 | + intent);
53 | }
54 | return mAuthenticator.getIBinder();
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/authenticator/Authenticator.java:
--------------------------------------------------------------------------------
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.authenticator;
18 |
19 | import android.accounts.AbstractAccountAuthenticator;
20 | import android.accounts.Account;
21 | import android.accounts.AccountAuthenticatorResponse;
22 | import android.accounts.AccountManager;
23 | import android.content.Context;
24 | import android.content.Intent;
25 | import android.os.Bundle;
26 |
27 | import com.example.android.samplesync.Constants;
28 | import com.example.android.samplesync.R;
29 | import com.example.android.samplesync.client.NetworkUtilities;
30 |
31 | /**
32 | * This class is an implementation of AbstractAccountAuthenticator for
33 | * authenticating accounts in the com.example.android.samplesync domain.
34 | */
35 | class Authenticator extends AbstractAccountAuthenticator {
36 | // Authentication Service context
37 | private final Context mContext;
38 |
39 | public Authenticator(Context context) {
40 | super(context);
41 | mContext = context;
42 | }
43 |
44 | /**
45 | * {@inheritDoc}
46 | */
47 | @Override
48 | public Bundle addAccount(AccountAuthenticatorResponse response,
49 | String accountType, String authTokenType, String[] requiredFeatures,
50 | Bundle options) {
51 | final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
52 | intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
53 | authTokenType);
54 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
55 | response);
56 | final Bundle bundle = new Bundle();
57 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
58 | return bundle;
59 | }
60 |
61 | /**
62 | * {@inheritDoc}
63 | */
64 | @Override
65 | public Bundle confirmCredentials(AccountAuthenticatorResponse response,
66 | Account account, Bundle options) {
67 | if (options != null && options.containsKey(AccountManager.KEY_PASSWORD)) {
68 | final String password =
69 | options.getString(AccountManager.KEY_PASSWORD);
70 | final boolean verified =
71 | onlineConfirmPassword(account.name, password);
72 | final Bundle result = new Bundle();
73 | result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, verified);
74 | return result;
75 | }
76 | // Launch AuthenticatorActivity to confirm credentials
77 | final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
78 | intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
79 | intent.putExtra(AuthenticatorActivity.PARAM_CONFIRMCREDENTIALS, true);
80 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
81 | response);
82 | final Bundle bundle = new Bundle();
83 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
84 | return bundle;
85 | }
86 |
87 | /**
88 | * {@inheritDoc}
89 | */
90 | @Override
91 | public Bundle editProperties(AccountAuthenticatorResponse response,
92 | String accountType) {
93 | throw new UnsupportedOperationException();
94 | }
95 |
96 | /**
97 | * {@inheritDoc}
98 | */
99 | @Override
100 | public Bundle getAuthToken(AccountAuthenticatorResponse response,
101 | Account account, String authTokenType, Bundle loginOptions) {
102 | if (!authTokenType.equals(Constants.AUTHTOKEN_TYPE)) {
103 | final Bundle result = new Bundle();
104 | result.putString(AccountManager.KEY_ERROR_MESSAGE,
105 | "invalid authTokenType");
106 | return result;
107 | }
108 | final AccountManager am = AccountManager.get(mContext);
109 | final String password = am.getPassword(account);
110 | if (password != null) {
111 | final boolean verified =
112 | onlineConfirmPassword(account.name, password);
113 | if (verified) {
114 | final Bundle result = new Bundle();
115 | result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
116 | result.putString(AccountManager.KEY_ACCOUNT_TYPE,
117 | Constants.ACCOUNT_TYPE);
118 | result.putString(AccountManager.KEY_AUTHTOKEN, password);
119 | return result;
120 | }
121 | }
122 | // the password was missing or incorrect, return an Intent to an
123 | // Activity that will prompt the user for the password.
124 | final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
125 | intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
126 | intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
127 | authTokenType);
128 | intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
129 | response);
130 | final Bundle bundle = new Bundle();
131 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
132 | return bundle;
133 | }
134 |
135 | /**
136 | * {@inheritDoc}
137 | */
138 | @Override
139 | public String getAuthTokenLabel(String authTokenType) {
140 | if (authTokenType.equals(Constants.AUTHTOKEN_TYPE)) {
141 | return mContext.getString(R.string.label);
142 | }
143 | return null;
144 |
145 | }
146 |
147 | /**
148 | * {@inheritDoc}
149 | */
150 | @Override
151 | public Bundle hasFeatures(AccountAuthenticatorResponse response,
152 | Account account, String[] features) {
153 | final Bundle result = new Bundle();
154 | result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
155 | return result;
156 | }
157 |
158 | /**
159 | * Validates user's password on the server
160 | */
161 | private boolean onlineConfirmPassword(String username, String password) {
162 | return NetworkUtilities.authenticate(username, password,
163 | null/* Handler */, null/* Context */);
164 | }
165 |
166 | /**
167 | * {@inheritDoc}
168 | */
169 | @Override
170 | public Bundle updateCredentials(AccountAuthenticatorResponse response,
171 | Account account, String authTokenType, Bundle loginOptions) {
172 | final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
173 | intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
174 | intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
175 | authTokenType);
176 | intent.putExtra(AuthenticatorActivity.PARAM_CONFIRMCREDENTIALS, false);
177 | final Bundle bundle = new Bundle();
178 | bundle.putParcelable(AccountManager.KEY_INTENT, intent);
179 | return bundle;
180 | }
181 |
182 | }
183 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/authenticator/AuthenticatorActivity.java:
--------------------------------------------------------------------------------
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.authenticator;
18 |
19 | import android.accounts.Account;
20 | import android.accounts.AccountAuthenticatorActivity;
21 | import android.accounts.AccountManager;
22 | import android.app.Dialog;
23 | import android.app.ProgressDialog;
24 | import android.content.ContentResolver;
25 | import android.content.DialogInterface;
26 | import android.content.Intent;
27 | import android.os.Bundle;
28 | import android.os.Handler;
29 | import android.provider.ContactsContract;
30 | import android.text.TextUtils;
31 | import android.util.Log;
32 | import android.view.View;
33 | import android.view.Window;
34 | import android.widget.EditText;
35 | import android.widget.TextView;
36 |
37 | import com.example.android.samplesync.Constants;
38 | import com.example.android.samplesync.R;
39 | import com.example.android.samplesync.client.NetworkUtilities;
40 |
41 | /**
42 | * Activity which displays login screen to the user.
43 | */
44 | public class AuthenticatorActivity extends AccountAuthenticatorActivity {
45 | public static final String PARAM_CONFIRMCREDENTIALS = "confirmCredentials";
46 | public static final String PARAM_PASSWORD = "password";
47 | public static final String PARAM_USERNAME = "username";
48 | public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";
49 |
50 | private static final String TAG = "AuthenticatorActivity";
51 |
52 | private AccountManager mAccountManager;
53 | private Thread mAuthThread;
54 | private String mAuthtoken;
55 | private String mAuthtokenType;
56 |
57 | /**
58 | * If set we are just checking that the user knows their credentials; this
59 | * doesn't cause the user's password to be changed on the device.
60 | */
61 | private Boolean mConfirmCredentials = false;
62 |
63 | /** for posting authentication attempts back to UI thread */
64 | private final Handler mHandler = new Handler();
65 | private TextView mMessage;
66 | private String mPassword;
67 | private EditText mPasswordEdit;
68 |
69 | /** Was the original caller asking for an entirely new account? */
70 | protected boolean mRequestNewAccount = false;
71 |
72 | private String mUsername;
73 | private EditText mUsernameEdit;
74 |
75 | /**
76 | * {@inheritDoc}
77 | */
78 | @Override
79 | public void onCreate(Bundle icicle) {
80 | Log.i(TAG, "onCreate(" + icicle + ")");
81 | super.onCreate(icicle);
82 | mAccountManager = AccountManager.get(this);
83 | Log.i(TAG, "loading data from Intent");
84 | final Intent intent = getIntent();
85 | mUsername = intent.getStringExtra(PARAM_USERNAME);
86 | mAuthtokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE);
87 | mRequestNewAccount = mUsername == null;
88 | mConfirmCredentials =
89 | intent.getBooleanExtra(PARAM_CONFIRMCREDENTIALS, false);
90 |
91 | Log.i(TAG, " request new: " + mRequestNewAccount);
92 | requestWindowFeature(Window.FEATURE_LEFT_ICON);
93 | setContentView(R.layout.login_activity);
94 | getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
95 | android.R.drawable.ic_dialog_alert);
96 |
97 | mMessage = (TextView) findViewById(R.id.message);
98 | mUsernameEdit = (EditText) findViewById(R.id.username_edit);
99 | mPasswordEdit = (EditText) findViewById(R.id.password_edit);
100 |
101 | mUsernameEdit.setText(mUsername);
102 | mMessage.setText(getMessage());
103 | }
104 |
105 | /*
106 | * {@inheritDoc}
107 | */
108 | @Override
109 | protected Dialog onCreateDialog(int id) {
110 | final ProgressDialog dialog = new ProgressDialog(this);
111 | dialog.setMessage(getText(R.string.ui_activity_authenticating));
112 | dialog.setIndeterminate(true);
113 | dialog.setCancelable(true);
114 | dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
115 | public void onCancel(DialogInterface dialog) {
116 | Log.i(TAG, "dialog cancel has been invoked");
117 | if (mAuthThread != null) {
118 | mAuthThread.interrupt();
119 | finish();
120 | }
121 | }
122 | });
123 | return dialog;
124 | }
125 |
126 | /**
127 | * Handles onClick event on the Submit button. Sends username/password to
128 | * the server for authentication.
129 | *
130 | * @param view The Submit button for which this method is invoked
131 | */
132 | public void handleLogin(View view) {
133 | if (mRequestNewAccount) {
134 | mUsername = mUsernameEdit.getText().toString();
135 | }
136 | mPassword = mPasswordEdit.getText().toString();
137 | if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) {
138 | mMessage.setText(getMessage());
139 | } else {
140 | showProgress();
141 | // Start authenticating...
142 | mAuthThread =
143 | NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler,
144 | AuthenticatorActivity.this);
145 | }
146 | }
147 |
148 | /**
149 | * Called when response is received from the server for confirm credentials
150 | * request. See onAuthenticationResult(). Sets the
151 | * AccountAuthenticatorResult which is sent back to the caller.
152 | *
153 | * @param the confirmCredentials result.
154 | */
155 | protected void finishConfirmCredentials(boolean result) {
156 | Log.i(TAG, "finishConfirmCredentials()");
157 | final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
158 | mAccountManager.setPassword(account, mPassword);
159 | final Intent intent = new Intent();
160 | intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
161 | setAccountAuthenticatorResult(intent.getExtras());
162 | setResult(RESULT_OK, intent);
163 | finish();
164 | }
165 |
166 | /**
167 | *
168 | * Called when response is received from the server for authentication
169 | * request. See onAuthenticationResult(). Sets the
170 | * AccountAuthenticatorResult which is sent back to the caller. Also sets
171 | * the authToken in AccountManager for this account.
172 | *
173 | * @param the confirmCredentials result.
174 | */
175 |
176 | protected void finishLogin() {
177 | Log.i(TAG, "finishLogin()");
178 | final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
179 |
180 | if (mRequestNewAccount) {
181 | mAccountManager.addAccountExplicitly(account, mPassword, null);
182 | // Set contacts sync for this account.
183 | ContentResolver.setSyncAutomatically(account,
184 | ContactsContract.AUTHORITY, true);
185 | } else {
186 | mAccountManager.setPassword(account, mPassword);
187 | }
188 | final Intent intent = new Intent();
189 | mAuthtoken = mPassword;
190 | intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
191 | intent
192 | .putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
193 | if (mAuthtokenType != null
194 | && mAuthtokenType.equals(Constants.AUTHTOKEN_TYPE)) {
195 | intent.putExtra(AccountManager.KEY_AUTHTOKEN, mAuthtoken);
196 | }
197 | setAccountAuthenticatorResult(intent.getExtras());
198 | setResult(RESULT_OK, intent);
199 | finish();
200 | }
201 |
202 | /**
203 | * Hides the progress UI for a lengthy operation.
204 | */
205 | protected void hideProgress() {
206 | dismissDialog(0);
207 | }
208 |
209 | /**
210 | * Called when the authentication process completes (see attemptLogin()).
211 | */
212 | public void onAuthenticationResult(boolean result) {
213 | Log.i(TAG, "onAuthenticationResult(" + result + ")");
214 | // Hide the progress dialog
215 | hideProgress();
216 | if (result) {
217 | if (!mConfirmCredentials) {
218 | finishLogin();
219 | } else {
220 | finishConfirmCredentials(true);
221 | }
222 | } else {
223 | Log.e(TAG, "onAuthenticationResult: failed to authenticate");
224 | if (mRequestNewAccount) {
225 | // "Please enter a valid username/password.
226 | mMessage
227 | .setText(getText(R.string.login_activity_loginfail_text_both));
228 | } else {
229 | // "Please enter a valid password." (Used when the
230 | // account is already in the database but the password
231 | // doesn't work.)
232 | mMessage
233 | .setText(getText(R.string.login_activity_loginfail_text_pwonly));
234 | }
235 | }
236 | }
237 |
238 | /**
239 | * Returns the message to be displayed at the top of the login dialog box.
240 | */
241 | private CharSequence getMessage() {
242 | getString(R.string.label);
243 | if (TextUtils.isEmpty(mUsername)) {
244 | // If no username, then we ask the user to log in using an
245 | // appropriate service.
246 | final CharSequence msg =
247 | getText(R.string.login_activity_newaccount_text);
248 | return msg;
249 | }
250 | if (TextUtils.isEmpty(mPassword)) {
251 | // We have an account but no password
252 | return getText(R.string.login_activity_loginfail_text_pwmissing);
253 | }
254 | return null;
255 | }
256 |
257 | /**
258 | * Shows the progress UI for a lengthy operation.
259 | */
260 | protected void showProgress() {
261 | showDialog(0);
262 | }
263 | }
264 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/client/NetworkUtilities.java:
--------------------------------------------------------------------------------
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 com.puny.android.network.util.DrupalJSONServerNetworkUtilityBase;
20 |
21 | import android.accounts.Account;
22 | import android.content.Context;
23 | import android.os.Handler;
24 | import android.util.Log;
25 |
26 | import com.example.android.samplesync.authenticator.AuthenticatorActivity;
27 |
28 | import org.apache.http.HttpEntity;
29 | import org.apache.http.HttpResponse;
30 | import org.apache.http.HttpStatus;
31 | import org.apache.http.NameValuePair;
32 | import org.apache.http.ParseException;
33 | import org.apache.http.auth.AuthenticationException;
34 | import org.apache.http.client.entity.UrlEncodedFormEntity;
35 | import org.apache.http.client.methods.HttpPost;
36 | import org.apache.http.message.BasicNameValuePair;
37 | import org.apache.http.util.EntityUtils;
38 |
39 | import org.json.JSONArray;
40 | import org.json.JSONException;
41 |
42 | import java.io.IOException;
43 | import java.text.SimpleDateFormat;
44 | import java.util.ArrayList;
45 | import java.util.Date;
46 | import java.util.List;
47 | import java.util.TimeZone;
48 |
49 | /**
50 | * Provides utility methods for communicating with the server.
51 | */
52 | public class NetworkUtilities extends DrupalJSONServerNetworkUtilityBase {
53 |
54 | public static final String PARAM_UPDATED = "timestamp";
55 | public static final String FETCH_FRIEND_UPDATES_URI = BASE_URL + "/fetch_friend_updates";
56 | public static final String FETCH_STATUS_URI = BASE_URL + "/fetch_status";
57 |
58 | /**
59 | * Attempts to authenticate the user credentials on a service.
60 | *
61 | * @param username The user's username
62 | * @param password The user's password to be authenticated
63 | * @param handler The main UI thread's handler instance.
64 | * @param context The caller Activity's context
65 | *
66 | * @return Thread The thread on which the network mOperations are executed.
67 | */
68 | public static Thread attemptAuth(final String username,
69 | final String password, final Handler handler, final Context context) {
70 | final Runnable runnable = new Runnable() {
71 | public void run() {
72 | boolean result = authenticate(username, password, handler, context);
73 | sendResult(result, handler, context);
74 | }
75 | };
76 | // run on background thread.
77 | return NetworkUtilities.performOnBackgroundThread(runnable);
78 | }
79 |
80 |
81 | /**
82 | * Fetches the list of friend data updates from the server
83 | *
84 | * @param account The account being synced.
85 | * @param authtoken The authtoken stored in AccountManager for this account
86 | * @param lastUpdated The last time that sync was performed
87 | * @return list The list of updates received from the server.
88 | */
89 | public static List fetchFriendUpdates(Account account,
90 | String authtoken, Date lastUpdated) throws JSONException,
91 | ParseException, IOException, AuthenticationException {
92 | final ArrayList friendList = new ArrayList();
93 | final ArrayList params = new ArrayList();
94 | params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
95 | params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
96 | if (lastUpdated != null) {
97 | final SimpleDateFormat formatter =
98 | new SimpleDateFormat("yyyy/MM/dd HH:mm");
99 | formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
100 | params.add(new BasicNameValuePair(PARAM_UPDATED, formatter
101 | .format(lastUpdated)));
102 | }
103 | Log.i(TAG, params.toString());
104 |
105 | HttpEntity entity = null;
106 | entity = new UrlEncodedFormEntity(params);
107 | final HttpPost post = new HttpPost(FETCH_FRIEND_UPDATES_URI);
108 | post.addHeader(entity.getContentType());
109 | post.setEntity(entity);
110 | maybeCreateHttpClient();
111 |
112 | final HttpResponse resp = mHttpClient.execute(post);
113 | final String response = EntityUtils.toString(resp.getEntity());
114 |
115 | if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
116 | // Succesfully connected to the samplesyncadapter server and
117 | // authenticated.
118 | // Extract friends data in json format.
119 | final JSONArray friends = new JSONArray(response);
120 | Log.d(TAG, response);
121 | for (int i = 0; i < friends.length(); i++) {
122 | friendList.add(User.valueOf(friends.getJSONObject(i)));
123 | }
124 | } else {
125 | if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
126 | Log.e(TAG,
127 | "Authentication exception in fetching remote contacts");
128 | throw new AuthenticationException();
129 | } else {
130 | Log.e(TAG, "Server error in fetching remote contacts: "
131 | + resp.getStatusLine());
132 | throw new IOException();
133 | }
134 | }
135 | return friendList;
136 | }
137 |
138 | /**
139 | * Fetches status messages for the user's friends from the server
140 | *
141 | * @param account The account being synced.
142 | * @param authtoken The authtoken stored in the AccountManager for the
143 | * account
144 | * @return list The list of status messages received from the server.
145 | */
146 | public static List fetchFriendStatuses(Account account,
147 | String authtoken) throws JSONException, ParseException, IOException,
148 | AuthenticationException {
149 |
150 | final ArrayList statusList = new ArrayList();
151 | final ArrayList params = new ArrayList();
152 | params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
153 | params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
154 |
155 | HttpEntity entity = null;
156 | entity = new UrlEncodedFormEntity(params);
157 | final HttpPost post = new HttpPost(FETCH_STATUS_URI);
158 | post.addHeader(entity.getContentType());
159 | post.setEntity(entity);
160 | maybeCreateHttpClient();
161 |
162 | final HttpResponse resp = mHttpClient.execute(post);
163 | final String response = EntityUtils.toString(resp.getEntity());
164 |
165 | if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
166 | // Extract friends data in json format.
167 | final JSONArray statuses = new JSONArray(response);
168 | for (int i = 0; i < statuses.length(); i++) {
169 | statusList.add(User.Status.valueOf(statuses.getJSONObject(i)));
170 | }
171 | } else {
172 | if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
173 | Log.e(TAG,
174 | "Authentication exception in fetching friend status list");
175 | throw new AuthenticationException();
176 | } else {
177 | Log.e(TAG, "Server error in fetching friend status list");
178 | throw new IOException();
179 | }
180 | }
181 | return statusList;
182 | }
183 |
184 |
185 | /**
186 | * Sends the authentication response from server back to the caller main UI
187 | * thread through its handler.
188 | *
189 | * @param result The boolean holding authentication result
190 | * @param handler The main UI thread's handler instance.
191 | * @param context The caller Activity's context.
192 | */
193 | protected static void sendResult(final Boolean result, final Handler handler,
194 | final Context context) {
195 | if (handler == null || context == null) {
196 | return;
197 | }
198 | handler.post(new Runnable() {
199 | public void run() {
200 | ((AuthenticatorActivity) context).onAuthenticationResult(result);
201 | }
202 | });
203 | }
204 | }
205 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/client/User.java:
--------------------------------------------------------------------------------
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.util.Log;
20 |
21 | import org.json.JSONObject;
22 |
23 | /**
24 | * Represents a sample SyncAdapter user
25 | */
26 | public class User {
27 |
28 | private final String mUserName;
29 | private final String mFirstName;
30 | private final String mLastName;
31 | private final String mCellPhone;
32 | private final String mOfficePhone;
33 | private final String mHomePhone;
34 | private final String mEmail;
35 | private final boolean mDeleted;
36 | private final int mUserId;
37 |
38 | public int getUserId() {
39 | return mUserId;
40 | }
41 |
42 | public String getUserName() {
43 | return mUserName;
44 | }
45 |
46 | public String getFirstName() {
47 | return mFirstName;
48 | }
49 |
50 | public String getLastName() {
51 | return mLastName;
52 | }
53 |
54 | public String getCellPhone() {
55 | return mCellPhone;
56 | }
57 |
58 | public String getOfficePhone() {
59 | return mOfficePhone;
60 | }
61 |
62 | public String getHomePhone() {
63 | return mHomePhone;
64 | }
65 |
66 | public String getEmail() {
67 | return mEmail;
68 | }
69 |
70 | public boolean isDeleted() {
71 | return mDeleted;
72 | }
73 |
74 | public User(String name, String firstName, String lastName,
75 | String cellPhone, String officePhone, String homePhone, String email,
76 | Boolean deleted, Integer userId) {
77 | mUserName = name;
78 | mFirstName = firstName;
79 | mLastName = lastName;
80 | mCellPhone = cellPhone;
81 | mOfficePhone = officePhone;
82 | mHomePhone = homePhone;
83 | mEmail = email;
84 | mDeleted = deleted;
85 | mUserId = userId;
86 | }
87 |
88 | /**
89 | * Creates and returns an instance of the user from the provided JSON data.
90 | *
91 | * @param user The JSONObject containing user data
92 | * @return user The new instance of Voiper user created from the JSON data.
93 | */
94 | public static User valueOf(JSONObject user) {
95 | try {
96 | final String userName = user.getString("u");
97 | final String firstName = user.has("f") ? user.getString("f") : null;
98 | final String lastName = user.has("l") ? user.getString("l") : null;
99 | final String cellPhone = user.has("m") ? user.getString("m") : null;
100 | final String officePhone =
101 | user.has("o") ? user.getString("o") : null;
102 | final String homePhone = user.has("h") ? user.getString("h") : null;
103 | final String email = user.has("e") ? user.getString("e") : null;
104 | final boolean deleted =
105 | user.has("d") ? user.getBoolean("d") : false;
106 | final int userId = user.getInt("i");
107 | return new User(userName, firstName, lastName, cellPhone,
108 | officePhone, homePhone, email, deleted, userId);
109 | } catch (final Exception ex) {
110 | Log.i("User", "Error parsing JSON user object" + ex.toString());
111 |
112 | }
113 | return null;
114 |
115 | }
116 |
117 | /**
118 | * Represents the User's status messages
119 | *
120 | */
121 | public static class Status {
122 | private final Integer mUserId;
123 | private final String mStatus;
124 |
125 | public int getUserId() {
126 | return mUserId;
127 | }
128 |
129 | public String getStatus() {
130 | return mStatus;
131 | }
132 |
133 | public Status(Integer userId, String status) {
134 | mUserId = userId;
135 | mStatus = status;
136 | }
137 |
138 | public static User.Status valueOf(JSONObject userStatus) {
139 | try {
140 | final int userId = userStatus.getInt("i");
141 | final String status = userStatus.getString("s");
142 | return new User.Status(userId, status);
143 | } catch (final Exception ex) {
144 | Log.i("User.Status", "Error parsing JSON user object");
145 | }
146 | return null;
147 | }
148 | }
149 |
150 | }
151 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/platform/BatchOperation.java:
--------------------------------------------------------------------------------
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.platform;
18 |
19 | import android.content.ContentProviderOperation;
20 | import android.content.ContentResolver;
21 | import android.content.Context;
22 | import android.content.OperationApplicationException;
23 | import android.os.RemoteException;
24 | import android.provider.ContactsContract;
25 | import android.util.Log;
26 |
27 | import java.util.ArrayList;
28 |
29 | /**
30 | * This class handles execution of batch mOperations on Contacts provider.
31 | */
32 | public class BatchOperation {
33 | private final String TAG = "BatchOperation";
34 |
35 | private final ContentResolver mResolver;
36 | // List for storing the batch mOperations
37 | ArrayList mOperations;
38 |
39 | public BatchOperation(Context context, ContentResolver resolver) {
40 | mResolver = resolver;
41 | mOperations = new ArrayList();
42 | }
43 |
44 | public int size() {
45 | return mOperations.size();
46 | }
47 |
48 | public void add(ContentProviderOperation cpo) {
49 | mOperations.add(cpo);
50 | }
51 |
52 | public void execute() {
53 | if (mOperations.size() == 0) {
54 | return;
55 | }
56 | // Apply the mOperations to the content provider
57 | try {
58 | mResolver.applyBatch(ContactsContract.AUTHORITY, mOperations);
59 | } catch (final OperationApplicationException e1) {
60 | Log.e(TAG, "storing contact data failed", e1);
61 | } catch (final RemoteException e2) {
62 | Log.e(TAG, "storing contact data failed", e2);
63 | }
64 | mOperations.clear();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/platform/ContactManager.java:
--------------------------------------------------------------------------------
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.platform;
18 |
19 | import android.content.ContentResolver;
20 | import android.content.ContentUris;
21 | import android.content.ContentValues;
22 | import android.content.Context;
23 | import android.database.Cursor;
24 | import android.net.Uri;
25 | import android.provider.ContactsContract.Data;
26 | import android.provider.ContactsContract.RawContacts;
27 | import android.provider.ContactsContract.StatusUpdates;
28 | import android.provider.ContactsContract.CommonDataKinds.Email;
29 | import android.provider.ContactsContract.CommonDataKinds.Im;
30 | import android.provider.ContactsContract.CommonDataKinds.Phone;
31 | import android.provider.ContactsContract.CommonDataKinds.StructuredName;
32 | import android.util.Log;
33 |
34 | import com.example.android.samplesync.Constants;
35 | import com.example.android.samplesync.R;
36 | import com.example.android.samplesync.client.User;
37 |
38 | import java.util.List;
39 |
40 | /**
41 | * Class for managing contacts sync related mOperations
42 | */
43 | public class ContactManager {
44 | /**
45 | * Custom IM protocol used when storing status messages.
46 | */
47 | public static final String CUSTOM_IM_PROTOCOL = "SampleSyncAdapter";
48 | private static final String TAG = "ContactManager";
49 |
50 | /**
51 | * Synchronize raw contacts
52 | *
53 | * @param context The context of Authenticator Activity
54 | * @param account The username for the account
55 | * @param users The list of users
56 | */
57 | public static synchronized void syncContacts(Context context,
58 | String account, List users) {
59 | long userId;
60 | long rawContactId = 0;
61 | final ContentResolver resolver = context.getContentResolver();
62 | final BatchOperation batchOperation =
63 | new BatchOperation(context, resolver);
64 | Log.d(TAG, "In SyncContacts");
65 | for (final User user : users) {
66 | userId = user.getUserId();
67 | // Check to see if the contact needs to be inserted or updated
68 | rawContactId = lookupRawContact(resolver, userId);
69 | if (rawContactId != 0) {
70 | if (!user.isDeleted()) {
71 | // update contact
72 | updateContact(context, resolver, account, user,
73 | rawContactId, batchOperation);
74 | } else {
75 | // delete contact
76 | deleteContact(context, rawContactId, batchOperation);
77 | }
78 | } else {
79 | // add new contact
80 | Log.d(TAG, "In addContact");
81 | if (!user.isDeleted()) {
82 | addContact(context, account, user, batchOperation);
83 | }
84 | }
85 | // A sync adapter should batch operations on multiple contacts,
86 | // because it will make a dramatic performance difference.
87 | if (batchOperation.size() >= 50) {
88 | batchOperation.execute();
89 | }
90 | }
91 | batchOperation.execute();
92 | }
93 |
94 | /**
95 | * Add a list of status messages to the contacts provider.
96 | *
97 | * @param context the context to use
98 | * @param accountName the username of the logged in user
99 | * @param statuses the list of statuses to store
100 | */
101 | public static void insertStatuses(Context context, String username,
102 | List list) {
103 | final ContentValues values = new ContentValues();
104 | final ContentResolver resolver = context.getContentResolver();
105 | final BatchOperation batchOperation =
106 | new BatchOperation(context, resolver);
107 | for (final User.Status status : list) {
108 | // Look up the user's sample SyncAdapter data row
109 | final long userId = status.getUserId();
110 | final long profileId = lookupProfile(resolver, userId);
111 |
112 | // Insert the activity into the stream
113 | if (profileId > 0) {
114 | values.put(StatusUpdates.DATA_ID, profileId);
115 | values.put(StatusUpdates.STATUS, status.getStatus());
116 | values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
117 | values.put(StatusUpdates.CUSTOM_PROTOCOL, CUSTOM_IM_PROTOCOL);
118 | values.put(StatusUpdates.IM_ACCOUNT, username);
119 | values.put(StatusUpdates.IM_HANDLE, status.getUserId());
120 | values.put(StatusUpdates.STATUS_RES_PACKAGE, context
121 | .getPackageName());
122 | values.put(StatusUpdates.STATUS_ICON, R.drawable.icon);
123 | values.put(StatusUpdates.STATUS_LABEL, R.string.label);
124 |
125 | batchOperation
126 | .add(ContactOperations.newInsertCpo(
127 | StatusUpdates.CONTENT_URI, true).withValues(values)
128 | .build());
129 | // A sync adapter should batch operations on multiple contacts,
130 | // because it will make a dramatic performance difference.
131 | if (batchOperation.size() >= 50) {
132 | batchOperation.execute();
133 | }
134 | }
135 | }
136 | batchOperation.execute();
137 | }
138 |
139 | /**
140 | * Adds a single contact to the platform contacts provider.
141 | *
142 | * @param context the Authenticator Activity context
143 | * @param accountName the account the contact belongs to
144 | * @param user the sample SyncAdapter User object
145 | */
146 | private static void addContact(Context context, String accountName,
147 | User user, BatchOperation batchOperation) {
148 | // Put the data in the contacts provider
149 | final ContactOperations contactOp =
150 | ContactOperations.createNewContact(context, user.getUserId(),
151 | accountName, batchOperation);
152 | contactOp.addName(user.getFirstName(), user.getLastName()).addEmail(
153 | user.getEmail()).addPhone(user.getCellPhone(), Phone.TYPE_MOBILE)
154 | .addPhone(user.getHomePhone(), Phone.TYPE_OTHER).addProfileAction(
155 | user.getUserId());
156 | }
157 |
158 | /**
159 | * Updates a single contact to the platform contacts provider.
160 | *
161 | * @param context the Authenticator Activity context
162 | * @param resolver the ContentResolver to use
163 | * @param accountName the account the contact belongs to
164 | * @param user the sample SyncAdapter contact object.
165 | * @param rawContactId the unique Id for this rawContact in contacts
166 | * provider
167 | */
168 | private static void updateContact(Context context,
169 | ContentResolver resolver, String accountName, User user,
170 | long rawContactId, BatchOperation batchOperation) {
171 | Uri uri;
172 | String cellPhone = null;
173 | String otherPhone = null;
174 | String email = null;
175 |
176 | final Cursor c =
177 | resolver.query(Data.CONTENT_URI, DataQuery.PROJECTION,
178 | DataQuery.SELECTION,
179 | new String[] {String.valueOf(rawContactId)}, null);
180 | final ContactOperations contactOp =
181 | ContactOperations.updateExistingContact(context, rawContactId,
182 | batchOperation);
183 |
184 | try {
185 | while (c.moveToNext()) {
186 | final long id = c.getLong(DataQuery.COLUMN_ID);
187 | final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
188 | uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
189 |
190 | if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
191 | final String lastName =
192 | c.getString(DataQuery.COLUMN_FAMILY_NAME);
193 | final String firstName =
194 | c.getString(DataQuery.COLUMN_GIVEN_NAME);
195 | contactOp.updateName(uri, firstName, lastName, user
196 | .getFirstName(), user.getLastName());
197 | }
198 |
199 | else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
200 | final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
201 |
202 | if (type == Phone.TYPE_MOBILE) {
203 | cellPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
204 | contactOp.updatePhone(cellPhone, user.getCellPhone(),
205 | uri);
206 | } else if (type == Phone.TYPE_OTHER) {
207 | otherPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
208 | contactOp.updatePhone(otherPhone, user.getHomePhone(),
209 | uri);
210 | }
211 | }
212 |
213 | else if (Data.MIMETYPE.equals(Email.CONTENT_ITEM_TYPE)) {
214 | email = c.getString(DataQuery.COLUMN_EMAIL_ADDRESS);
215 | contactOp.updateEmail(user.getEmail(), email, uri);
216 |
217 | }
218 | } // while
219 | } finally {
220 | c.close();
221 | }
222 |
223 | // Add the cell phone, if present and not updated above
224 | if (cellPhone == null) {
225 | contactOp.addPhone(user.getCellPhone(), Phone.TYPE_MOBILE);
226 | }
227 |
228 | // Add the other phone, if present and not updated above
229 | if (otherPhone == null) {
230 | contactOp.addPhone(user.getHomePhone(), Phone.TYPE_OTHER);
231 | }
232 |
233 | // Add the email address, if present and not updated above
234 | if (email == null) {
235 | contactOp.addEmail(user.getEmail());
236 | }
237 |
238 | }
239 |
240 | /**
241 | * Deletes a contact from the platform contacts provider.
242 | *
243 | * @param context the Authenticator Activity context
244 | * @param rawContactId the unique Id for this rawContact in contacts
245 | * provider
246 | */
247 | private static void deleteContact(Context context, long rawContactId,
248 | BatchOperation batchOperation) {
249 | batchOperation.add(ContactOperations.newDeleteCpo(
250 | ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
251 | true).build());
252 | }
253 |
254 | /**
255 | * Returns the RawContact id for a sample SyncAdapter contact, or 0 if the
256 | * sample SyncAdapter user isn't found.
257 | *
258 | * @param context the Authenticator Activity context
259 | * @param userId the sample SyncAdapter user ID to lookup
260 | * @return the RawContact id, or 0 if not found
261 | */
262 | private static long lookupRawContact(ContentResolver resolver, long userId) {
263 | long authorId = 0;
264 | final Cursor c =
265 | resolver.query(RawContacts.CONTENT_URI, UserIdQuery.PROJECTION,
266 | UserIdQuery.SELECTION, new String[] {String.valueOf(userId)},
267 | null);
268 | try {
269 | if (c.moveToFirst()) {
270 | authorId = c.getLong(UserIdQuery.COLUMN_ID);
271 | }
272 | } finally {
273 | if (c != null) {
274 | c.close();
275 | }
276 | }
277 | return authorId;
278 | }
279 |
280 | /**
281 | * Returns the Data id for a sample SyncAdapter contact's profile row, or 0
282 | * if the sample SyncAdapter user isn't found.
283 | *
284 | * @param resolver a content resolver
285 | * @param userId the sample SyncAdapter user ID to lookup
286 | * @return the profile Data row id, or 0 if not found
287 | */
288 | private static long lookupProfile(ContentResolver resolver, long userId) {
289 | long profileId = 0;
290 | final Cursor c =
291 | resolver.query(Data.CONTENT_URI, ProfileQuery.PROJECTION,
292 | ProfileQuery.SELECTION, new String[] {String.valueOf(userId)},
293 | null);
294 | try {
295 | if (c != null && c.moveToFirst()) {
296 | profileId = c.getLong(ProfileQuery.COLUMN_ID);
297 | }
298 | } finally {
299 | if (c != null) {
300 | c.close();
301 | }
302 | }
303 | return profileId;
304 | }
305 |
306 | /**
307 | * Constants for a query to find a contact given a sample SyncAdapter user
308 | * ID.
309 | */
310 | private interface ProfileQuery {
311 | public final static String[] PROJECTION = new String[] {Data._ID};
312 |
313 | public final static int COLUMN_ID = 0;
314 |
315 | public static final String SELECTION =
316 | Data.MIMETYPE + "='" + SampleSyncAdapterColumns.MIME_PROFILE
317 | + "' AND " + SampleSyncAdapterColumns.DATA_PID + "=?";
318 | }
319 | /**
320 | * Constants for a query to find a contact given a sample SyncAdapter user
321 | * ID.
322 | */
323 | private interface UserIdQuery {
324 | public final static String[] PROJECTION =
325 | new String[] {RawContacts._ID};
326 |
327 | public final static int COLUMN_ID = 0;
328 |
329 | public static final String SELECTION =
330 | RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND "
331 | + RawContacts.SOURCE_ID + "=?";
332 | }
333 |
334 | /**
335 | * Constants for a query to get contact data for a given rawContactId
336 | */
337 | private interface DataQuery {
338 | public static final String[] PROJECTION =
339 | new String[] {Data._ID, Data.MIMETYPE, Data.DATA1, Data.DATA2,
340 | Data.DATA3,};
341 |
342 | public static final int COLUMN_ID = 0;
343 | public static final int COLUMN_MIMETYPE = 1;
344 | public static final int COLUMN_DATA1 = 2;
345 | public static final int COLUMN_DATA2 = 3;
346 | public static final int COLUMN_DATA3 = 4;
347 | public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
348 | public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
349 | public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1;
350 | public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2;
351 | public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
352 | public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
353 |
354 | public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
355 | }
356 | }
357 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/platform/ContactOperations.java:
--------------------------------------------------------------------------------
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.platform;
18 |
19 | import android.content.ContentProviderOperation;
20 | import android.content.ContentValues;
21 | import android.content.Context;
22 | import android.net.Uri;
23 | import android.provider.ContactsContract;
24 | import android.provider.ContactsContract.Data;
25 | import android.provider.ContactsContract.RawContacts;
26 | import android.provider.ContactsContract.CommonDataKinds.Email;
27 | import android.provider.ContactsContract.CommonDataKinds.Phone;
28 | import android.provider.ContactsContract.CommonDataKinds.StructuredName;
29 | import android.text.TextUtils;
30 | import android.util.Log;
31 |
32 | import com.example.android.samplesync.Constants;
33 | import com.example.android.samplesync.R;
34 |
35 | /**
36 | * Helper class for storing data in the platform content providers.
37 | */
38 | public class ContactOperations {
39 |
40 | private final ContentValues mValues;
41 | private ContentProviderOperation.Builder mBuilder;
42 | private final BatchOperation mBatchOperation;
43 | private final Context mContext;
44 | private boolean mYield;
45 | private long mRawContactId;
46 | private int mBackReference;
47 | private boolean mIsNewContact;
48 |
49 | /**
50 | * Returns an instance of ContactOperations instance for adding new contact
51 | * to the platform contacts provider.
52 | *
53 | * @param context the Authenticator Activity context
54 | * @param userId the userId of the sample SyncAdapter user object
55 | * @param accountName the username of the current login
56 | * @return instance of ContactOperations
57 | */
58 | public static ContactOperations createNewContact(Context context,
59 | int userId, String accountName, BatchOperation batchOperation) {
60 | return new ContactOperations(context, userId, accountName,
61 | batchOperation);
62 | }
63 |
64 | /**
65 | * Returns an instance of ContactOperations for updating existing contact in
66 | * the platform contacts provider.
67 | *
68 | * @param context the Authenticator Activity context
69 | * @param rawContactId the unique Id of the existing rawContact
70 | * @return instance of ContactOperations
71 | */
72 | public static ContactOperations updateExistingContact(Context context,
73 | long rawContactId, BatchOperation batchOperation) {
74 | return new ContactOperations(context, rawContactId, batchOperation);
75 | }
76 |
77 | public ContactOperations(Context context, BatchOperation batchOperation) {
78 | mValues = new ContentValues();
79 | mYield = true;
80 | mContext = context;
81 | mBatchOperation = batchOperation;
82 | }
83 |
84 | public ContactOperations(Context context, int userId, String accountName,
85 | BatchOperation batchOperation) {
86 | this(context, batchOperation);
87 | mBackReference = mBatchOperation.size();
88 | mIsNewContact = true;
89 | mValues.put(RawContacts.SOURCE_ID, userId);
90 | mValues.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
91 | mValues.put(RawContacts.ACCOUNT_NAME, accountName);
92 | mBuilder =
93 | newInsertCpo(RawContacts.CONTENT_URI, true).withValues(mValues);
94 | mBatchOperation.add(mBuilder.build());
95 | }
96 |
97 | public ContactOperations(Context context, long rawContactId,
98 | BatchOperation batchOperation) {
99 | this(context, batchOperation);
100 | mIsNewContact = false;
101 | mRawContactId = rawContactId;
102 | }
103 |
104 | /**
105 | * Adds a contact name
106 | *
107 | * @param name Name of contact
108 | * @param nameType type of name: family name, given name, etc.
109 | * @return instance of ContactOperations
110 | */
111 | public ContactOperations addName(String firstName, String lastName) {
112 | mValues.clear();
113 | if (!TextUtils.isEmpty(firstName)) {
114 | mValues.put(StructuredName.GIVEN_NAME, firstName);
115 | mValues.put(StructuredName.MIMETYPE,
116 | StructuredName.CONTENT_ITEM_TYPE);
117 | }
118 | if (!TextUtils.isEmpty(lastName)) {
119 | mValues.put(StructuredName.FAMILY_NAME, lastName);
120 | mValues.put(StructuredName.MIMETYPE,
121 | StructuredName.CONTENT_ITEM_TYPE);
122 | }
123 | if (mValues.size() > 0) {
124 | addInsertOp();
125 | }
126 | return this;
127 | }
128 |
129 | /**
130 | * Adds an email
131 | *
132 | * @param new email for user
133 | * @return instance of ContactOperations
134 | */
135 | public ContactOperations addEmail(String email) {
136 | mValues.clear();
137 | if (!TextUtils.isEmpty(email)) {
138 | mValues.put(Email.DATA, email);
139 | mValues.put(Email.TYPE, Email.TYPE_OTHER);
140 | mValues.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
141 | addInsertOp();
142 | }
143 | return this;
144 | }
145 |
146 | /**
147 | * Adds a phone number
148 | *
149 | * @param phone new phone number for the contact
150 | * @param phoneType the type: cell, home, etc.
151 | * @return instance of ContactOperations
152 | */
153 | public ContactOperations addPhone(String phone, int phoneType) {
154 | mValues.clear();
155 | if (!TextUtils.isEmpty(phone)) {
156 | mValues.put(Phone.NUMBER, phone);
157 | mValues.put(Phone.TYPE, phoneType);
158 | mValues.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
159 | addInsertOp();
160 | }
161 | return this;
162 | }
163 |
164 | /**
165 | * Adds a profile action
166 | *
167 | * @param userId the userId of the sample SyncAdapter user object
168 | * @return instance of ContactOperations
169 | */
170 | public ContactOperations addProfileAction(long userId) {
171 | mValues.clear();
172 | if (userId != 0) {
173 | mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
174 | mValues.put(SampleSyncAdapterColumns.DATA_SUMMARY, mContext
175 | .getString(R.string.profile_action));
176 | mValues.put(SampleSyncAdapterColumns.DATA_DETAIL, mContext
177 | .getString(R.string.view_profile));
178 | mValues.put(Data.MIMETYPE, SampleSyncAdapterColumns.MIME_PROFILE);
179 | addInsertOp();
180 | }
181 | return this;
182 | }
183 |
184 | /**
185 | * Updates contact's email
186 | *
187 | * @param email email id of the sample SyncAdapter user
188 | * @param uri Uri for the existing raw contact to be updated
189 | * @return instance of ContactOperations
190 | */
191 | public ContactOperations updateEmail(String email, String existingEmail,
192 | Uri uri) {
193 | if (!TextUtils.equals(existingEmail, email)) {
194 | mValues.clear();
195 | mValues.put(Email.DATA, email);
196 | addUpdateOp(uri);
197 | }
198 | return this;
199 | }
200 |
201 | /**
202 | * Updates contact's name
203 | *
204 | * @param name Name of contact
205 | * @param existingName Name of contact stored in provider
206 | * @param nameType type of name: family name, given name, etc.
207 | * @param uri Uri for the existing raw contact to be updated
208 | * @return instance of ContactOperations
209 | */
210 | public ContactOperations updateName(Uri uri, String existingFirstName,
211 | String existingLastName, String firstName, String lastName) {
212 | Log.i("ContactOperations", "ef=" + existingFirstName + "el="
213 | + existingLastName + "f=" + firstName + "l=" + lastName);
214 | mValues.clear();
215 | if (!TextUtils.equals(existingFirstName, firstName)) {
216 | mValues.put(StructuredName.GIVEN_NAME, firstName);
217 | }
218 | if (!TextUtils.equals(existingLastName, lastName)) {
219 | mValues.put(StructuredName.FAMILY_NAME, lastName);
220 | }
221 | if (mValues.size() > 0) {
222 | addUpdateOp(uri);
223 | }
224 | return this;
225 | }
226 |
227 | /**
228 | * Updates contact's phone
229 | *
230 | * @param existingNumber phone number stored in contacts provider
231 | * @param phone new phone number for the contact
232 | * @param uri Uri for the existing raw contact to be updated
233 | * @return instance of ContactOperations
234 | */
235 | public ContactOperations updatePhone(String existingNumber, String phone,
236 | Uri uri) {
237 | if (!TextUtils.equals(phone, existingNumber)) {
238 | mValues.clear();
239 | mValues.put(Phone.NUMBER, phone);
240 | addUpdateOp(uri);
241 | }
242 | return this;
243 | }
244 |
245 | /**
246 | * Updates contact's profile action
247 | *
248 | * @param userId sample SyncAdapter user id
249 | * @param uri Uri for the existing raw contact to be updated
250 | * @return instance of ContactOperations
251 | */
252 | public ContactOperations updateProfileAction(Integer userId, Uri uri) {
253 | mValues.clear();
254 | mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
255 | addUpdateOp(uri);
256 | return this;
257 | }
258 |
259 | /**
260 | * Adds an insert operation into the batch
261 | */
262 | private void addInsertOp() {
263 | if (!mIsNewContact) {
264 | mValues.put(Phone.RAW_CONTACT_ID, mRawContactId);
265 | }
266 | mBuilder =
267 | newInsertCpo(addCallerIsSyncAdapterParameter(Data.CONTENT_URI),
268 | mYield);
269 | mBuilder.withValues(mValues);
270 | if (mIsNewContact) {
271 | mBuilder
272 | .withValueBackReference(Data.RAW_CONTACT_ID, mBackReference);
273 | }
274 | mYield = false;
275 | mBatchOperation.add(mBuilder.build());
276 | }
277 |
278 | /**
279 | * Adds an update operation into the batch
280 | */
281 | private void addUpdateOp(Uri uri) {
282 | mBuilder = newUpdateCpo(uri, mYield).withValues(mValues);
283 | mYield = false;
284 | mBatchOperation.add(mBuilder.build());
285 | }
286 |
287 | public static ContentProviderOperation.Builder newInsertCpo(Uri uri,
288 | boolean yield) {
289 | return ContentProviderOperation.newInsert(
290 | addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(yield);
291 | }
292 |
293 | public static ContentProviderOperation.Builder newUpdateCpo(Uri uri,
294 | boolean yield) {
295 | return ContentProviderOperation.newUpdate(
296 | addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(yield);
297 | }
298 |
299 | public static ContentProviderOperation.Builder newDeleteCpo(Uri uri,
300 | boolean yield) {
301 | return ContentProviderOperation.newDelete(
302 | addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(yield);
303 |
304 | }
305 |
306 | private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
307 | return uri.buildUpon().appendQueryParameter(
308 | ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
309 | }
310 |
311 | }
312 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/platform/SampleSyncAdapterColumns.java:
--------------------------------------------------------------------------------
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 | package com.example.android.samplesync.platform;
17 |
18 | import android.provider.ContactsContract.Data;
19 |
20 | /*
21 | * The standard columns representing contact's info from social apps.
22 | */
23 | public interface SampleSyncAdapterColumns {
24 | /**
25 | * MIME-type used when storing a profile {@link Data} entry.
26 | */
27 | public static final String MIME_PROFILE =
28 | "vnd.android.cursor.item/vnd.samplesyncadapter.profile";
29 |
30 | public static final String DATA_PID = Data.DATA1;
31 | public static final String DATA_SUMMARY = Data.DATA2;
32 | public static final String DATA_DETAIL = Data.DATA3;
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/syncadapter/SyncAdapter.java:
--------------------------------------------------------------------------------
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.syncadapter;
18 |
19 | import android.accounts.Account;
20 | import android.accounts.AccountManager;
21 | import android.accounts.AuthenticatorException;
22 | import android.accounts.OperationCanceledException;
23 | import android.content.AbstractThreadedSyncAdapter;
24 | import android.content.ContentProviderClient;
25 | import android.content.Context;
26 | import android.content.SyncResult;
27 | import android.os.Bundle;
28 | import android.util.Log;
29 |
30 | import com.example.android.samplesync.Constants;
31 | import com.example.android.samplesync.client.NetworkUtilities;
32 | import com.example.android.samplesync.client.User;
33 | import com.example.android.samplesync.client.User.Status;
34 | import com.example.android.samplesync.platform.ContactManager;
35 |
36 | import org.apache.http.ParseException;
37 | import org.apache.http.auth.AuthenticationException;
38 | import org.json.JSONException;
39 |
40 | import java.io.IOException;
41 | import java.util.Date;
42 | import java.util.List;
43 |
44 | /**
45 | * SyncAdapter implementation for syncing sample SyncAdapter contacts to the
46 | * platform ContactOperations provider.
47 | */
48 | public class SyncAdapter extends AbstractThreadedSyncAdapter {
49 | private static final String TAG = "SyncAdapter";
50 |
51 | private final AccountManager mAccountManager;
52 | private final Context mContext;
53 |
54 | private Date mLastUpdated;
55 |
56 | public SyncAdapter(Context context, boolean autoInitialize) {
57 | super(context, autoInitialize);
58 | mContext = context;
59 | mAccountManager = AccountManager.get(context);
60 | }
61 |
62 | @Override
63 | public void onPerformSync(Account account, Bundle extras, String authority,
64 | ContentProviderClient provider, SyncResult syncResult) {
65 | List users;
66 | List statuses;
67 | String authtoken = null;
68 | try {
69 | // use the account manager to request the credentials
70 | authtoken =
71 | mAccountManager.blockingGetAuthToken(account,
72 | Constants.AUTHTOKEN_TYPE, true /* notifyAuthFailure */);
73 | // fetch updates from the sample service over the cloud
74 | users =
75 | NetworkUtilities.fetchFriendUpdates(account, authtoken,
76 | mLastUpdated);
77 | // update the last synced date.
78 | mLastUpdated = new Date();
79 | // update platform contacts.
80 | Log.d(TAG, "Calling contactManager's sync contacts");
81 | ContactManager.syncContacts(mContext, account.name, users);
82 | // fetch and update status messages for all the synced users.
83 | statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken);
84 | ContactManager.insertStatuses(mContext, account.name, statuses);
85 | } catch (final AuthenticatorException e) {
86 | syncResult.stats.numParseExceptions++;
87 | Log.e(TAG, "AuthenticatorException", e);
88 | } catch (final OperationCanceledException e) {
89 | Log.e(TAG, "OperationCanceledExcetpion", e);
90 | } catch (final IOException e) {
91 | Log.e(TAG, "IOException", e);
92 | syncResult.stats.numIoExceptions++;
93 | } catch (final AuthenticationException e) {
94 | mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
95 | authtoken);
96 | syncResult.stats.numAuthExceptions++;
97 | Log.e(TAG, "AuthenticationException", e);
98 | } catch (final ParseException e) {
99 | syncResult.stats.numParseExceptions++;
100 | Log.e(TAG, "ParseException", e);
101 | } catch (final JSONException e) {
102 | syncResult.stats.numParseExceptions++;
103 | Log.e(TAG, "JSONException", e);
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/com/example/android/samplesync/syncadapter/SyncService.java:
--------------------------------------------------------------------------------
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.syncadapter;
18 |
19 | import android.app.Service;
20 | import android.content.Intent;
21 | import android.os.IBinder;
22 |
23 | /**
24 | * Service to handle Account sync. This is invoked with an intent with action
25 | * ACTION_AUTHENTICATOR_INTENT. It instantiates the syncadapter and returns its
26 | * IBinder.
27 | */
28 | public class SyncService extends Service {
29 | private static final Object sSyncAdapterLock = new Object();
30 | private static SyncAdapter sSyncAdapter = null;
31 |
32 | /*
33 | * {@inheritDoc}
34 | */
35 | @Override
36 | public void onCreate() {
37 | synchronized (sSyncAdapterLock) {
38 | if (sSyncAdapter == null) {
39 | sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
40 | }
41 | }
42 | }
43 |
44 | /*
45 | * {@inheritDoc}
46 | */
47 | @Override
48 | public IBinder onBind(Intent intent) {
49 | return sSyncAdapter.getSyncAdapterBinder();
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/com/puny/android/network/util/DrupalJSONServerNetworkUtilityBase.java:
--------------------------------------------------------------------------------
1 | package com.puny.android.network.util;
2 |
3 | import android.content.Context;
4 | import android.os.Handler;
5 | import android.util.Log;
6 |
7 | import org.apache.http.protocol.HTTP;
8 | import org.apache.http.entity.StringEntity;
9 | import org.apache.http.HttpResponse;
10 | import org.apache.http.HttpStatus;
11 | import org.apache.http.NameValuePair;
12 | import org.apache.http.client.HttpClient;
13 | import org.apache.http.client.methods.HttpPost;
14 | import org.apache.http.conn.params.ConnManagerParams;
15 | import org.apache.http.impl.client.DefaultHttpClient;
16 | import org.apache.http.message.BasicHeader;
17 | import org.apache.http.message.BasicNameValuePair;
18 | import org.apache.http.params.HttpConnectionParams;
19 | import org.apache.http.params.HttpParams;
20 |
21 | import org.json.JSONObject;
22 | import org.json.JSONException;
23 |
24 | import java.io.IOException;
25 | import java.io.BufferedReader;
26 | import java.io.InputStream;
27 | import java.io.InputStreamReader;
28 | import java.util.ArrayList;
29 | import java.util.Iterator;
30 |
31 | /*
32 | * This base class handles session-based authentication for the "JSON Server" Drupal module.
33 | * Extend this class, change to your BASE_URL and add requests for a particular REST data model implementation.
34 | */
35 | public class DrupalJSONServerNetworkUtilityBase {
36 |
37 | protected static String TAG = "JSONServerNetworkUtil";
38 |
39 | public static final String PARAM_SESSION_ID = "sessid";
40 | public static final String PARAM_USERNAME = "name";
41 | public static final String PARAM_PASSWORD = "pass";
42 |
43 | public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms
44 |
45 | public static final String BASE_URL = "[YOUR JSON SERVER REST ENDPOINT]";
46 |
47 | public static final String CONNECT_URI = BASE_URL + "/system/connect.json";
48 | public static final String AUTH_URI = BASE_URL + "/user/login.json";
49 |
50 |
51 | protected static HttpClient mHttpClient;
52 | protected static String mSessId;
53 |
54 |
55 | /**
56 | * Configures the httpClient to connect to the URL provided.
57 | */
58 | public static void maybeCreateHttpClient() {
59 | if (mHttpClient == null) {
60 | mHttpClient = new DefaultHttpClient();
61 | final HttpParams params = mHttpClient.getParams();
62 | HttpConnectionParams.setConnectionTimeout(params,
63 | REGISTRATION_TIMEOUT);
64 | HttpConnectionParams.setSoTimeout(params, REGISTRATION_TIMEOUT);
65 | ConnManagerParams.setTimeout(params, REGISTRATION_TIMEOUT);
66 | }
67 | }
68 |
69 | /**
70 | * Executes the network requests on a separate thread.
71 | *
72 | * @param runnable The runnable instance containing network mOperations to
73 | * be executed.
74 | */
75 | public static Thread performOnBackgroundThread(final Runnable runnable) {
76 | final Thread t = new Thread() {
77 | @Override
78 | public void run() {
79 | try {
80 | runnable.run();
81 | } finally {
82 |
83 | }
84 | }
85 | };
86 | t.start();
87 | return t;
88 | }
89 |
90 |
91 |
92 | /**
93 | * Connects to the JSON server, authenticates the provided username and
94 | * password.
95 | *
96 | * @param username The user's username
97 | * @param password The user's password
98 | * @param handler The hander instance from the calling UI thread.
99 | * @param context The context of the calling Activity.
100 | *
101 | * @return boolean The boolean result indicating whether the user was
102 | * successfully authenticated.
103 | */
104 | public static boolean authenticate(String username, String password,
105 | Handler handler, final Context context) {
106 |
107 | mSessId = connectForSessId(handler, context);
108 | Log.d(TAG, "sessid = "+mSessId);
109 |
110 | if (mSessId == null) {
111 | return false;
112 | }
113 |
114 | // Map params = new HashMap();
115 | // params.put(PARAM_SESSION_ID, mSessId);
116 | // params.put(PARAM_USERNAME, username);
117 | // params.put(PARAM_PASSWORD, password);
118 |
119 | ArrayList params = new ArrayList();
120 | params.add(new BasicNameValuePair(PARAM_SESSION_ID, mSessId));
121 | params.add(new BasicNameValuePair(PARAM_USERNAME, username));
122 | params.add(new BasicNameValuePair(PARAM_PASSWORD, password));
123 |
124 | JSONObject json = prepareAndSendHttpPost(AUTH_URI, params);
125 |
126 | if (json == null) {
127 | Log.d(TAG, "auth failed");
128 | return false;
129 | }
130 | else {
131 | Log.d(TAG, "auth successful "+json.toString());
132 | return true;
133 | }
134 |
135 | }
136 |
137 | /*
138 | * This handles the initial POST to JSON Server to retrieve the sessid.
139 | */
140 | protected static String connectForSessId(final Handler handler, final Context context) {
141 | Log.d(TAG, "connectForSessId()");
142 |
143 | JSONObject json = prepareAndSendHttpPost(CONNECT_URI, null);
144 | if (json != null) {
145 | try {
146 | return json.getString("sessid");
147 | }
148 | catch (JSONException e) {
149 | Log.d(TAG, "could not find sessid from system.connect");
150 | return null;
151 | }
152 | }
153 | else {
154 | return null;
155 | }
156 | }
157 |
158 |
159 |
160 | /*
161 | * Does all the work for JSON POST requests.
162 | *
163 | * @param URI The URI for the post
164 | * @param params The variables to be converted to JSON and POSTed with this request.
165 | */
166 | protected static JSONObject prepareAndSendHttpPost(String URI, ArrayList params) {
167 | JSONObject json = null;
168 | try {
169 | json = new JSONObject();
170 | if (params != null) {
171 | Iterator it = params.iterator();
172 | while (it.hasNext()) {
173 | NameValuePair pair = it.next();
174 | json.put(pair.getName().toString(), pair.getValue());
175 | }
176 | }
177 | }
178 | catch (JSONException e) {
179 | Log.e(TAG, "unable to encode JSON for http request");
180 | }
181 |
182 | StringEntity se = null;
183 | try {
184 | se = new StringEntity(json.toString());
185 | se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json"));
186 | }
187 | catch (Exception e) {
188 | Log.d(TAG, "unable to create string entity");
189 | }
190 |
191 | final HttpPost post = new HttpPost(URI);
192 | post.setEntity(se);
193 |
194 | post.setHeader("Accept", "application/json");
195 | post.setHeader("Content-type", "application/json");
196 |
197 | maybeCreateHttpClient();
198 |
199 | HttpResponse resp = null;
200 | try {
201 |
202 | resp = mHttpClient.execute(post);
203 |
204 | if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
205 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
206 | Log.v(TAG, "Successful POST");
207 | }
208 | return decodeJSONResponse(resp);
209 | } else {
210 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
211 | Log.v(TAG, "Error POSTing: " + resp.getStatusLine());
212 | }
213 | return null;
214 | }
215 | } catch (final IOException e) {
216 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
217 | Log.v(TAG, "IOException when POSTING", e);
218 | }
219 | return null;
220 | } finally {
221 | if (Log.isLoggable(TAG, Log.VERBOSE)) {
222 | Log.v(TAG, "POST completing");
223 | }
224 | }
225 | }
226 |
227 |
228 | /*
229 | * Process the raw HttpResponse into a JSON object.
230 | */
231 | protected static JSONObject decodeJSONResponse(HttpResponse resp) {
232 |
233 | InputStream is = null;
234 | try {
235 | is = resp.getEntity().getContent();
236 | }
237 | catch (IOException e) {
238 | Log.d(TAG, "unable to get content from response entity");
239 | e.printStackTrace();
240 | return null;
241 | }
242 |
243 | String in = convertStreamToString(is);
244 |
245 | JSONObject json = null;
246 | try {
247 | json = new JSONObject(in);
248 | }
249 | catch (JSONException e) {
250 | Log.d(TAG, "could not decode JSON response from: "+in);
251 | }
252 |
253 | return json;
254 | }
255 |
256 |
257 | /*
258 | * Server responses are in stream format. This delivers the data as a String for easy parsing.
259 | */
260 | protected static String convertStreamToString(InputStream is) {
261 |
262 | BufferedReader reader = new BufferedReader(new InputStreamReader(is));
263 | StringBuilder sb = new StringBuilder();
264 |
265 | String line = null;
266 | try {
267 | while ((line = reader.readLine()) != null) {
268 | sb.append(line + "\n");
269 | }
270 | } catch (IOException e) {
271 | e.printStackTrace();
272 | } finally {
273 | try {
274 | is.close();
275 | } catch (IOException e) {
276 | e.printStackTrace();
277 | }
278 | }
279 | return sb.toString();
280 | }
281 | }
282 |
--------------------------------------------------------------------------------