SampleSyncAdapter sample code.
This commit is contained in:
16
samples/SampleSyncAdapter/Android.mk
Normal file
16
samples/SampleSyncAdapter/Android.mk
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
LOCAL_PATH:= $(call my-dir)
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
LOCAL_MODULE_TAGS := samples tests
|
||||||
|
|
||||||
|
# Only compile source java files in this apk.
|
||||||
|
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||||
|
|
||||||
|
LOCAL_PACKAGE_NAME := Voiper
|
||||||
|
|
||||||
|
LOCAL_SDK_VERSION := current
|
||||||
|
|
||||||
|
include $(BUILD_PACKAGE)
|
||||||
|
|
||||||
|
# Use the folloing include to make our test apk.
|
||||||
|
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||||
92
samples/SampleSyncAdapter/AndroidManifest.xml
Normal file
92
samples/SampleSyncAdapter/AndroidManifest.xml
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
<manifest
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="com.example.android.samplesync"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0">
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.GET_ACCOUNTS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.USE_CREDENTIALS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.MANAGE_ACCOUNTS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_SETTINGS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_SECURE_SETTINGS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_CONTACTS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_CONTACTS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_SYNC_STATS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.READ_SYNC_SETTINGS" />
|
||||||
|
<uses-permission
|
||||||
|
android:name="android.permission.WRITE_SYNC_SETTINGS" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:label="@string/label">
|
||||||
|
<!-- The authenticator service -->
|
||||||
|
<service
|
||||||
|
android:name=".authenticator.AuthenticationService"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action
|
||||||
|
android:name="android.accounts.AccountAuthenticator" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.accounts.AccountAuthenticator"
|
||||||
|
android:resource="@xml/authenticator" />
|
||||||
|
</service>
|
||||||
|
<service
|
||||||
|
android:name=".syncadapter.SyncService"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action
|
||||||
|
android:name="android.content.SyncAdapter" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.content.SyncAdapter"
|
||||||
|
android:resource="@xml/syncadapter" />
|
||||||
|
<meta-data
|
||||||
|
android:name="android.provider.CONTACTS_STRUCTURE"
|
||||||
|
android:resource="@xml/contacts" />
|
||||||
|
</service>
|
||||||
|
<activity
|
||||||
|
android:name=".authenticator.AuthenticatorActivity"
|
||||||
|
android:label="@string/ui_activity_title"
|
||||||
|
android:theme="@android:style/Theme.Dialog"
|
||||||
|
android:excludeFromRecents="true"
|
||||||
|
>
|
||||||
|
<!--
|
||||||
|
No intent-filter here! This activity is only ever launched by
|
||||||
|
someone who explicitly knows the class name
|
||||||
|
-->
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
<uses-sdk
|
||||||
|
android:minSdkVersion="5" />
|
||||||
|
</manifest>
|
||||||
26
samples/SampleSyncAdapter/_index.html
Normal file
26
samples/SampleSyncAdapter/_index.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<p>A sample that demonstrates how an application can communicate with cloud-based services and synchronize their data with data stored locally in a content provider.
|
||||||
|
The sample uses two related parts of the Android framework — the account manager and the synchronization manager (through a sync adapter).</p>
|
||||||
|
|
||||||
|
<p> The <a href="../../../android/accounts/AccountManager">account manager</a> allows sharing of credentials across multiple applications and services.
|
||||||
|
Users enter the credentials for each account only once — applications with the <code>USE_CREDENTIALS</code> permission can then query the account manager
|
||||||
|
to obtain an auth token for the account.The authenticator (a pluggable component of account manager) requests credentials from the user, validates them
|
||||||
|
with an authentication server running in the cloud, and then stores them to the AccountManager.
|
||||||
|
This sample demonstrates how to write an authenticator for your
|
||||||
|
service by extending the new <code><a href="../../../android/accounts/AbstractAccountAuthenticator.html">AbstractAccountAuthenticator</a></code> abstract class.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>The sync adapter (essential to the synchronization service) declares the account type and ContentProvider authority to the sync manager.
|
||||||
|
This sample demosntrates how to write your own sync adapters by extending the <code><a href="../../../android/content/AbstractThreadedSyncAdapter.html">AbstractThreadedSyncAdapter</a></code>
|
||||||
|
abstract class and implementing the onPerformSync() method that gets called whenever the sync manager issues a sync operation for that sync adapter.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p> The service for this sample application is running at: <br>
|
||||||
|
http://samplesyncadapter.appspot.com/users
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p class="note">When you install this sample application, a new syncable "SampleSyncAdapter" account will be added to your phone's account manager.
|
||||||
|
You can go to "Settings | Accounts & sync" to view the accounts that are stored in the account manager and to change their sync settings. </p>
|
||||||
|
|
||||||
|
<img alt="Screenshot 1" src="../images/SampleSyncAdapter1.png" />
|
||||||
|
<img alt="Screenshot 2" src="../images/SampleSyncAdapter2.png" />
|
||||||
|
<img alt="Screenshot 3" src="../images/SampleSyncAdapter3.png" />
|
||||||
BIN
samples/SampleSyncAdapter/res/drawable/icon.png
Normal file
BIN
samples/SampleSyncAdapter/res/drawable/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
110
samples/SampleSyncAdapter/res/layout/login_activity.xml
Normal file
110
samples/SampleSyncAdapter/res/layout/login_activity.xml
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<!--
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="0dip"
|
||||||
|
android:layout_weight="1">
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="5dip"
|
||||||
|
android:paddingBottom="13dip"
|
||||||
|
android:paddingLeft="20dip"
|
||||||
|
android:paddingRight="20dip">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="5dip" />
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login_activity_username_label" />
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/username_edit"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="250dip"
|
||||||
|
android:scrollHorizontally="true"
|
||||||
|
android:capitalize="none"
|
||||||
|
android:autoText="false"
|
||||||
|
android:inputType="textEmailAddress" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/username_fixed"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_marginTop="2dip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
<TextView
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_marginTop="2dip"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login_activity_password_label" />
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/password_edit"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="250dip"
|
||||||
|
android:scrollHorizontally="true"
|
||||||
|
android:capitalize="none"
|
||||||
|
android:autoText="false"
|
||||||
|
android:password="true"
|
||||||
|
android:inputType="textPassword" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/message_bottom"
|
||||||
|
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="5dip" />
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="#c6c3c6"
|
||||||
|
android:minHeight="54dip"
|
||||||
|
android:paddingTop="4dip"
|
||||||
|
android:paddingLeft="2dip"
|
||||||
|
android:paddingRight="2dip">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/ok_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:minWidth="100dip"
|
||||||
|
android:text="@string/login_activity_ok_button"
|
||||||
|
android:onClick="handleLogin" />
|
||||||
|
</FrameLayout>
|
||||||
|
</LinearLayout>
|
||||||
93
samples/SampleSyncAdapter/res/values/strings.xml
Normal file
93
samples/SampleSyncAdapter/res/values/strings.xml
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
<resources
|
||||||
|
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
|
<!-- Label for this package -->
|
||||||
|
<string
|
||||||
|
name="label">SamplesyncAdapter</string>
|
||||||
|
|
||||||
|
<!-- Permission label -->
|
||||||
|
<string
|
||||||
|
name="permlab_samplesyncadapterAuthPassword">access to passwords for Sample SyncAdapter accounts</string>
|
||||||
|
<!-- Permission description -->
|
||||||
|
<string
|
||||||
|
name="permdesc_samplesyncadapterAuthPassword">Allows applications direct access to the passwords for the
|
||||||
|
Sample SyncAdapter account(s) you have configured.</string>
|
||||||
|
|
||||||
|
<!-- Permission label -->
|
||||||
|
<string
|
||||||
|
name="permlab_samplesyncadapterAuth">view configured accounts</string>
|
||||||
|
<!-- Permission description -->
|
||||||
|
<string
|
||||||
|
name="permdesc_samplesyncadapterAuth">Allows applications to see the usernames (email addresses) of
|
||||||
|
the Sample SyncAdapter account(s) you have configured.</string>
|
||||||
|
<string
|
||||||
|
name="notification_login_error">Touch to sign into your Sample SyncAdapter account.</string>
|
||||||
|
|
||||||
|
<!-- Title string for Login activity-->
|
||||||
|
<string
|
||||||
|
name="ui_activity_title">Sign-in</string>
|
||||||
|
<!-- Message shown in progress dialog while app connects to the server -->
|
||||||
|
<string
|
||||||
|
name="ui_activity_authenticating">Authenticating\u2026</string>
|
||||||
|
|
||||||
|
<!-- AuthenticatorActivity -->
|
||||||
|
<skip />
|
||||||
|
<!-- Label above username EditText -->
|
||||||
|
<string
|
||||||
|
name="login_activity_username_label">Username</string>
|
||||||
|
<!-- Label above password EditText -->
|
||||||
|
<string
|
||||||
|
name="login_activity_password_label">Password</string>
|
||||||
|
<!-- Button to sign in after entering username and password -->
|
||||||
|
<string
|
||||||
|
name="login_activity_ok_button">Sign in</string>
|
||||||
|
|
||||||
|
<!-- Message shown in dialog if the username or password is invalid. -->
|
||||||
|
<string
|
||||||
|
name="login_activity_loginfail_text_both">The username or password isn\'t valid. A Sample SyncAdapter account is
|
||||||
|
required. Please try again. </string>
|
||||||
|
<!-- Message shown in dialog if the password is invalid -->
|
||||||
|
<string
|
||||||
|
name="login_activity_loginfail_text_pwonly">You entered the wrong password or your account has changed.
|
||||||
|
Please re-enter your password.</string>
|
||||||
|
<!-- Message shown in dialog to prompt the user for their password -->
|
||||||
|
<string
|
||||||
|
name="login_activity_loginfail_text_pwmissing">Type the password for this account.</string>
|
||||||
|
<!--
|
||||||
|
Message shown if the provided account doesn't support the current
|
||||||
|
activity.
|
||||||
|
-->
|
||||||
|
<string
|
||||||
|
name="login_activity_newaccount_text">Sign in to your Sample SyncAdapter account. </string>
|
||||||
|
|
||||||
|
<!-- Button that takes the user to the "sign in" screen -->
|
||||||
|
<string
|
||||||
|
name="sign_in_button_label">Sign in</string>
|
||||||
|
<!-- Button for going to the previous screen or step -->
|
||||||
|
<string
|
||||||
|
name="back_button_label">Back</string>
|
||||||
|
<!-- Button to cancel the current operation -->
|
||||||
|
<string
|
||||||
|
name="cancel_button_label">Cancel</string>
|
||||||
|
<string
|
||||||
|
name="profile_action">Sample profile</string>
|
||||||
|
<string
|
||||||
|
name="view_profile">View Profile</string>
|
||||||
|
</resources>
|
||||||
28
samples/SampleSyncAdapter/res/xml/authenticator.xml
Normal file
28
samples/SampleSyncAdapter/res/xml/authenticator.xml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- The attributes in this XML file provide configuration information -->
|
||||||
|
<!-- for the Account Manager. -->
|
||||||
|
|
||||||
|
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:accountType="com.example.android.samplesync"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:smallIcon="@drawable/icon"
|
||||||
|
android:label="@string/label"
|
||||||
|
/>
|
||||||
29
samples/SampleSyncAdapter/res/xml/contacts.xml
Normal file
29
samples/SampleSyncAdapter/res/xml/contacts.xml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ContactsSource xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<ContactsDataKind
|
||||||
|
android:mimeType="vnd.android.cursor.item/vnd.samplesyncadapter.profile"
|
||||||
|
android:icon="@drawable/icon"
|
||||||
|
android:summaryColumn="data2"
|
||||||
|
android:detailColumn="data3"
|
||||||
|
android:detailSocialSummary="true" />
|
||||||
|
|
||||||
|
</ContactsSource>
|
||||||
27
samples/SampleSyncAdapter/res/xml/syncadapter.xml
Normal file
27
samples/SampleSyncAdapter/res/xml/syncadapter.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!-- The attributes in this XML file provide configuration information -->
|
||||||
|
<!-- for the SyncAdapter. -->
|
||||||
|
|
||||||
|
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:contentAuthority="com.android.contacts"
|
||||||
|
android:accountType="com.example.android.samplesync"
|
||||||
|
android:supportsUploading="false"
|
||||||
|
/>
|
||||||
44
samples/SampleSyncAdapter/samplesyncadapter_server/app.yaml
Normal file
44
samples/SampleSyncAdapter/samplesyncadapter_server/app.yaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
application: samplesyncadapter
|
||||||
|
version: 1
|
||||||
|
runtime: python
|
||||||
|
api_version: 1
|
||||||
|
|
||||||
|
handlers:
|
||||||
|
- url: /auth
|
||||||
|
script: main.py
|
||||||
|
|
||||||
|
- url: /login
|
||||||
|
script: main.py
|
||||||
|
|
||||||
|
- url: /fetch_friend_updates
|
||||||
|
script: main.py
|
||||||
|
|
||||||
|
- url: /fetch_status
|
||||||
|
script: main.py
|
||||||
|
|
||||||
|
- url: /add_user
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /edit_user
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /users
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /delete_friend
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /edit_user
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /add_credentials
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /user_credentials
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /add_friend
|
||||||
|
script: dashboard.py
|
||||||
|
|
||||||
|
- url: /user_friends
|
||||||
|
script: dashboard.py
|
||||||
273
samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
Normal file
273
samples/SampleSyncAdapter/samplesyncadapter_server/dashboard.py
Normal file
@@ -0,0 +1,273 @@
|
|||||||
|
#!/usr/bin/python2.5
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Defines Django forms for inserting/updating/viewing data
|
||||||
|
to/from SampleSyncAdapter datastore."""
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
import datetime
|
||||||
|
import os
|
||||||
|
|
||||||
|
from google.appengine.ext import db
|
||||||
|
from google.appengine.ext import webapp
|
||||||
|
from google.appengine.ext.webapp import template
|
||||||
|
from google.appengine.ext.db import djangoforms
|
||||||
|
from model import datastore
|
||||||
|
|
||||||
|
import wsgiref.handlers
|
||||||
|
|
||||||
|
|
||||||
|
class UserForm(djangoforms.ModelForm):
|
||||||
|
"""Represents django form for entering user info."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = datastore.User
|
||||||
|
|
||||||
|
|
||||||
|
class UserInsertPage(webapp.RequestHandler):
|
||||||
|
"""Inserts new users. GET presents a blank form. POST processes it."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/add_user">'
|
||||||
|
'<table>')
|
||||||
|
# This generates our shopping list form and writes it in the response
|
||||||
|
self.response.out.write(UserForm())
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>')
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
data = UserForm(data=self.request.POST)
|
||||||
|
if data.is_valid():
|
||||||
|
# Save the data, and redirect to the view page
|
||||||
|
entity = data.save(commit=False)
|
||||||
|
entity.put()
|
||||||
|
self.redirect('/users')
|
||||||
|
else:
|
||||||
|
# Reprint the form
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/">'
|
||||||
|
'<table>')
|
||||||
|
self.response.out.write(data)
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>')
|
||||||
|
|
||||||
|
|
||||||
|
class UserEditPage(webapp.RequestHandler):
|
||||||
|
"""Edits users. GET presents a form prefilled with user info
|
||||||
|
from datastore. POST processes it."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
id = int(self.request.get('user'))
|
||||||
|
user = datastore.User.get(db.Key.from_path('User', id))
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/edit_user">'
|
||||||
|
'<table>')
|
||||||
|
# This generates our shopping list form and writes it in the response
|
||||||
|
self.response.out.write(UserForm(instance=user))
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="hidden" name="_id" value="%s">'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>' % id)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
id = int(self.request.get('_id'))
|
||||||
|
user = datastore.User.get(db.Key.from_path('User', id))
|
||||||
|
data = UserForm(data=self.request.POST, instance=user)
|
||||||
|
if data.is_valid():
|
||||||
|
# Save the data, and redirect to the view page
|
||||||
|
entity = data.save(commit=False)
|
||||||
|
entity.updated = datetime.datetime.utcnow()
|
||||||
|
entity.put()
|
||||||
|
self.redirect('/users')
|
||||||
|
else:
|
||||||
|
# Reprint the form
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/edit_user">'
|
||||||
|
'<table>')
|
||||||
|
self.response.out.write(data)
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="hidden" name="_id" value="%s">'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>' % id)
|
||||||
|
|
||||||
|
|
||||||
|
class UsersListPage(webapp.RequestHandler):
|
||||||
|
"""Lists all Users. In addition displays links for editing user info,
|
||||||
|
viewing user's friends and adding new users."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
users = datastore.User.all()
|
||||||
|
template_values = {
|
||||||
|
'users': users
|
||||||
|
}
|
||||||
|
|
||||||
|
path = os.path.join(os.path.dirname(__file__), 'templates', 'users.html')
|
||||||
|
self.response.out.write(template.render(path, template_values))
|
||||||
|
|
||||||
|
|
||||||
|
class UserCredentialsForm(djangoforms.ModelForm):
|
||||||
|
"""Represents django form for entering user's credentials."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = datastore.UserCredentials
|
||||||
|
|
||||||
|
|
||||||
|
class UserCredentialsInsertPage(webapp.RequestHandler):
|
||||||
|
"""Inserts user credentials. GET shows a blank form, POST processes it."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/add_credentials">'
|
||||||
|
'<table>')
|
||||||
|
# This generates our shopping list form and writes it in the response
|
||||||
|
self.response.out.write(UserCredentialsForm())
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>')
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
data = UserCredentialsForm(data=self.request.POST)
|
||||||
|
if data.is_valid():
|
||||||
|
# Save the data, and redirect to the view page
|
||||||
|
entity = data.save(commit=False)
|
||||||
|
entity.put()
|
||||||
|
self.redirect('/users')
|
||||||
|
else:
|
||||||
|
# Reprint the form
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/add_credentials">'
|
||||||
|
'<table>')
|
||||||
|
self.response.out.write(data)
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>')
|
||||||
|
|
||||||
|
|
||||||
|
class UserFriendsForm(djangoforms.ModelForm):
|
||||||
|
"""Represents django form for entering user's friends."""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = datastore.UserFriends
|
||||||
|
exclude = ['deleted', 'username']
|
||||||
|
|
||||||
|
|
||||||
|
class UserFriendsInsertPage(webapp.RequestHandler):
|
||||||
|
"""Inserts user's new friends. GET shows a blank form, POST processes it."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
user = self.request.get('user')
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/add_friend">'
|
||||||
|
'<table>')
|
||||||
|
# This generates our shopping list form and writes it in the response
|
||||||
|
self.response.out.write(UserFriendsForm())
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type = hidden name = "user" value = "%s">'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>' % user)
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
data = UserFriendsForm(data=self.request.POST)
|
||||||
|
if data.is_valid():
|
||||||
|
user = self.request.get('user')
|
||||||
|
# Save the data, and redirect to the view page
|
||||||
|
entity = data.save(commit=False)
|
||||||
|
entity.username = user
|
||||||
|
query = datastore.UserFriends.all()
|
||||||
|
query.filter('username = ', user)
|
||||||
|
query.filter('friend_handle = ', entity.friend_handle)
|
||||||
|
result = query.get()
|
||||||
|
if result:
|
||||||
|
result.deleted = False
|
||||||
|
result.updated = datetime.datetime.utcnow()
|
||||||
|
result.put()
|
||||||
|
else:
|
||||||
|
entity.deleted = False
|
||||||
|
entity.put()
|
||||||
|
self.redirect('/user_friends?user=' + user)
|
||||||
|
else:
|
||||||
|
# Reprint the form
|
||||||
|
self.response.out.write('<html><body>'
|
||||||
|
'<form method="POST" '
|
||||||
|
'action="/add_friend">'
|
||||||
|
'<table>')
|
||||||
|
self.response.out.write(data)
|
||||||
|
self.response.out.write('</table>'
|
||||||
|
'<input type="submit">'
|
||||||
|
'</form></body></html>')
|
||||||
|
|
||||||
|
|
||||||
|
class UserFriendsListPage(webapp.RequestHandler):
|
||||||
|
"""Lists all friends for a user. In addition displays links for removing
|
||||||
|
friends and adding new friends."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
user = self.request.get('user')
|
||||||
|
query = datastore.UserFriends.all()
|
||||||
|
query.filter('deleted = ', False)
|
||||||
|
query.filter('username = ', user)
|
||||||
|
friends = query.fetch(50)
|
||||||
|
template_values = {
|
||||||
|
'friends': friends,
|
||||||
|
'user': user
|
||||||
|
}
|
||||||
|
path = os.path.join(os.path.dirname(__file__),
|
||||||
|
'templates', 'view_friends.html')
|
||||||
|
self.response.out.write(template.render(path, template_values))
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteFriendPage(webapp.RequestHandler):
|
||||||
|
"""Processes delete friend request."""
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
user = self.request.get('user')
|
||||||
|
friend = self.request.get('friend')
|
||||||
|
query = datastore.UserFriends.all()
|
||||||
|
query.filter('username =', user)
|
||||||
|
query.filter('friend_handle =', friend)
|
||||||
|
result = query.get()
|
||||||
|
result.deleted = True
|
||||||
|
result.updated = datetime.datetime.utcnow()
|
||||||
|
result.put()
|
||||||
|
|
||||||
|
self.redirect('/user_friends?user=' + user)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
application = webapp.WSGIApplication(
|
||||||
|
[('/add_user', UserInsertPage),
|
||||||
|
('/users', UsersListPage),
|
||||||
|
('/add_credentials', UserCredentialsInsertPage),
|
||||||
|
('/add_friend', UserFriendsInsertPage),
|
||||||
|
('/user_friends', UserFriendsListPage),
|
||||||
|
('/delete_friend', DeleteFriendPage),
|
||||||
|
('/edit_user', UserEditPage)
|
||||||
|
],
|
||||||
|
debug=True)
|
||||||
|
wsgiref.handlers.CGIHandler().run(application)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
indexes:
|
||||||
|
|
||||||
|
# This index.yaml is automatically updated whenever the dev_appserver
|
||||||
|
# detects that a new type of query is run. If you want to manage the
|
||||||
|
# index.yaml file manually, remove the above marker line (the line
|
||||||
|
# saying "# AUTOGENERATED"). If you want to manage some indexes
|
||||||
|
# manually, move them above the marker line. The index.yaml file is
|
||||||
|
# automatically uploaded to the admin console when you next deploy
|
||||||
|
# your application using appcfg.py.
|
||||||
|
|
||||||
|
- kind: UserFriends
|
||||||
|
properties:
|
||||||
|
- name: username
|
||||||
|
- name: updated
|
||||||
173
samples/SampleSyncAdapter/samplesyncadapter_server/main.py
Normal file
173
samples/SampleSyncAdapter/samplesyncadapter_server/main.py
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/python2.5
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Handlers for Sample SyncAdapter services.
|
||||||
|
|
||||||
|
Contains several RequestHandler subclasses used to handle post operations.
|
||||||
|
This script is designed to be run directly as a WSGI application.
|
||||||
|
|
||||||
|
Authenticate: Handles user requests for authentication.
|
||||||
|
FetchFriends: Handles user requests for friend list.
|
||||||
|
FriendData: Stores information about user's friends.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import cgi
|
||||||
|
from datetime import datetime
|
||||||
|
from django.utils import simplejson
|
||||||
|
from google.appengine.api import users
|
||||||
|
from google.appengine.ext import db
|
||||||
|
from google.appengine.ext import webapp
|
||||||
|
from model import datastore
|
||||||
|
import wsgiref.handlers
|
||||||
|
|
||||||
|
|
||||||
|
class Authenticate(webapp.RequestHandler):
|
||||||
|
"""Handles requests for login and authentication.
|
||||||
|
|
||||||
|
UpdateHandler only accepts post events. It expects each
|
||||||
|
request to include username and password fields. It returns authtoken
|
||||||
|
after successful authentication and "invalid credentials" error otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
self.username = self.request.get('username')
|
||||||
|
self.password = self.request.get('password')
|
||||||
|
password = datastore.UserCredentials.get(self.username)
|
||||||
|
if password == self.password:
|
||||||
|
self.response.set_status(200, 'OK')
|
||||||
|
# return the password as AuthToken
|
||||||
|
self.response.out.write(password)
|
||||||
|
else:
|
||||||
|
self.response.set_status(401, 'Invalid Credentials')
|
||||||
|
|
||||||
|
|
||||||
|
class FetchFriends(webapp.RequestHandler):
|
||||||
|
"""Handles requests for fetching user's friendlist.
|
||||||
|
|
||||||
|
UpdateHandler only accepts post events. It expects each
|
||||||
|
request to include username and authtoken. If the authtoken is valid
|
||||||
|
it returns user's friend info in JSON format.It uses helper
|
||||||
|
class FriendData to fetch user's friendlist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
self.username = self.request.get('username')
|
||||||
|
self.password = self.request.get('password')
|
||||||
|
self.timestamp = None
|
||||||
|
timestamp = self.request.get('timestamp')
|
||||||
|
if timestamp:
|
||||||
|
self.timestamp = datetime.strptime(timestamp, '%Y/%m/%d %H:%M')
|
||||||
|
password = datastore.UserCredentials.get(self.username)
|
||||||
|
if password == self.password:
|
||||||
|
self.friend_list = []
|
||||||
|
friends = datastore.UserFriends.get_friends(self.username)
|
||||||
|
if friends:
|
||||||
|
for friend in friends:
|
||||||
|
friend_handle = getattr(friend, 'friend_handle')
|
||||||
|
|
||||||
|
if self.timestamp is None or getattr(friend, 'updated') > self.timestamp:
|
||||||
|
if (getattr(friend, 'deleted')) == True:
|
||||||
|
friend = {}
|
||||||
|
friend['u'] = friend_handle
|
||||||
|
friend['d'] = 'true'
|
||||||
|
friend['i'] = str(datastore.User.get_user_id(friend_handle))
|
||||||
|
self.friend_list.append(friend)
|
||||||
|
else:
|
||||||
|
FriendsData(self.friend_list, friend_handle)
|
||||||
|
else:
|
||||||
|
if datastore.User.get_user_last_updated(friend_handle) > self.timestamp:
|
||||||
|
FriendsData(self.friend_list, friend_handle)
|
||||||
|
self.response.set_status(200)
|
||||||
|
self.response.out.write(toJSON(self.friend_list))
|
||||||
|
else:
|
||||||
|
self.response.set_status(401, 'Invalid Credentials')
|
||||||
|
|
||||||
|
class FetchStatus(webapp.RequestHandler):
|
||||||
|
"""Handles requests fetching friend statuses.
|
||||||
|
|
||||||
|
UpdateHandler only accepts post events. It expects each
|
||||||
|
request to include username and authtoken. If the authtoken is valid
|
||||||
|
it returns status info in JSON format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def post(self):
|
||||||
|
self.username = self.request.get('username')
|
||||||
|
self.password = self.request.get('password')
|
||||||
|
password = datastore.UserCredentials.get(self.username)
|
||||||
|
if password == self.password:
|
||||||
|
self.status_list = []
|
||||||
|
friends = datastore.UserFriends.get_friends(self.username)
|
||||||
|
if friends:
|
||||||
|
for friend in friends:
|
||||||
|
friend_handle = getattr(friend, 'friend_handle')
|
||||||
|
status_text = datastore.User.get_user_status(friend_handle)
|
||||||
|
user_id = datastore.User.get_user_id(friend_handle)
|
||||||
|
status = {}
|
||||||
|
status['i'] = str(user_id)
|
||||||
|
status['s'] = status_text
|
||||||
|
self.status_list.append(status)
|
||||||
|
self.response.set_status(200)
|
||||||
|
self.response.out.write(toJSON(self.status_list))
|
||||||
|
else:
|
||||||
|
self.response.set_status(401, 'Invalid Credentials')
|
||||||
|
|
||||||
|
def toJSON(self):
|
||||||
|
"""Dumps the data represented by the object to JSON for wire transfer."""
|
||||||
|
return simplejson.dumps(self.friend_list)
|
||||||
|
|
||||||
|
|
||||||
|
def toJSON(object):
|
||||||
|
"""Dumps the data represented by the object to JSON for wire transfer."""
|
||||||
|
return simplejson.dumps(object)
|
||||||
|
|
||||||
|
class FriendsData(object):
|
||||||
|
"""Holds data for user's friends.
|
||||||
|
|
||||||
|
This class knows how to serialize itself to JSON.
|
||||||
|
"""
|
||||||
|
__FIELD_MAP = {
|
||||||
|
'handle': 'u',
|
||||||
|
'firstname': 'f',
|
||||||
|
'lastname': 'l',
|
||||||
|
'status': 's',
|
||||||
|
'phone_home': 'h',
|
||||||
|
'phone_office': 'o',
|
||||||
|
'phone_mobile': 'm',
|
||||||
|
'email': 'e',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, friend_list, username):
|
||||||
|
obj = datastore.User.get_user_info(username)
|
||||||
|
friend = {}
|
||||||
|
for obj_name, json_name in self.__FIELD_MAP.items():
|
||||||
|
if hasattr(obj, obj_name):
|
||||||
|
friend[json_name] = str(getattr(obj, obj_name))
|
||||||
|
friend['i'] = str(obj.key().id())
|
||||||
|
friend_list.append(friend)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
application = webapp.WSGIApplication(
|
||||||
|
[('/auth', Authenticate),
|
||||||
|
('/login', Authenticate),
|
||||||
|
('/fetch_friend_updates', FetchFriends),
|
||||||
|
('/fetch_status', FetchStatus),
|
||||||
|
],
|
||||||
|
debug=True)
|
||||||
|
wsgiref.handlers.CGIHandler().run(application)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/python2.5
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""Represents user's contact information, friends and credentials."""
|
||||||
|
|
||||||
|
from google.appengine.ext import db
|
||||||
|
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
"""Data model class to hold user objects."""
|
||||||
|
|
||||||
|
handle = db.StringProperty(required=True)
|
||||||
|
firstname = db.TextProperty()
|
||||||
|
lastname = db.TextProperty()
|
||||||
|
status = db.TextProperty()
|
||||||
|
phone_home = db.PhoneNumberProperty()
|
||||||
|
phone_office = db.PhoneNumberProperty()
|
||||||
|
phone_mobile = db.PhoneNumberProperty()
|
||||||
|
email = db.EmailProperty()
|
||||||
|
deleted = db.BooleanProperty()
|
||||||
|
updated = db.DateTimeProperty(auto_now_add=True)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_info(cls, username):
|
||||||
|
if username not in (None, ''):
|
||||||
|
query = cls.gql('WHERE handle = :1', username)
|
||||||
|
return query.get()
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_last_updated(cls, username):
|
||||||
|
if username not in (None, ''):
|
||||||
|
query = cls.gql('WHERE handle = :1', username)
|
||||||
|
return query.get().updated
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_id(cls, username):
|
||||||
|
if username not in (None, ''):
|
||||||
|
query = cls.gql('WHERE handle = :1', username)
|
||||||
|
return query.get().key().id()
|
||||||
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_user_status(cls, username):
|
||||||
|
if username not in (None, ''):
|
||||||
|
query = cls.gql('WHERE handle = :1', username)
|
||||||
|
return query.get().status
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class UserCredentials(db.Model):
|
||||||
|
"""Data model class to hold credentials for a Voiper user."""
|
||||||
|
|
||||||
|
username = db.StringProperty(required=True)
|
||||||
|
password = db.StringProperty()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get(cls, username):
|
||||||
|
if username not in (None, ''):
|
||||||
|
query = cls.gql('WHERE username = :1', username)
|
||||||
|
return query.get().password
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class UserFriends(db.Model):
|
||||||
|
"""Data model class to hold user's friendlist info."""
|
||||||
|
|
||||||
|
username = db.StringProperty()
|
||||||
|
friend_handle = db.StringProperty(required=True)
|
||||||
|
updated = db.DateTimeProperty(auto_now_add=True)
|
||||||
|
deleted = db.BooleanProperty()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_friends(cls, username):
|
||||||
|
if username not in (None, ''):
|
||||||
|
query = cls.gql('WHERE username = :1', username)
|
||||||
|
friends = query.fetch(50)
|
||||||
|
return friends
|
||||||
|
return None
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1> Sample Sync Adapter </h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<h3> List of Users </h3>
|
||||||
|
<table>
|
||||||
|
|
||||||
|
{% for user in users %}
|
||||||
|
<tr><td>
|
||||||
|
<a
|
||||||
|
href="/edit_user?user={{ user.key.id}}">{{ user.firstname }} {{ user.lastname }} </a>
|
||||||
|
</td><td> <a href="/user_friends?user={{ user.handle }}">Friends</a> </td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href = "/add_user"> Insert More </a>
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1> Sample Sync Adapter </h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
|
||||||
|
{{user}}'s friends
|
||||||
|
<table>
|
||||||
|
{% for friend in friends %}
|
||||||
|
<tr><td>
|
||||||
|
{{ friend.friend_handle }} </td><td> <a href="/delete_friend?user={{ user }}&friend={{friend.friend_handle}}">Remove</a>
|
||||||
|
</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<a href = "/add_friend?user={{user}}"> Add More </a>
|
||||||
@@ -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";
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 + "=?";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user