SampleSyncAdapter sample code.

This commit is contained in:
Megha Joshi
2009-11-18 14:54:03 -08:00
parent c220077d52
commit 00bf0f0296
29 changed files with 2969 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
/*
* 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;
public class Constants {
/**
* Account type string.
*/
public static final String ACCOUNT_TYPE = "com.example.android.samplesync";
/**
* Authtoken type string.
*/
public static final String AUTHTOKEN_TYPE =
"com.example.android.samplesync";
}

View File

@@ -0,0 +1,56 @@
/*
* 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.authenticator;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
/**
* Service to handle Account authentication. It instantiates the authenticator
* and returns its IBinder.
*/
public class AuthenticationService extends Service {
private static final String TAG = "AuthenticationService";
private Authenticator mAuthenticator;
@Override
public void onCreate() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "SampleSyncAdapter Authentication Service started.");
}
mAuthenticator = new Authenticator(this);
}
@Override
public void onDestroy() {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "SampleSyncAdapter Authentication Service stopped.");
}
}
@Override
public IBinder onBind(Intent intent) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG,
"getBinder()... returning the AccountAuthenticator binder for intent "
+ intent);
}
return mAuthenticator.getIBinder();
}
}

View File

@@ -0,0 +1,182 @@
/*
* 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.authenticator;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.example.android.samplesync.Constants;
import com.example.android.samplesync.R;
import com.example.android.samplesync.client.NetworkUtilities;
/**
* This class is an implementation of AbstractAccountAuthenticator for
* authenticating accounts in the com.example.android.samplesync domain.
*/
class Authenticator extends AbstractAccountAuthenticator {
// Authentication Service context
private final Context mContext;
public Authenticator(Context context) {
super(context);
mContext = context;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle addAccount(AccountAuthenticatorResponse response,
String accountType, String authTokenType, String[] requiredFeatures,
Bundle options) {
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
authTokenType);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response,
Account account, Bundle options) {
if (options != null && options.containsKey(AccountManager.KEY_PASSWORD)) {
final String password =
options.getString(AccountManager.KEY_PASSWORD);
final boolean verified =
onlineConfirmPassword(account.name, password);
final Bundle result = new Bundle();
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, verified);
return result;
}
// Launch AuthenticatorActivity to confirm credentials
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
intent.putExtra(AuthenticatorActivity.PARAM_CONFIRMCREDENTIALS, true);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle editProperties(AccountAuthenticatorResponse response,
String accountType) {
throw new UnsupportedOperationException();
}
/**
* {@inheritDoc}
*/
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions) {
if (!authTokenType.equals(Constants.AUTHTOKEN_TYPE)) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ERROR_MESSAGE,
"invalid authTokenType");
return result;
}
final AccountManager am = AccountManager.get(mContext);
final String password = am.getPassword(account);
if (password != null) {
final boolean verified =
onlineConfirmPassword(account.name, password);
if (verified) {
final Bundle result = new Bundle();
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
result.putString(AccountManager.KEY_ACCOUNT_TYPE,
Constants.ACCOUNT_TYPE);
result.putString(AccountManager.KEY_AUTHTOKEN, password);
return result;
}
}
// the password was missing or incorrect, return an Intent to an
// Activity that will prompt the user for the password.
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
authTokenType);
intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE,
response);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
/**
* {@inheritDoc}
*/
@Override
public String getAuthTokenLabel(String authTokenType) {
if (authTokenType.equals(Constants.AUTHTOKEN_TYPE)) {
return mContext.getString(R.string.label);
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response,
Account account, String[] features) {
final Bundle result = new Bundle();
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
return result;
}
/**
* Validates user's password on the server
*/
private boolean onlineConfirmPassword(String username, String password) {
return NetworkUtilities.authenticate(username, password,
null/* Handler */, null/* Context */);
}
/**
* {@inheritDoc}
*/
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response,
Account account, String authTokenType, Bundle loginOptions) {
final Intent intent = new Intent(mContext, AuthenticatorActivity.class);
intent.putExtra(AuthenticatorActivity.PARAM_USERNAME, account.name);
intent.putExtra(AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE,
authTokenType);
intent.putExtra(AuthenticatorActivity.PARAM_CONFIRMCREDENTIALS, false);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}
}

View File

@@ -0,0 +1,263 @@
/*
* 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.authenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.ContentResolver;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.provider.ContactsContract;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.TextView;
import com.example.android.samplesync.Constants;
import com.example.android.samplesync.R;
import com.example.android.samplesync.client.NetworkUtilities;
/**
* Activity which displays login screen to the user.
*/
public class AuthenticatorActivity extends AccountAuthenticatorActivity {
public static final String PARAM_CONFIRMCREDENTIALS = "confirmCredentials";
public static final String PARAM_PASSWORD = "password";
public static final String PARAM_USERNAME = "username";
public static final String PARAM_AUTHTOKEN_TYPE = "authtokenType";
private static final String TAG = "AuthenticatorActivity";
private AccountManager mAccountManager;
private Thread mAuthThread;
private String mAuthtoken;
private String mAuthtokenType;
/**
* If set we are just checking that the user knows their credentials; this
* doesn't cause the user's password to be changed on the device.
*/
private Boolean mConfirmCredentials = false;
/** for posting authentication attempts back to UI thread */
private final Handler mHandler = new Handler();
private TextView mMessage;
private String mPassword;
private EditText mPasswordEdit;
/** Was the original caller asking for an entirely new account? */
protected boolean mRequestNewAccount = false;
private String mUsername;
private EditText mUsernameEdit;
/**
* {@inheritDoc}
*/
@Override
public void onCreate(Bundle icicle) {
Log.i(TAG, "onCreate(" + icicle + ")");
super.onCreate(icicle);
mAccountManager = AccountManager.get(this);
Log.i(TAG, "loading data from Intent");
final Intent intent = getIntent();
mUsername = intent.getStringExtra(PARAM_USERNAME);
mAuthtokenType = intent.getStringExtra(PARAM_AUTHTOKEN_TYPE);
mRequestNewAccount = mUsername == null;
mConfirmCredentials =
intent.getBooleanExtra(PARAM_CONFIRMCREDENTIALS, false);
Log.i(TAG, " request new: " + mRequestNewAccount);
requestWindowFeature(Window.FEATURE_LEFT_ICON);
setContentView(R.layout.login_activity);
getWindow().setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,
android.R.drawable.ic_dialog_alert);
mMessage = (TextView) findViewById(R.id.message);
mUsernameEdit = (EditText) findViewById(R.id.username_edit);
mPasswordEdit = (EditText) findViewById(R.id.password_edit);
mUsernameEdit.setText(mUsername);
mMessage.setText(getMessage());
}
/*
* {@inheritDoc}
*/
@Override
protected Dialog onCreateDialog(int id) {
final ProgressDialog dialog = new ProgressDialog(this);
dialog.setMessage(getText(R.string.ui_activity_authenticating));
dialog.setIndeterminate(true);
dialog.setCancelable(true);
dialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
Log.i(TAG, "dialog cancel has been invoked");
if (mAuthThread != null) {
mAuthThread.interrupt();
finish();
}
}
});
return dialog;
}
/**
* Handles onClick event on the Submit button. Sends username/password to
* the server for authentication.
*
* @param view The Submit button for which this method is invoked
*/
public void handleLogin(View view) {
if (mRequestNewAccount) {
mUsername = mUsernameEdit.getText().toString();
}
mPassword = mPasswordEdit.getText().toString();
if (TextUtils.isEmpty(mUsername) || TextUtils.isEmpty(mPassword)) {
mMessage.setText(getMessage());
} else {
showProgress();
// Start authenticating...
mAuthThread =
NetworkUtilities.attemptAuth(mUsername, mPassword, mHandler,
AuthenticatorActivity.this);
}
}
/**
* Called when response is received from the server for confirm credentials
* request. See onAuthenticationResult(). Sets the
* AccountAuthenticatorResult which is sent back to the caller.
*
* @param the confirmCredentials result.
*/
protected void finishConfirmCredentials(boolean result) {
Log.i(TAG, "finishConfirmCredentials()");
final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
mAccountManager.setPassword(account, mPassword);
final Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
/**
*
* Called when response is received from the server for authentication
* request. See onAuthenticationResult(). Sets the
* AccountAuthenticatorResult which is sent back to the caller. Also sets
* the authToken in AccountManager for this account.
*
* @param the confirmCredentials result.
*/
protected void finishLogin() {
Log.i(TAG, "finishLogin()");
final Account account = new Account(mUsername, Constants.ACCOUNT_TYPE);
if (mRequestNewAccount) {
mAccountManager.addAccountExplicitly(account, mPassword, null);
// Set contacts sync for this account.
ContentResolver.setSyncAutomatically(account,
ContactsContract.AUTHORITY, true);
} else {
mAccountManager.setPassword(account, mPassword);
}
final Intent intent = new Intent();
mAuthtoken = mPassword;
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, mUsername);
intent
.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
if (mAuthtokenType != null
&& mAuthtokenType.equals(Constants.AUTHTOKEN_TYPE)) {
intent.putExtra(AccountManager.KEY_AUTHTOKEN, mAuthtoken);
}
setAccountAuthenticatorResult(intent.getExtras());
setResult(RESULT_OK, intent);
finish();
}
/**
* Hides the progress UI for a lengthy operation.
*/
protected void hideProgress() {
dismissDialog(0);
}
/**
* Called when the authentication process completes (see attemptLogin()).
*/
public void onAuthenticationResult(boolean result) {
Log.i(TAG, "onAuthenticationResult(" + result + ")");
// Hide the progress dialog
hideProgress();
if (result) {
if (!mConfirmCredentials) {
finishLogin();
} else {
finishConfirmCredentials(true);
}
} else {
Log.e(TAG, "onAuthenticationResult: failed to authenticate");
if (mRequestNewAccount) {
// "Please enter a valid username/password.
mMessage
.setText(getText(R.string.login_activity_loginfail_text_both));
} else {
// "Please enter a valid password." (Used when the
// account is already in the database but the password
// doesn't work.)
mMessage
.setText(getText(R.string.login_activity_loginfail_text_pwonly));
}
}
}
/**
* Returns the message to be displayed at the top of the login dialog box.
*/
private CharSequence getMessage() {
getString(R.string.label);
if (TextUtils.isEmpty(mUsername)) {
// If no username, then we ask the user to log in using an
// appropriate service.
final CharSequence msg =
getText(R.string.login_activity_newaccount_text);
return msg;
}
if (TextUtils.isEmpty(mPassword)) {
// We have an account but no password
return getText(R.string.login_activity_loginfail_text_pwmissing);
}
return null;
}
/**
* Shows the progress UI for a lengthy operation.
*/
protected void showProgress() {
showDialog(0);
}
}

View File

@@ -0,0 +1,307 @@
/*
* 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 android.accounts.Account;
import android.content.Context;
import android.os.Handler;
import android.util.Log;
import com.example.android.samplesync.authenticator.AuthenticatorActivity;
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 java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
/**
* Provides utility methods for communicating with the server.
*/
public class NetworkUtilities {
private static final String TAG = "NetworkUtilities";
public static final String PARAM_USERNAME = "username";
public static final String PARAM_PASSWORD = "password";
public static final String PARAM_UPDATED = "timestamp";
public static final String USER_AGENT = "AuthenticationService/1.0";
public static final int REGISTRATION_TIMEOUT = 30 * 1000; // ms
public static final String BASE_URL =
"https://samplesyncadapter.appspot.com";
public static final String AUTH_URI = BASE_URL + "/auth";
public static final String FETCH_FRIEND_UPDATES_URI =
BASE_URL + "/fetch_friend_updates";
public static final String FETCH_STATUS_URI = BASE_URL + "/fetch_status";
private static HttpClient mHttpClient;
/**
* Configures the httpClient to connect to the URL provided.
*/
public static void maybeCreateHttpClient() {
if (mHttpClient == null) {
mHttpClient = new DefaultHttpClient();
final HttpParams params = mHttpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params,
REGISTRATION_TIMEOUT);
HttpConnectionParams.setSoTimeout(params, REGISTRATION_TIMEOUT);
ConnManagerParams.setTimeout(params, REGISTRATION_TIMEOUT);
}
}
/**
* Executes the network requests on a separate thread.
*
* @param runnable The runnable instance containing network mOperations to
* be executed.
*/
public static Thread performOnBackgroundThread(final Runnable runnable) {
final Thread t = new Thread() {
@Override
public void run() {
try {
runnable.run();
} finally {
}
}
};
t.start();
return t;
}
/**
* Connects to the Voiper server, authenticates the provided username and
* password.
*
* @param username The user's username
* @param password The user's password
* @param handler The hander instance from the calling UI thread.
* @param context The context of the calling Activity.
* @return boolean The boolean result indicating whether the user was
* successfully authenticated.
*/
public static boolean authenticate(String username, String password,
Handler handler, final Context context) {
final HttpResponse resp;
final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(PARAM_USERNAME, username));
params.add(new BasicNameValuePair(PARAM_PASSWORD, password));
HttpEntity entity = null;
try {
entity = new UrlEncodedFormEntity(params);
} catch (final UnsupportedEncodingException e) {
// this should never happen.
throw new AssertionError(e);
}
final HttpPost post = new HttpPost(AUTH_URI);
post.addHeader(entity.getContentType());
post.setEntity(entity);
maybeCreateHttpClient();
try {
resp = mHttpClient.execute(post);
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Successful authentication");
}
sendResult(true, handler, context);
return true;
} else {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "Error authenticating" + resp.getStatusLine());
}
sendResult(false, handler, context);
return false;
}
} catch (final IOException e) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "IOException when getting authtoken", e);
}
sendResult(false, handler, context);
return false;
} finally {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "getAuthtoken completing");
}
}
}
/**
* Sends the authentication response from server back to the caller main UI
* thread through its handler.
*
* @param result The boolean holding authentication result
* @param handler The main UI thread's handler instance.
* @param context The caller Activity's context.
*/
private static void sendResult(final Boolean result, final Handler handler,
final Context context) {
if (handler == null || context == null) {
return;
}
handler.post(new Runnable() {
public void run() {
((AuthenticatorActivity) context).onAuthenticationResult(result);
}
});
}
/**
* Attempts to authenticate the user credentials on the server.
*
* @param username The user's username
* @param password The user's password to be authenticated
* @param handler The main UI thread's handler instance.
* @param context The caller Activity's context
* @return Thread The thread on which the network mOperations are executed.
*/
public static Thread attemptAuth(final String username,
final String password, final Handler handler, final Context context) {
final Runnable runnable = new Runnable() {
public void run() {
authenticate(username, password, handler, context);
}
};
// run on background thread.
return NetworkUtilities.performOnBackgroundThread(runnable);
}
/**
* Fetches the list of friend data updates from the server
*
* @param account The account being synced.
* @param authtoken The authtoken stored in AccountManager for this account
* @param lastUpdated The last time that sync was performed
* @return list The list of updates received from the server.
*/
public static List<User> fetchFriendUpdates(Account account,
String authtoken, Date lastUpdated) throws JSONException,
ParseException, IOException, AuthenticationException {
final ArrayList<User> friendList = new ArrayList<User>();
final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
if (lastUpdated != null) {
final SimpleDateFormat formatter =
new SimpleDateFormat("yyyy/MM/dd HH:mm");
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
params.add(new BasicNameValuePair(PARAM_UPDATED, formatter
.format(lastUpdated)));
}
Log.i(TAG, params.toString());
HttpEntity entity = null;
entity = new UrlEncodedFormEntity(params);
final HttpPost post = new HttpPost(FETCH_FRIEND_UPDATES_URI);
post.addHeader(entity.getContentType());
post.setEntity(entity);
maybeCreateHttpClient();
final HttpResponse resp = mHttpClient.execute(post);
final String response = EntityUtils.toString(resp.getEntity());
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// Succesfully connected to the samplesyncadapter server and
// authenticated.
// Extract friends data in json format.
final JSONArray friends = new JSONArray(response);
Log.d(TAG, response);
for (int i = 0; i < friends.length(); i++) {
friendList.add(User.valueOf(friends.getJSONObject(i)));
}
} else {
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
Log.e(TAG,
"Authentication exception in fetching remote contacts");
throw new AuthenticationException();
} else {
Log.e(TAG, "Server error in fetching remote contacts: "
+ resp.getStatusLine());
throw new IOException();
}
}
return friendList;
}
/**
* Fetches status messages for the user's friends from the server
*
* @param account The account being synced.
* @param authtoken The authtoken stored in the AccountManager for the
* account
* @return list The list of status messages received from the server.
*/
public static List<User.Status> fetchFriendStatuses(Account account,
String authtoken) throws JSONException, ParseException, IOException,
AuthenticationException {
final ArrayList<User.Status> statusList = new ArrayList<User.Status>();
final ArrayList<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(PARAM_USERNAME, account.name));
params.add(new BasicNameValuePair(PARAM_PASSWORD, authtoken));
HttpEntity entity = null;
entity = new UrlEncodedFormEntity(params);
final HttpPost post = new HttpPost(FETCH_STATUS_URI);
post.addHeader(entity.getContentType());
post.setEntity(entity);
maybeCreateHttpClient();
final HttpResponse resp = mHttpClient.execute(post);
final String response = EntityUtils.toString(resp.getEntity());
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
// Succesfully connected to the samplesyncadapter server and
// authenticated.
// Extract friends data in json format.
final JSONArray statuses = new JSONArray(response);
for (int i = 0; i < statuses.length(); i++) {
statusList.add(User.Status.valueOf(statuses.getJSONObject(i)));
}
} else {
if (resp.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
Log.e(TAG,
"Authentication exception in fetching friend status list");
throw new AuthenticationException();
} else {
Log.e(TAG, "Server error in fetching friend status list");
throw new IOException();
}
}
return statusList;
}
}

View File

@@ -0,0 +1,150 @@
/*
* 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 android.util.Log;
import org.json.JSONObject;
/**
* Represents a sample SyncAdapter user
*/
public class User {
private final String mUserName;
private final String mFirstName;
private final String mLastName;
private final String mCellPhone;
private final String mOfficePhone;
private final String mHomePhone;
private final String mEmail;
private final boolean mDeleted;
private final int mUserId;
public int getUserId() {
return mUserId;
}
public String getUserName() {
return mUserName;
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
public String getCellPhone() {
return mCellPhone;
}
public String getOfficePhone() {
return mOfficePhone;
}
public String getHomePhone() {
return mHomePhone;
}
public String getEmail() {
return mEmail;
}
public boolean isDeleted() {
return mDeleted;
}
public User(String name, String firstName, String lastName,
String cellPhone, String officePhone, String homePhone, String email,
Boolean deleted, Integer userId) {
mUserName = name;
mFirstName = firstName;
mLastName = lastName;
mCellPhone = cellPhone;
mOfficePhone = officePhone;
mHomePhone = homePhone;
mEmail = email;
mDeleted = deleted;
mUserId = userId;
}
/**
* Creates and returns an instance of the user from the provided JSON data.
*
* @param user The JSONObject containing user data
* @return user The new instance of Voiper user created from the JSON data.
*/
public static User valueOf(JSONObject user) {
try {
final String userName = user.getString("u");
final String firstName = user.has("f") ? user.getString("f") : null;
final String lastName = user.has("l") ? user.getString("l") : null;
final String cellPhone = user.has("m") ? user.getString("m") : null;
final String officePhone =
user.has("o") ? user.getString("o") : null;
final String homePhone = user.has("h") ? user.getString("h") : null;
final String email = user.has("e") ? user.getString("e") : null;
final boolean deleted =
user.has("d") ? user.getBoolean("d") : false;
final int userId = user.getInt("i");
return new User(userName, firstName, lastName, cellPhone,
officePhone, homePhone, email, deleted, userId);
} catch (final Exception ex) {
Log.i("User", "Error parsing JSON user object" + ex.toString());
}
return null;
}
/**
* Represents the User's status messages
*
*/
public static class Status {
private final Integer mUserId;
private final String mStatus;
public int getUserId() {
return mUserId;
}
public String getStatus() {
return mStatus;
}
public Status(Integer userId, String status) {
mUserId = userId;
mStatus = status;
}
public static User.Status valueOf(JSONObject userStatus) {
try {
final int userId = userStatus.getInt("i");
final String status = userStatus.getString("s");
return new User.Status(userId, status);
} catch (final Exception ex) {
Log.i("User.Status", "Error parsing JSON user object");
}
return null;
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* 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.platform;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.Context;
import android.content.OperationApplicationException;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.util.Log;
import java.util.ArrayList;
/**
* This class handles execution of batch mOperations on Contacts provider.
*/
public class BatchOperation {
private final String TAG = "BatchOperation";
private final ContentResolver mResolver;
// List for storing the batch mOperations
ArrayList<ContentProviderOperation> mOperations;
public BatchOperation(Context context, ContentResolver resolver) {
mResolver = resolver;
mOperations = new ArrayList<ContentProviderOperation>();
}
public int size() {
return mOperations.size();
}
public void add(ContentProviderOperation cpo) {
mOperations.add(cpo);
}
public void execute() {
if (mOperations.size() == 0) {
return;
}
// Apply the mOperations to the content provider
try {
mResolver.applyBatch(ContactsContract.AUTHORITY, mOperations);
} catch (final OperationApplicationException e1) {
Log.e(TAG, "storing contact data failed", e1);
} catch (final RemoteException e2) {
Log.e(TAG, "storing contact data failed", e2);
}
mOperations.clear();
}
}

View File

@@ -0,0 +1,356 @@
/*
* 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.platform;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.StatusUpdates;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Im;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.util.Log;
import com.example.android.samplesync.Constants;
import com.example.android.samplesync.R;
import com.example.android.samplesync.client.User;
import java.util.List;
/**
* Class for managing contacts sync related mOperations
*/
public class ContactManager {
/**
* Custom IM protocol used when storing status messages.
*/
public static final String CUSTOM_IM_PROTOCOL = "SampleSyncAdapter";
private static final String TAG = "ContactManager";
/**
* Synchronize raw contacts
*
* @param context The context of Authenticator Activity
* @param account The username for the account
* @param users The list of users
*/
public static synchronized void syncContacts(Context context,
String account, List<User> users) {
long userId;
long rawContactId = 0;
final ContentResolver resolver = context.getContentResolver();
final BatchOperation batchOperation =
new BatchOperation(context, resolver);
Log.d(TAG, "In SyncContacts");
for (final User user : users) {
userId = user.getUserId();
// Check to see if the contact needs to be inserted or updated
rawContactId = lookupRawContact(resolver, userId);
if (rawContactId != 0) {
if (!user.isDeleted()) {
// update contact
updateContact(context, resolver, account, user,
rawContactId, batchOperation);
} else {
// delete contact
deleteContact(context, rawContactId, batchOperation);
}
} else {
// add new contact
Log.d(TAG, "In addContact");
if (!user.isDeleted()) {
addContact(context, account, user, batchOperation);
}
}
// A sync adapter should batch operations on multiple contacts,
// because it will make a dramatic performance difference.
if (batchOperation.size() >= 50) {
batchOperation.execute();
}
}
batchOperation.execute();
}
/**
* Add a list of status messages to the contacts provider.
*
* @param context the context to use
* @param accountName the username of the logged in user
* @param statuses the list of statuses to store
*/
public static void insertStatuses(Context context, String username,
List<User.Status> list) {
final ContentValues values = new ContentValues();
final ContentResolver resolver = context.getContentResolver();
final BatchOperation batchOperation =
new BatchOperation(context, resolver);
for (final User.Status status : list) {
// Look up the user's sample SyncAdapter data row
final long userId = status.getUserId();
final long profileId = lookupProfile(resolver, userId);
// Insert the activity into the stream
if (profileId > 0) {
values.put(StatusUpdates.DATA_ID, profileId);
values.put(StatusUpdates.STATUS, status.getStatus());
values.put(StatusUpdates.PROTOCOL, Im.PROTOCOL_CUSTOM);
values.put(StatusUpdates.CUSTOM_PROTOCOL, CUSTOM_IM_PROTOCOL);
values.put(StatusUpdates.IM_ACCOUNT, username);
values.put(StatusUpdates.IM_HANDLE, status.getUserId());
values.put(StatusUpdates.STATUS_RES_PACKAGE, context
.getPackageName());
values.put(StatusUpdates.STATUS_ICON, R.drawable.icon);
values.put(StatusUpdates.STATUS_LABEL, R.string.label);
batchOperation
.add(ContactOperations.newInsertCpo(
StatusUpdates.CONTENT_URI, true).withValues(values)
.build());
// A sync adapter should batch operations on multiple contacts,
// because it will make a dramatic performance difference.
if (batchOperation.size() >= 50) {
batchOperation.execute();
}
}
}
batchOperation.execute();
}
/**
* Adds a single contact to the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param accountName the account the contact belongs to
* @param user the sample SyncAdapter User object
*/
private static void addContact(Context context, String accountName,
User user, BatchOperation batchOperation) {
// Put the data in the contacts provider
final ContactOperations contactOp =
ContactOperations.createNewContact(context, user.getUserId(),
accountName, batchOperation);
contactOp.addName(user.getFirstName(), user.getLastName()).addEmail(
user.getEmail()).addPhone(user.getCellPhone(), Phone.TYPE_MOBILE)
.addPhone(user.getHomePhone(), Phone.TYPE_OTHER).addProfileAction(
user.getUserId());
}
/**
* Updates a single contact to the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param resolver the ContentResolver to use
* @param accountName the account the contact belongs to
* @param user the sample SyncAdapter contact object.
* @param rawContactId the unique Id for this rawContact in contacts
* provider
*/
private static void updateContact(Context context,
ContentResolver resolver, String accountName, User user,
long rawContactId, BatchOperation batchOperation) {
Uri uri;
String cellPhone = null;
String otherPhone = null;
String email = null;
final Cursor c =
resolver.query(Data.CONTENT_URI, DataQuery.PROJECTION,
DataQuery.SELECTION,
new String[] {String.valueOf(rawContactId)}, null);
final ContactOperations contactOp =
ContactOperations.updateExistingContact(context, rawContactId,
batchOperation);
try {
while (c.moveToNext()) {
final long id = c.getLong(DataQuery.COLUMN_ID);
final String mimeType = c.getString(DataQuery.COLUMN_MIMETYPE);
uri = ContentUris.withAppendedId(Data.CONTENT_URI, id);
if (mimeType.equals(StructuredName.CONTENT_ITEM_TYPE)) {
final String lastName =
c.getString(DataQuery.COLUMN_FAMILY_NAME);
final String firstName =
c.getString(DataQuery.COLUMN_GIVEN_NAME);
contactOp.updateName(uri, firstName, lastName, user
.getFirstName(), user.getLastName());
}
else if (mimeType.equals(Phone.CONTENT_ITEM_TYPE)) {
final int type = c.getInt(DataQuery.COLUMN_PHONE_TYPE);
if (type == Phone.TYPE_MOBILE) {
cellPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
contactOp.updatePhone(cellPhone, user.getCellPhone(),
uri);
} else if (type == Phone.TYPE_OTHER) {
otherPhone = c.getString(DataQuery.COLUMN_PHONE_NUMBER);
contactOp.updatePhone(otherPhone, user.getHomePhone(),
uri);
}
}
else if (Data.MIMETYPE.equals(Email.CONTENT_ITEM_TYPE)) {
email = c.getString(DataQuery.COLUMN_EMAIL_ADDRESS);
contactOp.updateEmail(user.getEmail(), email, uri);
}
} // while
} finally {
c.close();
}
// Add the cell phone, if present and not updated above
if (cellPhone == null) {
contactOp.addPhone(user.getCellPhone(), Phone.TYPE_MOBILE);
}
// Add the other phone, if present and not updated above
if (otherPhone == null) {
contactOp.addPhone(user.getHomePhone(), Phone.TYPE_OTHER);
}
// Add the email address, if present and not updated above
if (email == null) {
contactOp.addEmail(user.getEmail());
}
}
/**
* Deletes a contact from the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param rawContactId the unique Id for this rawContact in contacts
* provider
*/
private static void deleteContact(Context context, long rawContactId,
BatchOperation batchOperation) {
batchOperation.add(ContactOperations.newDeleteCpo(
ContentUris.withAppendedId(RawContacts.CONTENT_URI, rawContactId),
true).build());
}
/**
* Returns the RawContact id for a sample SyncAdapter contact, or 0 if the
* sample SyncAdapter user isn't found.
*
* @param context the Authenticator Activity context
* @param userId the sample SyncAdapter user ID to lookup
* @return the RawContact id, or 0 if not found
*/
private static long lookupRawContact(ContentResolver resolver, long userId) {
long authorId = 0;
final Cursor c =
resolver.query(RawContacts.CONTENT_URI, UserIdQuery.PROJECTION,
UserIdQuery.SELECTION, new String[] {String.valueOf(userId)},
null);
try {
if (c.moveToFirst()) {
authorId = c.getLong(UserIdQuery.COLUMN_ID);
}
} finally {
if (c != null) {
c.close();
}
}
return authorId;
}
/**
* Returns the Data id for a sample SyncAdapter contact's profile row, or 0
* if the sample SyncAdapter user isn't found.
*
* @param resolver a content resolver
* @param userId the sample SyncAdapter user ID to lookup
* @return the profile Data row id, or 0 if not found
*/
private static long lookupProfile(ContentResolver resolver, long userId) {
long profileId = 0;
final Cursor c =
resolver.query(Data.CONTENT_URI, ProfileQuery.PROJECTION,
ProfileQuery.SELECTION, new String[] {String.valueOf(userId)},
null);
try {
if (c != null && c.moveToFirst()) {
profileId = c.getLong(ProfileQuery.COLUMN_ID);
}
} finally {
if (c != null) {
c.close();
}
}
return profileId;
}
/**
* Constants for a query to find a contact given a sample SyncAdapter user
* ID.
*/
private interface ProfileQuery {
public final static String[] PROJECTION = new String[] {Data._ID};
public final static int COLUMN_ID = 0;
public static final String SELECTION =
Data.MIMETYPE + "='" + SampleSyncAdapterColumns.MIME_PROFILE
+ "' AND " + SampleSyncAdapterColumns.DATA_PID + "=?";
}
/**
* Constants for a query to find a contact given a sample SyncAdapter user
* ID.
*/
private interface UserIdQuery {
public final static String[] PROJECTION =
new String[] {RawContacts._ID};
public final static int COLUMN_ID = 0;
public static final String SELECTION =
RawContacts.ACCOUNT_TYPE + "='" + Constants.ACCOUNT_TYPE + "' AND "
+ RawContacts.SOURCE_ID + "=?";
}
/**
* Constants for a query to get contact data for a given rawContactId
*/
private interface DataQuery {
public static final String[] PROJECTION =
new String[] {Data._ID, Data.MIMETYPE, Data.DATA1, Data.DATA2,
Data.DATA3,};
public static final int COLUMN_ID = 0;
public static final int COLUMN_MIMETYPE = 1;
public static final int COLUMN_DATA1 = 2;
public static final int COLUMN_DATA2 = 3;
public static final int COLUMN_DATA3 = 4;
public static final int COLUMN_PHONE_NUMBER = COLUMN_DATA1;
public static final int COLUMN_PHONE_TYPE = COLUMN_DATA2;
public static final int COLUMN_EMAIL_ADDRESS = COLUMN_DATA1;
public static final int COLUMN_EMAIL_TYPE = COLUMN_DATA2;
public static final int COLUMN_GIVEN_NAME = COLUMN_DATA2;
public static final int COLUMN_FAMILY_NAME = COLUMN_DATA3;
public static final String SELECTION = Data.RAW_CONTACT_ID + "=?";
}
}

View File

@@ -0,0 +1,311 @@
/*
* 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.platform;
import android.content.ContentProviderOperation;
import android.content.ContentValues;
import android.content.Context;
import android.net.Uri;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Data;
import android.provider.ContactsContract.RawContacts;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredName;
import android.text.TextUtils;
import android.util.Log;
import com.example.android.samplesync.Constants;
import com.example.android.samplesync.R;
/**
* Helper class for storing data in the platform content providers.
*/
public class ContactOperations {
private final ContentValues mValues;
private ContentProviderOperation.Builder mBuilder;
private final BatchOperation mBatchOperation;
private final Context mContext;
private boolean mYield;
private long mRawContactId;
private int mBackReference;
private boolean mIsNewContact;
/**
* Returns an instance of ContactOperations instance for adding new contact
* to the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param userId the userId of the sample SyncAdapter user object
* @param accountName the username of the current login
* @return instance of ContactOperations
*/
public static ContactOperations createNewContact(Context context,
int userId, String accountName, BatchOperation batchOperation) {
return new ContactOperations(context, userId, accountName,
batchOperation);
}
/**
* Returns an instance of ContactOperations for updating existing contact in
* the platform contacts provider.
*
* @param context the Authenticator Activity context
* @param rawContactId the unique Id of the existing rawContact
* @return instance of ContactOperations
*/
public static ContactOperations updateExistingContact(Context context,
long rawContactId, BatchOperation batchOperation) {
return new ContactOperations(context, rawContactId, batchOperation);
}
public ContactOperations(Context context, BatchOperation batchOperation) {
mValues = new ContentValues();
mYield = true;
mContext = context;
mBatchOperation = batchOperation;
}
public ContactOperations(Context context, int userId, String accountName,
BatchOperation batchOperation) {
this(context, batchOperation);
mBackReference = mBatchOperation.size();
mIsNewContact = true;
mValues.put(RawContacts.SOURCE_ID, userId);
mValues.put(RawContacts.ACCOUNT_TYPE, Constants.ACCOUNT_TYPE);
mValues.put(RawContacts.ACCOUNT_NAME, accountName);
mBuilder =
newInsertCpo(RawContacts.CONTENT_URI, true).withValues(mValues);
mBatchOperation.add(mBuilder.build());
}
public ContactOperations(Context context, long rawContactId,
BatchOperation batchOperation) {
this(context, batchOperation);
mIsNewContact = false;
mRawContactId = rawContactId;
}
/**
* Adds a contact name
*
* @param name Name of contact
* @param nameType type of name: family name, given name, etc.
* @return instance of ContactOperations
*/
public ContactOperations addName(String firstName, String lastName) {
mValues.clear();
if (!TextUtils.isEmpty(firstName)) {
mValues.put(StructuredName.GIVEN_NAME, firstName);
mValues.put(StructuredName.MIMETYPE,
StructuredName.CONTENT_ITEM_TYPE);
}
if (!TextUtils.isEmpty(lastName)) {
mValues.put(StructuredName.FAMILY_NAME, lastName);
mValues.put(StructuredName.MIMETYPE,
StructuredName.CONTENT_ITEM_TYPE);
}
if (mValues.size() > 0) {
addInsertOp();
}
return this;
}
/**
* Adds an email
*
* @param new email for user
* @return instance of ContactOperations
*/
public ContactOperations addEmail(String email) {
mValues.clear();
if (!TextUtils.isEmpty(email)) {
mValues.put(Email.DATA, email);
mValues.put(Email.TYPE, Email.TYPE_OTHER);
mValues.put(Email.MIMETYPE, Email.CONTENT_ITEM_TYPE);
addInsertOp();
}
return this;
}
/**
* Adds a phone number
*
* @param phone new phone number for the contact
* @param phoneType the type: cell, home, etc.
* @return instance of ContactOperations
*/
public ContactOperations addPhone(String phone, int phoneType) {
mValues.clear();
if (!TextUtils.isEmpty(phone)) {
mValues.put(Phone.NUMBER, phone);
mValues.put(Phone.TYPE, phoneType);
mValues.put(Phone.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
addInsertOp();
}
return this;
}
/**
* Adds a profile action
*
* @param userId the userId of the sample SyncAdapter user object
* @return instance of ContactOperations
*/
public ContactOperations addProfileAction(long userId) {
mValues.clear();
if (userId != 0) {
mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
mValues.put(SampleSyncAdapterColumns.DATA_SUMMARY, mContext
.getString(R.string.profile_action));
mValues.put(SampleSyncAdapterColumns.DATA_DETAIL, mContext
.getString(R.string.view_profile));
mValues.put(Data.MIMETYPE, SampleSyncAdapterColumns.MIME_PROFILE);
addInsertOp();
}
return this;
}
/**
* Updates contact's email
*
* @param email email id of the sample SyncAdapter user
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updateEmail(String email, String existingEmail,
Uri uri) {
if (!TextUtils.equals(existingEmail, email)) {
mValues.clear();
mValues.put(Email.DATA, email);
addUpdateOp(uri);
}
return this;
}
/**
* Updates contact's name
*
* @param name Name of contact
* @param existingName Name of contact stored in provider
* @param nameType type of name: family name, given name, etc.
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updateName(Uri uri, String existingFirstName,
String existingLastName, String firstName, String lastName) {
Log.i("ContactOperations", "ef=" + existingFirstName + "el="
+ existingLastName + "f=" + firstName + "l=" + lastName);
mValues.clear();
if (!TextUtils.equals(existingFirstName, firstName)) {
mValues.put(StructuredName.GIVEN_NAME, firstName);
}
if (!TextUtils.equals(existingLastName, lastName)) {
mValues.put(StructuredName.FAMILY_NAME, lastName);
}
if (mValues.size() > 0) {
addUpdateOp(uri);
}
return this;
}
/**
* Updates contact's phone
*
* @param existingNumber phone number stored in contacts provider
* @param phone new phone number for the contact
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updatePhone(String existingNumber, String phone,
Uri uri) {
if (!TextUtils.equals(phone, existingNumber)) {
mValues.clear();
mValues.put(Phone.NUMBER, phone);
addUpdateOp(uri);
}
return this;
}
/**
* Updates contact's profile action
*
* @param userId sample SyncAdapter user id
* @param uri Uri for the existing raw contact to be updated
* @return instance of ContactOperations
*/
public ContactOperations updateProfileAction(Integer userId, Uri uri) {
mValues.clear();
mValues.put(SampleSyncAdapterColumns.DATA_PID, userId);
addUpdateOp(uri);
return this;
}
/**
* Adds an insert operation into the batch
*/
private void addInsertOp() {
if (!mIsNewContact) {
mValues.put(Phone.RAW_CONTACT_ID, mRawContactId);
}
mBuilder =
newInsertCpo(addCallerIsSyncAdapterParameter(Data.CONTENT_URI),
mYield);
mBuilder.withValues(mValues);
if (mIsNewContact) {
mBuilder
.withValueBackReference(Data.RAW_CONTACT_ID, mBackReference);
}
mYield = false;
mBatchOperation.add(mBuilder.build());
}
/**
* Adds an update operation into the batch
*/
private void addUpdateOp(Uri uri) {
mBuilder = newUpdateCpo(uri, mYield).withValues(mValues);
mYield = false;
mBatchOperation.add(mBuilder.build());
}
public static ContentProviderOperation.Builder newInsertCpo(Uri uri,
boolean yield) {
return ContentProviderOperation.newInsert(
addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(yield);
}
public static ContentProviderOperation.Builder newUpdateCpo(Uri uri,
boolean yield) {
return ContentProviderOperation.newUpdate(
addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(yield);
}
public static ContentProviderOperation.Builder newDeleteCpo(Uri uri,
boolean yield) {
return ContentProviderOperation.newDelete(
addCallerIsSyncAdapterParameter(uri)).withYieldAllowed(yield);
}
private static Uri addCallerIsSyncAdapterParameter(Uri uri) {
return uri.buildUpon().appendQueryParameter(
ContactsContract.CALLER_IS_SYNCADAPTER, "true").build();
}
}

View File

@@ -0,0 +1,34 @@
/*
* 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.platform;
import android.provider.ContactsContract.Data;
/*
* The standard columns representing contact's info from social apps.
*/
public interface SampleSyncAdapterColumns {
/**
* MIME-type used when storing a profile {@link Data} entry.
*/
public static final String MIME_PROFILE =
"vnd.android.cursor.item/vnd.samplesyncadapter.profile";
public static final String DATA_PID = Data.DATA1;
public static final String DATA_SUMMARY = Data.DATA2;
public static final String DATA_DETAIL = Data.DATA3;
}

View File

@@ -0,0 +1,106 @@
/*
* 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.syncadapter;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.Context;
import android.content.SyncResult;
import android.os.Bundle;
import android.util.Log;
import com.example.android.samplesync.Constants;
import com.example.android.samplesync.client.NetworkUtilities;
import com.example.android.samplesync.client.User;
import com.example.android.samplesync.client.User.Status;
import com.example.android.samplesync.platform.ContactManager;
import org.apache.http.ParseException;
import org.apache.http.auth.AuthenticationException;
import org.json.JSONException;
import java.io.IOException;
import java.util.Date;
import java.util.List;
/**
* SyncAdapter implementation for syncing sample SyncAdapter contacts to the
* platform ContactOperations provider.
*/
public class SyncAdapter extends AbstractThreadedSyncAdapter {
private static final String TAG = "SyncAdapter";
private final AccountManager mAccountManager;
private final Context mContext;
private Date mLastUpdated;
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
mContext = context;
mAccountManager = AccountManager.get(context);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
List<User> users;
List<Status> statuses;
String authtoken = null;
try {
// use the account manager to request the credentials
authtoken =
mAccountManager.blockingGetAuthToken(account,
Constants.AUTHTOKEN_TYPE, true /* notifyAuthFailure */);
// fetch updates from the sample service over the cloud
users =
NetworkUtilities.fetchFriendUpdates(account, authtoken,
mLastUpdated);
// update the last synced date.
mLastUpdated = new Date();
// update platform contacts.
Log.d(TAG, "Calling contactManager's sync contacts");
ContactManager.syncContacts(mContext, account.name, users);
// fetch and update status messages for all the synced users.
statuses = NetworkUtilities.fetchFriendStatuses(account, authtoken);
ContactManager.insertStatuses(mContext, account.name, statuses);
} catch (final AuthenticatorException e) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "AuthenticatorException", e);
} catch (final OperationCanceledException e) {
Log.e(TAG, "OperationCanceledExcetpion", e);
} catch (final IOException e) {
Log.e(TAG, "IOException", e);
syncResult.stats.numIoExceptions++;
} catch (final AuthenticationException e) {
mAccountManager.invalidateAuthToken(Constants.ACCOUNT_TYPE,
authtoken);
syncResult.stats.numAuthExceptions++;
Log.e(TAG, "AuthenticationException", e);
} catch (final ParseException e) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "ParseException", e);
} catch (final JSONException e) {
syncResult.stats.numParseExceptions++;
Log.e(TAG, "JSONException", e);
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* 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.syncadapter;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
/**
* Service to handle Account sync. This is invoked with an intent with action
* ACTION_AUTHENTICATOR_INTENT. It instantiates the syncadapter and returns its
* IBinder.
*/
public class SyncService extends Service {
private static final Object sSyncAdapterLock = new Object();
private static SyncAdapter sSyncAdapter = null;
/*
* {@inheritDoc}
*/
@Override
public void onCreate() {
synchronized (sSyncAdapterLock) {
if (sSyncAdapter == null) {
sSyncAdapter = new SyncAdapter(getApplicationContext(), true);
}
}
}
/*
* {@inheritDoc}
*/
@Override
public IBinder onBind(Intent intent) {
return sSyncAdapter.getSyncAdapterBinder();
}
}