- remove an un-needed constant
- add a content provider helper that manages a table for storing sync state by account
- add contact definitions to Contacts access the sync state
diff --git a/api/current.xml b/api/current.xml
index b17466f..88e5025 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -17434,17 +17434,6 @@
  visibility="public"
 >
 </field>
-<field name="PASSWORD_KEY"
- type="java.lang.String"
- transient="false"
- volatile="false"
- value="&quot;password&quot;"
- static="true"
- final="true"
- deprecated="not deprecated"
- visibility="public"
->
-</field>
 <field name="USERDATA_KEY"
  type="java.lang.String"
  transient="false"
@@ -114140,6 +114129,170 @@
 >
 </field>
 </class>
+<class name="SyncStateContract"
+ extends="java.lang.Object"
+ abstract="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SyncStateContract"
+ type="android.provider.SyncStateContract"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+</class>
+<interface name="SyncStateContract.Columns"
+ abstract="true"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.provider.BaseColumns">
+</implements>
+<field name="ACCOUNT_NAME"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;account_name&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="ACCOUNT_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;account_type&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DATA"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;data&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</interface>
+<class name="SyncStateContract.Constants"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.provider.SyncStateContract.Columns">
+</implements>
+<constructor name="SyncStateContract.Constants"
+ type="android.provider.SyncStateContract.Constants"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<field name="CONTENT_DIRECTORY"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;syncstate&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
+<class name="SyncStateContract.Helpers"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<constructor name="SyncStateContract.Helpers"
+ type="android.provider.SyncStateContract.Helpers"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="get"
+ return="byte[]"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="provider" type="android.content.ContentProviderClient">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+<method name="newSetOperation"
+ return="android.content.ContentProviderOperation"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="data" type="byte[]">
+</parameter>
+</method>
+<method name="set"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="provider" type="android.content.ContentProviderClient">
+</parameter>
+<parameter name="uri" type="android.net.Uri">
+</parameter>
+<parameter name="account" type="android.accounts.Account">
+</parameter>
+<parameter name="data" type="byte[]">
+</parameter>
+<exception name="RemoteException" type="android.os.RemoteException">
+</exception>
+</method>
+</class>
 <class name="UserDictionary"
  extends="java.lang.Object"
  abstract="false"
diff --git a/core/java/android/accounts/Constants.java b/core/java/android/accounts/Constants.java
index fde920e..4c1fa02 100644
--- a/core/java/android/accounts/Constants.java
+++ b/core/java/android/accounts/Constants.java
@@ -29,7 +29,6 @@
 
     public static final String ACCOUNTS_KEY = "accounts";
     public static final String AUTHENTICATOR_TYPES_KEY = "authenticator_types";
-    public static final String PASSWORD_KEY = "password";
     public static final String USERDATA_KEY = "userdata";
     public static final String AUTHTOKEN_KEY = "authtoken";
     public static final String ACCOUNT_NAME_KEY = "authAccount";
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index d03708c..04a5a0f 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -16,13 +16,14 @@
 
 package android.provider;
 
-import java.util.Arrays;
-import java.util.List;
-
 import android.content.Intent;
+import android.content.ContentProviderClient;
+import android.content.ContentProviderOperation;
 import android.graphics.BitmapFactory;
 import android.net.Uri;
 import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.accounts.Account;
+import android.os.RemoteException;
 
 /**
  * The contract between the contacts provider and applications. Contains definitions
@@ -36,6 +37,48 @@
     /** A content:// style uri to the authority for the contacts provider */
     public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);
 
+    public interface SyncStateColumns extends SyncStateContract.Columns {
+    }
+
+    public static final class SyncState {
+        /**
+         * This utility class cannot be instantiated
+         */
+        private SyncState() {}
+
+        public static final String CONTENT_DIRECTORY =
+                SyncStateContract.Constants.CONTENT_DIRECTORY;
+
+        /**
+         * The content:// style URI for this table
+         */
+        public static final Uri CONTENT_URI =
+                Uri.withAppendedPath(AUTHORITY_URI, CONTENT_DIRECTORY);
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#get
+         */
+        public static byte[] get(ContentProviderClient provider, Account account)
+                throws RemoteException {
+            return SyncStateContract.Helpers.get(provider, CONTENT_URI, account);
+        }
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#set
+         */
+        public static void set(ContentProviderClient provider, Account account, byte[] data)
+                throws RemoteException {
+            SyncStateContract.Helpers.set(provider, CONTENT_URI, account, data);
+        }
+
+        /**
+         * @see android.provider.SyncStateContract.Helpers#newSetOperation
+         */
+        public static ContentProviderOperation newSetOperation(Account account, byte[] data) {
+            return SyncStateContract.Helpers.newSetOperation(CONTENT_URI, account, data);
+        }
+    }
+
     public interface AggregatesColumns {
         /**
          * The display name for the contact.
diff --git a/core/java/android/provider/SyncStateContract.java b/core/java/android/provider/SyncStateContract.java
new file mode 100644
index 0000000..7927e28
--- /dev/null
+++ b/core/java/android/provider/SyncStateContract.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2009 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 android.provider;
+
+import android.net.Uri;
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.content.ContentProviderOperation;
+import android.accounts.Account;
+import android.database.Cursor;
+import android.os.RemoteException;
+
+/**
+ * The ContentProvider contract for associating data with ana data array account.
+ * This may be used by providers that want to store this data in a standard way.
+ */
+public class SyncStateContract {
+    public interface Columns extends BaseColumns {
+        /**
+         * A reference to the name of the account to which this data belongs
+         * <P>Type: STRING</P>
+         */
+        public static final String ACCOUNT_NAME = "account_name";
+
+        /**
+         * A reference to the type of the account to which this data belongs
+         * <P>Type: STRING</P>
+         */
+        public static final String ACCOUNT_TYPE = "account_type";
+
+        /**
+         * The sync data associated with this account.
+         * <P>Type: NONE</P>
+         */
+        public static final String DATA = "data";
+    }
+
+    public static class Constants implements Columns {
+        public static final String CONTENT_DIRECTORY = "syncstate";
+    }
+
+    public static final class Helpers {
+        private static final String[] DATA_PROJECTION = new String[]{Columns.DATA};
+        private static final String SELECT_BY_ACCOUNT =
+                Columns.ACCOUNT_NAME + "=? AND " + Columns.ACCOUNT_TYPE + "=?";
+
+        /**
+         * Get the sync state that is associated with the account or null.
+         * @param provider the {@link ContentProviderClient} that is to be used to communicate
+         * with the {@link android.content.ContentProvider} that contains the sync state.
+         * @param uri the uri of the sync state
+         * @param account the {@link Account} whose sync state should be returned
+         * @return the sync state or null if there is no sync state associated with the account
+         * @throws RemoteException if there is a failure communicating with the remote
+         * {@link android.content.ContentProvider}
+         */
+        public static byte[] get(ContentProviderClient provider, Uri uri,
+                Account account) throws RemoteException {
+            Cursor c = provider.query(uri, DATA_PROJECTION, SELECT_BY_ACCOUNT,
+                    new String[]{account.mName, account.mType}, null);
+            try {
+                if (c.moveToNext()) {
+                    return c.getBlob(c.getColumnIndexOrThrow(Columns.DATA));
+                }
+            } finally {
+                c.close();
+            }
+            return null;
+        }
+
+        /**
+         * Assigns the data array as the sync state for the given account.
+         * @param provider the {@link ContentProviderClient} that is to be used to communicate
+         * with the {@link android.content.ContentProvider} that contains the sync state.
+         * @param uri the uri of the sync state
+         * @param account the {@link Account} whose sync state should be set
+         * @param data the byte[] that contains the sync state
+         * @throws RemoteException if there is a failure communicating with the remote
+         * {@link android.content.ContentProvider}
+         */
+        public static void set(ContentProviderClient provider, Uri uri,
+                Account account, byte[] data) throws RemoteException {
+            ContentValues values = new ContentValues();
+            values.put(Columns.DATA, data);
+            values.put(Columns.ACCOUNT_NAME, account.mName);
+            values.put(Columns.ACCOUNT_TYPE, account.mType);
+            provider.insert(uri, values);
+        }
+
+        /**
+         * Creates and returns a ContentProviderOperation that assigns the data array as the
+         * sync state for the given account.
+         * @param uri the uri of the sync state
+         * @param account the {@link Account} whose sync state should be set
+         * @param data the byte[] that contains the sync state
+         * @return the new ContentProviderOperation that assigns the data array as the
+         * account's sync state
+         */
+        public static ContentProviderOperation newSetOperation(Uri uri,
+                Account account, byte[] data) {
+            ContentValues values = new ContentValues();
+            values.put(Columns.DATA, data);
+            return ContentProviderOperation
+                    .newInsert(uri)
+                    .withValue(Columns.ACCOUNT_NAME, account.mName)
+                    .withValue(Columns.ACCOUNT_TYPE, account.mType)
+                    .withValues(values)
+                    .build();
+        }
+    }
+}
diff --git a/core/java/com/android/internal/content/SyncStateContentProviderHelper.java b/core/java/com/android/internal/content/SyncStateContentProviderHelper.java
new file mode 100644
index 0000000..d2931a4b
--- /dev/null
+++ b/core/java/com/android/internal/content/SyncStateContentProviderHelper.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2007 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.android.internal.content;
+
+import com.android.internal.util.ArrayUtils;
+
+import android.database.Cursor;
+import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteDatabase;
+import android.accounts.Account;
+import android.content.ContentValues;
+import android.provider.SyncStateContract;
+
+/**
+ * Extends the schema of a ContentProvider to include the _sync_state table
+ * and implements query/insert/update/delete to access that table using the
+ * authority "syncstate". This can be used to store the sync state for a
+ * set of accounts.
+ *
+ * @hide
+ */
+public class SyncStateContentProviderHelper {
+    private static final String SELECT_BY_ACCOUNT =
+            SyncStateContract.Columns.ACCOUNT_NAME + "=? AND "
+                    + SyncStateContract.Columns.ACCOUNT_TYPE + "=?";
+
+    private static final String SYNC_STATE_TABLE = "_sync_state";
+    private static final String SYNC_STATE_META_TABLE = "_sync_state_metadata";
+    private static final String SYNC_STATE_META_VERSION_COLUMN = "version";
+
+    private static long DB_VERSION = 1;
+
+    private static final String[] ACCOUNT_PROJECTION =
+            new String[]{SyncStateContract.Columns.ACCOUNT_NAME,
+                    SyncStateContract.Columns.ACCOUNT_TYPE};
+
+    public static final String PATH = "syncstate";
+
+    public void createDatabase(SQLiteDatabase db) {
+        db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_TABLE);
+        db.execSQL("CREATE TABLE " + SYNC_STATE_TABLE + " ("
+                + SyncStateContract.Columns._ID + " INTEGER PRIMARY KEY,"
+                + SyncStateContract.Columns.ACCOUNT_NAME + " TEXT NOT NULL,"
+                + SyncStateContract.Columns.ACCOUNT_TYPE + " TEXT NOT NULL,"
+                + SyncStateContract.Columns.DATA + " TEXT,"
+                + "UNIQUE(" + SyncStateContract.Columns.ACCOUNT_NAME + ", "
+                + SyncStateContract.Columns.ACCOUNT_TYPE + "));");
+
+        db.execSQL("DROP TABLE IF EXISTS " + SYNC_STATE_META_TABLE);
+        db.execSQL("CREATE TABLE " + SYNC_STATE_META_TABLE + " ("
+                + SYNC_STATE_META_VERSION_COLUMN + " INTEGER);");
+        ContentValues values = new ContentValues();
+        values.put(SYNC_STATE_META_VERSION_COLUMN, DB_VERSION);
+        db.insert(SYNC_STATE_META_TABLE, SYNC_STATE_META_VERSION_COLUMN, values);
+    }
+
+    public void onDatabaseOpened(SQLiteDatabase db) {
+        long version = DatabaseUtils.longForQuery(db,
+                "SELECT " + SYNC_STATE_META_VERSION_COLUMN + " FROM " + SYNC_STATE_META_TABLE,
+                null);
+        if (version != DB_VERSION) {
+            createDatabase(db);
+        }
+    }
+
+    public Cursor query(SQLiteDatabase db, String[] projection,
+            String selection, String[] selectionArgs, String sortOrder) {
+        return db.query(SYNC_STATE_TABLE, projection, selection, selectionArgs,
+                null, null, sortOrder);
+    }
+
+    public long insert(SQLiteDatabase db, ContentValues values) {
+        return db.replace(SYNC_STATE_TABLE, SyncStateContract.Columns.ACCOUNT_NAME, values);
+    }
+
+    public int delete(SQLiteDatabase db, String userWhere, String[] whereArgs) {
+        return db.delete(SYNC_STATE_TABLE, userWhere, whereArgs);
+    }
+
+    public int update(SQLiteDatabase db, ContentValues values,
+            String selection, String[] selectionArgs) {
+        return db.update(SYNC_STATE_TABLE, values, selection, selectionArgs);
+    }
+
+    public void onAccountsChanged(SQLiteDatabase db, Account[] accounts) {
+        Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null);
+        try {
+            while (c.moveToNext()) {
+                final String accountName = c.getString(0);
+                final String accountType = c.getString(1);
+                Account account = new Account(accountName, accountType);
+                if (!ArrayUtils.contains(accounts, account)) {
+                    db.delete(SYNC_STATE_TABLE, SELECT_BY_ACCOUNT,
+                            new String[]{accountName, accountType});
+                }
+            }
+        } finally {
+            c.close();
+        }
+    }
+}
\ No newline at end of file