Merge "Improved rotation animation."
diff --git a/api/current.txt b/api/current.txt
index f79df94..a6bdf5a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10786,8 +10786,8 @@
 
   public class MediaActionSound {
     ctor public MediaActionSound();
-    method public void load(int);
-    method public void play(int);
+    method public synchronized void load(int);
+    method public synchronized void play(int);
     method public void release();
     field public static final int FOCUS_COMPLETE = 1; // 0x1
     field public static final int SHUTTER_CLICK = 0; // 0x0
@@ -23374,6 +23374,7 @@
     method public void onResolvedTextDirectionReset();
     method protected void onRestoreInstanceState(android.os.Parcelable);
     method protected android.os.Parcelable onSaveInstanceState();
+    method public void onScreenStateChanged(int);
     method protected void onScrollChanged(int, int, int, int);
     method protected boolean onSetAlpha(int);
     method protected void onSizeChanged(int, int, int, int);
@@ -23583,6 +23584,8 @@
     field public static final android.util.Property ROTATION_Y;
     field public static final android.util.Property SCALE_X;
     field public static final android.util.Property SCALE_Y;
+    field public static final int SCREEN_STATE_OFF = 0; // 0x0
+    field public static final int SCREEN_STATE_ON = 1; // 0x1
     field public static final int SCROLLBARS_INSIDE_INSET = 16777216; // 0x1000000
     field public static final int SCROLLBARS_INSIDE_OVERLAY = 0; // 0x0
     field public static final int SCROLLBARS_OUTSIDE_INSET = 50331648; // 0x3000000
@@ -27492,9 +27495,19 @@
     ctor public Spinner(android.content.Context, android.util.AttributeSet);
     ctor public Spinner(android.content.Context, android.util.AttributeSet, int);
     ctor public Spinner(android.content.Context, android.util.AttributeSet, int, int);
+    method public int getDropDownHorizontalOffset();
+    method public int getDropDownVerticalOffset();
+    method public int getDropDownWidth();
+    method public int getGravity();
+    method public android.graphics.drawable.Drawable getPopupBackground();
     method public java.lang.CharSequence getPrompt();
     method public void onClick(android.content.DialogInterface, int);
+    method public void setDropDownHorizontalOffset(int);
+    method public void setDropDownVerticalOffset(int);
+    method public void setDropDownWidth(int);
     method public void setGravity(int);
+    method public void setPopupBackgroundDrawable(android.graphics.drawable.Drawable);
+    method public void setPopupBackgroundResource(int);
     method public void setPrompt(java.lang.CharSequence);
     method public void setPromptId(int);
     field public static final int MODE_DIALOG = 0; // 0x0
@@ -27515,14 +27528,26 @@
     ctor public Switch(android.content.Context);
     ctor public Switch(android.content.Context, android.util.AttributeSet);
     ctor public Switch(android.content.Context, android.util.AttributeSet, int);
+    method public int getSwitchMinWidth();
+    method public int getSwitchPadding();
     method public java.lang.CharSequence getTextOff();
     method public java.lang.CharSequence getTextOn();
+    method public android.graphics.drawable.Drawable getThumbDrawable();
+    method public int getThumbTextPadding();
+    method public android.graphics.drawable.Drawable getTrackDrawable();
     method public void onMeasure(int, int);
+    method public void setSwitchMinWidth(int);
+    method public void setSwitchPadding(int);
     method public void setSwitchTextAppearance(android.content.Context, int);
     method public void setSwitchTypeface(android.graphics.Typeface, int);
     method public void setSwitchTypeface(android.graphics.Typeface);
     method public void setTextOff(java.lang.CharSequence);
     method public void setTextOn(java.lang.CharSequence);
+    method public void setThumbDrawable(android.graphics.drawable.Drawable);
+    method public void setThumbResource(int);
+    method public void setThumbTextPadding(int);
+    method public void setTrackDrawable(android.graphics.drawable.Drawable);
+    method public void setTrackResource(int);
   }
 
   public class TabHost extends android.widget.FrameLayout implements android.view.ViewTreeObserver.OnTouchModeChangeListener {
diff --git a/cmds/stagefright/codec.cpp b/cmds/stagefright/codec.cpp
index 0414b98..fea62cc 100644
--- a/cmds/stagefright/codec.cpp
+++ b/cmds/stagefright/codec.cpp
@@ -49,6 +49,7 @@
     size_t mCSDIndex;
     Vector<sp<ABuffer> > mInBuffers;
     Vector<sp<ABuffer> > mOutBuffers;
+    bool mSignalledInputEOS;
     bool mSawOutputEOS;
     int64_t mNumBuffersDecoded;
     int64_t mNumBytesDecoded;
@@ -127,6 +128,7 @@
         }
 
         state->mCSDIndex = 0;
+        state->mSignalledInputEOS = false;
         state->mSawOutputEOS = false;
 
         ALOGV("got %d pieces of codec specific data.", state->mCSD.size());
@@ -180,33 +182,7 @@
             status_t err = extractor->getSampleTrackIndex(&trackIndex);
 
             if (err != OK) {
-                ALOGV("signalling EOS.");
-
-                for (size_t i = 0; i < stateByTrack.size(); ++i) {
-                    CodecState *state = &stateByTrack.editValueAt(i);
-
-                    for (;;) {
-                        size_t index;
-                        err = state->mCodec->dequeueInputBuffer(&index, kTimeout);
-
-                        if (err == -EAGAIN) {
-                            continue;
-                        }
-
-                        CHECK_EQ(err, (status_t)OK);
-
-                        err = state->mCodec->queueInputBuffer(
-                                index,
-                                0 /* offset */,
-                                0 /* size */,
-                                0ll /* timeUs */,
-                                MediaCodec::BUFFER_FLAG_EOS);
-
-                        CHECK_EQ(err, (status_t)OK);
-                        break;
-                    }
-                }
-
+                ALOGV("saw input eos");
                 sawInputEOS = true;
             } else {
                 CodecState *state = &stateByTrack.editValueFor(trackIndex);
@@ -240,6 +216,33 @@
                     CHECK_EQ(err, -EAGAIN);
                 }
             }
+        } else {
+            for (size_t i = 0; i < stateByTrack.size(); ++i) {
+                CodecState *state = &stateByTrack.editValueAt(i);
+
+                if (!state->mSignalledInputEOS) {
+                    size_t index;
+                    status_t err =
+                        state->mCodec->dequeueInputBuffer(&index, kTimeout);
+
+                    if (err == OK) {
+                        ALOGV("signalling input EOS on track %d", i);
+
+                        err = state->mCodec->queueInputBuffer(
+                                index,
+                                0 /* offset */,
+                                0 /* size */,
+                                0ll /* timeUs */,
+                                MediaCodec::BUFFER_FLAG_EOS);
+
+                        CHECK_EQ(err, (status_t)OK);
+
+                        state->mSignalledInputEOS = true;
+                    } else {
+                        CHECK_EQ(err, -EAGAIN);
+                    }
+                }
+            }
         }
 
         bool sawOutputEOSOnAllTracks = true;
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 5fee4de..adc7d35 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -18,6 +18,7 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
+import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
@@ -33,6 +34,7 @@
 import android.content.pm.PackageManager;
 import android.content.pm.RegisteredServicesCache;
 import android.content.pm.RegisteredServicesCacheListener;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
@@ -48,11 +50,14 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.UserId;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
+import android.util.SparseArray;
 
 import com.android.internal.R;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -62,6 +67,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
@@ -94,7 +100,6 @@
     private static final int MESSAGE_TIMED_OUT = 3;
 
     private final IAccountAuthenticatorCache mAuthenticatorCache;
-    private final DatabaseHelper mOpenHelper;
 
     private static final String TABLE_ACCOUNTS = "accounts";
     private static final String ACCOUNTS_ID = "_id";
@@ -148,14 +153,36 @@
     private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
     private final AtomicInteger mNotificationIds = new AtomicInteger(1);
 
-    private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
-            mCredentialsPermissionNotificationIds =
-            new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
-    private final HashMap<Account, Integer> mSigninRequiredNotificationIds =
-            new HashMap<Account, Integer>();
+    static class UserAccounts {
+        private final int userId;
+        private final DatabaseHelper openHelper;
+        private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
+                credentialsPermissionNotificationIds =
+                new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
+        private final HashMap<Account, Integer> signinRequiredNotificationIds =
+                new HashMap<Account, Integer>();
+        private final Object cacheLock = new Object();
+        /** protected by the {@link #cacheLock} */
+        private final HashMap<String, Account[]> accountCache = new HashMap<String, Account[]>();
+        /** protected by the {@link #cacheLock} */
+        private HashMap<Account, HashMap<String, String>> userDataCache =
+                new HashMap<Account, HashMap<String, String>>();
+        /** protected by the {@link #cacheLock} */
+        private HashMap<Account, HashMap<String, String>> authTokenCache =
+                new HashMap<Account, HashMap<String, String>>();
+
+        UserAccounts(Context context, int userId) {
+            this.userId = userId;
+            synchronized (cacheLock) {
+                openHelper = new DatabaseHelper(context, userId);
+            }
+        }
+    }
+
+    private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>();
+
     private static AtomicReference<AccountManagerService> sThis =
             new AtomicReference<AccountManagerService>();
-
     private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
 
     static {
@@ -163,15 +190,6 @@
         ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
     }
 
-    private final Object mCacheLock = new Object();
-    /** protected by the {@link #mCacheLock} */
-    private final HashMap<String, Account[]> mAccountCache = new HashMap<String, Account[]>();
-    /** protected by the {@link #mCacheLock} */
-    private HashMap<Account, HashMap<String, String>> mUserDataCache =
-            new HashMap<Account, HashMap<String, String>>();
-    /** protected by the {@link #mCacheLock} */
-    private HashMap<Account, HashMap<String, String>> mAuthTokenCache =
-            new HashMap<Account, HashMap<String, String>>();
 
     /**
      * This should only be called by system code. One should only call this after the service
@@ -192,10 +210,6 @@
         mContext = context;
         mPackageManager = packageManager;
 
-        synchronized (mCacheLock) {
-            mOpenHelper = new DatabaseHelper(mContext);
-        }
-
         mMessageThread = new HandlerThread("AccountManagerService");
         mMessageThread.start();
         mMessageHandler = new MessageHandler(mMessageThread.getLooper());
@@ -203,6 +217,8 @@
         mAuthenticatorCache = authenticatorCache;
         mAuthenticatorCache.setListener(this, null /* Handler */);
 
+        UserAccounts accounts = initUser(0);
+
         sThis.set(this);
 
         IntentFilter intentFilter = new IntentFilter();
@@ -211,17 +227,36 @@
         mContext.registerReceiver(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context1, Intent intent) {
-                purgeOldGrants();
+                purgeOldGrantsAll();
             }
         }, intentFilter);
-        purgeOldGrants();
 
-        validateAccountsAndPopulateCache();
     }
 
-    private void purgeOldGrants() {
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+    private UserAccounts initUser(int userId) {
+        synchronized (mUsers) {
+            UserAccounts accounts = mUsers.get(userId);
+            if (accounts == null) {
+                accounts = new UserAccounts(mContext, userId);
+                mUsers.append(userId, accounts);
+                purgeOldGrants(accounts);
+                validateAccountsAndPopulateCache(accounts);
+            }
+            return accounts;
+        }
+    }
+
+    private void purgeOldGrantsAll() {
+        synchronized (mUsers) {
+            for (int i = 0; i < mUsers.size(); i++) {
+                purgeOldGrants(mUsers.valueAt(i));
+            }
+        }
+    }
+
+    private void purgeOldGrants(UserAccounts accounts) {
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             final Cursor cursor = db.query(TABLE_GRANTS,
                     new String[]{GRANTS_GRANTEE_UID},
                     null, null, GRANTS_GRANTEE_UID, null, null);
@@ -243,15 +278,15 @@
         }
     }
 
-    private void validateAccountsAndPopulateCache() {
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+    private void validateAccountsAndPopulateCache(UserAccounts accounts) {
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             boolean accountDeleted = false;
             Cursor cursor = db.query(TABLE_ACCOUNTS,
                     new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
                     null, null, null, null, null);
             try {
-                mAccountCache.clear();
+                accounts.accountCache.clear();
                 final HashMap<String, ArrayList<String>> accountNamesByType =
                         new HashMap<String, ArrayList<String>>();
                 while (cursor.moveToNext()) {
@@ -265,8 +300,8 @@
                         db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
                         accountDeleted = true;
                         final Account account = new Account(accountName, accountType);
-                        mUserDataCache.remove(account);
-                        mAuthTokenCache.remove(account);
+                        accounts.userDataCache.remove(account);
+                        accounts.authTokenCache.remove(account);
                     } else {
                         ArrayList<String> accountNames = accountNamesByType.get(accountType);
                         if (accountNames == null) {
@@ -286,19 +321,51 @@
                         accountsForType[i] = new Account(accountName, accountType);
                         ++i;
                     }
-                    mAccountCache.put(accountType, accountsForType);
+                    accounts.accountCache.put(accountType, accountsForType);
                 }
             } finally {
                 cursor.close();
                 if (accountDeleted) {
-                    sendAccountsChangedBroadcast();
+                    sendAccountsChangedBroadcast(accounts.userId);
                 }
             }
         }
     }
 
+    private UserAccounts getUserAccountsForCaller() {
+        return getUserAccounts(UserId.getCallingUserId());
+    }
+
+    protected UserAccounts getUserAccounts(int userId) {
+        synchronized (mUsers) {
+            UserAccounts accounts = mUsers.get(userId);
+            if (accounts == null) {
+                accounts = initUser(userId);
+                mUsers.append(userId, accounts);
+            }
+            return accounts;
+        }
+    }
+
+    private List<UserInfo> getAllUsers() {
+        try {
+            return AppGlobals.getPackageManager().getUsers();
+        } catch (RemoteException re) {
+            // Local to system process, shouldn't happen
+        }
+        return null;
+    }
+
     public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
-        validateAccountsAndPopulateCache();
+        // Validate accounts for all users
+        List<UserInfo> users = getAllUsers();
+        if (users == null) {
+            validateAccountsAndPopulateCache(getUserAccountsForCaller());
+        } else {
+            for (UserInfo user : users) {
+                validateAccountsAndPopulateCache(getUserAccounts(user.id));
+            }
+        }
     }
 
     public String getPassword(Account account) {
@@ -310,21 +377,22 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         checkAuthenticateAccountsPermission(account);
 
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            return readPasswordInternal(account);
+            return readPasswordInternal(accounts, account);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private String readPasswordInternal(Account account) {
+    private String readPasswordInternal(UserAccounts accounts, Account account) {
         if (account == null) {
             return null;
         }
 
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
             Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
                     ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
                     new String[]{account.name, account.type}, null, null, null);
@@ -349,9 +417,10 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (key == null) throw new IllegalArgumentException("key is null");
         checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            return readUserDataInternal(account, key);
+            return readUserDataInternal(accounts, account, key);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -390,21 +459,23 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         checkAuthenticateAccountsPermission(account);
 
+        UserAccounts accounts = getUserAccountsForCaller();
         // fails if the account already exists
         long identityToken = clearCallingIdentity();
         try {
-            return addAccountInternal(account, password, extras);
+            return addAccountInternal(accounts, account, password, extras);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private boolean addAccountInternal(Account account, String password, Bundle extras) {
+    private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
+            Bundle extras) {
         if (account == null) {
             return false;
         }
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.beginTransaction();
             try {
                 long numMatches = DatabaseUtils.longForQuery(db,
@@ -437,11 +508,11 @@
                     }
                 }
                 db.setTransactionSuccessful();
-                insertAccountIntoCacheLocked(account);
+                insertAccountIntoCacheLocked(accounts, account);
             } finally {
                 db.endTransaction();
             }
-            sendAccountsChangedBroadcast();
+            sendAccountsChangedBroadcast(accounts.userId);
             return true;
         }
     }
@@ -467,9 +538,10 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (features == null) throw new IllegalArgumentException("features is null");
         checkReadAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            new TestFeaturesSession(response, account, features).bind();
+            new TestFeaturesSession(accounts, response, account, features).bind();
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -479,9 +551,9 @@
         private final String[] mFeatures;
         private final Account mAccount;
 
-        public TestFeaturesSession(IAccountManagerResponse response,
+        public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
                 Account account, String[] features) {
-            super(response, account.type, false /* expectActivityLaunch */,
+            super(accounts, response, account.type, false /* expectActivityLaunch */,
                     true /* stripAuthTokenFromResult */);
             mFeatures = features;
             mAccount = account;
@@ -537,21 +609,22 @@
         if (response == null) throw new IllegalArgumentException("response is null");
         if (account == null) throw new IllegalArgumentException("account is null");
         checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
 
-        cancelNotification(getSigninRequiredNotificationId(account));
-        synchronized(mCredentialsPermissionNotificationIds) {
+        cancelNotification(getSigninRequiredNotificationId(accounts, account));
+        synchronized(accounts.credentialsPermissionNotificationIds) {
             for (Pair<Pair<Account, String>, Integer> pair:
-                mCredentialsPermissionNotificationIds.keySet()) {
+                accounts.credentialsPermissionNotificationIds.keySet()) {
                 if (account.equals(pair.first.first)) {
-                    int id = mCredentialsPermissionNotificationIds.get(pair);
+                    int id = accounts.credentialsPermissionNotificationIds.get(pair);
                     cancelNotification(id);
                 }
             }
         }
 
         try {
-            new RemoveAccountSession(response, account).bind();
+            new RemoveAccountSession(accounts, response, account).bind();
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -559,8 +632,9 @@
 
     private class RemoveAccountSession extends Session {
         final Account mAccount;
-        public RemoveAccountSession(IAccountManagerResponse response, Account account) {
-            super(response, account.type, false /* expectActivityLaunch */,
+        public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
+                Account account) {
+            super(accounts, response, account.type, false /* expectActivityLaunch */,
                     true /* stripAuthTokenFromResult */);
             mAccount = account;
         }
@@ -579,7 +653,7 @@
                     && !result.containsKey(AccountManager.KEY_INTENT)) {
                 final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
                 if (removalAllowed) {
-                    removeAccountInternal(mAccount);
+                    removeAccountInternal(mAccounts, mAccount);
                 }
                 IAccountManagerResponse response = getResponseAndClose();
                 if (response != null) {
@@ -600,13 +674,18 @@
         }
     }
 
+    /* For testing */
     protected void removeAccountInternal(Account account) {
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        removeAccountInternal(getUserAccountsForCaller(), account);
+    }
+
+    private void removeAccountInternal(UserAccounts accounts, Account account) {
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
                     new String[]{account.name, account.type});
-            removeAccountFromCacheLocked(account);
-            sendAccountsChangedBroadcast();
+            removeAccountFromCacheLocked(accounts, account);
+            sendAccountsChangedBroadcast(accounts.userId);
         }
     }
 
@@ -619,13 +698,14 @@
         if (accountType == null) throw new IllegalArgumentException("accountType is null");
         if (authToken == null) throw new IllegalArgumentException("authToken is null");
         checkManageAccountsOrUseCredentialsPermissions();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            synchronized (mCacheLock) {
-                final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+            synchronized (accounts.cacheLock) {
+                final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
                 db.beginTransaction();
                 try {
-                    invalidateAuthTokenLocked(db, accountType, authToken);
+                    invalidateAuthTokenLocked(accounts, db, accountType, authToken);
                     db.setTransactionSuccessful();
                 } finally {
                     db.endTransaction();
@@ -636,7 +716,8 @@
         }
     }
 
-    private void invalidateAuthTokenLocked(SQLiteDatabase db, String accountType, String authToken) {
+    private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db,
+            String accountType, String authToken) {
         if (authToken == null || accountType == null) {
             return;
         }
@@ -657,7 +738,7 @@
                 String accountName = cursor.getString(1);
                 String authTokenType = cursor.getString(2);
                 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
-                writeAuthTokenIntoCacheLocked(db, new Account(accountName, accountType),
+                writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType),
                         authTokenType, null);
             }
         } finally {
@@ -665,13 +746,14 @@
         }
     }
 
-    private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) {
+    private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type,
+            String authToken) {
         if (account == null || type == null) {
             return false;
         }
-        cancelNotification(getSigninRequiredNotificationId(account));
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        cancelNotification(getSigninRequiredNotificationId(accounts, account));
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.beginTransaction();
             try {
                 long accountId = getAccountIdLocked(db, account);
@@ -687,7 +769,7 @@
                 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
                 if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
                     db.setTransactionSuccessful();
-                    writeAuthTokenIntoCacheLocked(db, account, type, authToken);
+                    writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
                     return true;
                 }
                 return false;
@@ -707,9 +789,10 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
         checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            return readAuthTokenInternal(account, authTokenType);
+            return readAuthTokenInternal(accounts, account, authTokenType);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -725,9 +808,10 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
         checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            saveAuthTokenToDatabase(account, authTokenType, authToken);
+            saveAuthTokenToDatabase(accounts, account, authTokenType, authToken);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -741,20 +825,21 @@
         }
         if (account == null) throw new IllegalArgumentException("account is null");
         checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            setPasswordInternal(account, password);
+            setPasswordInternal(accounts, account, password);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private void setPasswordInternal(Account account, String password) {
+    private void setPasswordInternal(UserAccounts accounts, Account account, String password) {
         if (account == null) {
             return;
         }
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.beginTransaction();
             try {
                 final ContentValues values = new ContentValues();
@@ -764,20 +849,20 @@
                     final String[] argsAccountId = {String.valueOf(accountId)};
                     db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
                     db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
-                    mAuthTokenCache.remove(account);
+                    accounts.authTokenCache.remove(account);
                     db.setTransactionSuccessful();
                 }
             } finally {
                 db.endTransaction();
             }
-            sendAccountsChangedBroadcast();
+            sendAccountsChangedBroadcast(accounts.userId);
         }
     }
 
-    private void sendAccountsChangedBroadcast() {
+    private void sendAccountsChangedBroadcast(int userId) {
         Log.i(TAG, "the accounts changed, sending broadcast of "
                 + ACCOUNTS_CHANGED_INTENT.getAction());
-        mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
+        mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId);
     }
 
     public void clearPassword(Account account) {
@@ -788,9 +873,10 @@
         }
         if (account == null) throw new IllegalArgumentException("account is null");
         checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            setPasswordInternal(account, null);
+            setPasswordInternal(accounts, account, null);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -806,20 +892,22 @@
         if (key == null) throw new IllegalArgumentException("key is null");
         if (account == null) throw new IllegalArgumentException("account is null");
         checkAuthenticateAccountsPermission(account);
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            setUserdataInternal(account, key, value);
+            setUserdataInternal(accounts, account, key, value);
         } finally {
             restoreCallingIdentity(identityToken);
         }
     }
 
-    private void setUserdataInternal(Account account, String key, String value) {
+    private void setUserdataInternal(UserAccounts accounts, Account account, String key,
+            String value) {
         if (account == null || key == null) {
             return;
         }
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.beginTransaction();
             try {
                 long accountId = getAccountIdLocked(db, account);
@@ -840,7 +928,7 @@
                     }
 
                 }
-                writeUserDataIntoCacheLocked(db, account, key, value);
+                writeUserDataIntoCacheLocked(accounts, db, account, key, value);
                 db.setTransactionSuccessful();
             } finally {
                 db.endTransaction();
@@ -868,15 +956,16 @@
     }
 
     void getAuthTokenLabel(final IAccountManagerResponse response,
-            final Account account, final String authTokenType) {
+            final Account account,
+            final String authTokenType, int uid) {
         if (account == null) throw new IllegalArgumentException("account is null");
         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
 
         checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
-
+        UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
         long identityToken = clearCallingIdentity();
         try {
-            new Session(response, account.type, false,
+            new Session(accounts, response, account.type, false,
                     false /* stripAuthTokenFromResult */) {
                 protected String toDebugString(long now) {
                     return super.toDebugString(now) + ", getAuthTokenLabel"
@@ -921,6 +1010,7 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
         checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
+        UserAccounts accounts = getUserAccountsForCaller();
         AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
             mAuthenticatorCache.getServiceInfo(
                     AuthenticatorDescription.newKey(account.type));
@@ -946,7 +1036,7 @@
             // if the caller has permission, do the peek. otherwise go the more expensive
             // route of starting a Session
             if (!customTokens && permissionGranted) {
-                String authToken = readAuthTokenInternal(account, authTokenType);
+                String authToken = readAuthTokenInternal(accounts, account, authTokenType);
                 if (authToken != null) {
                     Bundle result = new Bundle();
                     result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
@@ -957,7 +1047,7 @@
                 }
             }
 
-            new Session(response, account.type, expectActivityLaunch,
+            new Session(accounts, response, account.type, expectActivityLaunch,
                     false /* stripAuthTokenFromResult */) {
                 protected String toDebugString(long now) {
                     if (loginOptions != null) loginOptions.keySet();
@@ -1000,14 +1090,14 @@
                                 return;
                             }
                             if (!customTokens) {
-                                saveAuthTokenToDatabase(new Account(name, type),
+                                saveAuthTokenToDatabase(mAccounts, new Account(name, type),
                                         authTokenType, authToken);
                             }
                         }
 
                         Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
                         if (intent != null && notifyOnAuthFailure && !customTokens) {
-                            doNotification(
+                            doNotification(mAccounts,
                                     account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
                                     intent);
                         }
@@ -1090,26 +1180,27 @@
     private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
             int uid) {
         Integer id;
-        synchronized(mCredentialsPermissionNotificationIds) {
+        UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
+        synchronized (accounts.credentialsPermissionNotificationIds) {
             final Pair<Pair<Account, String>, Integer> key =
                     new Pair<Pair<Account, String>, Integer>(
                             new Pair<Account, String>(account, authTokenType), uid);
-            id = mCredentialsPermissionNotificationIds.get(key);
+            id = accounts.credentialsPermissionNotificationIds.get(key);
             if (id == null) {
                 id = mNotificationIds.incrementAndGet();
-                mCredentialsPermissionNotificationIds.put(key, id);
+                accounts.credentialsPermissionNotificationIds.put(key, id);
             }
         }
         return id;
     }
 
-    private Integer getSigninRequiredNotificationId(Account account) {
+    private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) {
         Integer id;
-        synchronized(mSigninRequiredNotificationIds) {
-            id = mSigninRequiredNotificationIds.get(account);
+        synchronized (accounts.signinRequiredNotificationIds) {
+            id = accounts.signinRequiredNotificationIds.get(account);
             if (id == null) {
                 id = mNotificationIds.incrementAndGet();
-                mSigninRequiredNotificationIds.put(account, id);
+                accounts.signinRequiredNotificationIds.put(account, id);
             }
         }
         return id;
@@ -1131,6 +1222,7 @@
         if (accountType == null) throw new IllegalArgumentException("accountType is null");
         checkManageAccountsPermission();
 
+        UserAccounts accounts = getUserAccountsForCaller();
         final int pid = Binder.getCallingPid();
         final int uid = Binder.getCallingUid();
         final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
@@ -1139,7 +1231,7 @@
 
         long identityToken = clearCallingIdentity();
         try {
-            new Session(response, accountType, expectActivityLaunch,
+            new Session(accounts, response, accountType, expectActivityLaunch,
                     true /* stripAuthTokenFromResult */) {
                 public void run() throws RemoteException {
                     mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1172,9 +1264,10 @@
         if (response == null) throw new IllegalArgumentException("response is null");
         if (account == null) throw new IllegalArgumentException("account is null");
         checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            new Session(response, account.type, expectActivityLaunch,
+            new Session(accounts, response, account.type, expectActivityLaunch,
                     true /* stripAuthTokenFromResult */) {
                 public void run() throws RemoteException {
                     mAuthenticator.confirmCredentials(this, account, options);
@@ -1204,9 +1297,10 @@
         if (account == null) throw new IllegalArgumentException("account is null");
         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
         checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            new Session(response, account.type, expectActivityLaunch,
+            new Session(accounts, response, account.type, expectActivityLaunch,
                     true /* stripAuthTokenFromResult */) {
                 public void run() throws RemoteException {
                     mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
@@ -1236,9 +1330,10 @@
         if (response == null) throw new IllegalArgumentException("response is null");
         if (accountType == null) throw new IllegalArgumentException("accountType is null");
         checkManageAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            new Session(response, accountType, expectActivityLaunch,
+            new Session(accounts, response, accountType, expectActivityLaunch,
                     true /* stripAuthTokenFromResult */) {
                 public void run() throws RemoteException {
                     mAuthenticator.editProperties(this, mAccountType);
@@ -1259,16 +1354,16 @@
         private volatile ArrayList<Account> mAccountsWithFeatures = null;
         private volatile int mCurrentAccount = 0;
 
-        public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
-            String type, String[] features) {
-            super(response, type, false /* expectActivityLaunch */,
+        public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
+                IAccountManagerResponse response, String type, String[] features) {
+            super(accounts, response, type, false /* expectActivityLaunch */,
                     true /* stripAuthTokenFromResult */);
             mFeatures = features;
         }
 
         public void run() throws RemoteException {
-            synchronized (mCacheLock) {
-                mAccountsOfType = getAccountsFromCacheLocked(mAccountType);
+            synchronized (mAccounts.cacheLock) {
+                mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType);
             }
             // check whether each account matches the requested features
             mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
@@ -1346,6 +1441,23 @@
         }
     }
 
+    /**
+     * Returns the accounts for a specific user
+     * @hide
+     */
+    public Account[] getAccounts(int userId) {
+        checkReadAccountsPermission();
+        UserAccounts accounts = getUserAccounts(userId);
+        long identityToken = clearCallingIdentity();
+        try {
+            synchronized (accounts.cacheLock) {
+                return getAccountsFromCacheLocked(accounts, null);
+            }
+        } finally {
+            restoreCallingIdentity(identityToken);
+        }
+    }
+
     public Account[] getAccounts(String type) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "getAccounts: accountType " + type
@@ -1353,10 +1465,11 @@
                     + ", pid " + Binder.getCallingPid());
         }
         checkReadAccountsPermission();
+        UserAccounts accounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
-            synchronized (mCacheLock) {
-                return getAccountsFromCacheLocked(type);
+            synchronized (accounts.cacheLock) {
+                return getAccountsFromCacheLocked(accounts, type);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -1375,19 +1488,20 @@
         if (response == null) throw new IllegalArgumentException("response is null");
         if (type == null) throw new IllegalArgumentException("accountType is null");
         checkReadAccountsPermission();
+        UserAccounts userAccounts = getUserAccountsForCaller();
         long identityToken = clearCallingIdentity();
         try {
             if (features == null || features.length == 0) {
                 Account[] accounts;
-                synchronized (mCacheLock) {
-                    accounts = getAccountsFromCacheLocked(type);
+                synchronized (userAccounts.cacheLock) {
+                    accounts = getAccountsFromCacheLocked(userAccounts, type);
                 }
                 Bundle result = new Bundle();
                 result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
                 onResult(response, result);
                 return;
             }
-            new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
+            new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind();
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -1435,12 +1549,14 @@
         IAccountAuthenticator mAuthenticator = null;
 
         private final boolean mStripAuthTokenFromResult;
+        protected final UserAccounts mAccounts;
 
-        public Session(IAccountManagerResponse response, String accountType,
+        public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
                 boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
             super();
             if (response == null) throw new IllegalArgumentException("response is null");
             if (accountType == null) throw new IllegalArgumentException("accountType is null");
+            mAccounts = accounts;
             mStripAuthTokenFromResult = stripAuthTokenFromResult;
             mResponse = response;
             mAccountType = accountType;
@@ -1578,7 +1694,7 @@
                 String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
                 if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
                     Account account = new Account(accountName, accountType);
-                    cancelNotification(getSigninRequiredNotificationId(account));
+                    cancelNotification(getSigninRequiredNotificationId(mAccounts, account));
                 }
             }
             IAccountManagerResponse response;
@@ -1694,20 +1810,23 @@
         }
     }
 
-    private static String getDatabaseName() {
-        if(Environment.isEncryptedFilesystemEnabled()) {
-            // Hard-coded path in case of encrypted file system
-            return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME;
-        } else {
-            // Regular path in case of non-encrypted file system
-            return DATABASE_NAME;
+    private static String getDatabaseName(int userId) {
+        File systemDir = Environment.getSystemSecureDirectory();
+        File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME);
+        if (userId == 0) {
+            // Migrate old file, if it exists, to the new location
+            File oldFile = new File(systemDir, DATABASE_NAME);
+            if (oldFile.exists()) {
+                oldFile.renameTo(databaseFile);
+            }
         }
+        return databaseFile.getPath();
     }
 
-    private class DatabaseHelper extends SQLiteOpenHelper {
+    static class DatabaseHelper extends SQLiteOpenHelper {
 
-        public DatabaseHelper(Context context) {
-            super(context, AccountManagerService.getDatabaseName(), null, DATABASE_VERSION);
+        public DatabaseHelper(Context context, int userId) {
+            super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION);
         }
 
         /**
@@ -1799,15 +1918,6 @@
         }
     }
 
-    private void setMetaValue(String key, String value) {
-        ContentValues values = new ContentValues();
-        values.put(META_KEY, key);
-        values.put(META_VALUE, value);
-        synchronized (mCacheLock) {
-            mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values);
-        }
-    }
-
     public IBinder onBind(Intent intent) {
         return asBinder();
     }
@@ -1837,11 +1947,25 @@
                     + " without permission " + android.Manifest.permission.DUMP);
             return;
         }
+        final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
 
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+        fout = new IndentingPrintWriter(fout, "  ");
+        int size = mUsers.size();
+        for (int i = 0; i < size; i++) {
+            fout.println("User " + mUsers.keyAt(i) + ":");
+            ((IndentingPrintWriter) fout).increaseIndent();
+            dumpUser(mUsers.valueAt(i), fd, fout, args, isCheckinRequest);
+            ((IndentingPrintWriter) fout).decreaseIndent();
+            if (i < size - 1) {
+                fout.println();
+            }
+        }
+    }
 
-            final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
+    private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout,
+            String[] args, boolean isCheckinRequest) {
+        synchronized (userAccounts.cacheLock) {
+            final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase();
 
             if (isCheckinRequest) {
                 // This is a checkin request. *Only* upload the account types and the count of each.
@@ -1858,7 +1982,7 @@
                     }
                 }
             } else {
-                Account[] accounts = getAccountsFromCacheLocked(null /* type */);
+                Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */);
                 fout.println("Accounts: " + accounts.length);
                 for (Account account : accounts) {
                     fout.println("  " + account);
@@ -1879,7 +2003,8 @@
         }
     }
 
-    private void doNotification(Account account, CharSequence message, Intent intent) {
+    private void doNotification(UserAccounts accounts, Account account, CharSequence message,
+            Intent intent) {
         long identityToken = clearCallingIdentity();
         try {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1891,7 +2016,7 @@
                             intent.getComponent().getClassName())) {
                 createNoCredentialsPermissionNotification(account, intent);
             } else {
-                final Integer notificationId = getSigninRequiredNotificationId(account);
+                final Integer notificationId = getSigninRequiredNotificationId(accounts, account);
                 intent.addCategory(String.valueOf(notificationId));
                 Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
                         0 /* when */);
@@ -1962,7 +2087,7 @@
         final boolean fromAuthenticator = account != null
                 && hasAuthenticatorUid(account.type, callerUid);
         final boolean hasExplicitGrants = account != null
-                && hasExplicitlyGrantedPermission(account, authTokenType);
+                && hasExplicitlyGrantedPermission(account, authTokenType, callerUid);
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
                     + callerUid + ", " + account
@@ -1984,13 +2109,15 @@
         return false;
     }
 
-    private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType) {
-        if (Binder.getCallingUid() == android.os.Process.SYSTEM_UID) {
+    private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType,
+            int callerUid) {
+        if (callerUid == android.os.Process.SYSTEM_UID) {
             return true;
         }
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
-            String[] args = {String.valueOf(Binder.getCallingUid()), authTokenType,
+        UserAccounts accounts = getUserAccountsForCaller();
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
+            String[] args = { String.valueOf(callerUid), authTokenType,
                     account.name, account.type};
             final boolean permissionGranted =
                     DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
@@ -1998,7 +2125,7 @@
                 // TODO: Skip this check when running automated tests. Replace this
                 // with a more general solution.
                 Log.d(TAG, "no credentials permission for usage of " + account + ", "
-                        + authTokenType + " by uid " + Binder.getCallingUid()
+                        + authTokenType + " by uid " + callerUid
                         + " but ignoring since device is in test harness.");
                 return true;
             }
@@ -2048,8 +2175,9 @@
             Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
             return;
         }
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.beginTransaction();
             try {
                 long accountId = getAccountIdLocked(db, account);
@@ -2081,8 +2209,9 @@
             Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
             return;
         }
-        synchronized (mCacheLock) {
-            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
+        UserAccounts accounts = getUserAccounts(UserId.getUserId(uid));
+        synchronized (accounts.cacheLock) {
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
             db.beginTransaction();
             try {
                 long accountId = getAccountIdLocked(db, account);
@@ -2105,8 +2234,8 @@
         return value != null ? ("[" + TextUtils.join(",", value) + "]") : null;
     }
 
-    private void removeAccountFromCacheLocked(Account account) {
-        final Account[] oldAccountsForType = mAccountCache.get(account.type);
+    private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) {
+        final Account[] oldAccountsForType = accounts.accountCache.get(account.type);
         if (oldAccountsForType != null) {
             ArrayList<Account> newAccountsList = new ArrayList<Account>();
             for (Account curAccount : oldAccountsForType) {
@@ -2115,34 +2244,34 @@
                 }
             }
             if (newAccountsList.isEmpty()) {
-                mAccountCache.remove(account.type);
+                accounts.accountCache.remove(account.type);
             } else {
                 Account[] newAccountsForType = new Account[newAccountsList.size()];
                 newAccountsForType = newAccountsList.toArray(newAccountsForType);
-                mAccountCache.put(account.type, newAccountsForType);
+                accounts.accountCache.put(account.type, newAccountsForType);
             }
         }
-        mUserDataCache.remove(account);
-        mAuthTokenCache.remove(account);
+        accounts.userDataCache.remove(account);
+        accounts.authTokenCache.remove(account);
     }
 
     /**
      * This assumes that the caller has already checked that the account is not already present.
      */
-    private void insertAccountIntoCacheLocked(Account account) {
-        Account[] accountsForType = mAccountCache.get(account.type);
+    private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) {
+        Account[] accountsForType = accounts.accountCache.get(account.type);
         int oldLength = (accountsForType != null) ? accountsForType.length : 0;
         Account[] newAccountsForType = new Account[oldLength + 1];
         if (accountsForType != null) {
             System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength);
         }
         newAccountsForType[oldLength] = account;
-        mAccountCache.put(account.type, newAccountsForType);
+        accounts.accountCache.put(account.type, newAccountsForType);
     }
 
-    protected Account[] getAccountsFromCacheLocked(String accountType) {
+    protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) {
         if (accountType != null) {
-            final Account[] accounts = mAccountCache.get(accountType);
+            final Account[] accounts = userAccounts.accountCache.get(accountType);
             if (accounts == null) {
                 return EMPTY_ACCOUNT_ARRAY;
             } else {
@@ -2150,7 +2279,7 @@
             }
         } else {
             int totalLength = 0;
-            for (Account[] accounts : mAccountCache.values()) {
+            for (Account[] accounts : userAccounts.accountCache.values()) {
                 totalLength += accounts.length;
             }
             if (totalLength == 0) {
@@ -2158,7 +2287,7 @@
             }
             Account[] accounts = new Account[totalLength];
             totalLength = 0;
-            for (Account[] accountsOfType : mAccountCache.values()) {
+            for (Account[] accountsOfType : userAccounts.accountCache.values()) {
                 System.arraycopy(accountsOfType, 0, accounts, totalLength,
                         accountsOfType.length);
                 totalLength += accountsOfType.length;
@@ -2167,12 +2296,12 @@
         }
     }
 
-    protected void writeUserDataIntoCacheLocked(final SQLiteDatabase db, Account account,
-            String key, String value) {
-        HashMap<String, String> userDataForAccount = mUserDataCache.get(account);
+    protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+            Account account, String key, String value) {
+        HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
         if (userDataForAccount == null) {
             userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
-            mUserDataCache.put(account, userDataForAccount);
+            accounts.userDataCache.put(account, userDataForAccount);
         }
         if (value == null) {
             userDataForAccount.remove(key);
@@ -2181,12 +2310,12 @@
         }
     }
 
-    protected void writeAuthTokenIntoCacheLocked(final SQLiteDatabase db, Account account,
-            String key, String value) {
-        HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account);
+    protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db,
+            Account account, String key, String value) {
+        HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
         if (authTokensForAccount == null) {
             authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
-            mAuthTokenCache.put(account, authTokensForAccount);
+            accounts.authTokenCache.put(account, authTokensForAccount);
         }
         if (value == null) {
             authTokensForAccount.remove(key);
@@ -2195,27 +2324,28 @@
         }
     }
 
-    protected String readAuthTokenInternal(Account account, String authTokenType) {
-        synchronized (mCacheLock) {
-            HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account);
+    protected String readAuthTokenInternal(UserAccounts accounts, Account account,
+            String authTokenType) {
+        synchronized (accounts.cacheLock) {
+            HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
             if (authTokensForAccount == null) {
                 // need to populate the cache for this account
-                final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
                 authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
-                mAuthTokenCache.put(account, authTokensForAccount);
+                accounts.authTokenCache.put(account, authTokensForAccount);
             }
             return authTokensForAccount.get(authTokenType);
         }
     }
 
-    protected String readUserDataInternal(Account account, String key) {
-        synchronized (mCacheLock) {
-            HashMap<String, String> userDataForAccount = mUserDataCache.get(account);
+    protected String readUserDataInternal(UserAccounts accounts, Account account, String key) {
+        synchronized (accounts.cacheLock) {
+            HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
             if (userDataForAccount == null) {
                 // need to populate the cache for this account
-                final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
+                final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
                 userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
-                mUserDataCache.put(account, userDataForAccount);
+                accounts.userDataCache.put(account, userDataForAccount);
             }
             return userDataForAccount.get(key);
         }
diff --git a/core/java/android/accounts/GrantCredentialsPermissionActivity.java b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
index 0ee683c..4419c8c 100644
--- a/core/java/android/accounts/GrantCredentialsPermissionActivity.java
+++ b/core/java/android/accounts/GrantCredentialsPermissionActivity.java
@@ -113,8 +113,7 @@
             }
         };
 
-        accountManagerService.getAuthTokenLabel(
-                response, mAccount, mAuthTokenType);
+        accountManagerService.getAuthTokenLabel(response, mAccount, mAuthTokenType, mUid);
 
         findViewById(R.id.allow_button).setOnClickListener(this);
         findViewById(R.id.deny_button).setOnClickListener(this);
diff --git a/core/java/android/accounts/OnAccountsUpdateListener.java b/core/java/android/accounts/OnAccountsUpdateListener.java
index 38b371d..2b4ee50 100644
--- a/core/java/android/accounts/OnAccountsUpdateListener.java
+++ b/core/java/android/accounts/OnAccountsUpdateListener.java
@@ -17,11 +17,11 @@
 package android.accounts;
 
 /**
- * An interface that contains the callback used by the AccountMonitor
+ * An interface that contains the callback used by the AccountManager
  */
 public interface OnAccountsUpdateListener {
     /**
-     * This invoked when the AccountMonitor starts up and whenever the account
+     * This invoked when the AccountManager starts up and whenever the account
      * set changes.
      * @param accounts the current accounts
      */
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 6fbeee3..954ae66 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -645,7 +645,7 @@
             // onAnimate to process the next frame of the animations.
             if (!mAnimationScheduled
                     && (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty())) {
-                mChoreographer.postAnimationCallback(this);
+                mChoreographer.postAnimationCallback(this, null);
                 mAnimationScheduled = true;
             }
         }
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index fc4c262..f827c3d 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -26,6 +26,7 @@
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserId;
 import android.util.Log;
 import android.util.SparseIntArray;
 import android.Manifest;
@@ -163,6 +164,8 @@
             Log.v(TAG, "Notifying update of " + uri + " from observer " + observer
                     + ", syncToNetwork " + syncToNetwork);
         }
+
+        int userId = UserId.getCallingUserId();
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
@@ -201,7 +204,8 @@
             if (syncToNetwork) {
                 SyncManager syncManager = getSyncManager();
                 if (syncManager != null) {
-                    syncManager.scheduleLocalSync(null /* all accounts */, uri.getAuthority());
+                    syncManager.scheduleLocalSync(null /* all accounts */, userId,
+                            uri.getAuthority());
                 }
             }
         } finally {
@@ -229,13 +233,15 @@
 
     public void requestSync(Account account, String authority, Bundle extras) {
         ContentResolver.validateSyncExtrasBundle(extras);
+        int userId = UserId.getCallingUserId();
+
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.scheduleSync(account, authority, extras, 0 /* no delay */,
+                syncManager.scheduleSync(account, userId, authority, extras, 0 /* no delay */,
                         false /* onlyThoseWithUnkownSyncableState */);
             }
         } finally {
@@ -250,14 +256,16 @@
      * @param authority filter the pending and active syncs to cancel using this authority
      */
     public void cancelSync(Account account, String authority) {
+        int userId = UserId.getCallingUserId();
+
         // This makes it so that future permission checks will be in the context of this
         // process rather than the caller's process. We will restore this before returning.
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.clearScheduledSyncOperations(account, authority);
-                syncManager.cancelActiveSync(account, authority);
+                syncManager.clearScheduledSyncOperations(account, userId, authority);
+                syncManager.cancelActiveSync(account, userId, authority);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -283,12 +291,14 @@
     public boolean getSyncAutomatically(Account account, String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
                 return syncManager.getSyncStorageEngine().getSyncAutomatically(
-                        account, providerName);
+                        account, userId, providerName);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -299,12 +309,14 @@
     public void setSyncAutomatically(Account account, String providerName, boolean sync) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
                 syncManager.getSyncStorageEngine().setSyncAutomatically(
-                        account, providerName, sync);
+                        account, userId, providerName, sync);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -315,10 +327,12 @@
             long pollFrequency) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             getSyncManager().getSyncStorageEngine().addPeriodicSync(
-                    account, authority, extras, pollFrequency);
+                    account, userId, authority, extras, pollFrequency);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -327,9 +341,12 @@
     public void removePeriodicSync(Account account, String authority, Bundle extras) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
-            getSyncManager().getSyncStorageEngine().removePeriodicSync(account, authority, extras);
+            getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority,
+                    extras);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -338,10 +355,12 @@
     public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             return getSyncManager().getSyncStorageEngine().getPeriodicSyncs(
-                    account, providerName);
+                    account, userId, providerName);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -350,12 +369,14 @@
     public int getIsSyncable(Account account, String providerName) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
                 return syncManager.getSyncStorageEngine().getIsSyncable(
-                        account, providerName);
+                        account, userId, providerName);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -366,12 +387,14 @@
     public void setIsSyncable(Account account, String providerName, int syncable) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
                 syncManager.getSyncStorageEngine().setIsSyncable(
-                        account, providerName, syncable);
+                        account, userId, providerName, syncable);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -381,11 +404,13 @@
     public boolean getMasterSyncAutomatically() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
                 "no permission to read the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically();
+                return syncManager.getSyncStorageEngine().getMasterSyncAutomatically(userId);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -396,11 +421,13 @@
     public void setMasterSyncAutomatically(boolean flag) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
                 "no permission to write the sync settings");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag);
+                syncManager.getSyncStorageEngine().setMasterSyncAutomatically(flag, userId);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -410,12 +437,14 @@
     public boolean isSyncActive(Account account, String authority) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
                 return syncManager.getSyncStorageEngine().isSyncActive(
-                        account, authority);
+                        account, userId, authority);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -426,9 +455,11 @@
     public List<SyncInfo> getCurrentSyncs() {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
-            return getSyncManager().getSyncStorageEngine().getCurrentSyncs();
+            return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId);
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -437,12 +468,14 @@
     public SyncStatusInfo getSyncStatus(Account account, String authority) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
                 return syncManager.getSyncStorageEngine().getStatusByAccountAndAuthority(
-                    account, authority);
+                        account, userId, authority);
             }
         } finally {
             restoreCallingIdentity(identityToken);
@@ -453,11 +486,13 @@
     public boolean isSyncPending(Account account, String authority) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
                 "no permission to read the sync stats");
+        int userId = UserId.getCallingUserId();
+
         long identityToken = clearCallingIdentity();
         try {
             SyncManager syncManager = getSyncManager();
             if (syncManager != null) {
-                return syncManager.getSyncStorageEngine().isSyncPending(account, authority);
+                return syncManager.getSyncStorageEngine().isSyncPending(account, userId, authority);
             }
         } finally {
             restoreCallingIdentity(identityToken);
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index ba24036..b7dfe92 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -23,18 +23,23 @@
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
+import android.accounts.AccountManagerService;
 import android.accounts.OnAccountsUpdateListener;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
+import android.app.AppGlobals;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.DownloadManager.Request;
+import android.content.SyncStorageEngine.OnSyncRequestListener;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.RegisteredServicesCache;
 import android.content.pm.RegisteredServicesCacheListener;
 import android.content.pm.ResolveInfo;
+import android.content.pm.UserInfo;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.os.Bundle;
@@ -48,6 +53,7 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.UserId;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.format.DateUtils;
@@ -132,7 +138,9 @@
 
     private Context mContext;
 
-    private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
+    private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
+
+    private volatile AccountAndUser[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
 
     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
     volatile private PowerManager.WakeLock mSyncManagerWakeLock;
@@ -166,7 +174,8 @@
                             Log.v(TAG, "Internal storage is low.");
                         }
                         mStorageIsLow = true;
-                        cancelActiveSync(null /* any account */, null /* any authority */);
+                        cancelActiveSync(null /* any account */, UserId.USER_ALL,
+                                null /* any authority */);
                     } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "Internal storage is ok.");
@@ -186,28 +195,73 @@
     private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
         public void onReceive(Context context, Intent intent) {
             if (getConnectivityManager().getBackgroundDataSetting()) {
-                scheduleSync(null /* account */, null /* authority */, new Bundle(), 0 /* delay */,
+                scheduleSync(null /* account */, UserId.USER_ALL, null /* authority */,
+                        new Bundle(), 0 /* delay */,
                         false /* onlyThoseWithUnknownSyncableState */);
             }
         }
     };
 
-    private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
-
     private final PowerManager mPowerManager;
 
     private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
     private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
 
+    private List<UserInfo> getAllUsers() {
+        try {
+            return AppGlobals.getPackageManager().getUsers();
+        } catch (RemoteException re) {
+            // Local to system process, shouldn't happen
+        }
+        return null;
+    }
+
+    private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
+        boolean found = false;
+        for (int i = 0; i < accounts.length; i++) {
+            if (accounts[i].userId == userId
+                    && accounts[i].account.equals(account)) {
+                found = true;
+                break;
+            }
+        }
+        return found;
+    }
+
     public void onAccountsUpdated(Account[] accounts) {
         // remember if this was the first time this was called after an update
         final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
-        mAccounts = accounts;
 
-        // if a sync is in progress yet it is no longer in the accounts list,
-        // cancel it
+        List<UserInfo> users = getAllUsers();
+        if (users == null)  return;
+
+        int count = 0;
+
+        // For all known users on the system, get their accounts and add them to the list
+        // TODO: Limit this to active users, when such a concept exists.
+        for (UserInfo user : users) {
+            accounts = AccountManagerService.getSingleton().getAccounts(user.id);
+            count += accounts.length;
+        }
+
+        AccountAndUser[] allAccounts = new AccountAndUser[count];
+        int index = 0;
+        for (UserInfo user : users) {
+            accounts = AccountManagerService.getSingleton().getAccounts(user.id);
+            for (Account account : accounts) {
+                allAccounts[index++] = new AccountAndUser(account, user.id);
+            }
+            if (mBootCompleted) {
+                mSyncStorageEngine.doDatabaseCleanup(accounts, user.id);
+            }
+        }
+
+        mAccounts = allAccounts;
+
         for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
-            if (!ArrayUtils.contains(accounts, currentSyncContext.mSyncOperation.account)) {
+            if (!containsAccountAndUser(allAccounts,
+                    currentSyncContext.mSyncOperation.account,
+                    currentSyncContext.mSyncOperation.userId)) {
                 Log.d(TAG, "canceling sync since the account has been removed");
                 sendSyncFinishedOrCanceledMessage(currentSyncContext,
                         null /* no result since this is a cancel */);
@@ -218,11 +272,7 @@
         // the accounts are not set yet
         sendCheckAlarmsMessage();
 
-        if (mBootCompleted) {
-            mSyncStorageEngine.doDatabaseCleanup(accounts);
-        }
-
-        if (accounts.length > 0) {
+        if (allAccounts.length > 0) {
             // If this is the first time this was called after a bootup then
             // the accounts haven't really changed, instead they were just loaded
             // from the AccountManager. Otherwise at least one of the accounts
@@ -238,7 +288,8 @@
             // a chance to set their syncable state.
 
             boolean onlyThoseWithUnkownSyncableState = justBootedUp;
-            scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
+            scheduleSync(null, UserId.USER_ALL, null, null, 0 /* no delay */,
+                    onlyThoseWithUnkownSyncableState);
         }
     }
 
@@ -277,10 +328,36 @@
 
     private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
     private final SyncHandler mSyncHandler;
-    private final Handler mMainHandler;
 
     private volatile boolean mBootCompleted = false;
 
+    static class AccountAndUser {
+        Account account;
+        int userId;
+
+        AccountAndUser(Account account, int userId) {
+            this.account = account;
+            this.userId = userId;
+        }
+
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof AccountAndUser)) return false;
+            final AccountAndUser other = (AccountAndUser) o;
+            return this.account.equals(other.account)
+                    && this.userId == other.userId;
+        }
+
+        @Override
+        public int hashCode() {
+            return account.hashCode() + userId;
+        }
+
+        public String toString() {
+            return account.toString() + " u" + userId;
+        }
+    }
+
     private ConnectivityManager getConnectivityManager() {
         synchronized (this) {
             if (mConnManagerDoNotUseDirectly == null) {
@@ -297,6 +374,13 @@
         mContext = context;
         SyncStorageEngine.init(context);
         mSyncStorageEngine = SyncStorageEngine.getSingleton();
+        mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
+            public void onSyncRequest(Account account, int userId, String authority,
+                    Bundle extras) {
+                scheduleSync(account, userId, authority, extras, 0, false);
+            }
+        });
+
         mSyncAdapters = new SyncAdaptersCache(mContext);
         mSyncQueue = new SyncQueue(mSyncStorageEngine, mSyncAdapters);
 
@@ -304,12 +388,11 @@
                 Process.THREAD_PRIORITY_BACKGROUND);
         syncThread.start();
         mSyncHandler = new SyncHandler(syncThread.getLooper());
-        mMainHandler = new Handler(mContext.getMainLooper());
 
         mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
             public void onServiceChanged(SyncAdapterType type, boolean removed) {
                 if (!removed) {
-                    scheduleSync(null, type.authority, null, 0 /* no delay */,
+                    scheduleSync(null, UserId.USER_ALL, type.authority, null, 0 /* no delay */,
                             false /* onlyThoseWithUnkownSyncableState */);
                 }
             }
@@ -376,7 +459,7 @@
             AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
                 mSyncHandler, false /* updateImmediately */);
             // do this synchronously to ensure we have the accounts before this call returns
-            onAccountsUpdated(AccountManager.get(mContext).getAccounts());
+            onAccountsUpdated(null);
         }
     }
 
@@ -404,82 +487,6 @@
         }
     }
 
-    private void initializeSyncAdapter(Account account, String authority) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "initializeSyncAdapter: " + account + ", authority " + authority);
-        }
-        SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
-        RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
-                mSyncAdapters.getServiceInfo(syncAdapterType);
-        if (syncAdapterInfo == null) {
-            Log.w(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing");
-            mSyncStorageEngine.removeAuthority(account, authority);
-            return;
-        }
-
-        Intent intent = new Intent();
-        intent.setAction("android.content.SyncAdapter");
-        intent.setComponent(syncAdapterInfo.componentName);
-        if (!mContext.bindService(intent,
-                new InitializerServiceConnection(account, authority, mContext, mMainHandler),
-                Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
-                | Context.BIND_ALLOW_OOM_MANAGEMENT)) {
-            Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
-        }
-    }
-
-    private static class InitializerServiceConnection implements ServiceConnection {
-        private final Account mAccount;
-        private final String mAuthority;
-        private final Handler mHandler;
-        private volatile Context mContext;
-        private volatile boolean mInitialized;
-
-        public InitializerServiceConnection(Account account, String authority, Context context,
-                Handler handler) {
-            mAccount = account;
-            mAuthority = authority;
-            mContext = context;
-            mHandler = handler;
-            mInitialized = false;
-        }
-
-        public void onServiceConnected(ComponentName name, IBinder service) {
-            try {
-                if (!mInitialized) {
-                    mInitialized = true;
-                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.v(TAG, "calling initialize: " + mAccount + ", authority " + mAuthority);
-                    }
-                    ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
-                }
-            } catch (RemoteException e) {
-                // doesn't matter, we will retry again later
-                Log.d(TAG, "error while initializing: " + mAccount + ", authority " + mAuthority,
-                        e);
-            } finally {
-                // give the sync adapter time to initialize before unbinding from it
-                // TODO: change this API to not rely on this timing, http://b/2500805
-                mHandler.postDelayed(new Runnable() {
-                    public void run() {
-                        if (mContext != null) {
-                            mContext.unbindService(InitializerServiceConnection.this);
-                            mContext = null;
-                        }
-                    }
-                }, INITIALIZATION_UNBIND_DELAY_MS);
-            }
-        }
-
-        public void onServiceDisconnected(ComponentName name) {
-            if (mContext != null) {
-                mContext.unbindService(InitializerServiceConnection.this);
-                mContext = null;
-            }
-        }
-
-    }
-
     /**
      * Initiate a sync. This can start a sync for all providers
      * (pass null to url, set onlyTicklable to false), only those
@@ -500,6 +507,8 @@
      * <p>You'll start getting callbacks after this.
      *
      * @param requestedAccount the account to sync, may be null to signify all accounts
+     * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
+     *          then all users' accounts are considered.
      * @param requestedAuthority the authority to sync, may be null to indicate all authorities
      * @param extras a Map of SyncAdapter-specific information to control
      *          syncs of a specific provider. Can be null. Is ignored
@@ -507,7 +516,7 @@
      * @param delay how many milliseconds in the future to wait before performing this
      * @param onlyThoseWithUnkownSyncableState
      */
-    public void scheduleSync(Account requestedAccount, String requestedAuthority,
+    public void scheduleSync(Account requestedAccount, int userId, String requestedAuthority,
             Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
 
@@ -521,9 +530,9 @@
             delay = -1; // this means schedule at the front of the queue
         }
 
-        Account[] accounts;
-        if (requestedAccount != null) {
-            accounts = new Account[]{requestedAccount};
+        AccountAndUser[] accounts;
+        if (requestedAccount != null && userId != UserId.USER_ALL) {
+            accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
         } else {
             // if the accounts aren't configured yet then we can't support an account-less
             // sync request
@@ -574,24 +583,23 @@
             if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
         }
 
-        final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
-
         for (String authority : syncableAuthorities) {
-            for (Account account : accounts) {
-                int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority);
+            for (AccountAndUser account : accounts) {
+                int isSyncable = mSyncStorageEngine.getIsSyncable(account.account, account.userId,
+                        authority);
                 if (isSyncable == 0) {
                     continue;
                 }
                 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
                         mSyncAdapters.getServiceInfo(
-                                SyncAdapterType.newKey(authority, account.type));
+                                SyncAdapterType.newKey(authority, account.account.type));
                 if (syncAdapterInfo == null) {
                     continue;
                 }
                 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
                 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
                 if (isSyncable < 0 && isAlwaysSyncable) {
-                    mSyncStorageEngine.setIsSyncable(account, authority, 1);
+                    mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
                     isSyncable = 1;
                 }
                 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
@@ -605,8 +613,10 @@
                 boolean syncAllowed =
                         (isSyncable < 0)
                         || ignoreSettings
-                        || (backgroundDataUsageAllowed && masterSyncAutomatically
-                            && mSyncStorageEngine.getSyncAutomatically(account, authority));
+                        || (backgroundDataUsageAllowed
+                                && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
+                                && mSyncStorageEngine.getSyncAutomatically(account.account,
+                                        account.userId, authority));
                 if (!syncAllowed) {
                     if (isLoggable) {
                         Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
@@ -615,8 +625,10 @@
                     continue;
                 }
 
-                Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(account, authority);
-                long delayUntil = mSyncStorageEngine.getDelayUntilTime(account, authority);
+                Pair<Long, Long> backoff = mSyncStorageEngine
+                        .getBackoff(account.account, account.userId, authority);
+                long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
+                        account.userId, authority);
                 final long backoffTime = backoff != null ? backoff.first : 0;
                 if (isSyncable < 0) {
                     Bundle newExtras = new Bundle();
@@ -630,9 +642,8 @@
                                 + ", extras " + newExtras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account, source, authority, newExtras, 0,
-                                    backoffTime, delayUntil,
-                                    allowParallelSyncs));
+                            new SyncOperation(account.account, account.userId, source, authority,
+                                    newExtras, 0, backoffTime, delayUntil, allowParallelSyncs));
                 }
                 if (!onlyThoseWithUnkownSyncableState) {
                     if (isLoggable) {
@@ -644,18 +655,17 @@
                                 + ", extras " + extras);
                     }
                     scheduleSyncOperation(
-                            new SyncOperation(account, source, authority, extras, delay,
-                                    backoffTime, delayUntil,
-                                    allowParallelSyncs));
+                            new SyncOperation(account.account, account.userId, source, authority,
+                                    extras, delay, backoffTime, delayUntil, allowParallelSyncs));
                 }
             }
         }
     }
 
-    public void scheduleLocalSync(Account account, String authority) {
+    public void scheduleLocalSync(Account account, int userId, String authority) {
         final Bundle extras = new Bundle();
         extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
-        scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY,
+        scheduleSync(account, userId, authority, extras, LOCAL_SYNC_DELAY,
                 false /* onlyThoseWithUnkownSyncableState */);
     }
 
@@ -691,11 +701,13 @@
         mSyncHandler.sendMessage(msg);
     }
 
-    private void sendCancelSyncsMessage(final Account account, final String authority) {
+    private void sendCancelSyncsMessage(final Account account, final int userId,
+            final String authority) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
         Message msg = mSyncHandler.obtainMessage();
         msg.what = SyncHandler.MESSAGE_CANCEL;
         msg.obj = Pair.create(account, authority);
+        msg.arg1 = userId;
         mSyncHandler.sendMessage(msg);
     }
 
@@ -717,10 +729,10 @@
     }
 
     private void clearBackoffSetting(SyncOperation op) {
-        mSyncStorageEngine.setBackoff(op.account, op.authority,
+        mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
         synchronized (mSyncQueue) {
-            mSyncQueue.onBackoffChanged(op.account, op.authority, 0);
+            mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, 0);
         }
     }
 
@@ -728,7 +740,7 @@
         final long now = SystemClock.elapsedRealtime();
 
         final Pair<Long, Long> previousSettings =
-                mSyncStorageEngine.getBackoff(op.account, op.authority);
+                mSyncStorageEngine.getBackoff(op.account, op.userId, op.authority);
         long newDelayInMs = -1;
         if (previousSettings != null) {
             // don't increase backoff before current backoff is expired. This will happen for op's
@@ -759,14 +771,14 @@
 
         final long backoff = now + newDelayInMs;
 
-        mSyncStorageEngine.setBackoff(op.account, op.authority,
+        mSyncStorageEngine.setBackoff(op.account, op.userId, op.authority,
                 backoff, newDelayInMs);
 
         op.backoff = backoff;
         op.updateEffectiveRunTime();
 
         synchronized (mSyncQueue) {
-            mSyncQueue.onBackoffChanged(op.account, op.authority, backoff);
+            mSyncQueue.onBackoffChanged(op.account, op.userId, op.authority, backoff);
         }
     }
 
@@ -779,7 +791,8 @@
         } else {
             newDelayUntilTime = 0;
         }
-        mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
+        mSyncStorageEngine
+                .setDelayUntilTime(op.account, op.userId, op.authority, newDelayUntilTime);
         synchronized (mSyncQueue) {
             mSyncQueue.onDelayUntilTimeChanged(op.account, op.authority, newDelayUntilTime);
         }
@@ -790,8 +803,8 @@
      * @param account limit the cancelations to syncs with this account, if non-null
      * @param authority limit the cancelations to syncs with this authority, if non-null
      */
-    public void cancelActiveSync(Account account, String authority) {
-        sendCancelSyncsMessage(account, authority);
+    public void cancelActiveSync(Account account, int userId, String authority) {
+        sendCancelSyncsMessage(account, userId, authority);
     }
 
     /**
@@ -823,11 +836,11 @@
      * @param account limit the removals to operations with this account, if non-null
      * @param authority limit the removals to operations with this authority, if non-null
      */
-    public void clearScheduledSyncOperations(Account account, String authority) {
+    public void clearScheduledSyncOperations(Account account, int userId, String authority) {
         synchronized (mSyncQueue) {
-            mSyncQueue.remove(account, authority);
+            mSyncQueue.remove(account, userId, authority);
         }
-        mSyncStorageEngine.setBackoff(account, authority,
+        mSyncStorageEngine.setBackoff(account, userId, authority,
                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
     }
 
@@ -875,7 +888,8 @@
                 Log.d(TAG, "retrying sync operation that failed because there was already a "
                         + "sync in progress: " + operation);
             }
-            scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
+            scheduleSyncOperation(new SyncOperation(operation.account, operation.userId,
+                    operation.syncSource,
                     operation.authority, operation.extras,
                     DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
                     operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
@@ -979,7 +993,8 @@
             mBound = true;
             final boolean bindResult = mContext.bindService(intent, this,
                     Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
-                    | Context.BIND_ALLOW_OOM_MANAGEMENT);
+                    | Context.BIND_ALLOW_OOM_MANAGEMENT,
+                    mSyncOperation.userId);
             if (!bindResult) {
                 mBound = false;
             }
@@ -1034,10 +1049,19 @@
 
     protected void dumpSyncState(PrintWriter pw) {
         pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
-        pw.print("auto sync: "); pw.println(mSyncStorageEngine.getMasterSyncAutomatically());
+        pw.print("auto sync: ");
+        List<UserInfo> users = getAllUsers();
+        if (users != null) {
+            for (UserInfo user : users) {
+                pw.print("u" + user.id + "="
+                        + mSyncStorageEngine.getMasterSyncAutomatically(user.id));
+            }
+            pw.println();
+        }
         pw.print("memory low: "); pw.println(mStorageIsLow);
 
-        final Account[] accounts = mAccounts;
+        final AccountAndUser[] accounts = mAccounts;
+
         pw.print("accounts: ");
         if (accounts != INITIAL_ACCOUNTS_ARRAY) {
             pw.println(accounts.length);
@@ -1090,18 +1114,20 @@
         // join the installed sync adapter with the accounts list and emit for everything
         pw.println();
         pw.println("Sync Status");
-        for (Account account : accounts) {
-            pw.print("  Account "); pw.print(account.name);
-                    pw.print(" "); pw.print(account.type);
+        for (AccountAndUser account : accounts) {
+            pw.print("  Account "); pw.print(account.account.name);
+                    pw.print(" u"); pw.print(account.userId);
+                    pw.print(" "); pw.print(account.account.type);
                     pw.println(":");
             for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
                     mSyncAdapters.getAllServices()) {
-                if (!syncAdapterType.type.accountType.equals(account.type)) {
+                if (!syncAdapterType.type.accountType.equals(account.account.type)) {
                     continue;
                 }
 
-                SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getOrCreateAuthority(
-                        account, syncAdapterType.type.authority);
+                SyncStorageEngine.AuthorityInfo settings =
+                        mSyncStorageEngine.getOrCreateAuthority(
+                                account.account, account.userId, syncAdapterType.type.authority);
                 SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
                 pw.print("    "); pw.print(settings.authority);
                 pw.println(":");
@@ -1554,7 +1580,16 @@
         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
         public void onBootCompleted() {
             mBootCompleted = true;
-            mSyncStorageEngine.doDatabaseCleanup(AccountManager.get(mContext).getAccounts());
+            // TODO: Handle bootcompleted event for specific user. Now let's just iterate through
+            // all the users.
+            List<UserInfo> users = getAllUsers();
+            if (users != null) {
+                for (UserInfo user : users) {
+                    mSyncStorageEngine.doDatabaseCleanup(
+                            AccountManagerService.getSingleton().getAccounts(user.id),
+                            user.id);
+                }
+            }
             if (mReadyToRunLatch != null) {
                 mReadyToRunLatch.countDown();
             }
@@ -1635,7 +1670,7 @@
                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
                                     + payload.first + ", " + payload.second);
                         }
-                        cancelActiveSyncLocked(payload.first, payload.second);
+                        cancelActiveSyncLocked(payload.first, msg.arg1, payload.second);
                         nextPendingSyncTime = maybeStartNextSyncLocked();
                         break;
                     }
@@ -1740,22 +1775,28 @@
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
             long earliestFuturePollTime = Long.MAX_VALUE;
-            if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
+            if (!backgroundDataUsageAllowed) {
                 return earliestFuturePollTime;
             }
+
+            AccountAndUser[] accounts = mAccounts;
+
             final long nowAbsolute = System.currentTimeMillis();
             ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
             for (SyncStorageEngine.AuthorityInfo info : infos) {
                 // skip the sync if the account of this operation no longer exists
-                if (!ArrayUtils.contains(mAccounts, info.account)) {
+                if (!containsAccountAndUser(accounts, info.account, info.userId)) {
                     continue;
                 }
 
-                if (!mSyncStorageEngine.getSyncAutomatically(info.account, info.authority)) {
+                if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId)
+                        || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId,
+                                info.authority)) {
                     continue;
                 }
 
-                if (mSyncStorageEngine.getIsSyncable(info.account, info.authority) == 0) {
+                if (mSyncStorageEngine.getIsSyncable(info.account, info.userId, info.authority)
+                        == 0) {
                     continue;
                 }
 
@@ -1772,8 +1813,8 @@
                             : lastPollTimeAbsolute + periodInSeconds * 1000;
                     // if it is ready to run then schedule it and mark it as having been scheduled
                     if (nextPollTimeAbsolute <= nowAbsolute) {
-                        final Pair<Long, Long> backoff =
-                                mSyncStorageEngine.getBackoff(info.account, info.authority);
+                        final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
+                                info.account, info.userId, info.authority);
                         final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
                                 mSyncAdapters.getServiceInfo(
                                         SyncAdapterType.newKey(info.authority, info.account.type));
@@ -1781,11 +1822,12 @@
                             continue;
                         }
                         scheduleSyncOperation(
-                                new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
+                                new SyncOperation(info.account, info.userId,
+                                        SyncStorageEngine.SOURCE_PERIODIC,
                                         info.authority, extras, 0 /* delay */,
                                         backoff != null ? backoff.first : 0,
                                         mSyncStorageEngine.getDelayUntilTime(
-                                                info.account, info.authority),
+                                                info.account, info.userId, info.authority),
                                         syncAdapterInfo.type.allowParallelSyncs()));
                         status.setPeriodicSyncTime(i, nowAbsolute);
                     } else {
@@ -1830,7 +1872,7 @@
 
             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
             // when the account lookup request does complete.
-            Account[] accounts = mAccounts;
+            AccountAndUser[] accounts = mAccounts;
             if (accounts == INITIAL_ACCOUNTS_ARRAY) {
                 if (isLoggable) {
                     Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
@@ -1843,7 +1885,6 @@
             // start it, otherwise just get out.
             final boolean backgroundDataUsageAllowed =
                     getConnectivityManager().getBackgroundDataSetting();
-            final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
 
             final long now = SystemClock.elapsedRealtime();
 
@@ -1863,14 +1904,15 @@
                     final SyncOperation op = operationIterator.next();
 
                     // drop the sync if the account of this operation no longer exists
-                    if (!ArrayUtils.contains(mAccounts, op.account)) {
+                    if (!containsAccountAndUser(accounts, op.account, op.userId)) {
                         operationIterator.remove();
                         mSyncStorageEngine.deleteFromPending(op.pendingOperation);
                         continue;
                     }
 
                     // drop this sync request if it isn't syncable
-                    int syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+                    int syncableState = mSyncStorageEngine.getIsSyncable(
+                            op.account, op.userId, op.authority);
                     if (syncableState == 0) {
                         operationIterator.remove();
                         mSyncStorageEngine.deleteFromPending(op.pendingOperation);
@@ -1905,11 +1947,11 @@
                     // disconnected for the target UID.
                     if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
                             && (syncableState > 0)
-                            && (!masterSyncAutomatically
+                            && (!mSyncStorageEngine.getMasterSyncAutomatically(op.userId)
                                 || !backgroundDataUsageAllowed
                                 || !uidNetworkConnected
                                 || !mSyncStorageEngine.getSyncAutomatically(
-                                       op.account, op.authority))) {
+                                       op.account, op.userId, op.authority))) {
                         operationIterator.remove();
                         mSyncStorageEngine.deleteFromPending(op.pendingOperation);
                         continue;
@@ -1946,6 +1988,7 @@
                     }
                     if (activeOp.account.type.equals(candidate.account.type)
                             && activeOp.authority.equals(candidate.authority)
+                            && activeOp.userId == candidate.userId
                             && (!activeOp.allowParallelSyncs
                                 || activeOp.account.name.equals(candidate.account.name))) {
                         conflict = activeSyncContext;
@@ -2033,7 +2076,7 @@
             if (syncAdapterInfo == null) {
                 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
                         + ", removing settings for it");
-                mSyncStorageEngine.removeAuthority(op.account, op.authority);
+                mSyncStorageEngine.removeAuthority(op.account, op.userId, op.authority);
                 return false;
             }
 
@@ -2074,7 +2117,7 @@
             }
         }
 
-        private void cancelActiveSyncLocked(Account account, String authority) {
+        private void cancelActiveSyncLocked(Account account, int userId, String authority) {
             ArrayList<ActiveSyncContext> activeSyncs =
                     new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
             for (ActiveSyncContext activeSyncContext : activeSyncs) {
@@ -2082,15 +2125,20 @@
                     // if an authority was specified then only cancel the sync if it matches
                     if (account != null) {
                         if (!account.equals(activeSyncContext.mSyncOperation.account)) {
-                            return;
+                            continue;
                         }
                     }
                     // if an account was specified then only cancel the sync if it matches
                     if (authority != null) {
                         if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
-                            return;
+                            continue;
                         }
                     }
+                    // check if the userid matches
+                    if (userId != UserId.USER_ALL
+                            && userId != activeSyncContext.mSyncOperation.userId) {
+                        continue;
+                    }
                     runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
                             activeSyncContext);
                 }
@@ -2169,7 +2217,7 @@
             }
 
             if (syncResult != null && syncResult.fullSyncRequested) {
-                scheduleSyncOperation(new SyncOperation(syncOperation.account,
+                scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId,
                         syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
                         syncOperation.backoff, syncOperation.delayUntil,
                         syncOperation.allowParallelSyncs));
@@ -2180,7 +2228,8 @@
         private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
             activeSyncContext.close();
             mActiveSyncContexts.remove(activeSyncContext);
-            mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo);
+            mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
+                    activeSyncContext.mSyncOperation.userId);
         }
 
         /**
@@ -2446,7 +2495,8 @@
                                 syncOperation.account.name.hashCode());
 
             return mSyncStorageEngine.insertStartSyncEvent(
-                    syncOperation.account, syncOperation.authority, now, source);
+                    syncOperation.account, syncOperation.userId, syncOperation.authority,
+                    now, source);
         }
 
         public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
diff --git a/core/java/android/content/SyncOperation.java b/core/java/android/content/SyncOperation.java
index 94c2247..4e86ef8 100644
--- a/core/java/android/content/SyncOperation.java
+++ b/core/java/android/content/SyncOperation.java
@@ -26,6 +26,7 @@
  */
 public class SyncOperation implements Comparable {
     public final Account account;
+    public final int userId;
     public int syncSource;
     public String authority;
     public final boolean allowParallelSyncs;
@@ -38,9 +39,10 @@
     public long delayUntil;
     public long effectiveRunTime;
 
-    public SyncOperation(Account account, int source, String authority, Bundle extras,
+    public SyncOperation(Account account, int userId, int source, String authority, Bundle extras,
             long delayInMs, long backoff, long delayUntil, boolean allowParallelSyncs) {
         this.account = account;
+        this.userId = userId;
         this.syncSource = source;
         this.authority = authority;
         this.allowParallelSyncs = allowParallelSyncs;
@@ -75,6 +77,7 @@
 
     SyncOperation(SyncOperation other) {
         this.account = other.account;
+        this.userId = other.userId;
         this.syncSource = other.syncSource;
         this.authority = other.authority;
         this.extras = new Bundle(other.extras);
@@ -120,7 +123,8 @@
     private String toKey() {
         StringBuilder sb = new StringBuilder();
         sb.append("authority: ").append(authority);
-        sb.append(" account {name=" + account.name + ", type=" + account.type + "}");
+        sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
+                + "}");
         sb.append(" extras: ");
         extrasToStringBuilder(extras, sb);
         return sb.toString();
diff --git a/core/java/android/content/SyncQueue.java b/core/java/android/content/SyncQueue.java
index bfdf4a1..06da6fa 100644
--- a/core/java/android/content/SyncQueue.java
+++ b/core/java/android/content/SyncQueue.java
@@ -49,7 +49,8 @@
         final int N = ops.size();
         for (int i=0; i<N; i++) {
             SyncStorageEngine.PendingOperation op = ops.get(i);
-            final Pair<Long, Long> backoff = syncStorageEngine.getBackoff(op.account, op.authority);
+            final Pair<Long, Long> backoff =
+                    syncStorageEngine.getBackoff(op.account, op.userId, op.authority);
             final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
                     syncAdapters.getServiceInfo(
                             SyncAdapterType.newKey(op.authority, op.account.type));
@@ -57,9 +58,9 @@
                 continue;
             }
             SyncOperation syncOperation = new SyncOperation(
-                    op.account, op.syncSource, op.authority, op.extras, 0 /* delay */,
+                    op.account, op.userId, op.syncSource, op.authority, op.extras, 0 /* delay */,
                     backoff != null ? backoff.first : 0,
-                    syncStorageEngine.getDelayUntilTime(op.account, op.authority),
+                    syncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
                     syncAdapterInfo.type.allowParallelSyncs());
             syncOperation.expedited = op.expedited;
             syncOperation.pendingOperation = op;
@@ -102,8 +103,8 @@
         operation.pendingOperation = pop;
         if (operation.pendingOperation == null) {
             pop = new SyncStorageEngine.PendingOperation(
-                            operation.account, operation.syncSource,
-                            operation.authority, operation.extras, operation.expedited);
+                    operation.account, operation.userId, operation.syncSource,
+                    operation.authority, operation.extras, operation.expedited);
             pop = mSyncStorageEngine.insertIntoPending(pop);
             if (pop == null) {
                 throw new IllegalStateException("error adding pending sync operation "
@@ -131,11 +132,12 @@
         }
     }
 
-    public void onBackoffChanged(Account account, String providerName, long backoff) {
+    public void onBackoffChanged(Account account, int userId, String providerName, long backoff) {
         // for each op that matches the account and provider update its
         // backoff and effectiveStartTime
         for (SyncOperation op : mOperationsMap.values()) {
-            if (op.account.equals(account) && op.authority.equals(providerName)) {
+            if (op.account.equals(account) && op.authority.equals(providerName)
+                    && op.userId == userId) {
                 op.backoff = backoff;
                 op.updateEffectiveRunTime();
             }
@@ -153,7 +155,7 @@
         }
     }
 
-    public void remove(Account account, String authority) {
+    public void remove(Account account, int userId, String authority) {
         Iterator<Map.Entry<String, SyncOperation>> entries = mOperationsMap.entrySet().iterator();
         while (entries.hasNext()) {
             Map.Entry<String, SyncOperation> entry = entries.next();
@@ -164,6 +166,9 @@
             if (authority != null && !syncOperation.authority.equals(authority)) {
                 continue;
             }
+            if (userId != syncOperation.userId) {
+                continue;
+            }
             entries.remove();
             if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
                 final String errorMessage = "unable to find pending row for " + syncOperation;
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index a1e174b..7bb9866 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -25,6 +25,7 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import android.accounts.Account;
+import android.content.SyncManager.AccountAndUser;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
@@ -58,9 +59,16 @@
  * @hide
  */
 public class SyncStorageEngine extends Handler {
+
     private static final String TAG = "SyncManager";
     private static final boolean DEBUG_FILE = false;
 
+    private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
+    private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
+    private static final String XML_ATTR_ENABLED = "enabled";
+    private static final String XML_ATTR_USER = "user";
+    private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
+
     private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
 
     // @VisibleForTesting
@@ -133,6 +141,7 @@
 
     public static class PendingOperation {
         final Account account;
+        final int userId;
         final int syncSource;
         final String authority;
         final Bundle extras;        // note: read-only.
@@ -141,9 +150,10 @@
         int authorityId;
         byte[] flatExtras;
 
-        PendingOperation(Account account, int source,
+        PendingOperation(Account account, int userId, int source,
                 String authority, Bundle extras, boolean expedited) {
             this.account = account;
+            this.userId = userId;
             this.syncSource = source;
             this.authority = authority;
             this.extras = extras != null ? new Bundle(extras) : extras;
@@ -153,6 +163,7 @@
 
         PendingOperation(PendingOperation other) {
             this.account = other.account;
+            this.userId = other.userId;
             this.syncSource = other.syncSource;
             this.authority = other.authority;
             this.extras = other.extras;
@@ -162,17 +173,18 @@
     }
 
     static class AccountInfo {
-        final Account account;
+        final AccountAndUser accountAndUser;
         final HashMap<String, AuthorityInfo> authorities =
                 new HashMap<String, AuthorityInfo>();
 
-        AccountInfo(Account account) {
-            this.account = account;
+        AccountInfo(AccountAndUser accountAndUser) {
+            this.accountAndUser = accountAndUser;
         }
     }
 
     public static class AuthorityInfo {
         final Account account;
+        final int userId;
         final String authority;
         final int ident;
         boolean enabled;
@@ -182,8 +194,9 @@
         long delayUntil;
         final ArrayList<Pair<Bundle, Long>> periodicSyncs;
 
-        AuthorityInfo(Account account, String authority, int ident) {
+        AuthorityInfo(Account account, int userId, String authority, int ident) {
             this.account = account;
+            this.userId = userId;
             this.authority = authority;
             this.ident = ident;
             enabled = SYNC_ENABLED_DEFAULT;
@@ -219,17 +232,29 @@
         }
     }
 
+    interface OnSyncRequestListener {
+        /**
+         * Called when a sync is needed on an account(s) due to some change in state.
+         * @param account
+         * @param userId
+         * @param authority
+         * @param extras
+         */
+        public void onSyncRequest(Account account, int userId, String authority, Bundle extras);
+    }
+
     // Primary list of all syncable authorities.  Also our global lock.
     private final SparseArray<AuthorityInfo> mAuthorities =
             new SparseArray<AuthorityInfo>();
 
-    private final HashMap<Account, AccountInfo> mAccounts =
-        new HashMap<Account, AccountInfo>();
+    private final HashMap<AccountAndUser, AccountInfo> mAccounts
+            = new HashMap<AccountAndUser, AccountInfo>();
 
     private final ArrayList<PendingOperation> mPendingOperations =
             new ArrayList<PendingOperation>();
 
-    private final ArrayList<SyncInfo> mCurrentSyncs = new ArrayList<SyncInfo>();
+    private final SparseArray<ArrayList<SyncInfo>> mCurrentSyncs
+            = new SparseArray<ArrayList<SyncInfo>>();
 
     private final SparseArray<SyncStatusInfo> mSyncStatus =
             new SparseArray<SyncStatusInfo>();
@@ -282,7 +307,9 @@
     private int mNumPendingFinished = 0;
 
     private int mNextHistoryId = 0;
-    private boolean mMasterSyncAutomatically = true;
+    private SparseArray<Boolean> mMasterSyncAutomatically = new SparseArray<Boolean>();
+
+    private OnSyncRequestListener mSyncRequestListener;
 
     private SyncStorageEngine(Context context, File dataDir) {
         mContext = context;
@@ -330,6 +357,12 @@
         return sSyncStorageEngine;
     }
 
+    protected void setOnSyncRequestListener(OnSyncRequestListener listener) {
+        if (mSyncRequestListener == null) {
+            mSyncRequestListener = listener;
+        }
+    }
+
     @Override public void handleMessage(Message msg) {
         if (msg.what == MSG_WRITE_STATUS) {
             synchronized (mAuthorities) {
@@ -389,10 +422,10 @@
         }
     }
 
-    public boolean getSyncAutomatically(Account account, String providerName) {
+    public boolean getSyncAutomatically(Account account, int userId, String providerName) {
         synchronized (mAuthorities) {
             if (account != null) {
-                AuthorityInfo authority = getAuthorityLocked(account, providerName,
+                AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
                         "getSyncAutomatically");
                 return authority != null && authority.enabled;
             }
@@ -402,6 +435,7 @@
                 i--;
                 AuthorityInfo authority = mAuthorities.valueAt(i);
                 if (authority.authority.equals(providerName)
+                        && authority.userId == userId
                         && authority.enabled) {
                     return true;
                 }
@@ -410,11 +444,13 @@
         }
     }
 
-    public void setSyncAutomatically(Account account, String providerName, boolean sync) {
-        Log.d(TAG, "setSyncAutomatically: " + /*account +*/ ", provider " + providerName
-                + " -> " + sync);
+    public void setSyncAutomatically(Account account, int userId, String providerName,
+            boolean sync) {
+        Log.d(TAG, "setSyncAutomatically: " + /* account + */" provider " + providerName
+                + ", user " + userId + " -> " + sync);
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+            AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
+                    false);
             if (authority.enabled == sync) {
                 Log.d(TAG, "setSyncAutomatically: already set to " + sync + ", doing nothing");
                 return;
@@ -424,15 +460,15 @@
         }
 
         if (sync) {
-            ContentResolver.requestSync(account, providerName, new Bundle());
+            requestSync(account, userId, providerName, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public int getIsSyncable(Account account, String providerName) {
+    public int getIsSyncable(Account account, int userId, String providerName) {
         synchronized (mAuthorities) {
             if (account != null) {
-                AuthorityInfo authority = getAuthorityLocked(account, providerName,
+                AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
                         "getIsSyncable");
                 if (authority == null) {
                     return -1;
@@ -452,15 +488,17 @@
         }
     }
 
-    public void setIsSyncable(Account account, String providerName, int syncable) {
+    public void setIsSyncable(Account account, int userId, String providerName, int syncable) {
         if (syncable > 1) {
             syncable = 1;
         } else if (syncable < -1) {
             syncable = -1;
         }
-        Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName + " -> " + syncable);
+        Log.d(TAG, "setIsSyncable: " + account + ", provider " + providerName
+                + ", user " + userId + " -> " + syncable);
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+            AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
+                    false);
             if (authority.syncable == syncable) {
                 Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
                 return;
@@ -470,14 +508,15 @@
         }
 
         if (syncable > 0) {
-            ContentResolver.requestSync(account, providerName, new Bundle());
+            requestSync(account, userId, providerName, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public Pair<Long, Long> getBackoff(Account account, String providerName) {
+    public Pair<Long, Long> getBackoff(Account account, int userId, String providerName) {
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getAuthorityLocked(account, providerName, "getBackoff");
+            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                    "getBackoff");
             if (authority == null || authority.backoffTime < 0) {
                 return null;
             }
@@ -485,17 +524,21 @@
         }
     }
 
-    public void setBackoff(Account account, String providerName,
+    public void setBackoff(Account account, int userId, String providerName,
             long nextSyncTime, long nextDelay) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "setBackoff: " + account + ", provider " + providerName
+                    + ", user " + userId
                     + " -> nextSyncTime " + nextSyncTime + ", nextDelay " + nextDelay);
         }
         boolean changed = false;
         synchronized (mAuthorities) {
             if (account == null || providerName == null) {
                 for (AccountInfo accountInfo : mAccounts.values()) {
-                    if (account != null && !account.equals(accountInfo.account)) continue;
+                    if (account != null && !account.equals(accountInfo.accountAndUser.account)
+                            && userId != accountInfo.accountAndUser.userId) {
+                        continue;
+                    }
                     for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) {
                         if (providerName != null && !providerName.equals(authorityInfo.authority)) {
                             continue;
@@ -510,7 +553,8 @@
                 }
             } else {
                 AuthorityInfo authority =
-                        getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true);
+                        getOrCreateAuthorityLocked(account, userId, providerName, -1 /* ident */,
+                                true);
                 if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
                     return;
                 }
@@ -535,13 +579,15 @@
                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
                             Log.v(TAG, "clearAllBackoffs:"
                                     + " authority:" + authorityInfo.authority
-                                    + " account:" + accountInfo.account.name
+                                    + " account:" + accountInfo.accountAndUser.account.name
+                                    + " user:" + accountInfo.accountAndUser.userId
                                     + " backoffTime was: " + authorityInfo.backoffTime
                                     + " backoffDelay was: " + authorityInfo.backoffDelay);
                         }
                         authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE;
                         authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE;
-                        syncQueue.onBackoffChanged(accountInfo.account, authorityInfo.authority, 0);
+                        syncQueue.onBackoffChanged(accountInfo.accountAndUser.account,
+                                accountInfo.accountAndUser.userId, authorityInfo.authority, 0);
                         changed = true;
                     }
                 }
@@ -553,14 +599,15 @@
         }
     }
 
-    public void setDelayUntilTime(Account account, String providerName, long delayUntil) {
+    public void setDelayUntilTime(Account account, int userId, String providerName,
+            long delayUntil) {
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
             Log.v(TAG, "setDelayUntil: " + account + ", provider " + providerName
-                    + " -> delayUntil " + delayUntil);
+                    + ", user " + userId + " -> delayUntil " + delayUntil);
         }
         synchronized (mAuthorities) {
             AuthorityInfo authority = getOrCreateAuthorityLocked(
-                    account, providerName, -1 /* ident */, true);
+                    account, userId, providerName, -1 /* ident */, true);
             if (authority.delayUntil == delayUntil) {
                 return;
             }
@@ -570,9 +617,10 @@
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public long getDelayUntilTime(Account account, String providerName) {
+    public long getDelayUntilTime(Account account, int userId, String providerName) {
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getAuthorityLocked(account, providerName, "getDelayUntil");
+            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                    "getDelayUntil");
             if (authority == null) {
                 return 0;
             }
@@ -580,7 +628,8 @@
         }
     }
 
-    private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras,
+    private void updateOrRemovePeriodicSync(Account account, int userId, String providerName,
+            Bundle extras,
             long period, boolean add) {
         if (period <= 0) {
             period = 0;
@@ -589,13 +638,14 @@
             extras = new Bundle();
         }
         if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName
+            Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId
+                    + ", provider " + providerName
                     + " -> period " + period + ", extras " + extras);
         }
         synchronized (mAuthorities) {
             try {
                 AuthorityInfo authority =
-                        getOrCreateAuthorityLocked(account, providerName, -1, false);
+                        getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
                 if (add) {
                     // add this periodic sync if one with the same extras doesn't already
                     // exist in the periodicSyncs array
@@ -652,61 +702,67 @@
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
     }
 
-    public void addPeriodicSync(Account account, String providerName, Bundle extras,
+    public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras,
             long pollFrequency) {
-        updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */);
+        updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency,
+                true /* add */);
     }
 
-    public void removePeriodicSync(Account account, String providerName, Bundle extras) {
-        updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */,
+    public void removePeriodicSync(Account account, int userId, String providerName,
+            Bundle extras) {
+        updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */,
                 false /* remove */);
     }
 
-    public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+    public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
         ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
         synchronized (mAuthorities) {
-            AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs");
+            AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
+                    "getPeriodicSyncs");
             if (authority != null) {
                 for (Pair<Bundle, Long> item : authority.periodicSyncs) {
-                    syncs.add(new PeriodicSync(account, providerName, item.first, item.second));
+                    syncs.add(new PeriodicSync(account, providerName, item.first,
+                            item.second));
                 }
             }
         }
         return syncs;
     }
 
-    public void setMasterSyncAutomatically(boolean flag) {
+    public void setMasterSyncAutomatically(boolean flag, int userId) {
         synchronized (mAuthorities) {
-            if (mMasterSyncAutomatically == flag) {
+            Boolean auto = mMasterSyncAutomatically.get(userId);
+            if (auto != null && (boolean) auto == flag) {
                 return;
             }
-            mMasterSyncAutomatically = flag;
+            mMasterSyncAutomatically.put(userId, flag);
             writeAccountInfoLocked();
         }
         if (flag) {
-            ContentResolver.requestSync(null, null, new Bundle());
+            requestSync(null, userId, null, new Bundle());
         }
         reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
         mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
     }
 
-    public boolean getMasterSyncAutomatically() {
+    public boolean getMasterSyncAutomatically(int userId) {
         synchronized (mAuthorities) {
-            return mMasterSyncAutomatically;
+            Boolean auto = mMasterSyncAutomatically.get(userId);
+            return auto == null ? true : auto;
         }
     }
 
-    public AuthorityInfo getOrCreateAuthority(Account account, String authority) {
+    public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) {
         synchronized (mAuthorities) {
-            return getOrCreateAuthorityLocked(account, authority,
+            return getOrCreateAuthorityLocked(account, userId, authority,
                     -1 /* assign a new identifier if creating a new authority */,
                     true /* write to storage if this results in a change */);
         }
     }
 
-    public void removeAuthority(Account account, String authority) {
+    public void removeAuthority(Account account, int userId, String authority) {
         synchronized (mAuthorities) {
-            removeAuthorityLocked(account, authority, true /* doWrite */);
+            removeAuthorityLocked(account, userId, authority, true /* doWrite */);
         }
     }
 
@@ -720,12 +776,13 @@
      * Returns true if there is currently a sync operation for the given
      * account or authority actively being processed.
      */
-    public boolean isSyncActive(Account account, String authority) {
+    public boolean isSyncActive(Account account, int userId, String authority) {
         synchronized (mAuthorities) {
-            for (SyncInfo syncInfo : mCurrentSyncs) {
+            for (SyncInfo syncInfo : getCurrentSyncs(userId)) {
                 AuthorityInfo ainfo = getAuthority(syncInfo.authorityId);
                 if (ainfo != null && ainfo.account.equals(account)
-                        && ainfo.authority.equals(authority)) {
+                        && ainfo.authority.equals(authority)
+                        && ainfo.userId == userId) {
                     return true;
                 }
             }
@@ -738,12 +795,13 @@
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "insertIntoPending: account=" + op.account
-                    + " auth=" + op.authority
-                    + " src=" + op.syncSource
-                    + " extras=" + op.extras);
+                        + " user=" + op.userId
+                        + " auth=" + op.authority
+                        + " src=" + op.syncSource
+                        + " extras=" + op.extras);
             }
 
-            AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
+            AuthorityInfo authority = getOrCreateAuthorityLocked(op.account, op.userId,
                     op.authority,
                     -1 /* desired identifier */,
                     true /* write accounts to storage */);
@@ -769,6 +827,7 @@
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "deleteFromPending: account=" + op.account
+                    + " user=" + op.userId
                     + " auth=" + op.authority
                     + " src=" + op.syncSource
                     + " extras=" + op.extras);
@@ -782,7 +841,7 @@
                     mNumPendingFinished++;
                 }
 
-                AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
+                AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
                         "deleteFromPending");
                 if (authority != null) {
                     if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "removing - " + authority);
@@ -791,7 +850,8 @@
                     for (int i=0; i<N; i++) {
                         PendingOperation cur = mPendingOperations.get(i);
                         if (cur.account.equals(op.account)
-                                && cur.authority.equals(op.authority)) {
+                                && cur.authority.equals(op.authority)
+                                && cur.userId == op.userId) {
                             morePending = true;
                             break;
                         }
@@ -812,24 +872,6 @@
         return res;
     }
 
-    public int clearPending() {
-        int num;
-        synchronized (mAuthorities) {
-            if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "clearPending");
-            }
-            num = mPendingOperations.size();
-            mPendingOperations.clear();
-            final int N = mSyncStatus.size();
-            for (int i=0; i<N; i++) {
-                mSyncStatus.valueAt(i).pending = false;
-            }
-            writePendingOperationsLocked();
-        }
-        reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
-        return num;
-    }
-
     /**
      * Return a copy of the current array of pending operations.  The
      * PendingOperation objects are the real objects stored inside, so that
@@ -854,17 +896,18 @@
      * Called when the set of account has changed, given the new array of
      * active accounts.
      */
-    public void doDatabaseCleanup(Account[] accounts) {
+    public void doDatabaseCleanup(Account[] accounts, int userId) {
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.w(TAG, "Updating for new accounts...");
             SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
             Iterator<AccountInfo> accIt = mAccounts.values().iterator();
             while (accIt.hasNext()) {
                 AccountInfo acc = accIt.next();
-                if (!ArrayUtils.contains(accounts, acc.account)) {
+                if (!ArrayUtils.contains(accounts, acc.accountAndUser.account)
+                        && acc.accountAndUser.userId == userId) {
                     // This account no longer exists...
                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                        Log.w(TAG, "Account removed: " + acc.account);
+                        Log.w(TAG, "Account removed: " + acc.accountAndUser);
                     }
                     for (AuthorityInfo auth : acc.authorities.values()) {
                         removing.put(auth.ident, auth);
@@ -919,13 +962,14 @@
             }
             AuthorityInfo authority = getOrCreateAuthorityLocked(
                     activeSyncContext.mSyncOperation.account,
+                    activeSyncContext.mSyncOperation.userId,
                     activeSyncContext.mSyncOperation.authority,
                     -1 /* assign a new identifier if creating a new authority */,
                     true /* write to storage if this results in a change */);
             syncInfo = new SyncInfo(authority.ident,
                     authority.account, authority.authority,
                     activeSyncContext.mStartTime);
-            mCurrentSyncs.add(syncInfo);
+            getCurrentSyncs(authority.userId).add(syncInfo);
         }
 
         reportActiveChange();
@@ -935,13 +979,14 @@
     /**
      * Called to indicate that a previously active sync is no longer active.
      */
-    public void removeActiveSync(SyncInfo syncInfo) {
+    public void removeActiveSync(SyncInfo syncInfo, int userId) {
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "removeActiveSync: account="
-                        + syncInfo.account + " auth=" + syncInfo.authority);
+                Log.v(TAG, "removeActiveSync: account=" + syncInfo.account
+                        + " user=" + userId
+                        + " auth=" + syncInfo.authority);
             }
-            mCurrentSyncs.remove(syncInfo);
+            getCurrentSyncs(userId).remove(syncInfo);
         }
 
         reportActiveChange();
@@ -957,15 +1002,15 @@
     /**
      * Note that sync has started for the given account and authority.
      */
-    public long insertStartSyncEvent(Account accountName, String authorityName,
+    public long insertStartSyncEvent(Account accountName, int userId, String authorityName,
             long now, int source) {
         long id;
         synchronized (mAuthorities) {
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                Log.v(TAG, "insertStartSyncEvent: account=" + accountName
+                Log.v(TAG, "insertStartSyncEvent: account=" + accountName + "user=" + userId
                     + " auth=" + authorityName + " source=" + source);
             }
-            AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
+            AuthorityInfo authority = getAuthorityLocked(accountName, userId, authorityName,
                     "insertStartSyncEvent");
             if (authority == null) {
                 return -1;
@@ -1119,9 +1164,14 @@
      * Return a list of the currently active syncs. Note that the returned items are the
      * real, live active sync objects, so be careful what you do with it.
      */
-    public List<SyncInfo> getCurrentSyncs() {
+    public List<SyncInfo> getCurrentSyncs(int userId) {
         synchronized (mAuthorities) {
-            return new ArrayList<SyncInfo>(mCurrentSyncs);
+            ArrayList<SyncInfo> syncs = mCurrentSyncs.get(userId);
+            if (syncs == null) {
+                syncs = new ArrayList<SyncInfo>();
+                mCurrentSyncs.put(userId, syncs);
+            }
+            return new ArrayList<SyncInfo>(syncs);
         }
     }
 
@@ -1164,7 +1214,8 @@
      * @param authority the authority whose row should be selected
      * @return the SyncStatusInfo for the authority
      */
-    public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) {
+    public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId,
+            String authority) {
         if (account == null || authority == null) {
           throw new IllegalArgumentException();
         }
@@ -1174,8 +1225,9 @@
                 SyncStatusInfo cur = mSyncStatus.valueAt(i);
                 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
 
-                if (ainfo != null && ainfo.authority.equals(authority) &&
-                    account.equals(ainfo.account)) {
+                if (ainfo != null && ainfo.authority.equals(authority)
+                        && ainfo.userId == userId
+                        && account.equals(ainfo.account)) {
                   return cur;
                 }
             }
@@ -1186,7 +1238,7 @@
     /**
      * Return true if the pending status is true of any matching authorities.
      */
-    public boolean isSyncPending(Account account, String authority) {
+    public boolean isSyncPending(Account account, int userId, String authority) {
         synchronized (mAuthorities) {
             final int N = mSyncStatus.size();
             for (int i=0; i<N; i++) {
@@ -1195,6 +1247,9 @@
                 if (ainfo == null) {
                     continue;
                 }
+                if (userId != ainfo.userId) {
+                    continue;
+                }
                 if (account != null && !ainfo.account.equals(account)) {
                     continue;
                 }
@@ -1235,34 +1290,6 @@
         }
     }
 
-    /**
-     * If sync is failing for any of the provider/accounts then determine the time at which it
-     * started failing and return the earliest time over all the provider/accounts. If none are
-     * failing then return 0.
-     */
-    public long getInitialSyncFailureTime() {
-        synchronized (mAuthorities) {
-            if (!mMasterSyncAutomatically) {
-                return 0;
-            }
-
-            long oldest = 0;
-            int i = mSyncStatus.size();
-            while (i > 0) {
-                i--;
-                SyncStatusInfo stats = mSyncStatus.valueAt(i);
-                AuthorityInfo authority = mAuthorities.get(stats.authorityId);
-                if (authority != null && authority.enabled) {
-                    if (oldest == 0 || stats.initialFailureTime < oldest) {
-                        oldest = stats.initialFailureTime;
-                    }
-                }
-            }
-
-            return oldest;
-        }
-    }
-
     private int getCurrentDayLocked() {
         mCal.setTimeInMillis(System.currentTimeMillis());
         final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
@@ -1283,18 +1310,19 @@
      * @param tag If non-null, this will be used in a log message if the
      * requested authority does not exist.
      */
-    private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName,
+    private AuthorityInfo getAuthorityLocked(Account accountName, int userId, String authorityName,
             String tag) {
-        AccountInfo account = mAccounts.get(accountName);
-        if (account == null) {
+        AccountAndUser au = new AccountAndUser(accountName, userId);
+        AccountInfo accountInfo = mAccounts.get(au);
+        if (accountInfo == null) {
             if (tag != null) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG, tag + ": unknown account " + accountName);
+                    Log.v(TAG, tag + ": unknown account " + au);
                 }
             }
             return null;
         }
-        AuthorityInfo authority = account.authorities.get(authorityName);
+        AuthorityInfo authority = accountInfo.authorities.get(authorityName);
         if (authority == null) {
             if (tag != null) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
@@ -1307,12 +1335,13 @@
         return authority;
     }
 
-    private AuthorityInfo getOrCreateAuthorityLocked(Account accountName,
+    private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
             String authorityName, int ident, boolean doWrite) {
-        AccountInfo account = mAccounts.get(accountName);
+        AccountAndUser au = new AccountAndUser(accountName, userId);
+        AccountInfo account = mAccounts.get(au);
         if (account == null) {
-            account = new AccountInfo(accountName);
-            mAccounts.put(accountName, account);
+            account = new AccountInfo(au);
+            mAccounts.put(au, account);
         }
         AuthorityInfo authority = account.authorities.get(authorityName);
         if (authority == null) {
@@ -1323,9 +1352,10 @@
             }
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Log.v(TAG, "created a new AuthorityInfo for " + accountName
-                    + ", provider " + authorityName);
+                        + ", user " + userId
+                        + ", provider " + authorityName);
             }
-            authority = new AuthorityInfo(accountName, authorityName, ident);
+            authority = new AuthorityInfo(accountName, userId, authorityName, ident);
             account.authorities.put(authorityName, authority);
             mAuthorities.put(ident, authority);
             if (doWrite) {
@@ -1336,8 +1366,9 @@
         return authority;
     }
 
-    private void removeAuthorityLocked(Account account, String authorityName, boolean doWrite) {
-        AccountInfo accountInfo = mAccounts.get(account);
+    private void removeAuthorityLocked(Account account, int userId, String authorityName,
+            boolean doWrite) {
+        AccountInfo accountInfo = mAccounts.get(new AccountAndUser(account, userId));
         if (accountInfo != null) {
             final AuthorityInfo authorityInfo = accountInfo.authorities.remove(authorityName);
             if (authorityInfo != null) {
@@ -1419,8 +1450,7 @@
             }
             String tagName = parser.getName();
             if ("accounts".equals(tagName)) {
-                String listen = parser.getAttributeValue(
-                        null, "listen-for-tickles");
+                String listen = parser.getAttributeValue(null, XML_ATTR_LISTEN_FOR_TICKLES);
                 String versionString = parser.getAttributeValue(null, "version");
                 int version;
                 try {
@@ -1428,14 +1458,14 @@
                 } catch (NumberFormatException e) {
                     version = 0;
                 }
-                String nextIdString = parser.getAttributeValue(null, "nextAuthorityId");
+                String nextIdString = parser.getAttributeValue(null, XML_ATTR_NEXT_AUTHORITY_ID);
                 try {
                     int id = (nextIdString == null) ? 0 : Integer.parseInt(nextIdString);
                     mNextAuthorityId = Math.max(mNextAuthorityId, id);
                 } catch (NumberFormatException e) {
                     // don't care
                 }
-                mMasterSyncAutomatically = listen == null || Boolean.parseBoolean(listen);
+                mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
                 eventType = parser.next();
                 AuthorityInfo authority = null;
                 Pair<Bundle, Long> periodicSync = null;
@@ -1449,6 +1479,8 @@
                                 if (authority.ident > highestAuthorityId) {
                                     highestAuthorityId = authority.ident;
                                 }
+                            } else if (XML_TAG_LISTEN_FOR_TICKLES.equals(tagName)) {
+                                parseListenForTickles(parser);
                             }
                         } else if (parser.getDepth() == 3) {
                             if ("periodicSync".equals(tagName) && authority != null) {
@@ -1511,25 +1543,41 @@
             }
 
             // if we already have a record of this new authority then don't copy over the settings
-            if (getAuthorityLocked(authority.account, newAuthorityName, "cleanup") != null) {
+            if (getAuthorityLocked(authority.account, authority.userId, newAuthorityName, "cleanup")
+                    != null) {
                 continue;
             }
 
             AuthorityInfo newAuthority = getOrCreateAuthorityLocked(authority.account,
-                    newAuthorityName, -1 /* ident */, false /* doWrite */);
+                    authority.userId, newAuthorityName, -1 /* ident */, false /* doWrite */);
             newAuthority.enabled = true;
             writeNeeded = true;
         }
 
         for (AuthorityInfo authorityInfo : authoritiesToRemove) {
-            removeAuthorityLocked(authorityInfo.account, authorityInfo.authority,
-                    false /* doWrite */);
+            removeAuthorityLocked(authorityInfo.account, authorityInfo.userId,
+                    authorityInfo.authority, false /* doWrite */);
             writeNeeded = true;
         }
 
         return writeNeeded;
     }
 
+    private void parseListenForTickles(XmlPullParser parser) {
+        String user = parser.getAttributeValue(null, XML_ATTR_USER);
+        int userId = 0;
+        try {
+            userId = Integer.parseInt(user);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "error parsing the user for listen-for-tickles", e);
+        } catch (NullPointerException e) {
+            Log.e(TAG, "the user in listen-for-tickles is null", e);
+        }
+        String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
+        boolean listen = enabled == null || Boolean.parseBoolean(enabled);
+        mMasterSyncAutomatically.put(userId, listen);
+    }
+
     private AuthorityInfo parseAuthority(XmlPullParser parser, int version) {
         AuthorityInfo authority = null;
         int id = -1;
@@ -1543,10 +1591,12 @@
         }
         if (id >= 0) {
             String authorityName = parser.getAttributeValue(null, "authority");
-            String enabled = parser.getAttributeValue(null, "enabled");
+            String enabled = parser.getAttributeValue(null, XML_ATTR_ENABLED);
             String syncable = parser.getAttributeValue(null, "syncable");
             String accountName = parser.getAttributeValue(null, "account");
             String accountType = parser.getAttributeValue(null, "type");
+            String user = parser.getAttributeValue(null, XML_ATTR_USER);
+            int userId = user == null ? 0 : Integer.parseInt(user);
             if (accountType == null) {
                 accountType = "com.google";
                 syncable = "unknown";
@@ -1554,12 +1604,13 @@
             authority = mAuthorities.get(id);
             if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
                     + accountName + " auth=" + authorityName
+                    + " user=" + userId
                     + " enabled=" + enabled
                     + " syncable=" + syncable);
             if (authority == null) {
                 if (DEBUG_FILE) Log.v(TAG, "Creating entry");
                 authority = getOrCreateAuthorityLocked(
-                        new Account(accountName, accountType), authorityName, id, false);
+                        new Account(accountName, accountType), userId, authorityName, id, false);
                 // If the version is 0 then we are upgrading from a file format that did not
                 // know about periodic syncs. In that case don't clear the list since we
                 // want the default, which is a daily periodioc sync.
@@ -1653,9 +1704,17 @@
 
             out.startTag(null, "accounts");
             out.attribute(null, "version", Integer.toString(ACCOUNTS_VERSION));
-            out.attribute(null, "nextAuthorityId", Integer.toString(mNextAuthorityId));
-            if (!mMasterSyncAutomatically) {
-                out.attribute(null, "listen-for-tickles", "false");
+            out.attribute(null, XML_ATTR_NEXT_AUTHORITY_ID, Integer.toString(mNextAuthorityId));
+
+            // Write the Sync Automatically flags for each user
+            final int M = mMasterSyncAutomatically.size();
+            for (int m = 0; m < M; m++) {
+                int userId = mMasterSyncAutomatically.keyAt(m);
+                Boolean listen = mMasterSyncAutomatically.valueAt(m);
+                out.startTag(null, XML_TAG_LISTEN_FOR_TICKLES);
+                out.attribute(null, XML_ATTR_USER, Integer.toString(userId));
+                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(listen));
+                out.endTag(null, XML_TAG_LISTEN_FOR_TICKLES);
             }
 
             final int N = mAuthorities.size();
@@ -1664,9 +1723,10 @@
                 out.startTag(null, "authority");
                 out.attribute(null, "id", Integer.toString(authority.ident));
                 out.attribute(null, "account", authority.account.name);
+                out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
                 out.attribute(null, "type", authority.account.type);
                 out.attribute(null, "authority", authority.authority);
-                out.attribute(null, "enabled", Boolean.toString(authority.enabled));
+                out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
                 if (authority.syncable < 0) {
                     out.attribute(null, "syncable", "unknown");
                 } else {
@@ -1788,7 +1848,7 @@
                 }
                 String authorityName = c.getString(c.getColumnIndex("authority"));
                 AuthorityInfo authority = this.getOrCreateAuthorityLocked(
-                        new Account(accountName, accountType),
+                        new Account(accountName, accountType), 0 /* legacy is single-user */,
                         authorityName, -1, false);
                 if (authority != null) {
                     int i = mSyncStatus.size();
@@ -1833,7 +1893,7 @@
                 String value = c.getString(c.getColumnIndex("value"));
                 if (name == null) continue;
                 if (name.equals("listen_for_tickles")) {
-                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value));
+                    setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value), 0);
                 } else if (name.startsWith("sync_provider_")) {
                     String provider = name.substring("sync_provider_".length(),
                             name.length());
@@ -1964,7 +2024,7 @@
                         extras = new Bundle();
                     }
                     PendingOperation op = new PendingOperation(
-                            authority.account, syncSource,
+                            authority.account, authority.userId, syncSource,
                             authority.authority, extras, expedited);
                     op.authorityId = authorityId;
                     op.flatExtras = flatExtras;
@@ -2084,6 +2144,19 @@
         return bundle;
     }
 
+    private void requestSync(Account account, int userId, String authority, Bundle extras) {
+        // If this is happening in the system process, then call the syncrequest listener
+        // to make a request back to the SyncManager directly.
+        // If this is probably a test instance, then call back through the ContentResolver
+        // which will know which userId to apply based on the Binder id.
+        if (android.os.Process.myUid() == android.os.Process.SYSTEM_UID
+                && mSyncRequestListener != null) {
+            mSyncRequestListener.onSyncRequest(account, userId, authority, extras);
+        } else {
+            ContentResolver.requestSync(account, authority, extras);
+        }
+    }
+
     public static final int STATISTICS_FILE_END = 0;
     public static final int STATISTICS_FILE_ITEM_OLD = 100;
     public static final int STATISTICS_FILE_ITEM = 101;
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 215e836..6c1445d 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -28,6 +28,8 @@
 import java.util.zip.CRC32;
 import java.util.zip.CheckedInputStream;
 
+import libcore.io.Os;
+import libcore.io.StructStat;
 
 /**
  * Tools for managing files.  Not for public consumption.
@@ -52,8 +54,10 @@
     
     /**
      * File status information. This class maps directly to the POSIX stat structure.
+     * @deprecated use {@link StructStat} instead.
      * @hide
      */
+    @Deprecated
     public static final class FileStatus {
         public int dev;
         public int ino;
@@ -77,7 +81,9 @@
      * exists. 
      * @return true if the file exists and false if it does not exist. If you do not have 
      * permission to stat the file, then this method will return false.
+     * @deprecated use {@link Os#stat(String)} instead.
      */
+    @Deprecated
     public static boolean getFileStatus(String path, FileStatus status) {
         StrictMode.noteDiskRead();
         return getFileStatusNative(path, status);
@@ -90,6 +96,10 @@
 
     public static native int setPermissions(String file, int mode, int uid, int gid);
 
+    /**
+     * @deprecated use {@link Os#stat(String)} instead.
+     */
+    @Deprecated
     public static native int getPermissions(String file, int[] outPermissions);
 
     public static native int setUMask(int mask);
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 15e3af4..1507387 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -180,9 +180,14 @@
     private static final String TAG = "Parcel";
 
     @SuppressWarnings({"UnusedDeclaration"})
-    private int mObject; // used by native code
-    @SuppressWarnings({"UnusedDeclaration"})
-    private int mOwnObject; // used by native code
+    private int mNativePtr; // used by native code
+
+    /**
+     * Flag indicating if {@link #mNativePtr} was allocated by this object,
+     * indicating that we're responsible for its lifecycle.
+     */
+    private boolean mOwnsNativeParcelObject;
+
     private RuntimeException mStack;
 
     private static final int POOL_SIZE = 6;
@@ -224,6 +229,48 @@
     private static final int EX_ILLEGAL_STATE = -5;
     private static final int EX_HAS_REPLY_HEADER = -128;  // special; see below
 
+    private static native int nativeDataSize(int nativePtr);
+    private static native int nativeDataAvail(int nativePtr);
+    private static native int nativeDataPosition(int nativePtr);
+    private static native int nativeDataCapacity(int nativePtr);
+    private static native void nativeSetDataSize(int nativePtr, int size);
+    private static native void nativeSetDataPosition(int nativePtr, int pos);
+    private static native void nativeSetDataCapacity(int nativePtr, int size);
+
+    private static native boolean nativePushAllowFds(int nativePtr, boolean allowFds);
+    private static native void nativeRestoreAllowFds(int nativePtr, boolean lastValue);
+
+    private static native void nativeWriteByteArray(int nativePtr, byte[] b, int offset, int len);
+    private static native void nativeWriteInt(int nativePtr, int val);
+    private static native void nativeWriteLong(int nativePtr, long val);
+    private static native void nativeWriteFloat(int nativePtr, float val);
+    private static native void nativeWriteDouble(int nativePtr, double val);
+    private static native void nativeWriteString(int nativePtr, String val);
+    private static native void nativeWriteStrongBinder(int nativePtr, IBinder val);
+    private static native void nativeWriteFileDescriptor(int nativePtr, FileDescriptor val);
+
+    private static native byte[] nativeCreateByteArray(int nativePtr);
+    private static native int nativeReadInt(int nativePtr);
+    private static native long nativeReadLong(int nativePtr);
+    private static native float nativeReadFloat(int nativePtr);
+    private static native double nativeReadDouble(int nativePtr);
+    private static native String nativeReadString(int nativePtr);
+    private static native IBinder nativeReadStrongBinder(int nativePtr);
+    private static native FileDescriptor nativeReadFileDescriptor(int nativePtr);
+
+    private static native int nativeCreate();
+    private static native void nativeFreeBuffer(int nativePtr);
+    private static native void nativeDestroy(int nativePtr);
+
+    private static native byte[] nativeMarshall(int nativePtr);
+    private static native void nativeUnmarshall(
+            int nativePtr, byte[] data, int offest, int length);
+    private static native void nativeAppendFrom(
+            int thisNativePtr, int otherNativePtr, int offset, int length);
+    private static native boolean nativeHasFileDescriptors(int nativePtr);
+    private static native void nativeWriteInterfaceToken(int nativePtr, String interfaceName);
+    private static native void nativeEnforceInterface(int nativePtr, String interfaceName);
+
     public final static Parcelable.Creator<String> STRING_CREATOR
              = new Parcelable.Creator<String>() {
         public String createFromParcel(Parcel source) {
@@ -262,7 +309,15 @@
     public final void recycle() {
         if (DEBUG_RECYCLE) mStack = null;
         freeBuffer();
-        final Parcel[] pool = mOwnObject != 0 ? sOwnedPool : sHolderPool;
+
+        final Parcel[] pool;
+        if (mOwnsNativeParcelObject) {
+            pool = sOwnedPool;
+        } else {
+            mNativePtr = 0;
+            pool = sHolderPool;
+        }
+
         synchronized (pool) {
             for (int i=0; i<POOL_SIZE; i++) {
                 if (pool[i] == null) {
@@ -276,19 +331,25 @@
     /**
      * Returns the total amount of data contained in the parcel.
      */
-    public final native int dataSize();
+    public final int dataSize() {
+        return nativeDataSize(mNativePtr);
+    }
 
     /**
      * Returns the amount of data remaining to be read from the
      * parcel.  That is, {@link #dataSize}-{@link #dataPosition}.
      */
-    public final native int dataAvail();
+    public final int dataAvail() {
+        return nativeDataAvail(mNativePtr);
+    }
 
     /**
      * Returns the current position in the parcel data.  Never
      * more than {@link #dataSize}.
      */
-    public final native int dataPosition();
+    public final int dataPosition() {
+        return nativeDataPosition(mNativePtr);
+    }
 
     /**
      * Returns the total amount of space in the parcel.  This is always
@@ -296,7 +357,9 @@
      * amount of room left until the parcel needs to re-allocate its
      * data buffer.
      */
-    public final native int dataCapacity();
+    public final int dataCapacity() {
+        return nativeDataCapacity(mNativePtr);
+    }
 
     /**
      * Change the amount of data in the parcel.  Can be either smaller or
@@ -305,14 +368,18 @@
      *
      * @param size The new number of bytes in the Parcel.
      */
-    public final native void setDataSize(int size);
+    public final void setDataSize(int size) {
+        nativeSetDataSize(mNativePtr, size);
+    }
 
     /**
      * Move the current read/write position in the parcel.
      * @param pos New offset in the parcel; must be between 0 and
      * {@link #dataSize}.
      */
-    public final native void setDataPosition(int pos);
+    public final void setDataPosition(int pos) {
+        nativeSetDataPosition(mNativePtr, pos);
+    }
 
     /**
      * Change the capacity (current available space) of the parcel.
@@ -321,13 +388,19 @@
      * less than {@link #dataSize} -- that is, you can not drop existing data
      * with this method.
      */
-    public final native void setDataCapacity(int size);
+    public final void setDataCapacity(int size) {
+        nativeSetDataCapacity(mNativePtr, size);
+    }
 
     /** @hide */
-    public final native boolean pushAllowFds(boolean allowFds);
+    public final boolean pushAllowFds(boolean allowFds) {
+        return nativePushAllowFds(mNativePtr, allowFds);
+    }
 
     /** @hide */
-    public final native void restoreAllowFds(boolean lastValue);
+    public final void restoreAllowFds(boolean lastValue) {
+        nativeRestoreAllowFds(mNativePtr, lastValue);
+    }
 
     /**
      * Returns the raw bytes of the parcel.
@@ -340,27 +413,40 @@
      * such does not attempt to maintain compatibility with data created
      * in different versions of the platform.
      */
-    public final native byte[] marshall();
+    public final byte[] marshall() {
+        return nativeMarshall(mNativePtr);
+    }
 
     /**
      * Set the bytes in data to be the raw bytes of this Parcel.
      */
-    public final native void unmarshall(byte[] data, int offest, int length);
+    public final void unmarshall(byte[] data, int offest, int length) {
+        nativeUnmarshall(mNativePtr, data, offest, length);
+    }
 
-    public final native void appendFrom(Parcel parcel, int offset, int length);
+    public final void appendFrom(Parcel parcel, int offset, int length) {
+        nativeAppendFrom(mNativePtr, parcel.mNativePtr, offset, length);
+    }
 
     /**
      * Report whether the parcel contains any marshalled file descriptors.
      */
-    public final native boolean hasFileDescriptors();
+    public final boolean hasFileDescriptors() {
+        return nativeHasFileDescriptors(mNativePtr);
+    }
 
     /**
      * Store or read an IBinder interface token in the parcel at the current
      * {@link #dataPosition}.  This is used to validate that the marshalled
      * transaction is intended for the target interface.
      */
-    public final native void writeInterfaceToken(String interfaceName);
-    public final native void enforceInterface(String interfaceName);
+    public final void writeInterfaceToken(String interfaceName) {
+        nativeWriteInterfaceToken(mNativePtr, interfaceName);
+    }
+
+    public final void enforceInterface(String interfaceName) {
+        nativeEnforceInterface(mNativePtr, interfaceName);
+    }
 
     /**
      * Write a byte array into the parcel at the current {@link #dataPosition},
@@ -384,40 +470,48 @@
             return;
         }
         Arrays.checkOffsetAndCount(b.length, offset, len);
-        writeNative(b, offset, len);
+        nativeWriteByteArray(mNativePtr, b, offset, len);
     }
 
-    private native void writeNative(byte[] b, int offset, int len);
-
     /**
      * Write an integer value into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
-    public final native void writeInt(int val);
+    public final void writeInt(int val) {
+        nativeWriteInt(mNativePtr, val);
+    }
 
     /**
      * Write a long integer value into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
-    public final native void writeLong(long val);
+    public final void writeLong(long val) {
+        nativeWriteLong(mNativePtr, val);
+    }
 
     /**
      * Write a floating point value into the parcel at the current
      * dataPosition(), growing dataCapacity() if needed.
      */
-    public final native void writeFloat(float val);
+    public final void writeFloat(float val) {
+        nativeWriteFloat(mNativePtr, val);
+    }
 
     /**
      * Write a double precision floating point value into the parcel at the
      * current dataPosition(), growing dataCapacity() if needed.
      */
-    public final native void writeDouble(double val);
+    public final void writeDouble(double val) {
+        nativeWriteDouble(mNativePtr, val);
+    }
 
     /**
      * Write a string value into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
-    public final native void writeString(String val);
+    public final void writeString(String val) {
+        nativeWriteString(mNativePtr, val);
+    }
 
     /**
      * Write a CharSequence value into the parcel at the current dataPosition(),
@@ -432,7 +526,9 @@
      * Write an object into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
-    public final native void writeStrongBinder(IBinder val);
+    public final void writeStrongBinder(IBinder val) {
+        nativeWriteStrongBinder(mNativePtr, val);
+    }
 
     /**
      * Write an object into the parcel at the current dataPosition(),
@@ -452,7 +548,9 @@
      * accepts contextual flags and will close the original file descriptor
      * if {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE} is set.</p>
      */
-    public final native void writeFileDescriptor(FileDescriptor val);
+    public final void writeFileDescriptor(FileDescriptor val) {
+        nativeWriteFileDescriptor(mNativePtr, val);
+    }
 
     /**
      * Write an byte value into the parcel at the current dataPosition(),
@@ -1341,29 +1439,39 @@
     /**
      * Read an integer value from the parcel at the current dataPosition().
      */
-    public final native int readInt();
+    public final int readInt() {
+        return nativeReadInt(mNativePtr);
+    }
 
     /**
      * Read a long integer value from the parcel at the current dataPosition().
      */
-    public final native long readLong();
+    public final long readLong() {
+        return nativeReadLong(mNativePtr);
+    }
 
     /**
      * Read a floating point value from the parcel at the current
      * dataPosition().
      */
-    public final native float readFloat();
+    public final float readFloat() {
+        return nativeReadFloat(mNativePtr);
+    }
 
     /**
      * Read a double precision floating point value from the parcel at the
      * current dataPosition().
      */
-    public final native double readDouble();
+    public final double readDouble() {
+        return nativeReadDouble(mNativePtr);
+    }
 
     /**
      * Read a string value from the parcel at the current dataPosition().
      */
-    public final native String readString();
+    public final String readString() {
+        return nativeReadString(mNativePtr);
+    }
 
     /**
      * Read a CharSequence value from the parcel at the current dataPosition().
@@ -1376,17 +1484,18 @@
     /**
      * Read an object from the parcel at the current dataPosition().
      */
-    public final native IBinder readStrongBinder();
+    public final IBinder readStrongBinder() {
+        return nativeReadStrongBinder(mNativePtr);
+    }
 
     /**
      * Read a FileDescriptor from the parcel at the current dataPosition().
      */
     public final ParcelFileDescriptor readFileDescriptor() {
-        FileDescriptor fd = internalReadFileDescriptor();
+        FileDescriptor fd = nativeReadFileDescriptor(mNativePtr);
         return fd != null ? new ParcelFileDescriptor(fd) : null;
     }
 
-    private native FileDescriptor internalReadFileDescriptor();
     /*package*/ static native FileDescriptor openFileDescriptor(String file,
             int mode) throws FileNotFoundException;
     /*package*/ static native FileDescriptor dupFileDescriptor(FileDescriptor orig)
@@ -1471,7 +1580,9 @@
     /**
      * Read and return a byte[] object from the parcel.
      */
-    public final native byte[] createByteArray();
+    public final byte[] createByteArray() {
+        return nativeCreateByteArray(mNativePtr);
+    }
 
     /**
      * Read a byte[] object from the parcel and copy it into the
@@ -2065,12 +2176,37 @@
         return new Parcel(obj);
     }
 
-    private Parcel(int obj) {
+    private Parcel(int nativePtr) {
         if (DEBUG_RECYCLE) {
             mStack = new RuntimeException();
         }
         //Log.i(TAG, "Initializing obj=0x" + Integer.toHexString(obj), mStack);
-        init(obj);
+        init(nativePtr);
+    }
+
+    private void init(int nativePtr) {
+        if (nativePtr != 0) {
+            mNativePtr = nativePtr;
+            mOwnsNativeParcelObject = false;
+        } else {
+            mNativePtr = nativeCreate();
+            mOwnsNativeParcelObject = true;
+        }
+    }
+
+    private void freeBuffer() {
+        if (mOwnsNativeParcelObject) {
+            nativeFreeBuffer(mNativePtr);
+        }
+    }
+
+    private void destroy() {
+        if (mNativePtr != 0) {
+            if (mOwnsNativeParcelObject) {
+                nativeDestroy(mNativePtr);
+            }
+            mNativePtr = 0;
+        }
     }
 
     @Override
@@ -2083,10 +2219,6 @@
         destroy();
     }
 
-    private native void freeBuffer();
-    private native void init(int obj);
-    private native void destroy();
-
     /* package */ void readMapInternal(Map outVal, int N,
         ClassLoader loader) {
         while (N > 0) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index bc6594b..fbb3273 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3129,6 +3129,14 @@
                 "wifi_watchdog_arp_interval_ms";
 
         /**
+         * ms delay interval between rssi polling when the signal is known to be weak
+         * @hide
+         */
+        public static final String WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS =
+                "wifi_watchdog_rssi_fetch_interval_ms";
+
+
+        /**
          * ms delay before rechecking a connect SSID for walled garden with a http download.
          * @hide
          */
diff --git a/core/java/android/view/Choreographer.java b/core/java/android/view/Choreographer.java
index f4d7af9..10edc06 100644
--- a/core/java/android/view/Choreographer.java
+++ b/core/java/android/view/Choreographer.java
@@ -81,8 +81,8 @@
     private static final int MSG_DO_ANIMATION = 0;
     private static final int MSG_DO_DRAW = 1;
     private static final int MSG_DO_SCHEDULE_VSYNC = 2;
-    private static final int MSG_POST_DELAYED_ANIMATION = 3;
-    private static final int MSG_POST_DELAYED_DRAW = 4;
+    private static final int MSG_DO_SCHEDULE_ANIMATION = 3;
+    private static final int MSG_DO_SCHEDULE_DRAW = 4;
 
     private final Object mLock = new Object();
 
@@ -152,134 +152,158 @@
     }
 
     /**
+     * Subtracts typical frame delay time from a delay interval in milliseconds.
+     *
+     * This method can be used to compensate for animation delay times that have baked
+     * in assumptions about the frame delay.  For example, it's quite common for code to
+     * assume a 60Hz frame time and bake in a 16ms delay.  When we call
+     * {@link #postAnimationCallbackDelayed} we want to know how long to wait before
+     * posting the animation callback but let the animation timer take care of the remaining
+     * frame delay time.
+     *
+     * This method is somewhat conservative about how much of the frame delay it
+     * subtracts.  It uses the same value returned by {@link #getFrameDelay} which by
+     * default is 10ms even though many parts of the system assume 16ms.  Consequently,
+     * we might still wait 6ms before posting an animation callback that we want to run
+     * on the next frame, but this is much better than waiting a whole 16ms and likely
+     * missing the deadline.
+     *
+     * @param delayMillis The original delay time including an assumed frame delay.
+     * @return The adjusted delay time with the assumed frame delay subtracted out.
+     */
+    public static long subtractFrameDelay(long delayMillis) {
+        final long frameDelay = sFrameDelay;
+        return delayMillis <= frameDelay ? 0 : delayMillis - frameDelay;
+    }
+
+    /**
      * Posts a callback to run on the next animation cycle.
      * The callback only runs once and then is automatically removed.
      *
-     * @param runnable The callback to run during the next animation cycle.
+     * @param action The callback action to run during the next animation cycle.
+     * @param token The callback token, or null if none.
      *
      * @see #removeAnimationCallback
      */
-    public void postAnimationCallback(Runnable runnable) {
-        if (runnable == null) {
-            throw new IllegalArgumentException("runnable must not be null");
-        }
-        postAnimationCallbackUnchecked(runnable);
-    }
-
-    private void postAnimationCallbackUnchecked(Runnable runnable) {
-        synchronized (mLock) {
-            mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, runnable);
-            scheduleAnimationLocked();
-        }
+    public void postAnimationCallback(Runnable action, Object token) {
+        postAnimationCallbackDelayed(action, token, 0);
     }
 
     /**
      * Posts a callback to run on the next animation cycle following the specified delay.
      * The callback only runs once and then is automatically removed.
      *
-     * @param runnable The callback to run during the next animation cycle following
+     * @param action The callback action to run during the next animation cycle after
      * the specified delay.
+     * @param token The callback token, or null if none.
      * @param delayMillis The delay time in milliseconds.
      *
      * @see #removeAnimationCallback
      */
-    public void postAnimationCallbackDelayed(Runnable runnable, long delayMillis) {
-        if (runnable == null) {
-            throw new IllegalArgumentException("runnable must not be null");
+    public void postAnimationCallbackDelayed(Runnable action, Object token, long delayMillis) {
+        if (action == null) {
+            throw new IllegalArgumentException("action must not be null");
         }
-        if (delayMillis <= 0) {
-            postAnimationCallbackUnchecked(runnable);
-        } else {
-            Message msg = mHandler.obtainMessage(MSG_POST_DELAYED_ANIMATION, runnable);
-            mHandler.sendMessageDelayed(msg, delayMillis);
+
+        synchronized (mLock) {
+            final long now = SystemClock.uptimeMillis();
+            final long dueTime = now + delayMillis;
+            mAnimationCallbacks = addCallbackLocked(mAnimationCallbacks, dueTime, action, token);
+
+            if (dueTime <= now) {
+                scheduleAnimationLocked(now);
+            } else {
+                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_ANIMATION, action);
+                mHandler.sendMessageAtTime(msg, dueTime);
+            }
         }
     }
 
     /**
-     * Removes animation callbacks for the specified runnable.
-     * Does nothing if the specified animation callback has not been posted or has already
-     * been removed.
+     * Removes animation callbacks that have the specified action and token.
      *
-     * @param runnable The animation callback to remove.
+     * @param action The action property of the callbacks to remove, or null to remove
+     * callbacks with any action.
+     * @param token The token property of the callbacks to remove, or null to remove
+     * callbacks with any token.
      *
      * @see #postAnimationCallback
      * @see #postAnimationCallbackDelayed
      */
-    public void removeAnimationCallbacks(Runnable runnable) {
-        if (runnable == null) {
-            throw new IllegalArgumentException("runnable must not be null");
-        }
+    public void removeAnimationCallbacks(Runnable action, Object token) {
         synchronized (mLock) {
-            mAnimationCallbacks = removeCallbacksLocked(mAnimationCallbacks, runnable);
+            mAnimationCallbacks = removeCallbacksLocked(mAnimationCallbacks, action, token);
+            if (action != null && token == null) {
+                mHandler.removeMessages(MSG_DO_SCHEDULE_ANIMATION, action);
+            }
         }
-        mHandler.removeMessages(MSG_POST_DELAYED_ANIMATION, runnable);
     }
 
     /**
      * Posts a callback to run on the next draw cycle.
      * The callback only runs once and then is automatically removed.
      *
-     * @param runnable The callback to run during the next draw cycle.
+     * @param action The callback action to run during the next draw cycle.
+     * @param token The callback token, or null if none.
      *
      * @see #removeDrawCallback
      */
-    public void postDrawCallback(Runnable runnable) {
-        if (runnable == null) {
-            throw new IllegalArgumentException("runnable must not be null");
-        }
-        postDrawCallbackUnchecked(runnable);
-    }
-
-    private void postDrawCallbackUnchecked(Runnable runnable) {
-        synchronized (mLock) {
-            mDrawCallbacks = addCallbackLocked(mDrawCallbacks, runnable);
-            scheduleDrawLocked();
-        }
+    public void postDrawCallback(Runnable action, Object token) {
+        postDrawCallbackDelayed(action, token, 0);
     }
 
     /**
      * Posts a callback to run on the next draw cycle following the specified delay.
      * The callback only runs once and then is automatically removed.
      *
-     * @param runnable The callback to run during the next draw cycle following
+     * @param action The callback action to run during the next animation cycle after
      * the specified delay.
+     * @param token The callback token, or null if none.
      * @param delayMillis The delay time in milliseconds.
      *
      * @see #removeDrawCallback
      */
-    public void postDrawCallbackDelayed(Runnable runnable, long delayMillis) {
-        if (runnable == null) {
-            throw new IllegalArgumentException("runnable must not be null");
+    public void postDrawCallbackDelayed(Runnable action, Object token, long delayMillis) {
+        if (action == null) {
+            throw new IllegalArgumentException("action must not be null");
         }
-        if (delayMillis <= 0) {
-            postDrawCallbackUnchecked(runnable);
-        } else {
-            Message msg = mHandler.obtainMessage(MSG_POST_DELAYED_DRAW, runnable);
-            mHandler.sendMessageDelayed(msg, delayMillis);
+
+        synchronized (mLock) {
+            final long now = SystemClock.uptimeMillis();
+            final long dueTime = now + delayMillis;
+            mDrawCallbacks = addCallbackLocked(mDrawCallbacks, dueTime, action, token);
+            scheduleDrawLocked(now);
+
+            if (dueTime <= now) {
+                scheduleDrawLocked(now);
+            } else {
+                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_DRAW, action);
+                mHandler.sendMessageAtTime(msg, dueTime);
+            }
         }
     }
 
     /**
-     * Removes draw callbacks for the specified runnable.
-     * Does nothing if the specified draw callback has not been posted or has already
-     * been removed.
+     * Removes draw callbacks that have the specified action and token.
      *
-     * @param runnable The draw callback to remove.
+     * @param action The action property of the callbacks to remove, or null to remove
+     * callbacks with any action.
+     * @param token The token property of the callbacks to remove, or null to remove
+     * callbacks with any token.
      *
      * @see #postDrawCallback
      * @see #postDrawCallbackDelayed
      */
-    public void removeDrawCallbacks(Runnable runnable) {
-        if (runnable == null) {
-            throw new IllegalArgumentException("runnable must not be null");
-        }
+    public void removeDrawCallbacks(Runnable action, Object token) {
         synchronized (mLock) {
-            mDrawCallbacks = removeCallbacksLocked(mDrawCallbacks, runnable);
+            mDrawCallbacks = removeCallbacksLocked(mDrawCallbacks, action, token);
+            if (action != null && token == null) {
+                mHandler.removeMessages(MSG_DO_SCHEDULE_DRAW, action);
+            }
         }
-        mHandler.removeMessages(MSG_POST_DELAYED_DRAW, runnable);
     }
 
-    private void scheduleAnimationLocked() {
+    private void scheduleAnimationLocked(long now) {
         if (!mAnimationScheduled) {
             mAnimationScheduled = true;
             if (USE_VSYNC) {
@@ -291,14 +315,13 @@
                 // otherwise post a message to schedule the vsync from the UI thread
                 // as soon as possible.
                 if (isRunningOnLooperThreadLocked()) {
-                    doScheduleVsyncLocked();
+                    scheduleVsyncLocked();
                 } else {
                     Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                     msg.setAsynchronous(true);
                     mHandler.sendMessageAtFrontOfQueue(msg);
                 }
             } else {
-                final long now = SystemClock.uptimeMillis();
                 final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now);
                 if (DEBUG) {
                     Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms.");
@@ -310,18 +333,18 @@
         }
     }
 
-    private void scheduleDrawLocked() {
+    private void scheduleDrawLocked(long now) {
         if (!mDrawScheduled) {
             mDrawScheduled = true;
             if (USE_ANIMATION_TIMER_FOR_DRAW) {
-                scheduleAnimationLocked();
+                scheduleAnimationLocked(now);
             } else {
                 if (DEBUG) {
                     Log.d(TAG, "Scheduling draw immediately.");
                 }
                 Message msg = mHandler.obtainMessage(MSG_DO_DRAW);
                 msg.setAsynchronous(true);
-                mHandler.sendMessage(msg);
+                mHandler.sendMessageAtTime(msg, now);
             }
         }
     }
@@ -336,7 +359,7 @@
 
     void doAnimationInner() {
         final long start;
-        final Callback callbacks;
+        Callback callbacks;
         synchronized (mLock) {
             if (!mAnimationScheduled) {
                 return; // no work to do
@@ -351,7 +374,23 @@
             mLastAnimationTime = start;
 
             callbacks = mAnimationCallbacks;
-            mAnimationCallbacks = null;
+            if (callbacks != null) {
+                if (callbacks.dueTime > start) {
+                    callbacks = null;
+                } else {
+                    Callback predecessor = callbacks;
+                    Callback successor = predecessor.next;
+                    while (successor != null) {
+                        if (successor.dueTime > start) {
+                            predecessor.next = null;
+                            break;
+                        }
+                        predecessor = successor;
+                        successor = successor.next;
+                    }
+                    mAnimationCallbacks = successor;
+                }
+            }
         }
 
         if (callbacks != null) {
@@ -368,7 +407,7 @@
 
     void doDraw() {
         final long start;
-        final Callback callbacks;
+        Callback callbacks;
         synchronized (mLock) {
             if (!mDrawScheduled) {
                 return; // no work to do
@@ -383,7 +422,23 @@
             mLastDrawTime = start;
 
             callbacks = mDrawCallbacks;
-            mDrawCallbacks = null;
+            if (callbacks != null) {
+                if (callbacks.dueTime > start) {
+                    callbacks = null;
+                } else {
+                    Callback predecessor = callbacks;
+                    Callback successor = predecessor.next;
+                    while (successor != null) {
+                        if (successor.dueTime > start) {
+                            predecessor.next = null;
+                            break;
+                        }
+                        predecessor = successor;
+                        successor = successor.next;
+                    }
+                    mDrawCallbacks = successor;
+                }
+            }
         }
 
         if (callbacks != null) {
@@ -400,38 +455,66 @@
 
     void doScheduleVsync() {
         synchronized (mLock) {
-            doScheduleVsyncLocked();
+            if (mAnimationScheduled) {
+                scheduleVsyncLocked();
+            }
         }
     }
 
-    private void doScheduleVsyncLocked() {
-        if (mAnimationScheduled) {
-            mDisplayEventReceiver.scheduleVsync();
+    void doScheduleAnimation() {
+        synchronized (mLock) {
+            final long now = SystemClock.uptimeMillis();
+            if (mAnimationCallbacks != null && mAnimationCallbacks.dueTime <= now) {
+                scheduleAnimationLocked(now);
+            }
         }
     }
 
+    void doScheduleDraw() {
+        synchronized (mLock) {
+            final long now = SystemClock.uptimeMillis();
+            if (mDrawCallbacks != null && mDrawCallbacks.dueTime <= now) {
+                scheduleDrawLocked(now);
+            }
+        }
+    }
+
+    private void scheduleVsyncLocked() {
+        mDisplayEventReceiver.scheduleVsync();
+    }
+
     private boolean isRunningOnLooperThreadLocked() {
         return Looper.myLooper() == mLooper;
     }
 
-    private Callback addCallbackLocked(Callback head, Runnable runnable) {
-        Callback callback = obtainCallbackLocked(runnable);
+    private Callback addCallbackLocked(Callback head,
+            long dueTime, Runnable action, Object token) {
+        Callback callback = obtainCallbackLocked(dueTime, action, token);
         if (head == null) {
             return callback;
         }
-        Callback tail = head;
-        while (tail.next != null) {
-            tail = tail.next;
+        Callback entry = head;
+        if (dueTime < entry.dueTime) {
+            callback.next = entry;
+            return callback;
         }
-        tail.next = callback;
+        while (entry.next != null) {
+            if (dueTime < entry.next.dueTime) {
+                callback.next = entry.next;
+                break;
+            }
+            entry = entry.next;
+        }
+        entry.next = callback;
         return head;
     }
 
-    private Callback removeCallbacksLocked(Callback head, Runnable runnable) {
+    private Callback removeCallbacksLocked(Callback head, Runnable action, Object token) {
         Callback predecessor = null;
         for (Callback callback = head; callback != null;) {
             final Callback next = callback.next;
-            if (callback.runnable == runnable) {
+            if ((action == null || callback.action == action)
+                    && (token == null || callback.token == token)) {
                 if (predecessor != null) {
                     predecessor.next = next;
                 } else {
@@ -448,7 +531,7 @@
 
     private void runCallbacks(Callback head) {
         while (head != null) {
-            head.runnable.run();
+            head.action.run();
             head = head.next;
         }
     }
@@ -461,7 +544,7 @@
         }
     }
 
-    private Callback obtainCallbackLocked(Runnable runnable) {
+    private Callback obtainCallbackLocked(long dueTime, Runnable action, Object token) {
         Callback callback = mCallbackPool;
         if (callback == null) {
             callback = new Callback();
@@ -469,12 +552,15 @@
             mCallbackPool = callback.next;
             callback.next = null;
         }
-        callback.runnable = runnable;
+        callback.dueTime = dueTime;
+        callback.action = action;
+        callback.token = token;
         return callback;
     }
 
     private void recycleCallbackLocked(Callback callback) {
-        callback.runnable = null;
+        callback.action = null;
+        callback.token = null;
         callback.next = mCallbackPool;
         mCallbackPool = callback;
     }
@@ -496,11 +582,11 @@
                 case MSG_DO_SCHEDULE_VSYNC:
                     doScheduleVsync();
                     break;
-                case MSG_POST_DELAYED_ANIMATION:
-                    postAnimationCallbackUnchecked((Runnable)msg.obj);
+                case MSG_DO_SCHEDULE_ANIMATION:
+                    doScheduleAnimation();
                     break;
-                case MSG_POST_DELAYED_DRAW:
-                    postDrawCallbackUnchecked((Runnable)msg.obj);
+                case MSG_DO_SCHEDULE_DRAW:
+                    doScheduleDraw();
                     break;
             }
         }
@@ -519,6 +605,8 @@
 
     private static final class Callback {
         public Callback next;
-        public Runnable runnable;
+        public long dueTime;
+        public Runnable action;
+        public Object token;
     }
 }
diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl
index acb1387..497bc90b 100644
--- a/core/java/android/view/IWindow.aidl
+++ b/core/java/android/view/IWindow.aidl
@@ -49,7 +49,7 @@
             boolean reportDraw, in Configuration newConfig);
     void dispatchAppVisibility(boolean visible);
     void dispatchGetNewSurface();
-    void dispatchScreenStatus(boolean on);
+    void dispatchScreenState(boolean on);
 
     /**
      * Tell the window that it is either gaining or losing focus.  Keep it up
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 94531c8..a651362 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1994,6 +1994,20 @@
     public static final int FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS = 0x00000004;
 
     /**
+     * Indicates that the screen has changed state and is now off.
+     *
+     * @see #onScreenStateChanged(int)
+     */
+    public static final int SCREEN_STATE_OFF = 0x0;
+
+    /**
+     * Indicates that the screen has changed state and is now on.
+     *
+     * @see #onScreenStateChanged(int)
+     */
+    public static final int SCREEN_STATE_ON = 0x1;
+
+    /**
      * Controls the over-scroll mode for this view.
      * See {@link #overScrollBy(int, int, int, int, int, int, int, int, boolean)},
      * {@link #OVER_SCROLL_ALWAYS}, {@link #OVER_SCROLL_IF_CONTENT_SCROLLS},
@@ -8779,6 +8793,52 @@
     }
 
     /**
+     * <p>Causes the Runnable to execute on the next animation time step.
+     * The runnable will be run on the user interface thread.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @param action The Runnable that will be executed.
+     *
+     * @hide
+     */
+    public void postOnAnimation(Runnable action) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            attachInfo.mViewRootImpl.mChoreographer.postAnimationCallback(action, null);
+        } else {
+            // Assume that post will succeed later
+            ViewRootImpl.getRunQueue().post(action);
+        }
+    }
+
+    /**
+     * <p>Causes the Runnable to execute on the next animation time step,
+     * after the specified amount of time elapses.
+     * The runnable will be run on the user interface thread.</p>
+     *
+     * <p>This method can be invoked from outside of the UI thread
+     * only when this View is attached to a window.</p>
+     *
+     * @param action The Runnable that will be executed.
+     * @param delayMillis The delay (in milliseconds) until the Runnable
+     *        will be executed.
+     *
+     * @hide
+     */
+    public void postOnAnimationDelayed(Runnable action, long delayMillis) {
+        final AttachInfo attachInfo = mAttachInfo;
+        if (attachInfo != null) {
+            attachInfo.mViewRootImpl.mChoreographer.postAnimationCallbackDelayed(
+                    action, null, delayMillis);
+        } else {
+            // Assume that post will succeed later
+            ViewRootImpl.getRunQueue().postDelayed(action, delayMillis);
+        }
+    }
+
+    /**
      * <p>Removes the specified Runnable from the message queue.</p>
      * 
      * <p>This method can be invoked from outside of the UI thread
@@ -8795,6 +8855,7 @@
         final AttachInfo attachInfo = mAttachInfo;
         if (attachInfo != null) {
             attachInfo.mHandler.removeCallbacks(action);
+            attachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(action, null);
         } else {
             // Assume that post will succeed later
             ViewRootImpl.getRunQueue().removeCallbacks(action);
@@ -9604,6 +9665,25 @@
     }
 
     /**
+     * @see #onScreenStateChanged(int)
+     */
+    void dispatchScreenStateChanged(int screenState) {
+        onScreenStateChanged(screenState);
+    }
+
+    /**
+     * This method is called whenever the state of the screen this view is
+     * attached to changes. A state change will usually occurs when the screen
+     * turns on or off (whether it happens automatically or the user does it
+     * manually.)
+     *
+     * @param screenState The new state of the screen. Can be either
+     *                    {@link #SCREEN_STATE_ON} or {@link #SCREEN_STATE_OFF}
+     */
+    public void onScreenStateChanged(int screenState) {
+    }
+
+    /**
      * Resolve and cache the layout direction. LTR is set initially. This is implicitly supposing
      * that the parent directionality can and will be resolved before its children.
      */
@@ -11847,10 +11927,12 @@
      */
     public void scheduleDrawable(Drawable who, Runnable what, long when) {
         if (verifyDrawable(who) && what != null) {
+            final long delay = when - SystemClock.uptimeMillis();
             if (mAttachInfo != null) {
-                mAttachInfo.mHandler.postAtTime(what, who, when);
+                mAttachInfo.mViewRootImpl.mChoreographer.postAnimationCallbackDelayed(
+                        what, who, Choreographer.subtractFrameDelay(delay));
             } else {
-                ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis());
+                ViewRootImpl.getRunQueue().postDelayed(what, delay);
             }
         }
     }
@@ -11864,7 +11946,7 @@
     public void unscheduleDrawable(Drawable who, Runnable what) {
         if (verifyDrawable(who) && what != null) {
             if (mAttachInfo != null) {
-                mAttachInfo.mHandler.removeCallbacks(what, who);
+                mAttachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(what, who);
             } else {
                 ViewRootImpl.getRunQueue().removeCallbacks(what);
             }
@@ -11882,7 +11964,7 @@
      */
     public void unscheduleDrawable(Drawable who) {
         if (mAttachInfo != null) {
-            mAttachInfo.mHandler.removeCallbacksAndMessages(who);
+            mAttachInfo.mViewRootImpl.mChoreographer.removeAnimationCallbacks(null, who);
         }
     }
 
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 0c63286..c9e0242 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -2254,6 +2254,17 @@
     }
 
     @Override
+    void dispatchScreenStateChanged(int screenState) {
+        super.dispatchScreenStateChanged(screenState);
+
+        final int count = mChildrenCount;
+        final View[] children = mChildren;
+        for (int i = 0; i < count; i++) {
+            children[i].dispatchScreenStateChanged(screenState);
+        }
+    }
+
+    @Override
     boolean dispatchPopulateAccessibilityEventInternal(AccessibilityEvent event) {
         boolean handled = super.dispatchPopulateAccessibilityEventInternal(event);
         if (handled) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 7fd05c3..72365c7 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -761,9 +761,12 @@
         scheduleTraversals();
     }
 
-    void handleScreenStatusChange(boolean on) {
+    void handleScreenStateChange(boolean on) {
         if (on != mAttachInfo.mScreenOn) {
             mAttachInfo.mScreenOn = on;
+            if (mView != null) {
+                mView.dispatchScreenStateChanged(on ? View.SCREEN_STATE_ON : View.SCREEN_STATE_OFF);
+            }
             if (on) {
                 mFullRedrawNeeded = true;
                 scheduleTraversals();
@@ -881,7 +884,7 @@
     void scheduleFrame() {
         if (!mFrameScheduled) {
             mFrameScheduled = true;
-            mChoreographer.postDrawCallback(mFrameRunnable);
+            mChoreographer.postDrawCallback(mFrameRunnable, null);
         }
     }
 
@@ -890,7 +893,7 @@
 
         if (mFrameScheduled) {
             mFrameScheduled = false;
-            mChoreographer.removeDrawCallbacks(mFrameRunnable);
+            mChoreographer.removeDrawCallbacks(mFrameRunnable, null);
         }
     }
 
@@ -2500,7 +2503,7 @@
     private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 21;
     private final static int MSG_FIND_ACCESSIBLITY_NODE_INFO_BY_TEXT = 22;
     private final static int MSG_PROCESS_INPUT_EVENTS = 23;
-    private final static int MSG_DISPATCH_SCREEN_STATUS = 24;
+    private final static int MSG_DISPATCH_SCREEN_STATE = 24;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -2757,9 +2760,9 @@
                         .findAccessibilityNodeInfosByTextUiThread(msg);
                 }
             } break;
-            case MSG_DISPATCH_SCREEN_STATUS: {
+            case MSG_DISPATCH_SCREEN_STATE: {
                 if (mView != null) {
-                    handleScreenStatusChange(msg.arg1 == 1);
+                    handleScreenStateChange(msg.arg1 == 1);
                 }
             } break;
             }
@@ -4048,7 +4051,7 @@
                 }
 
                 if (mPosted && mViews.isEmpty() && mViewRects.isEmpty()) {
-                    mChoreographer.removeAnimationCallbacks(this);
+                    mChoreographer.removeAnimationCallbacks(this, null);
                     mPosted = false;
                 }
             }
@@ -4089,7 +4092,7 @@
 
         private void postIfNeededLocked() {
             if (!mPosted) {
-                mChoreographer.postAnimationCallback(this);
+                mChoreographer.postAnimationCallback(this, null);
                 mPosted = true;
             }
         }
@@ -4142,8 +4145,8 @@
         mHandler.sendMessage(msg);
     }
 
-    public void dispatchScreenStatusChange(boolean on) {
-        Message msg = mHandler.obtainMessage(MSG_DISPATCH_SCREEN_STATUS);
+    public void dispatchScreenStateChange(boolean on) {
+        Message msg = mHandler.obtainMessage(MSG_DISPATCH_SCREEN_STATE);
         msg.arg1 = on ? 1 : 0;
         mHandler.sendMessage(msg);
     }
@@ -4349,13 +4352,13 @@
             }
         }
 
-        public void dispatchScreenStatus(boolean on) {
+        public void dispatchScreenState(boolean on) {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
-                viewAncestor.dispatchScreenStatusChange(on);
+                viewAncestor.dispatchScreenStateChange(on);
             }
         }
-        
+
         public void dispatchGetNewSurface() {
             final ViewRootImpl viewAncestor = mViewAncestor.get();
             if (viewAncestor != null) {
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index 72aed4b..af011a1b 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -3896,6 +3896,7 @@
     // helper to pin the scrollTo parameters (already in view coordinates)
     // returns true if the scroll was changed
     private boolean pinScrollTo(int x, int y, boolean animate, int animationDuration) {
+        abortAnimation();
         x = pinLocX(x);
         y = pinLocY(y);
         int dx = x - getScrollX();
@@ -3904,7 +3905,6 @@
         if ((dx | dy) == 0) {
             return false;
         }
-        abortAnimation();
         if (animate) {
             //        Log.d(LOGTAG, "startScroll: " + dx + " " + dy);
             mScroller.startScroll(getScrollX(), getScrollY(), dx, dy,
@@ -4176,9 +4176,9 @@
             // is used in the view system.
             return;
         }
-        int vx = contentToViewX(cx);
-        int vy = contentToViewY(cy);
-        pinScrollTo(vx, vy, true, 0);
+        int vx = contentToViewDimension(cx - mScrollOffset.x);
+        int vy = contentToViewDimension(cy - mScrollOffset.y);
+        pinScrollBy(vx, vy, true, 0);
     }
 
     /**
diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java
index 19fb7b1..233d892 100644
--- a/core/java/android/widget/CheckedTextView.java
+++ b/core/java/android/widget/CheckedTextView.java
@@ -227,16 +227,6 @@
     }
 
     @Override
-    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
-        super.onPopulateAccessibilityEvent(event);
-        if (isChecked()) {
-            event.getText().add(mContext.getString(R.string.radiobutton_selected));
-        } else {
-            event.getText().add(mContext.getString(R.string.radiobutton_not_selected));
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfo(info);
         info.setClassName(CheckedTextView.class.getName());
diff --git a/core/java/android/widget/RadioButton.java b/core/java/android/widget/RadioButton.java
index b6dac3e..b1bb1c0 100644
--- a/core/java/android/widget/RadioButton.java
+++ b/core/java/android/widget/RadioButton.java
@@ -78,16 +78,6 @@
     }
 
     @Override
-    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
-        super.onPopulateAccessibilityEvent(event);
-        if (isChecked()) {
-            event.getText().add(mContext.getString(R.string.radiobutton_selected));
-        } else {
-            event.getText().add(mContext.getString(R.string.radiobutton_not_selected));
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setClassName(RadioButton.class.getName());
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 6540613..aef8a34 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -26,6 +26,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -202,6 +203,130 @@
         }
     }
 
+    /**
+     * Set the background drawable for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param background Background drawable
+     *
+     * @attr ref android.R.styleable#Spinner_popupBackground
+     */
+    public void setPopupBackgroundDrawable(Drawable background) {
+        if (!(mPopup instanceof DropdownPopup)) {
+            Log.e(TAG, "setPopupBackgroundDrawable: incompatible spinner mode; ignoring...");
+            return;
+        }
+        ((DropdownPopup) mPopup).setBackgroundDrawable(background);
+    }
+
+    /**
+     * Set the background drawable for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param resId Resource ID of a background drawable
+     *
+     * @attr ref android.R.styleable#Spinner_popupBackground
+     */
+    public void setPopupBackgroundResource(int resId) {
+        setPopupBackgroundDrawable(getContext().getResources().getDrawable(resId));
+    }
+
+    /**
+     * Get the background drawable for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; other modes will return null.
+     *
+     * @return background Background drawable
+     *
+     * @attr ref android.R.styleable#Spinner_popupBackground
+     */
+    public Drawable getPopupBackground() {
+        return mPopup.getBackground();
+    }
+
+    /**
+     * Set a vertical offset in pixels for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param pixels Vertical offset in pixels
+     *
+     * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
+     */
+    public void setDropDownVerticalOffset(int pixels) {
+        mPopup.setVerticalOffset(pixels);
+    }
+
+    /**
+     * Get the configured vertical offset in pixels for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
+     *
+     * @return Vertical offset in pixels
+     *
+     * @attr ref android.R.styleable#Spinner_dropDownVerticalOffset
+     */
+    public int getDropDownVerticalOffset() {
+        return mPopup.getVerticalOffset();
+    }
+
+    /**
+     * Set a horizontal offset in pixels for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.
+     *
+     * @param pixels Horizontal offset in pixels
+     *
+     * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
+     */
+    public void setDropDownHorizontalOffset(int pixels) {
+        mPopup.setHorizontalOffset(pixels);
+    }
+
+    /**
+     * Get the configured horizontal offset in pixels for the spinner's popup window of choices.
+     * Only valid in {@link #MODE_DROPDOWN}; other modes will return 0.
+     *
+     * @return Horizontal offset in pixels
+     *
+     * @attr ref android.R.styleable#Spinner_dropDownHorizontalOffset
+     */
+    public int getDropDownHorizontalOffset() {
+        return mPopup.getHorizontalOffset();
+    }
+
+    /**
+     * Set the width of the spinner's popup window of choices in pixels. This value
+     * may also be set to {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+     * to match the width of the Spinner itself, or
+     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
+     * of contained dropdown list items.
+     *
+     * <p>Only valid in {@link #MODE_DROPDOWN}; this method is a no-op in other modes.</p>
+     *
+     * @param pixels Width in pixels, WRAP_CONTENT, or MATCH_PARENT
+     *
+     * @attr ref android.R.styleable#Spinner_dropDownWidth
+     */
+    public void setDropDownWidth(int pixels) {
+        if (!(mPopup instanceof DropdownPopup)) {
+            Log.e(TAG, "Cannot set dropdown width for MODE_DIALOG, ignoring");
+            return;
+        }
+        mDropDownWidth = pixels;
+    }
+
+    /**
+     * Get the configured width of the spinner's popup window of choices in pixels.
+     * The returned value may also be {@link android.view.ViewGroup.LayoutParams#MATCH_PARENT}
+     * meaning the popup window will match the width of the Spinner itself, or
+     * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} to wrap to the measured size
+     * of contained dropdown list items.
+     *
+     * @return Width in pixels, WRAP_CONTENT, or MATCH_PARENT
+     *
+     * @attr ref android.R.styleable#Spinner_dropDownWidth
+     */
+    public int getDropDownWidth() {
+        return mDropDownWidth;
+    }
+
     @Override
     public void setEnabled(boolean enabled) {
         super.setEnabled(enabled);
@@ -231,6 +356,16 @@
         }
     }
 
+    /**
+     * Describes how the selected item view is positioned. The default is determined by the
+     * current theme.
+     *
+     * @return A {@link android.view.Gravity Gravity} value
+     */
+    public int getGravity() {
+        return mGravity;
+    }
+
     @Override
     public void setAdapter(SpinnerAdapter adapter) {
         super.setAdapter(adapter);
@@ -675,6 +810,13 @@
          */
         public void setPromptText(CharSequence hintText);
         public CharSequence getHintText();
+
+        public void setBackgroundDrawable(Drawable bg);
+        public void setVerticalOffset(int px);
+        public void setHorizontalOffset(int px);
+        public Drawable getBackground();
+        public int getVerticalOffset();
+        public int getHorizontalOffset();
     }
     
     private class DialogPopup implements SpinnerPopup, DialogInterface.OnClickListener {
@@ -719,6 +861,36 @@
             }
             dismiss();
         }
+
+        @Override
+        public void setBackgroundDrawable(Drawable bg) {
+            Log.e(TAG, "Cannot set popup background for MODE_DIALOG, ignoring");
+        }
+
+        @Override
+        public void setVerticalOffset(int px) {
+            Log.e(TAG, "Cannot set vertical offset for MODE_DIALOG, ignoring");
+        }
+
+        @Override
+        public void setHorizontalOffset(int px) {
+            Log.e(TAG, "Cannot set horizontal offset for MODE_DIALOG, ignoring");
+        }
+
+        @Override
+        public Drawable getBackground() {
+            return null;
+        }
+
+        @Override
+        public int getVerticalOffset() {
+            return 0;
+        }
+
+        @Override
+        public int getHorizontalOffset() {
+            return 0;
+        }
     }
     
     private class DropdownPopup extends ListPopupWindow implements SpinnerPopup {
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 334b9c4..a897cc3 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -169,6 +169,8 @@
     /**
      * Sets the switch text color, size, style, hint color, and highlight color
      * from the specified TextAppearance resource.
+     *
+     * @attr ref android.R.styleable#Switch_switchTextAppearance
      */
     public void setSwitchTextAppearance(Context context, int resid) {
         TypedArray appearance =
@@ -274,7 +276,151 @@
     }
 
     /**
+     * Set the amount of horizontal padding between the switch and the associated text.
+     *
+     * @param pixels Amount of padding in pixels
+     *
+     * @attr ref android.R.styleable#Switch_switchPadding
+     */
+    public void setSwitchPadding(int pixels) {
+        mSwitchPadding = pixels;
+        requestLayout();
+    }
+
+    /**
+     * Get the amount of horizontal padding between the switch and the associated text.
+     *
+     * @return Amount of padding in pixels
+     *
+     * @attr ref android.R.styleable#Switch_switchPadding
+     */
+    public int getSwitchPadding() {
+        return mSwitchPadding;
+    }
+
+    /**
+     * Set the minimum width of the switch in pixels. The switch's width will be the maximum
+     * of this value and its measured width as determined by the switch drawables and text used.
+     *
+     * @param pixels Minimum width of the switch in pixels
+     *
+     * @attr ref android.R.styleable#Switch_switchMinWidth
+     */
+    public void setSwitchMinWidth(int pixels) {
+        mSwitchMinWidth = pixels;
+        requestLayout();
+    }
+
+    /**
+     * Get the minimum width of the switch in pixels. The switch's width will be the maximum
+     * of this value and its measured width as determined by the switch drawables and text used.
+     *
+     * @return Minimum width of the switch in pixels
+     *
+     * @attr ref android.R.styleable#Switch_switchMinWidth
+     */
+    public int getSwitchMinWidth() {
+        return mSwitchMinWidth;
+    }
+
+    /**
+     * Set the horizontal padding around the text drawn on the switch itself.
+     *
+     * @param pixels Horizontal padding for switch thumb text in pixels
+     *
+     * @attr ref android.R.styleable#Switch_thumbTextPadding
+     */
+    public void setThumbTextPadding(int pixels) {
+        mThumbTextPadding = pixels;
+        requestLayout();
+    }
+
+    /**
+     * Get the horizontal padding around the text drawn on the switch itself.
+     *
+     * @return Horizontal padding for switch thumb text in pixels
+     *
+     * @attr ref android.R.styleable#Switch_thumbTextPadding
+     */
+    public int getThumbTextPadding() {
+        return mThumbTextPadding;
+    }
+
+    /**
+     * Set the drawable used for the track that the switch slides within.
+     *
+     * @param track Track drawable
+     *
+     * @attr ref android.R.styleable#Switch_track
+     */
+    public void setTrackDrawable(Drawable track) {
+        mTrackDrawable = track;
+        requestLayout();
+    }
+
+    /**
+     * Set the drawable used for the track that the switch slides within.
+     *
+     * @param resId Resource ID of a track drawable
+     *
+     * @attr ref android.R.styleable#Switch_track
+     */
+    public void setTrackResource(int resId) {
+        setTrackDrawable(getContext().getResources().getDrawable(resId));
+    }
+
+    /**
+     * Get the drawable used for the track that the switch slides within.
+     *
+     * @return Track drawable
+     *
+     * @attr ref android.R.styleable#Switch_track
+     */
+    public Drawable getTrackDrawable() {
+        return mTrackDrawable;
+    }
+
+    /**
+     * Set the drawable used for the switch "thumb" - the piece that the user
+     * can physically touch and drag along the track.
+     *
+     * @param thumb Thumb drawable
+     *
+     * @attr ref android.R.styleable#Switch_thumb
+     */
+    public void setThumbDrawable(Drawable thumb) {
+        mThumbDrawable = thumb;
+        requestLayout();
+    }
+
+    /**
+     * Set the drawable used for the switch "thumb" - the piece that the user
+     * can physically touch and drag along the track.
+     *
+     * @param resId Resource ID of a thumb drawable
+     *
+     * @attr ref android.R.styleable#Switch_thumb
+     */
+    public void setThumbResource(int resId) {
+        setThumbDrawable(getContext().getResources().getDrawable(resId));
+    }
+
+    /**
+     * Get the drawable used for the switch "thumb" - the piece that the user
+     * can physically touch and drag along the track.
+     *
+     * @return Thumb drawable
+     *
+     * @attr ref android.R.styleable#Switch_thumb
+     */
+    public Drawable getThumbDrawable() {
+        return mThumbDrawable;
+    }
+
+    /**
      * Returns the text displayed when the button is in the checked state.
+     *
+     * @attr ref android.R.styleable#Switch_textOn
      */
     public CharSequence getTextOn() {
         return mTextOn;
@@ -282,6 +428,8 @@
 
     /**
      * Sets the text displayed when the button is in the checked state.
+     *
+     * @attr ref android.R.styleable#Switch_textOn
      */
     public void setTextOn(CharSequence textOn) {
         mTextOn = textOn;
@@ -290,6 +438,8 @@
 
     /**
      * Returns the text displayed when the button is not in the checked state.
+     *
+     * @attr ref android.R.styleable#Switch_textOff
      */
     public CharSequence getTextOff() {
         return mTextOff;
@@ -297,6 +447,8 @@
 
     /**
      * Sets the text displayed when the button is not in the checked state.
+     *
+     * @attr ref android.R.styleable#Switch_textOff
      */
     public void setTextOff(CharSequence textOff) {
         mTextOff = textOff;
@@ -367,17 +519,8 @@
     @Override
     public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
         super.onPopulateAccessibilityEvent(event);
-        if (isChecked()) {
-            CharSequence text = mOnLayout.getText();
-            if (TextUtils.isEmpty(text)) {
-                text = mContext.getString(R.string.switch_on);
-            }
-            event.getText().add(text);
-        } else {
-            CharSequence text = mOffLayout.getText();
-            if (TextUtils.isEmpty(text)) {
-                text = mContext.getString(R.string.switch_off);
-            }
+        CharSequence text = isChecked() ? mOnLayout.getText() : mOffLayout.getText();
+        if (!TextUtils.isEmpty(text)) {
             event.getText().add(text);
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 5ee7392..4c89218 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4280,6 +4280,12 @@
     }
 
     @Override
+    public void onScreenStateChanged(int screenState) {
+        super.onScreenStateChanged(screenState);
+        if (mEditor != null) getEditor().onScreenStateChanged(screenState);
+    }
+
+    @Override
     protected boolean isPaddingOffsetRequired() {
         return mShadowRadius != 0 || mDrawables != null;
     }
@@ -11400,6 +11406,30 @@
             hideControllers();
         }
 
+        void onScreenStateChanged(int screenState) {
+            switch (screenState) {
+                case SCREEN_STATE_ON:
+                    resumeBlink();
+                    break;
+                case SCREEN_STATE_OFF:
+                    suspendBlink();
+                    break;
+            }
+        }
+
+        private void suspendBlink() {
+            if (mBlink != null) {
+                mBlink.cancel();
+            }
+        }
+
+        private void resumeBlink() {
+            if (mBlink != null) {
+                mBlink.uncancel();
+                makeBlink();
+            }
+        }
+
         void adjustInputType(boolean password, boolean passwordInputType,
                 boolean webPasswordInputType, boolean numberPasswordInputType) {
             // mInputType has been set from inputType, possibly modified by mInputMethod.
diff --git a/core/java/android/widget/ToggleButton.java b/core/java/android/widget/ToggleButton.java
index a0edafe..4beee96 100644
--- a/core/java/android/widget/ToggleButton.java
+++ b/core/java/android/widget/ToggleButton.java
@@ -154,16 +154,6 @@
     }
 
     @Override
-    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
-        super.onPopulateAccessibilityEvent(event);
-        if (isChecked()) {
-            event.getText().add(mContext.getString(R.string.togglebutton_pressed));
-        } else {
-            event.getText().add(mContext.getString(R.string.togglebutton_not_pressed));
-        }
-    }
-
-    @Override
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setClassName(ToggleButton.class.getName());
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 6a99a2b..998c037 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -348,6 +348,7 @@
                 TypedArray ar = mResources.obtainTypedArray(
                         com.android.internal.R.array.preloaded_drawables);
                 int N = preloadDrawables(runtime, ar);
+                ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
                         + (SystemClock.uptimeMillis()-startTime) + "ms.");
 
@@ -355,6 +356,7 @@
                 ar = mResources.obtainTypedArray(
                         com.android.internal.R.array.preloaded_color_state_lists);
                 N = preloadColorStateLists(runtime, ar);
+                ar.recycle();
                 Log.i(TAG, "...preloaded " + N + " resources in "
                         + (SystemClock.uptimeMillis()-startTime) + "ms.");
             }
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index e695f8e..15d11d8 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -49,7 +49,7 @@
     public void dispatchGetNewSurface() {
     }
 
-    public void dispatchScreenStatus(boolean on) {
+    public void dispatchScreenState(boolean on) {
     }
 
     public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index c389cf7..642988b 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -64,6 +64,7 @@
 	android_os_MemoryFile.cpp \
 	android_os_MessageQueue.cpp \
 	android_os_ParcelFileDescriptor.cpp \
+	android_os_Parcel.cpp \
 	android_os_Power.cpp \
 	android_os_StatFs.cpp \
 	android_os_SystemClock.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 3067e75..de9fd33 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -130,6 +130,7 @@
 extern int register_android_text_format_Time(JNIEnv* env);
 extern int register_android_os_Debug(JNIEnv* env);
 extern int register_android_os_MessageQueue(JNIEnv* env);
+extern int register_android_os_Parcel(JNIEnv* env);
 extern int register_android_os_ParcelFileDescriptor(JNIEnv *env);
 extern int register_android_os_Power(JNIEnv *env);
 extern int register_android_os_StatFs(JNIEnv *env);
@@ -1094,6 +1095,7 @@
     REG_JNI(register_android_os_Process),
     REG_JNI(register_android_os_SystemProperties),
     REG_JNI(register_android_os_Binder),
+    REG_JNI(register_android_os_Parcel),
     REG_JNI(register_android_view_Display),
     REG_JNI(register_android_view_DisplayEventReceiver),
     REG_JNI(register_android_nio_utils),
diff --git a/core/jni/android/graphics/Bitmap.cpp b/core/jni/android/graphics/Bitmap.cpp
index d1d3b78..5e73a5f 100644
--- a/core/jni/android/graphics/Bitmap.cpp
+++ b/core/jni/android/graphics/Bitmap.cpp
@@ -7,6 +7,7 @@
 #include "SkUnPreMultiply.h"

 

 #include <binder/Parcel.h>

+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"

 #include "android_nio_utils.h"

 #include "CreateJavaOutputStreamAdaptor.h"

diff --git a/core/jni/android/graphics/Region.cpp b/core/jni/android/graphics/Region.cpp
index 5c6ebdf..866d223 100644
--- a/core/jni/android/graphics/Region.cpp
+++ b/core/jni/android/graphics/Region.cpp
@@ -19,6 +19,7 @@
 #include "GraphicsJNI.h"
 
 #include <binder/Parcel.h>
+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
 #include <jni.h>
diff --git a/core/jni/android_database_CursorWindow.cpp b/core/jni/android_database_CursorWindow.cpp
index 579d6ad..ea02f53 100644
--- a/core/jni/android_database_CursorWindow.cpp
+++ b/core/jni/android_database_CursorWindow.cpp
@@ -31,6 +31,7 @@
 #include <unistd.h>
 
 #include <androidfw/CursorWindow.h>
+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 #include "android_database_SQLiteCommon.h"
 
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
new file mode 100644
index 0000000..3dfaac3
--- /dev/null
+++ b/core/jni/android_os_Parcel.cpp
@@ -0,0 +1,676 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#define LOG_TAG "Parcel"
+//#define LOG_NDEBUG 0
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+
+#include "JNIHelp.h"
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <utils/Atomic.h>
+#include <binder/IInterface.h>
+#include <binder/IPCThreadState.h>
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+#include <utils/List.h>
+#include <utils/KeyedVector.h>
+#include <cutils/logger.h>
+#include <binder/Parcel.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <utils/threads.h>
+#include <utils/String8.h>
+
+#include <ScopedUtfChars.h>
+#include <ScopedLocalRef.h>
+
+#include <android_runtime/AndroidRuntime.h>
+
+//#undef ALOGV
+//#define ALOGV(...) fprintf(stderr, __VA_ARGS__)
+
+#define DEBUG_DEATH 0
+#if DEBUG_DEATH
+#define LOGDEATH ALOGD
+#else
+#define LOGDEATH ALOGV
+#endif
+
+namespace android {
+
+static struct parcel_offsets_t
+{
+    jfieldID mNativePtr;
+} gParcelOffsets;
+
+Parcel* parcelForJavaObject(JNIEnv* env, jobject obj)
+{
+    if (obj) {
+        Parcel* p = (Parcel*)env->GetIntField(obj, gParcelOffsets.mNativePtr);
+        if (p != NULL) {
+            return p;
+        }
+        jniThrowException(env, "java/lang/IllegalStateException", "Parcel has been finalized!");
+    }
+    return NULL;
+}
+
+static jint android_os_Parcel_dataSize(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    return parcel ? parcel->dataSize() : 0;
+}
+
+static jint android_os_Parcel_dataAvail(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    return parcel ? parcel->dataAvail() : 0;
+}
+
+static jint android_os_Parcel_dataPosition(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    return parcel ? parcel->dataPosition() : 0;
+}
+
+static jint android_os_Parcel_dataCapacity(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    return parcel ? parcel->dataCapacity() : 0;
+}
+
+static void android_os_Parcel_setDataSize(JNIEnv* env, jclass clazz, jint nativePtr, jint size)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err = parcel->setDataSize(size);
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_setDataPosition(JNIEnv* env, jclass clazz, jint nativePtr, jint pos)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        parcel->setDataPosition(pos);
+    }
+}
+
+static void android_os_Parcel_setDataCapacity(JNIEnv* env, jclass clazz, jint nativePtr, jint size)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err = parcel->setDataCapacity(size);
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static jboolean android_os_Parcel_pushAllowFds(JNIEnv* env, jclass clazz, jint nativePtr, jboolean allowFds)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    jboolean ret = JNI_TRUE;
+    if (parcel != NULL) {
+        ret = (jboolean)parcel->pushAllowFds(allowFds);
+    }
+    return ret;
+}
+
+static void android_os_Parcel_restoreAllowFds(JNIEnv* env, jclass clazz, jint nativePtr, jboolean lastValue)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        parcel->restoreAllowFds((bool)lastValue);
+    }
+}
+
+static void android_os_Parcel_writeNative(JNIEnv* env, jclass clazz, jint nativePtr, jobject data,
+                                          jint offset, jint length)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel == NULL) {
+        return;
+    }
+
+    const status_t err = parcel->writeInt32(length);
+    if (err != NO_ERROR) {
+        signalExceptionForError(env, clazz, err);
+        return;
+    }
+
+    void* dest = parcel->writeInplace(length);
+    if (dest == NULL) {
+        signalExceptionForError(env, clazz, NO_MEMORY);
+        return;
+    }
+
+    jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0);
+    if (ar) {
+        memcpy(dest, ar + offset, length);
+        env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0);
+    }
+}
+
+static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jint nativePtr, jint val) {
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    const status_t err = parcel->writeInt32(val);
+    if (err != NO_ERROR) {
+        signalExceptionForError(env, clazz, err);
+    }
+}
+
+static void android_os_Parcel_writeLong(JNIEnv* env, jclass clazz, jint nativePtr, jlong val)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeInt64(val);
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_writeFloat(JNIEnv* env, jclass clazz, jint nativePtr, jfloat val)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeFloat(val);
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_writeDouble(JNIEnv* env, jclass clazz, jint nativePtr, jdouble val)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeDouble(val);
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jint nativePtr, jstring val)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        status_t err = NO_MEMORY;
+        if (val) {
+            const jchar* str = env->GetStringCritical(val, 0);
+            if (str) {
+                err = parcel->writeString16(str, env->GetStringLength(val));
+                env->ReleaseStringCritical(val, str);
+            }
+        } else {
+            err = parcel->writeString16(NULL, 0);
+        }
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jint nativePtr, jobject object)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jclass clazz, jint nativePtr, jobject object)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const status_t err =
+                parcel->writeDupFileDescriptor(jniGetFDFromFileDescriptor(env, object));
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    jbyteArray ret = NULL;
+
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        int32_t len = parcel->readInt32();
+
+        // sanity check the stored length against the true data size
+        if (len >= 0 && len <= (int32_t)parcel->dataAvail()) {
+            ret = env->NewByteArray(len);
+
+            if (ret != NULL) {
+                jbyte* a2 = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
+                if (a2) {
+                    const void* data = parcel->readInplace(len);
+                    memcpy(a2, data, len);
+                    env->ReleasePrimitiveArrayCritical(ret, a2, 0);
+                }
+            }
+        }
+    }
+
+    return ret;
+}
+
+static jint android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        return parcel->readInt32();
+    }
+    return 0;
+}
+
+static jlong android_os_Parcel_readLong(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        return parcel->readInt64();
+    }
+    return 0;
+}
+
+static jfloat android_os_Parcel_readFloat(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        return parcel->readFloat();
+    }
+    return 0;
+}
+
+static jdouble android_os_Parcel_readDouble(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        return parcel->readDouble();
+    }
+    return 0;
+}
+
+static jstring android_os_Parcel_readString(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        size_t len;
+        const char16_t* str = parcel->readString16Inplace(&len);
+        if (str) {
+            return env->NewString(str, len);
+        }
+        return NULL;
+    }
+    return NULL;
+}
+
+static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        return javaObjectForIBinder(env, parcel->readStrongBinder());
+    }
+    return NULL;
+}
+
+static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        int fd = parcel->readFileDescriptor();
+        if (fd < 0) return NULL;
+        fd = dup(fd);
+        if (fd < 0) return NULL;
+        return jniCreateFileDescriptor(env, fd);
+    }
+    return NULL;
+}
+
+static jobject android_os_Parcel_openFileDescriptor(JNIEnv* env, jclass clazz,
+                                                    jstring name, jint mode)
+{
+    if (name == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return NULL;
+    }
+    const jchar* str = env->GetStringCritical(name, 0);
+    if (str == NULL) {
+        // Whatever, whatever.
+        jniThrowException(env, "java/lang/IllegalStateException", NULL);
+        return NULL;
+    }
+    String8 name8(str, env->GetStringLength(name));
+    env->ReleaseStringCritical(name, str);
+    int flags=0;
+    switch (mode&0x30000000) {
+        case 0:
+        case 0x10000000:
+            flags = O_RDONLY;
+            break;
+        case 0x20000000:
+            flags = O_WRONLY;
+            break;
+        case 0x30000000:
+            flags = O_RDWR;
+            break;
+    }
+
+    if (mode&0x08000000) flags |= O_CREAT;
+    if (mode&0x04000000) flags |= O_TRUNC;
+    if (mode&0x02000000) flags |= O_APPEND;
+
+    int realMode = S_IRWXU|S_IRWXG;
+    if (mode&0x00000001) realMode |= S_IROTH;
+    if (mode&0x00000002) realMode |= S_IWOTH;
+
+    int fd = open(name8.string(), flags, realMode);
+    if (fd < 0) {
+        jniThrowException(env, "java/io/FileNotFoundException", strerror(errno));
+        return NULL;
+    }
+    jobject object = jniCreateFileDescriptor(env, fd);
+    if (object == NULL) {
+        close(fd);
+    }
+    return object;
+}
+
+static jobject android_os_Parcel_dupFileDescriptor(JNIEnv* env, jclass clazz, jobject orig)
+{
+    if (orig == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return NULL;
+    }
+    int origfd = jniGetFDFromFileDescriptor(env, orig);
+    if (origfd < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "bad FileDescriptor");
+        return NULL;
+    }
+
+    int fd = dup(origfd);
+    if (fd < 0) {
+        jniThrowIOException(env, errno);
+        return NULL;
+    }
+    jobject object = jniCreateFileDescriptor(env, fd);
+    if (object == NULL) {
+        close(fd);
+    }
+    return object;
+}
+
+static void android_os_Parcel_closeFileDescriptor(JNIEnv* env, jclass clazz, jobject object)
+{
+    if (object == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return;
+    }
+    int fd = jniGetFDFromFileDescriptor(env, object);
+    if (fd >= 0) {
+        jniSetFileDescriptorOfFD(env, object, -1);
+        //ALOGI("Closing ParcelFileDescriptor %d\n", fd);
+        close(fd);
+    }
+}
+
+static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, jclass clazz, jobject object)
+{
+    if (object == NULL) {
+        jniThrowNullPointerException(env, NULL);
+        return;
+    }
+    int fd = jniGetFDFromFileDescriptor(env, object);
+    if (fd >= 0) {
+        jniSetFileDescriptorOfFD(env, object, -1);
+    }
+}
+
+static jint android_os_Parcel_create(JNIEnv* env, jclass clazz)
+{
+    Parcel* parcel = new Parcel();
+    return reinterpret_cast<jint>(parcel);
+}
+
+static void android_os_Parcel_freeBuffer(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        parcel->freeData();
+    }
+}
+
+static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    delete parcel;
+}
+
+static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel == NULL) {
+       return NULL;
+    }
+
+    // do not marshall if there are binder objects in the parcel
+    if (parcel->objectsCount())
+    {
+        jniThrowException(env, "java/lang/RuntimeException", "Tried to marshall a Parcel that contained Binder objects.");
+        return NULL;
+    }
+
+    jbyteArray ret = env->NewByteArray(parcel->dataSize());
+
+    if (ret != NULL)
+    {
+        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
+        if (array != NULL)
+        {
+            memcpy(array, parcel->data(), parcel->dataSize());
+            env->ReleasePrimitiveArrayCritical(ret, array, 0);
+        }
+    }
+
+    return ret;
+}
+
+static void android_os_Parcel_unmarshall(JNIEnv* env, jclass clazz, jint nativePtr,
+                                         jbyteArray data, jint offset, jint length)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel == NULL || length < 0) {
+       return;
+    }
+
+    jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
+    if (array)
+    {
+        parcel->setDataSize(length);
+        parcel->setDataPosition(0);
+
+        void* raw = parcel->writeInplace(length);
+        memcpy(raw, (array + offset), length);
+
+        env->ReleasePrimitiveArrayCritical(data, array, 0);
+    }
+}
+
+static void android_os_Parcel_appendFrom(JNIEnv* env, jclass clazz, jint thisNativePtr,
+                                         jint otherNativePtr, jint offset, jint length)
+{
+    Parcel* thisParcel = reinterpret_cast<Parcel*>(thisNativePtr);
+    if (thisParcel == NULL) {
+       return;
+    }
+    Parcel* otherParcel = reinterpret_cast<Parcel*>(otherNativePtr);
+    if (otherParcel == NULL) {
+       return;
+    }
+
+    status_t err = thisParcel->appendFrom(otherParcel, offset, length);
+    if (err != NO_ERROR) {
+        signalExceptionForError(env, clazz, err);
+    }
+}
+
+static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jclass clazz, jint nativePtr)
+{
+    jboolean ret = JNI_FALSE;
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        if (parcel->hasFileDescriptors()) {
+            ret = JNI_TRUE;
+        }
+    }
+    return ret;
+}
+
+static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jclass clazz, jint nativePtr,
+                                                  jstring name)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        // In the current implementation, the token is just the serialized interface name that
+        // the caller expects to be invoking
+        const jchar* str = env->GetStringCritical(name, 0);
+        if (str != NULL) {
+            parcel->writeInterfaceToken(String16(str, env->GetStringLength(name)));
+            env->ReleaseStringCritical(name, str);
+        }
+    }
+}
+
+static void android_os_Parcel_enforceInterface(JNIEnv* env, jclass clazz, jint nativePtr, jstring name)
+{
+    jboolean ret = JNI_FALSE;
+
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        const jchar* str = env->GetStringCritical(name, 0);
+        if (str) {
+            IPCThreadState* threadState = IPCThreadState::self();
+            const int32_t oldPolicy = threadState->getStrictModePolicy();
+            const bool isValid = parcel->enforceInterface(
+                String16(str, env->GetStringLength(name)),
+                threadState);
+            env->ReleaseStringCritical(name, str);
+            if (isValid) {
+                const int32_t newPolicy = threadState->getStrictModePolicy();
+                if (oldPolicy != newPolicy) {
+                    // Need to keep the Java-level thread-local strict
+                    // mode policy in sync for the libcore
+                    // enforcements, which involves an upcall back
+                    // into Java.  (We can't modify the
+                    // Parcel.enforceInterface signature, as it's
+                    // pseudo-public, and used via AIDL
+                    // auto-generation...)
+                    set_dalvik_blockguard_policy(env, newPolicy);
+                }
+                return;     // everything was correct -> return silently
+            }
+        }
+    }
+
+    // all error conditions wind up here
+    jniThrowException(env, "java/lang/SecurityException",
+            "Binder invocation to an incorrect interface");
+}
+
+// ----------------------------------------------------------------------------
+
+static const JNINativeMethod gParcelMethods[] = {
+    {"nativeDataSize",            "(I)I", (void*)android_os_Parcel_dataSize},
+    {"nativeDataAvail",           "(I)I", (void*)android_os_Parcel_dataAvail},
+    {"nativeDataPosition",        "(I)I", (void*)android_os_Parcel_dataPosition},
+    {"nativeDataCapacity",        "(I)I", (void*)android_os_Parcel_dataCapacity},
+    {"nativeSetDataSize",         "(II)V", (void*)android_os_Parcel_setDataSize},
+    {"nativeSetDataPosition",     "(II)V", (void*)android_os_Parcel_setDataPosition},
+    {"nativeSetDataCapacity",     "(II)V", (void*)android_os_Parcel_setDataCapacity},
+
+    {"nativePushAllowFds",        "(IZ)Z", (void*)android_os_Parcel_pushAllowFds},
+    {"nativeRestoreAllowFds",     "(IZ)V", (void*)android_os_Parcel_restoreAllowFds},
+
+    {"nativeWriteByteArray",      "(I[BII)V", (void*)android_os_Parcel_writeNative},
+    {"nativeWriteInt",            "(II)V", (void*)android_os_Parcel_writeInt},
+    {"nativeWriteLong",           "(IJ)V", (void*)android_os_Parcel_writeLong},
+    {"nativeWriteFloat",          "(IF)V", (void*)android_os_Parcel_writeFloat},
+    {"nativeWriteDouble",         "(ID)V", (void*)android_os_Parcel_writeDouble},
+    {"nativeWriteString",         "(ILjava/lang/String;)V", (void*)android_os_Parcel_writeString},
+    {"nativeWriteStrongBinder",   "(ILandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
+    {"nativeWriteFileDescriptor", "(ILjava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
+
+    {"nativeCreateByteArray",     "(I)[B", (void*)android_os_Parcel_createByteArray},
+    {"nativeReadInt",             "(I)I", (void*)android_os_Parcel_readInt},
+    {"nativeReadLong",            "(I)J", (void*)android_os_Parcel_readLong},
+    {"nativeReadFloat",           "(I)F", (void*)android_os_Parcel_readFloat},
+    {"nativeReadDouble",          "(I)D", (void*)android_os_Parcel_readDouble},
+    {"nativeReadString",          "(I)Ljava/lang/String;", (void*)android_os_Parcel_readString},
+    {"nativeReadStrongBinder",    "(I)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
+    {"nativeReadFileDescriptor",  "(I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
+
+    {"openFileDescriptor",        "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
+    {"dupFileDescriptor",         "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
+    {"closeFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
+    {"clearFileDescriptor",       "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
+
+    {"nativeCreate",              "()I", (void*)android_os_Parcel_create},
+    {"nativeFreeBuffer",          "(I)V", (void*)android_os_Parcel_freeBuffer},
+    {"nativeDestroy",             "(I)V", (void*)android_os_Parcel_destroy},
+
+    {"nativeMarshall",            "(I)[B", (void*)android_os_Parcel_marshall},
+    {"nativeUnmarshall",          "(I[BII)V", (void*)android_os_Parcel_unmarshall},
+    {"nativeAppendFrom",          "(IIII)V", (void*)android_os_Parcel_appendFrom},
+    {"nativeHasFileDescriptors",  "(I)Z", (void*)android_os_Parcel_hasFileDescriptors},
+    {"nativeWriteInterfaceToken", "(ILjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
+    {"nativeEnforceInterface",    "(ILjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
+};
+
+const char* const kParcelPathName = "android/os/Parcel";
+
+int register_android_os_Parcel(JNIEnv* env)
+{
+    jclass clazz;
+
+    clazz = env->FindClass(kParcelPathName);
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Parcel");
+
+    gParcelOffsets.mNativePtr
+        = env->GetFieldID(clazz, "mNativePtr", "I");
+
+    return AndroidRuntime::registerNativeMethods(
+        env, kParcelPathName,
+        gParcelMethods, NELEM(gParcelMethods));
+}
+
+};
diff --git a/core/jni/android_os_Parcel.h b/core/jni/android_os_Parcel.h
new file mode 100644
index 0000000..65f3819e
--- /dev/null
+++ b/core/jni/android_os_Parcel.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#include <binder/IBinder.h>
+
+#include "jni.h"
+
+namespace android {
+
+// Conversion from Java Parcel Object to C++ Parcel instance.
+// Note: does not type checking; must guarantee jobject is a Java Parcel
+extern Parcel* parcelForJavaObject(JNIEnv* env, jobject obj);
+
+}
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index e00970a..0f99fb2 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -17,7 +17,9 @@
 #define LOG_TAG "JavaBinder"
 //#define LOG_NDEBUG 0
 
+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
+
 #include "JNIHelp.h"
 
 #include <fcntl.h>
@@ -127,12 +129,6 @@
 
 // ----------------------------------------------------------------------------
 
-static struct parcel_offsets_t
-{
-    jfieldID mObject;
-    jfieldID mOwnObject;
-} gParcelOffsets;
-
 static struct log_offsets_t
 {
     // Class state.
@@ -232,15 +228,6 @@
     env->DeleteLocalRef(msgstr);
 }
 
-static void set_dalvik_blockguard_policy(JNIEnv* env, jint strict_policy)
-{
-    // Call back into android.os.StrictMode#onBinderStrictModePolicyChange
-    // to sync our state back to it.  See the comments in StrictMode.java.
-    env->CallStaticVoidMethod(gStrictModeCallbackOffsets.mClass,
-                              gStrictModeCallbackOffsets.mCallback,
-                              strict_policy);
-}
-
 class JavaBBinderHolder;
 
 class JavaBBinder : public BBinder
@@ -634,26 +621,23 @@
     return NULL;
 }
 
-Parcel* parcelForJavaObject(JNIEnv* env, jobject obj)
-{
-    if (obj) {
-        Parcel* p = (Parcel*)env->GetIntField(obj, gParcelOffsets.mObject);
-        if (p != NULL) {
-            return p;
-        }
-        jniThrowException(env, "java/lang/IllegalStateException", "Parcel has been finalized!");
-    }
-    return NULL;
-}
-
 jobject newParcelFileDescriptor(JNIEnv* env, jobject fileDesc)
 {
     return env->NewObject(
             gParcelFileDescriptorOffsets.mClass, gParcelFileDescriptorOffsets.mConstructor, fileDesc);
 }
 
-static void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
-        bool canThrowRemoteException = false)
+void set_dalvik_blockguard_policy(JNIEnv* env, jint strict_policy)
+{
+    // Call back into android.os.StrictMode#onBinderStrictModePolicyChange
+    // to sync our state back to it.  See the comments in StrictMode.java.
+    env->CallStaticVoidMethod(gStrictModeCallbackOffsets.mClass,
+                              gStrictModeCallbackOffsets.mCallback,
+                              strict_policy);
+}
+
+void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
+        bool canThrowRemoteException)
 {
     switch (err) {
         case UNKNOWN_ERROR:
@@ -1273,612 +1257,15 @@
 // ****************************************************************************
 // ****************************************************************************
 
-static jint android_os_Parcel_dataSize(JNIEnv* env, jobject clazz)
+int register_android_os_Binder(JNIEnv* env)
 {
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    return parcel ? parcel->dataSize() : 0;
-}
+    if (int_register_android_os_Binder(env) < 0)
+        return -1;
+    if (int_register_android_os_BinderInternal(env) < 0)
+        return -1;
+    if (int_register_android_os_BinderProxy(env) < 0)
+        return -1;
 
-static jint android_os_Parcel_dataAvail(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    return parcel ? parcel->dataAvail() : 0;
-}
-
-static jint android_os_Parcel_dataPosition(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    return parcel ? parcel->dataPosition() : 0;
-}
-
-static jint android_os_Parcel_dataCapacity(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    return parcel ? parcel->dataCapacity() : 0;
-}
-
-static void android_os_Parcel_setDataSize(JNIEnv* env, jobject clazz, jint size)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->setDataSize(size);
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_setDataPosition(JNIEnv* env, jobject clazz, jint pos)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        parcel->setDataPosition(pos);
-    }
-}
-
-static void android_os_Parcel_setDataCapacity(JNIEnv* env, jobject clazz, jint size)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->setDataCapacity(size);
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static jboolean android_os_Parcel_pushAllowFds(JNIEnv* env, jobject clazz, jboolean allowFds)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    jboolean ret = JNI_TRUE;
-    if (parcel != NULL) {
-        ret = (jboolean)parcel->pushAllowFds(allowFds);
-    }
-    return ret;
-}
-
-static void android_os_Parcel_restoreAllowFds(JNIEnv* env, jobject clazz, jboolean lastValue)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        parcel->restoreAllowFds((bool)lastValue);
-    }
-}
-
-static void android_os_Parcel_writeNative(JNIEnv* env, jobject clazz,
-                                          jobject data, jint offset,
-                                          jint length)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel == NULL) {
-        return;
-    }
-
-    const status_t err = parcel->writeInt32(length);
-    if (err != NO_ERROR) {
-        signalExceptionForError(env, clazz, err);
-        return;
-    }
-
-    void* dest = parcel->writeInplace(length);
-    if (dest == NULL) {
-        signalExceptionForError(env, clazz, NO_MEMORY);
-        return;
-    }
-
-    jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0);
-    if (ar) {
-        memcpy(dest, ar + offset, length);
-        env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0);
-    }
-}
-
-
-static void android_os_Parcel_writeInt(JNIEnv* env, jobject clazz, jint val)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->writeInt32(val);
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_writeLong(JNIEnv* env, jobject clazz, jlong val)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->writeInt64(val);
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_writeFloat(JNIEnv* env, jobject clazz, jfloat val)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->writeFloat(val);
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_writeDouble(JNIEnv* env, jobject clazz, jdouble val)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->writeDouble(val);
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_writeString(JNIEnv* env, jobject clazz, jstring val)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        status_t err = NO_MEMORY;
-        if (val) {
-            const jchar* str = env->GetStringCritical(val, 0);
-            if (str) {
-                err = parcel->writeString16(str, env->GetStringLength(val));
-                env->ReleaseStringCritical(val, str);
-            }
-        } else {
-            err = parcel->writeString16(NULL, 0);
-        }
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jobject clazz, jobject object)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object));
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static void android_os_Parcel_writeFileDescriptor(JNIEnv* env, jobject clazz, jobject object)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const status_t err =
-                parcel->writeDupFileDescriptor(jniGetFDFromFileDescriptor(env, object));
-        if (err != NO_ERROR) {
-            signalExceptionForError(env, clazz, err);
-        }
-    }
-}
-
-static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, jobject clazz)
-{
-    jbyteArray ret = NULL;
-
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        int32_t len = parcel->readInt32();
-
-        // sanity check the stored length against the true data size
-        if (len >= 0 && len <= (int32_t)parcel->dataAvail()) {
-            ret = env->NewByteArray(len);
-
-            if (ret != NULL) {
-                jbyte* a2 = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
-                if (a2) {
-                    const void* data = parcel->readInplace(len);
-                    memcpy(a2, data, len);
-                    env->ReleasePrimitiveArrayCritical(ret, a2, 0);
-                }
-            }
-        }
-    }
-
-    return ret;
-}
-
-static jint android_os_Parcel_readInt(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        return parcel->readInt32();
-    }
-    return 0;
-}
-
-static jlong android_os_Parcel_readLong(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        return parcel->readInt64();
-    }
-    return 0;
-}
-
-static jfloat android_os_Parcel_readFloat(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        return parcel->readFloat();
-    }
-    return 0;
-}
-
-static jdouble android_os_Parcel_readDouble(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        return parcel->readDouble();
-    }
-    return 0;
-}
-
-static jstring android_os_Parcel_readString(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        size_t len;
-        const char16_t* str = parcel->readString16Inplace(&len);
-        if (str) {
-            return env->NewString(str, len);
-        }
-        return NULL;
-    }
-    return NULL;
-}
-
-static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        return javaObjectForIBinder(env, parcel->readStrongBinder());
-    }
-    return NULL;
-}
-
-static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        int fd = parcel->readFileDescriptor();
-        if (fd < 0) return NULL;
-        fd = dup(fd);
-        if (fd < 0) return NULL;
-        return jniCreateFileDescriptor(env, fd);
-    }
-    return NULL;
-}
-
-static jobject android_os_Parcel_openFileDescriptor(JNIEnv* env, jobject clazz,
-                                                    jstring name, jint mode)
-{
-    if (name == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return NULL;
-    }
-    const jchar* str = env->GetStringCritical(name, 0);
-    if (str == NULL) {
-        // Whatever, whatever.
-        jniThrowException(env, "java/lang/IllegalStateException", NULL);
-        return NULL;
-    }
-    String8 name8(str, env->GetStringLength(name));
-    env->ReleaseStringCritical(name, str);
-    int flags=0;
-    switch (mode&0x30000000) {
-        case 0:
-        case 0x10000000:
-            flags = O_RDONLY;
-            break;
-        case 0x20000000:
-            flags = O_WRONLY;
-            break;
-        case 0x30000000:
-            flags = O_RDWR;
-            break;
-    }
-
-    if (mode&0x08000000) flags |= O_CREAT;
-    if (mode&0x04000000) flags |= O_TRUNC;
-    if (mode&0x02000000) flags |= O_APPEND;
-
-    int realMode = S_IRWXU|S_IRWXG;
-    if (mode&0x00000001) realMode |= S_IROTH;
-    if (mode&0x00000002) realMode |= S_IWOTH;
-
-    int fd = open(name8.string(), flags, realMode);
-    if (fd < 0) {
-        jniThrowException(env, "java/io/FileNotFoundException", strerror(errno));
-        return NULL;
-    }
-    jobject object = jniCreateFileDescriptor(env, fd);
-    if (object == NULL) {
-        close(fd);
-    }
-    return object;
-}
-
-static jobject android_os_Parcel_dupFileDescriptor(JNIEnv* env, jobject clazz, jobject orig)
-{
-    if (orig == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return NULL;
-    }
-    int origfd = jniGetFDFromFileDescriptor(env, orig);
-    if (origfd < 0) {
-        jniThrowException(env, "java/lang/IllegalArgumentException", "bad FileDescriptor");
-        return NULL;
-    }
-
-    int fd = dup(origfd);
-    if (fd < 0) {
-        jniThrowIOException(env, errno);
-        return NULL;
-    }
-    jobject object = jniCreateFileDescriptor(env, fd);
-    if (object == NULL) {
-        close(fd);
-    }
-    return object;
-}
-
-static void android_os_Parcel_closeFileDescriptor(JNIEnv* env, jobject clazz, jobject object)
-{
-    if (object == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-    int fd = jniGetFDFromFileDescriptor(env, object);
-    if (fd >= 0) {
-        jniSetFileDescriptorOfFD(env, object, -1);
-        //ALOGI("Closing ParcelFileDescriptor %d\n", fd);
-        close(fd);
-    }
-}
-
-static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, jobject clazz, jobject object)
-{
-    if (object == NULL) {
-        jniThrowNullPointerException(env, NULL);
-        return;
-    }
-    int fd = jniGetFDFromFileDescriptor(env, object);
-    if (fd >= 0) {
-        jniSetFileDescriptorOfFD(env, object, -1);
-    }
-}
-
-static void android_os_Parcel_freeBuffer(JNIEnv* env, jobject clazz)
-{
-    int32_t own = env->GetIntField(clazz, gParcelOffsets.mOwnObject);
-    if (own) {
-        Parcel* parcel = parcelForJavaObject(env, clazz);
-        if (parcel != NULL) {
-            //ALOGI("Parcel.freeBuffer() called for C++ Parcel %p\n", parcel);
-            parcel->freeData();
-        }
-    }
-}
-
-static void android_os_Parcel_init(JNIEnv* env, jobject clazz, jint parcelInt)
-{
-    Parcel* parcel = (Parcel*)parcelInt;
-    int own = 0;
-    if (!parcel) {
-        //ALOGI("Initializing obj %p: creating new Parcel\n", clazz);
-        own = 1;
-        parcel = new Parcel;
-    } else {
-        //ALOGI("Initializing obj %p: given existing Parcel %p\n", clazz, parcel);
-    }
-    if (parcel == NULL) {
-        jniThrowException(env, "java/lang/OutOfMemoryError", NULL);
-        return;
-    }
-    //ALOGI("Initializing obj %p from C++ Parcel %p, own=%d\n", clazz, parcel, own);
-    env->SetIntField(clazz, gParcelOffsets.mOwnObject, own);
-    env->SetIntField(clazz, gParcelOffsets.mObject, (int)parcel);
-}
-
-static void android_os_Parcel_destroy(JNIEnv* env, jobject clazz)
-{
-    int32_t own = env->GetIntField(clazz, gParcelOffsets.mOwnObject);
-    if (own) {
-        Parcel* parcel = parcelForJavaObject(env, clazz);
-        env->SetIntField(clazz, gParcelOffsets.mObject, 0);
-        //ALOGI("Destroying obj %p: deleting C++ Parcel %p\n", clazz, parcel);
-        delete parcel;
-    } else {
-        env->SetIntField(clazz, gParcelOffsets.mObject, 0);
-        //ALOGI("Destroying obj %p: leaving C++ Parcel %p\n", clazz);
-    }
-}
-
-static jbyteArray android_os_Parcel_marshall(JNIEnv* env, jobject clazz)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel == NULL) {
-       return NULL;
-    }
-
-    // do not marshall if there are binder objects in the parcel
-    if (parcel->objectsCount())
-    {
-        jniThrowException(env, "java/lang/RuntimeException", "Tried to marshall a Parcel that contained Binder objects.");
-        return NULL;
-    }
-
-    jbyteArray ret = env->NewByteArray(parcel->dataSize());
-
-    if (ret != NULL)
-    {
-        jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
-        if (array != NULL)
-        {
-            memcpy(array, parcel->data(), parcel->dataSize());
-            env->ReleasePrimitiveArrayCritical(ret, array, 0);
-        }
-    }
-
-    return ret;
-}
-
-static void android_os_Parcel_unmarshall(JNIEnv* env, jobject clazz, jbyteArray data, jint offset, jint length)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel == NULL || length < 0) {
-       return;
-    }
-
-    jbyte* array = (jbyte*)env->GetPrimitiveArrayCritical(data, 0);
-    if (array)
-    {
-        parcel->setDataSize(length);
-        parcel->setDataPosition(0);
-
-        void* raw = parcel->writeInplace(length);
-        memcpy(raw, (array + offset), length);
-
-        env->ReleasePrimitiveArrayCritical(data, array, 0);
-    }
-}
-
-static void android_os_Parcel_appendFrom(JNIEnv* env, jobject clazz, jobject parcel, jint offset, jint length)
-{
-    Parcel* thisParcel = parcelForJavaObject(env, clazz);
-    if (thisParcel == NULL) {
-       return;
-    }
-    Parcel* otherParcel = parcelForJavaObject(env, parcel);
-    if (otherParcel == NULL) {
-       return;
-    }
-
-    status_t err = thisParcel->appendFrom(otherParcel, offset, length);
-    if (err != NO_ERROR) {
-        signalExceptionForError(env, clazz, err);
-    }
-}
-
-static jboolean android_os_Parcel_hasFileDescriptors(JNIEnv* env, jobject clazz)
-{
-    jboolean ret = JNI_FALSE;
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        if (parcel->hasFileDescriptors()) {
-            ret = JNI_TRUE;
-        }
-    }
-    return ret;
-}
-
-static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, jobject clazz, jstring name)
-{
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        // In the current implementation, the token is just the serialized interface name that
-        // the caller expects to be invoking
-        const jchar* str = env->GetStringCritical(name, 0);
-        if (str != NULL) {
-            parcel->writeInterfaceToken(String16(str, env->GetStringLength(name)));
-            env->ReleaseStringCritical(name, str);
-        }
-    }
-}
-
-static void android_os_Parcel_enforceInterface(JNIEnv* env, jobject clazz, jstring name)
-{
-    jboolean ret = JNI_FALSE;
-
-    Parcel* parcel = parcelForJavaObject(env, clazz);
-    if (parcel != NULL) {
-        const jchar* str = env->GetStringCritical(name, 0);
-        if (str) {
-            IPCThreadState* threadState = IPCThreadState::self();
-            const int32_t oldPolicy = threadState->getStrictModePolicy();
-            const bool isValid = parcel->enforceInterface(
-                String16(str, env->GetStringLength(name)),
-                threadState);
-            env->ReleaseStringCritical(name, str);
-            if (isValid) {
-                const int32_t newPolicy = threadState->getStrictModePolicy();
-                if (oldPolicy != newPolicy) {
-                    // Need to keep the Java-level thread-local strict
-                    // mode policy in sync for the libcore
-                    // enforcements, which involves an upcall back
-                    // into Java.  (We can't modify the
-                    // Parcel.enforceInterface signature, as it's
-                    // pseudo-public, and used via AIDL
-                    // auto-generation...)
-                    set_dalvik_blockguard_policy(env, newPolicy);
-                }
-                return;     // everything was correct -> return silently
-            }
-        }
-    }
-
-    // all error conditions wind up here
-    jniThrowException(env, "java/lang/SecurityException",
-            "Binder invocation to an incorrect interface");
-}
-
-// ----------------------------------------------------------------------------
-
-static const JNINativeMethod gParcelMethods[] = {
-    {"dataSize",            "()I", (void*)android_os_Parcel_dataSize},
-    {"dataAvail",           "()I", (void*)android_os_Parcel_dataAvail},
-    {"dataPosition",        "()I", (void*)android_os_Parcel_dataPosition},
-    {"dataCapacity",        "()I", (void*)android_os_Parcel_dataCapacity},
-    {"setDataSize",         "(I)V", (void*)android_os_Parcel_setDataSize},
-    {"setDataPosition",     "(I)V", (void*)android_os_Parcel_setDataPosition},
-    {"setDataCapacity",     "(I)V", (void*)android_os_Parcel_setDataCapacity},
-    {"pushAllowFds",        "(Z)Z", (void*)android_os_Parcel_pushAllowFds},
-    {"restoreAllowFds",     "(Z)V", (void*)android_os_Parcel_restoreAllowFds},
-    {"writeNative",         "([BII)V", (void*)android_os_Parcel_writeNative},
-    {"writeInt",            "(I)V", (void*)android_os_Parcel_writeInt},
-    {"writeLong",           "(J)V", (void*)android_os_Parcel_writeLong},
-    {"writeFloat",          "(F)V", (void*)android_os_Parcel_writeFloat},
-    {"writeDouble",         "(D)V", (void*)android_os_Parcel_writeDouble},
-    {"writeString",         "(Ljava/lang/String;)V", (void*)android_os_Parcel_writeString},
-    {"writeStrongBinder",   "(Landroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
-    {"writeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
-    {"createByteArray",     "()[B", (void*)android_os_Parcel_createByteArray},
-    {"readInt",             "()I", (void*)android_os_Parcel_readInt},
-    {"readLong",            "()J", (void*)android_os_Parcel_readLong},
-    {"readFloat",           "()F", (void*)android_os_Parcel_readFloat},
-    {"readDouble",          "()D", (void*)android_os_Parcel_readDouble},
-    {"readString",          "()Ljava/lang/String;", (void*)android_os_Parcel_readString},
-    {"readStrongBinder",    "()Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
-    {"internalReadFileDescriptor",  "()Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor},
-    {"openFileDescriptor",  "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor},
-    {"dupFileDescriptor",   "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor},
-    {"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor},
-    {"clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor},
-    {"freeBuffer",          "()V", (void*)android_os_Parcel_freeBuffer},
-    {"init",                "(I)V", (void*)android_os_Parcel_init},
-    {"destroy",             "()V", (void*)android_os_Parcel_destroy},
-    {"marshall",            "()[B", (void*)android_os_Parcel_marshall},
-    {"unmarshall",          "([BII)V", (void*)android_os_Parcel_unmarshall},
-    {"appendFrom",          "(Landroid/os/Parcel;II)V", (void*)android_os_Parcel_appendFrom},
-    {"hasFileDescriptors",  "()Z", (void*)android_os_Parcel_hasFileDescriptors},
-    {"writeInterfaceToken", "(Ljava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken},
-    {"enforceInterface",    "(Ljava/lang/String;)V", (void*)android_os_Parcel_enforceInterface},
-};
-
-const char* const kParcelPathName = "android/os/Parcel";
-
-static int int_register_android_os_Parcel(JNIEnv* env)
-{
     jclass clazz;
 
     clazz = env->FindClass("android/util/Log");
@@ -1894,14 +1281,6 @@
     gParcelFileDescriptorOffsets.mConstructor
         = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
 
-    clazz = env->FindClass(kParcelPathName);
-    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.Parcel");
-
-    gParcelOffsets.mObject
-        = env->GetFieldID(clazz, "mObject", "I");
-    gParcelOffsets.mOwnObject
-        = env->GetFieldID(clazz, "mOwnObject", "I");
-
     clazz = env->FindClass("android/os/StrictMode");
     LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.StrictMode");
     gStrictModeCallbackOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
@@ -1910,20 +1289,5 @@
     LOG_FATAL_IF(gStrictModeCallbackOffsets.mCallback == NULL,
                  "Unable to find strict mode callback.");
 
-    return AndroidRuntime::registerNativeMethods(
-        env, kParcelPathName,
-        gParcelMethods, NELEM(gParcelMethods));
-}
-
-int register_android_os_Binder(JNIEnv* env)
-{
-    if (int_register_android_os_Binder(env) < 0)
-        return -1;
-    if (int_register_android_os_BinderInternal(env) < 0)
-        return -1;
-    if (int_register_android_os_BinderProxy(env) < 0)
-        return -1;
-    if (int_register_android_os_Parcel(env) < 0)
-        return -1;
     return 0;
 }
diff --git a/core/jni/android_util_Binder.h b/core/jni/android_util_Binder.h
index 0122691..ca320ef 100644
--- a/core/jni/android_util_Binder.h
+++ b/core/jni/android_util_Binder.h
@@ -15,6 +15,9 @@
 ** limitations under the License.
 */
 
+#ifndef ANDROID_UTIL_BINDER_H
+#define ANDROID_UTIL_BINDER_H
+
 #include <binder/IBinder.h>
 
 #include "jni.h"
@@ -25,10 +28,13 @@
 extern jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val);
 extern sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj);
 
-// Conversion from Java Parcel Object to C++ Parcel instance.
-// Note: does not type checking; must guarantee jobject is a Java Parcel
-extern Parcel* parcelForJavaObject(JNIEnv* env, jobject obj);
-
 extern jobject newParcelFileDescriptor(JNIEnv* env, jobject fileDesc);
 
+extern void set_dalvik_blockguard_policy(JNIEnv* env, jint strict_policy);
+
+extern void signalExceptionForError(JNIEnv* env, jobject obj, status_t err,
+        bool canThrowRemoteException = false);
+
 }
+
+#endif
diff --git a/core/jni/android_view_InputChannel.cpp b/core/jni/android_view_InputChannel.cpp
index 8350e73..9c44a59 100644
--- a/core/jni/android_view_InputChannel.cpp
+++ b/core/jni/android_view_InputChannel.cpp
@@ -23,6 +23,7 @@
 #include <utils/Log.h>
 #include <androidfw/InputTransport.h>
 #include "android_view_InputChannel.h"
+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 
 namespace android {
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index 0fb1b17..e69fb74 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -21,6 +21,7 @@
 #include <android_runtime/AndroidRuntime.h>
 #include <utils/Log.h>
 #include <androidfw/Input.h>
+#include "android_os_Parcel.h"
 #include "android_view_MotionEvent.h"
 #include "android_util_Binder.h"
 #include "android/graphics/Matrix.h"
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index c387752..30d4e20 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -888,7 +888,7 @@
     no.native_region = env->GetFieldID(region, "mNativeRegion", "I");
 
     jclass parcel = env->FindClass("android/os/Parcel");
-    no.native_parcel = env->GetFieldID(parcel, "mObject", "I");
+    no.native_parcel = env->GetFieldID(parcel, "mNativePtr", "I");
 
     jclass rect = env->FindClass("android/graphics/Rect");
     ro.l = env->GetFieldID(rect, "left", "I");
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 1a0d479..8e36948 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -671,7 +671,7 @@
     <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Se superó el máximo de intentos permitido para el desbloqueo facial del dispositivo."</string>
     <string name="lockscreen_plugged_in" msgid="8057762828355572315">"Cargando <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Cargada."</string>
-    <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="lockscreen_low_battery" msgid="1482873981919249740">"Conecta tu cargador."</string>
     <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"No hay tarjeta SIM."</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"No hay tarjeta SIM en el tablet."</string>
@@ -768,8 +768,8 @@
     <string name="permdesc_serialPort" msgid="2991639985224598193">"Permite acceder a puertos serie a través de la API SerialManager."</string>
     <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"acceder a proveedores externamente"</string>
     <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Permite acceder a los proveedores de contenido desde la interfaz. Las aplicaciones normales nunca deberían necesitarlo."</string>
-    <string name="permlab_updateLock" msgid="3527558366616680889">"desalentar a las actualizaciones automáticas de dispositivos"</string>
-    <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite a su titular a ofrecer información al sistema acerca de cuándo sería un buen momento para reiniciar el sistema no interactivo para actualizar el dispositivo."</string>
+    <string name="permlab_updateLock" msgid="3527558366616680889">"no realizar actualizaciones automáticas"</string>
+    <string name="permdesc_updateLock" msgid="1655625832166778492">"Permite a su propietario ofrecer información al sistema acerca de cuándo sería adecuado reiniciar el sistema de forma no interactiva y actualizar el dispositivo."</string>
     <string name="save_password_message" msgid="767344687139195790">"¿Quieres recordar esta contraseña en el navegador?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Ahora no."</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Recuerda"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 5bb95af..385bebb 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -769,7 +769,7 @@
     <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"akses pembekal kandungan secara luaran"</string>
     <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Membolehkan pemegang mengakses pembekal kandungan dari luar. Tidak akan sekali-kali diperlukan untuk apl biasa."</string>
     <string name="permlab_updateLock" msgid="3527558366616680889">"tidak menggalakkan kemas kini peranti automatik"</string>
-    <string name="permdesc_updateLock" msgid="1655625832166778492">"Membenarkan pemegang untuk menawarkan maklumat kepada sistem tentang bila akan menjadi masa yang baik untuk but semula bukan interaktif untuk menaik taraf peranti."</string>
+    <string name="permdesc_updateLock" msgid="1655625832166778492">"Membenarkan aplikasi untuk menawarkan maklumat kepada sistem tentang bila akan menjadi masa yang baik untuk but semula bukan interaktif untuk menaik taraf peranti."</string>
     <string name="save_password_message" msgid="767344687139195790">"Adakah anda mahu penyemak imbas mengingati kata laluan ini?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Bukan sekarang"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Ingat"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 6a9f846..e70a664 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -671,7 +671,7 @@
     <string name="faceunlock_multiple_failures" msgid="754137583022792429">"Du har overskredet grensen for opplåsingsforsøk med Ansiktslås"</string>
     <string name="lockscreen_plugged_in" msgid="8057762828355572315">"Lader, <xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="lockscreen_charged" msgid="4938930459620989972">"Fullt ladet"</string>
-    <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g><xliff:g id="PERCENT">%%</xliff:g>"</string>
+    <string name="lockscreen_battery_short" msgid="4477264849386850266">"<xliff:g id="NUMBER">%d</xliff:g> <xliff:g id="PERCENT">%%</xliff:g>"</string>
     <string name="lockscreen_low_battery" msgid="1482873981919249740">"Koble til en batterilader."</string>
     <string name="lockscreen_missing_sim_message_short" msgid="7381499217732227295">"Mangler SIM-kort."</string>
     <string name="lockscreen_missing_sim_message" product="tablet" msgid="151659196095791474">"Nettbrettet mangler SIM-kort."</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 286c32c..96dcfa0 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -768,8 +768,8 @@
     <string name="permdesc_serialPort" msgid="2991639985224598193">"Umożliwia posiadaczowi dostęp do portów szeregowych przy użyciu interfejsu API narzędzia SerialManager."</string>
     <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"Dostęp do dostawców treści z zewnątrz"</string>
     <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Pozwala na dostęp do dostawców treści z powłoki. To uprawnienie nie powinno być potrzebne zwykłym aplikacjom."</string>
-    <string name="permlab_updateLock" msgid="3527558366616680889">"odradź automatyczne aktualizacje urządzenia"</string>
-    <string name="permdesc_updateLock" msgid="1655625832166778492">"Umożliwia posiadaczowi poinformowanie systemu, kiedy będzie dobry moment na nieinteraktywne uruchomienie ponowne wymagane do uaktualnienia urządzenia."</string>
+    <string name="permlab_updateLock" msgid="3527558366616680889">"odradzanie automatycznych aktualizacji urządzenia"</string>
+    <string name="permdesc_updateLock" msgid="1655625832166778492">"Umożliwia posiadaczowi poinformowanie systemu, kiedy będzie dobry moment na ponowne uruchomienie wymagane do uaktualnienia urządzenia."</string>
     <string name="save_password_message" msgid="767344687139195790">"Czy chcesz, aby zapamiętać to hasło w przeglądarce?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Nie teraz"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Zapamiętaj"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 286e6d6..13053ab 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -768,8 +768,8 @@
     <string name="permdesc_serialPort" msgid="2991639985224598193">"Inaruhusu mmiliki kufikia vituo tambulishi kwa kutumia KisimamiziTambulishi cha API."</string>
     <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"fikia watoa huduma nje"</string>
     <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"Inaruhusu mmiliki kufikia watoa huduma  kutoka kwa onyesho. Haifai kuhitajika kamwe kwa programu za kawaida."</string>
-    <string name="permlab_updateLock" msgid="3527558366616680889">"katisha tamaa usasishaji kifaa kiotomatiki"</string>
-    <string name="permdesc_updateLock" msgid="1655625832166778492">"Inaruhusu mmiliki kutoa maelezo kwa mfumo kuhusu ni lini itakuwa wakati mzuri wa uwashaji upya usiotagusana ili kuboresha kifaa."</string>
+    <string name="permlab_updateLock" msgid="3527558366616680889">"pinga usasishaji kifaa kiotomatiki"</string>
+    <string name="permdesc_updateLock" msgid="1655625832166778492">"Inaruhusu mmiliki kutoa maelezo kwa mfumo kuhusu ni lini itakuwa wakati mzuri wa uwashaji upya usiotagusana ili kupandisha gredi kifaa."</string>
     <string name="save_password_message" msgid="767344687139195790">"Unataka kuvinjari ili ukumbuke nenosiri hili?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"Si Sasa"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"Kumbuka"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 24b726d..2664ad3 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -769,7 +769,7 @@
     <string name="permlab_accessContentProvidersExternally" msgid="5077774297943409285">"从外部访问内容提供程序"</string>
     <string name="permdesc_accessContentProvidersExternally" msgid="4544346486697853685">"允许持有者通过界面访问内容提供程序。普通应用绝不需要此权限。"</string>
     <string name="permlab_updateLock" msgid="3527558366616680889">"阻止自动设备更新"</string>
-    <string name="permdesc_updateLock" msgid="1655625832166778492">"允许持有人向系统提供相关信息,以确定什么时候适合执行非交互式重新启动来升级设备。"</string>
+    <string name="permdesc_updateLock" msgid="1655625832166778492">"允许应用向系统提供相关信息,以确定何时适合执行非交互式重启以升级设备。"</string>
     <string name="save_password_message" msgid="767344687139195790">"是否希望浏览器记住此密码?"</string>
     <string name="save_password_notnow" msgid="6389675316706699758">"暂不保存"</string>
     <string name="save_password_remember" msgid="6491879678996749466">"记住"</string>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c8df649..373e88c 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3194,30 +3194,6 @@
     <!-- Description of the button to decrement the DatePicker's year value. [CHAR LIMIT=NONE] -->
     <string name="date_picker_decrement_year_button">Decrement year</string>
 
-    <!-- CheckBox - accessibility support -->
-    <!-- Description of the checked state of a CheckBox. [CHAR LIMIT=NONE] -->
-    <string name="checkbox_checked">checked</string>
-    <!-- Description of the not checked state of a CheckBox. [CHAR LIMIT=NONE] -->
-    <string name="checkbox_not_checked">not checked</string>
-
-    <!-- RadioButton/CheckedTextView - accessibility support -->
-    <!-- Description of the selected state of a RadioButton. [CHAR LIMIT=NONE] -->
-    <string name="radiobutton_selected">selected</string>
-    <!-- Description of the not selected state of a RadioButton. [CHAR LIMIT=NONE] -->
-    <string name="radiobutton_not_selected">not selected</string>
-
-    <!-- Switch - accessibility support -->
-    <!-- Description of the on state of a Switch. [CHAR LIMIT=NONE] -->
-    <string name="switch_on">on</string>
-    <!-- Description of the off state of a Switch. [CHAR LIMIT=NONE] -->
-    <string name="switch_off">off</string>
-
-    <!-- ToggleButton - accessibility support -->
-    <!-- Description of the pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
-    <string name="togglebutton_pressed">pressed</string>
-    <!-- Description of the not pressed state of a ToggleButton. [CHAR LIMIT=NONE] -->
-    <string name="togglebutton_not_pressed">not pressed</string>
-
     <!-- KeyboardView - accessibility support -->
     <!-- Description of the Alt button in a KeyboardView. [CHAR LIMIT=NONE] -->
     <string name="keyboardview_keycode_alt">Alt</string>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
index 259f15f..19aa77b 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/ConnectivityManagerTestActivity.java
@@ -241,6 +241,7 @@
         mCM = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
         // Get an instance of WifiManager
         mWifiManager =(WifiManager)getSystemService(Context.WIFI_SERVICE);
+        mContext = this;
         mChannel = mWifiManager.initialize(mContext, mContext.getMainLooper(), null);
 
         initializeNetworkStates();
diff --git a/core/tests/coretests/src/android/content/SyncOperationTest.java b/core/tests/coretests/src/android/content/SyncOperationTest.java
index 37e948d..910c721 100644
--- a/core/tests/coretests/src/android/content/SyncOperationTest.java
+++ b/core/tests/coretests/src/android/content/SyncOperationTest.java
@@ -41,7 +41,7 @@
         Bundle b2 = new Bundle();
         b2.putBoolean("b2", true);
 
-        SyncOperation op1 = new SyncOperation(account1,
+        SyncOperation op1 = new SyncOperation(account1, 0,
                 1,
                 "authority1",
                 b1,
@@ -51,7 +51,7 @@
                 false);
 
         // Same as op1 but different time infos
-        SyncOperation op2 = new SyncOperation(account1,
+        SyncOperation op2 = new SyncOperation(account1, 0,
                 1,
                 "authority1",
                 b1,
@@ -61,7 +61,7 @@
                 false);
 
         // Same as op1 but different authority
-        SyncOperation op3 = new SyncOperation(account1,
+        SyncOperation op3 = new SyncOperation(account1, 0,
                 1,
                 "authority2",
                 b1,
@@ -71,7 +71,7 @@
                 false);
 
         // Same as op1 but different account
-        SyncOperation op4 = new SyncOperation(account2,
+        SyncOperation op4 = new SyncOperation(account2, 0,
                 1,
                 "authority1",
                 b1,
@@ -81,7 +81,7 @@
                 false);
 
         // Same as op1 but different bundle
-        SyncOperation op5 = new SyncOperation(account1,
+        SyncOperation op5 = new SyncOperation(account1, 0,
                 1,
                 "authority1",
                 b2,
diff --git a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
index ae41409..96f313a 100644
--- a/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
+++ b/core/tests/coretests/src/android/content/SyncStorageEngineTest.java
@@ -20,6 +20,7 @@
 
 import android.accounts.Account;
 import android.os.Bundle;
+import android.os.Debug;
 import android.test.AndroidTestCase;
 import android.test.RenamingDelegatingContext;
 import android.test.mock.MockContentResolver;
@@ -34,6 +35,10 @@
 
 public class SyncStorageEngineTest extends AndroidTestCase {
 
+    private File getSyncDir() {
+        return new File(new File(getContext().getFilesDir(), "system"), "sync");
+    }
+
     /**
      * Test that we handle the case of a history row being old enough to purge before the
      * correcponding sync is finished. This can happen if the clock changes while we are syncing.
@@ -52,7 +57,7 @@
 
         long time0 = 1000;
         long historyId = engine.insertStartSyncEvent(
-                account, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
+                account, 0, authority, time0, SyncStorageEngine.SOURCE_LOCAL);
         long time1 = time0 + SyncStorageEngine.MILLIS_IN_4WEEKS * 2;
         engine.stopSyncEvent(historyId, time1 - time0, "yay", 0, 0);
     }
@@ -82,38 +87,47 @@
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
                 new TestContext(mockResolver, getContext()));
 
-        removePeriodicSyncs(engine, account1, authority);
-        removePeriodicSyncs(engine, account2, authority);
+        removePeriodicSyncs(engine, account1, 0, authority);
+        removePeriodicSyncs(engine, account2, 0, authority);
+        removePeriodicSyncs(engine, account1, 1, authority);
 
         // this should add two distinct periodic syncs for account1 and one for account2
-        engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
-        engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
-        engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
-        engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
+        engine.addPeriodicSync(sync1.account, 0, sync1.authority, sync1.extras, sync1.period);
+        engine.addPeriodicSync(sync2.account, 0, sync2.authority, sync2.extras, sync2.period);
+        engine.addPeriodicSync(sync3.account, 0, sync3.authority, sync3.extras, sync3.period);
+        engine.addPeriodicSync(sync4.account, 0, sync4.authority, sync4.extras, sync4.period);
+        // add a second user
+        engine.addPeriodicSync(sync2.account, 1, sync2.authority, sync2.extras, sync2.period);
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority);
 
         assertEquals(2, syncs.size());
 
         assertEquals(sync1, syncs.get(0));
         assertEquals(sync3, syncs.get(1));
 
-        engine.removePeriodicSync(sync1.account, sync1.authority, sync1.extras);
+        engine.removePeriodicSync(sync1.account, 0, sync1.authority, sync1.extras);
 
-        syncs = engine.getPeriodicSyncs(account1, authority);
+        syncs = engine.getPeriodicSyncs(account1, 0, authority);
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account2, authority);
+        syncs = engine.getPeriodicSyncs(account2, 0, authority);
         assertEquals(1, syncs.size());
         assertEquals(sync4, syncs.get(0));
+
+        syncs = engine.getPeriodicSyncs(sync2.account, 1, sync2.authority);
+        assertEquals(1, syncs.size());
+        assertEquals(sync2, syncs.get(0));
     }
 
-    private void removePeriodicSyncs(SyncStorageEngine engine, Account account, String authority) {
-        engine.setIsSyncable(account, authority, engine.getIsSyncable(account, authority));
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority);
+    private void removePeriodicSyncs(SyncStorageEngine engine, Account account, int userId,
+            String authority) {
+        engine.setIsSyncable(account, userId, authority,
+                engine.getIsSyncable(account, 0, authority));
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, userId, authority);
         for (PeriodicSync sync : syncs) {
-            engine.removePeriodicSync(sync.account, sync.authority, sync.extras);
+            engine.removePeriodicSync(sync.account, userId, sync.authority, sync.extras);
         }
     }
 
@@ -147,57 +161,57 @@
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
                 new TestContext(mockResolver, getContext()));
 
-        removePeriodicSyncs(engine, account1, authority1);
-        removePeriodicSyncs(engine, account2, authority1);
-        removePeriodicSyncs(engine, account1, authority2);
-        removePeriodicSyncs(engine, account2, authority2);
+        removePeriodicSyncs(engine, account1, 0, authority1);
+        removePeriodicSyncs(engine, account2, 0, authority1);
+        removePeriodicSyncs(engine, account1, 0, authority2);
+        removePeriodicSyncs(engine, account2, 0, authority2);
 
-        engine.setMasterSyncAutomatically(false);
+        engine.setMasterSyncAutomatically(false, 0);
 
-        engine.setIsSyncable(account1, authority1, 1);
-        engine.setSyncAutomatically(account1, authority1, true);
+        engine.setIsSyncable(account1, 0, authority1, 1);
+        engine.setSyncAutomatically(account1, 0, authority1, true);
 
-        engine.setIsSyncable(account2, authority1, 1);
-        engine.setSyncAutomatically(account2, authority1, true);
+        engine.setIsSyncable(account2, 0, authority1, 1);
+        engine.setSyncAutomatically(account2, 0, authority1, true);
 
-        engine.setIsSyncable(account1, authority2, 1);
-        engine.setSyncAutomatically(account1, authority2, false);
+        engine.setIsSyncable(account1, 0, authority2, 1);
+        engine.setSyncAutomatically(account1, 0, authority2, false);
 
-        engine.setIsSyncable(account2, authority2, 0);
-        engine.setSyncAutomatically(account2, authority2, true);
+        engine.setIsSyncable(account2, 0, authority2, 0);
+        engine.setSyncAutomatically(account2, 0, authority2, true);
 
-        engine.addPeriodicSync(sync1.account, sync1.authority, sync1.extras, sync1.period);
-        engine.addPeriodicSync(sync2.account, sync2.authority, sync2.extras, sync2.period);
-        engine.addPeriodicSync(sync3.account, sync3.authority, sync3.extras, sync3.period);
-        engine.addPeriodicSync(sync4.account, sync4.authority, sync4.extras, sync4.period);
-        engine.addPeriodicSync(sync5.account, sync5.authority, sync5.extras, sync5.period);
+        engine.addPeriodicSync(sync1.account, 0, sync1.authority, sync1.extras, sync1.period);
+        engine.addPeriodicSync(sync2.account, 0, sync2.authority, sync2.extras, sync2.period);
+        engine.addPeriodicSync(sync3.account, 0, sync3.authority, sync3.extras, sync3.period);
+        engine.addPeriodicSync(sync4.account, 0, sync4.authority, sync4.extras, sync4.period);
+        engine.addPeriodicSync(sync5.account, 0, sync5.authority, sync5.extras, sync5.period);
 
         engine.writeAllState();
         engine.clearAndReadState();
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, authority1);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority1);
         assertEquals(2, syncs.size());
         assertEquals(sync1, syncs.get(0));
         assertEquals(sync2, syncs.get(1));
 
-        syncs = engine.getPeriodicSyncs(account1, authority2);
+        syncs = engine.getPeriodicSyncs(account1, 0, authority2);
         assertEquals(2, syncs.size());
         assertEquals(sync3, syncs.get(0));
         assertEquals(sync4, syncs.get(1));
 
-        syncs = engine.getPeriodicSyncs(account2, authority1);
+        syncs = engine.getPeriodicSyncs(account2, 0, authority1);
         assertEquals(1, syncs.size());
         assertEquals(sync5, syncs.get(0));
 
-        assertEquals(true, engine.getSyncAutomatically(account1, authority1));
-        assertEquals(true, engine.getSyncAutomatically(account2, authority1));
-        assertEquals(false, engine.getSyncAutomatically(account1, authority2));
-        assertEquals(true, engine.getSyncAutomatically(account2, authority2));
+        assertEquals(true, engine.getSyncAutomatically(account1, 0, authority1));
+        assertEquals(true, engine.getSyncAutomatically(account2, 0, authority1));
+        assertEquals(false, engine.getSyncAutomatically(account1, 0, authority2));
+        assertEquals(true, engine.getSyncAutomatically(account2, 0, authority2));
 
-        assertEquals(1, engine.getIsSyncable(account1, authority1));
-        assertEquals(1, engine.getIsSyncable(account2, authority1));
-        assertEquals(1, engine.getIsSyncable(account1, authority2));
-        assertEquals(0, engine.getIsSyncable(account2, authority2));
+        assertEquals(1, engine.getIsSyncable(account1, 0, authority1));
+        assertEquals(1, engine.getIsSyncable(account2, 0, authority1));
+        assertEquals(1, engine.getIsSyncable(account1, 0, authority2));
+        assertEquals(0, engine.getIsSyncable(account2, 0, authority2));
     }
 
     @MediumTest
@@ -220,12 +234,13 @@
 
         byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
                 + "<accounts>\n"
-                + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
-                + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
-                + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+                + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+                + "<authority id=\"1\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
+                + "<authority id=\"2\"            account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+                + "<authority id=\"3\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
                 + "</accounts>\n").getBytes();
 
-        File syncDir = new File(new File(testContext.getFilesDir(), "system"), "sync");
+        File syncDir = getSyncDir();
         syncDir.mkdirs();
         AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
         FileOutputStream fos = accountInfoFile.startWrite();
@@ -234,15 +249,19 @@
 
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
-        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, authority1);
+        List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, 0, authority1);
         assertEquals(1, syncs.size());
         assertEquals(sync1, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, authority2);
+        syncs = engine.getPeriodicSyncs(account, 0, authority2);
         assertEquals(1, syncs.size());
         assertEquals(sync2, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, authority3);
+        syncs = engine.getPeriodicSyncs(account, 0, authority3);
+        assertEquals(1, syncs.size());
+        assertEquals(sync3, syncs.get(0));
+
+        syncs = engine.getPeriodicSyncs(account, 1, authority3);
         assertEquals(1, syncs.size());
         assertEquals(sync3, syncs.get(0));
 
@@ -260,13 +279,13 @@
 
         engine.clearAndReadState();
 
-        syncs = engine.getPeriodicSyncs(account, authority1);
+        syncs = engine.getPeriodicSyncs(account, 0, authority1);
         assertEquals(0, syncs.size());
 
-        syncs = engine.getPeriodicSyncs(account, authority2);
+        syncs = engine.getPeriodicSyncs(account, 0, authority2);
         assertEquals(0, syncs.size());
 
-        syncs = engine.getPeriodicSyncs(account, authority3);
+        syncs = engine.getPeriodicSyncs(account, 0, authority3);
         assertEquals(0, syncs.size());
 
         accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
@@ -289,20 +308,48 @@
 
         engine.clearAndReadState();
 
-        syncs = engine.getPeriodicSyncs(account, authority1);
+        syncs = engine.getPeriodicSyncs(account, 0, authority1);
         assertEquals(1, syncs.size());
         assertEquals(sync1s, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, authority2);
+        syncs = engine.getPeriodicSyncs(account, 0, authority2);
         assertEquals(1, syncs.size());
         assertEquals(sync2s, syncs.get(0));
 
-        syncs = engine.getPeriodicSyncs(account, authority3);
+        syncs = engine.getPeriodicSyncs(account, 0, authority3);
         assertEquals(1, syncs.size());
         assertEquals(sync3s, syncs.get(0));
     }
 
     @MediumTest
+    public void testListenForTicklesParsing() throws Exception {
+        byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+                + "<accounts>\n"
+                + "<listenForTickles user=\"0\" enabled=\"false\" />"
+                + "<listenForTickles user=\"1\" enabled=\"true\" />"
+                + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+                + "<authority id=\"1\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+                + "</accounts>\n").getBytes();
+
+        MockContentResolver mockResolver = new MockContentResolver();
+        final TestContext testContext = new TestContext(mockResolver, getContext());
+
+        File syncDir = getSyncDir();
+        syncDir.mkdirs();
+        AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+        FileOutputStream fos = accountInfoFile.startWrite();
+        fos.write(accountsFileData);
+        accountInfoFile.finishWrite(fos);
+
+        SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+        assertEquals(false, engine.getMasterSyncAutomatically(0));
+        assertEquals(true, engine.getMasterSyncAutomatically(1));
+        assertEquals(true, engine.getMasterSyncAutomatically(2));
+
+    }
+
+    @MediumTest
     public void testAuthorityRenaming() throws Exception {
         final Account account1 = new Account("acc1", "type1");
         final Account account2 = new Account("acc2", "type2");
@@ -339,17 +386,17 @@
 
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
-        assertEquals(false, engine.getSyncAutomatically(account1, authorityContacts));
-        assertEquals(false, engine.getSyncAutomatically(account1, authorityCalendar));
-        assertEquals(true, engine.getSyncAutomatically(account1, authorityOther));
-        assertEquals(true, engine.getSyncAutomatically(account1, authorityContactsNew));
-        assertEquals(true, engine.getSyncAutomatically(account1, authorityCalendarNew));
+        assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityContacts));
+        assertEquals(false, engine.getSyncAutomatically(account1, 0, authorityCalendar));
+        assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityOther));
+        assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityContactsNew));
+        assertEquals(true, engine.getSyncAutomatically(account1, 0, authorityCalendarNew));
 
-        assertEquals(false, engine.getSyncAutomatically(account2, authorityContacts));
-        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendar));
-        assertEquals(true, engine.getSyncAutomatically(account2, authorityOther));
-        assertEquals(false, engine.getSyncAutomatically(account2, authorityContactsNew));
-        assertEquals(false, engine.getSyncAutomatically(account2, authorityCalendarNew));
+        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContacts));
+        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendar));
+        assertEquals(true, engine.getSyncAutomatically(account2, 0, authorityOther));
+        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityContactsNew));
+        assertEquals(false, engine.getSyncAutomatically(account2, 0, authorityCalendarNew));
     }
 
     @SmallTest
@@ -379,10 +426,10 @@
 
         SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
 
-        assertEquals(-1, engine.getIsSyncable(account, "other1"));
-        assertEquals(1, engine.getIsSyncable(account, "other2"));
-        assertEquals(0, engine.getIsSyncable(account, "other3"));
-        assertEquals(1, engine.getIsSyncable(account, "other4"));
+        assertEquals(-1, engine.getIsSyncable(account, 0, "other1"));
+        assertEquals(1, engine.getIsSyncable(account, 0, "other2"));
+        assertEquals(0, engine.getIsSyncable(account, 0, "other3"));
+        assertEquals(1, engine.getIsSyncable(account, 0, "other4"));
     }
 }
 
diff --git a/include/media/AudioSystem.h b/include/media/AudioSystem.h
index d54ab35..cc0a594 100644
--- a/include/media/AudioSystem.h
+++ b/include/media/AudioSystem.h
@@ -185,7 +185,7 @@
                                          audio_devices_t device);
 
     static uint32_t getStrategyForStream(audio_stream_type_t stream);
-    static uint32_t getDevicesForStream(audio_stream_type_t stream);
+    static audio_devices_t getDevicesForStream(audio_stream_type_t stream);
 
     static audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc);
     static status_t registerEffect(effect_descriptor_t *desc,
diff --git a/include/media/IAudioPolicyService.h b/include/media/IAudioPolicyService.h
index bdd7747..04c927a 100644
--- a/include/media/IAudioPolicyService.h
+++ b/include/media/IAudioPolicyService.h
@@ -79,7 +79,7 @@
                                           int *index,
                                           audio_devices_t device) = 0;
     virtual uint32_t getStrategyForStream(audio_stream_type_t stream) = 0;
-    virtual uint32_t getDevicesForStream(audio_stream_type_t stream) = 0;
+    virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream) = 0;
     virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc) = 0;
     virtual status_t registerEffect(effect_descriptor_t *desc,
                                     audio_io_handle_t io,
diff --git a/libs/rs/RenderScript.cpp b/libs/rs/RenderScript.cpp
index 0b42055..217b921 100644
--- a/libs/rs/RenderScript.cpp
+++ b/libs/rs/RenderScript.cpp
@@ -21,6 +21,7 @@
 #include <string.h>
 
 #include "RenderScript.h"
+#include "rs.h"
 
 bool RenderScript::gInitialized = false;
 pthread_mutex_t RenderScript::gInitMutex = PTHREAD_MUTEX_INITIALIZER;
diff --git a/libs/rs/RenderScript.h b/libs/rs/RenderScript.h
index 0eb6a6d..5ad76e2 100644
--- a/libs/rs/RenderScript.h
+++ b/libs/rs/RenderScript.h
@@ -22,7 +22,7 @@
 #include <utils/String8.h>
 #include <utils/Vector.h>
 
-#include "rs.h"
+#include "rsDefines.h"
 
 class Element;
 class Type;
diff --git a/libs/rs/rsComponent.h b/libs/rs/rsComponent.h
index 8629d0d..a2e8c0f 100644
--- a/libs/rs/rsComponent.h
+++ b/libs/rs/rsComponent.h
@@ -18,6 +18,7 @@
 #define ANDROID_COMPONENT_H
 
 #include "rsUtils.h"
+#include "rsDefines.h"
 
 // ---------------------------------------------------------------------------
 namespace android {
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 0f3cea7..748b72a 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include "rs.h"
 #include "rsDevice.h"
 #include "rsContext.h"
 #include "rsThreadIO.h"
diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h
index 4b6b460..b86d3bc 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -19,6 +19,7 @@
 
 #include "rsComponent.h"
 #include "rsUtils.h"
+#include "rsDefines.h"
 #include "rsObjectBase.h"
 
 // ---------------------------------------------------------------------------
diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp
index ac658c8..173b9a5 100644
--- a/libs/rs/rsFileA3D.cpp
+++ b/libs/rs/rsFileA3D.cpp
@@ -20,7 +20,7 @@
 
 #include "rsMesh.h"
 #include "rsAnimation.h"
-
+#include "rs.h"
 
 using namespace android;
 using namespace android::renderscript;
diff --git a/libs/rs/rsFileA3D.h b/libs/rs/rsFileA3D.h
index baf81de5..08062c6 100644
--- a/libs/rs/rsFileA3D.h
+++ b/libs/rs/rsFileA3D.h
@@ -17,7 +17,6 @@
 #ifndef ANDROID_RS_FILE_A3D_H
 #define ANDROID_RS_FILE_A3D_H
 
-#include "rs.h"
 #include "rsMesh.h"
 
 #include <androidfw/Asset.h>
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index c4276cf..2bf9a75 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -16,7 +16,7 @@
  */
 
 #include "rsContext.h"
-
+#include "rs.h"
 #include "rsFont.h"
 #include "rsProgramFragment.h"
 #include <cutils/properties.h>
diff --git a/libs/rs/rsFont.h b/libs/rs/rsFont.h
index 88c4795..2bd30b7 100644
--- a/libs/rs/rsFont.h
+++ b/libs/rs/rsFont.h
@@ -17,7 +17,6 @@
 #ifndef ANDROID_RS_FONT_H
 #define ANDROID_RS_FONT_H
 
-#include "rs.h"
 #include "rsStream.h"
 #include <utils/String8.h>
 #include <utils/Vector.h>
diff --git a/libs/rs/rsMesh.cpp b/libs/rs/rsMesh.cpp
index 67c7299..f9dbc33 100644
--- a/libs/rs/rsMesh.cpp
+++ b/libs/rs/rsMesh.cpp
@@ -15,6 +15,7 @@
  */
 
 #include "rsContext.h"
+#include "rs.h"
 
 using namespace android;
 using namespace android::renderscript;
diff --git a/libs/rs/rsMesh.h b/libs/rs/rsMesh.h
index 8eea427..7ca63cf 100644
--- a/libs/rs/rsMesh.h
+++ b/libs/rs/rsMesh.h
@@ -18,7 +18,7 @@
 #define ANDROID_RS_MESH_H
 
 
-#include "rs.h"
+#include "rsObjectBase.h"
 
 // ---------------------------------------------------------------------------
 namespace android {
diff --git a/libs/rs/rsObjectBase.h b/libs/rs/rsObjectBase.h
index d9f5f3b..586da19 100644
--- a/libs/rs/rsObjectBase.h
+++ b/libs/rs/rsObjectBase.h
@@ -18,6 +18,7 @@
 #define ANDROID_RS_OBJECT_BASE_H
 
 #include "rsUtils.h"
+#include "rsDefines.h"
 
 #define RS_OBJECT_DEBUG 0
 
diff --git a/libs/rs/rsSampler.cpp b/libs/rs/rsSampler.cpp
index 5fc64a4..c7180bd 100644
--- a/libs/rs/rsSampler.cpp
+++ b/libs/rs/rsSampler.cpp
@@ -16,7 +16,7 @@
 
 #include "rsContext.h"
 #include "rsSampler.h"
-
+#include "rs.h"
 
 using namespace android;
 using namespace android::renderscript;
diff --git a/libs/rs/rsSampler.h b/libs/rs/rsSampler.h
index 013e4ca..dea4cb6 100644
--- a/libs/rs/rsSampler.h
+++ b/libs/rs/rsSampler.h
@@ -18,7 +18,6 @@
 #define ANDROID_RS_SAMPLER_H
 
 #include "rsAllocation.h"
-#include "rs.h"
 
 // ---------------------------------------------------------------------------
 namespace android {
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 70ab7b7..9ac553e 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -20,9 +20,8 @@
 using namespace android::renderscript;
 
 Type::Type(Context *rsc) : ObjectBase(rsc) {
-    mLODs = 0;
-    mLODCount = 0;
-    clear();
+    memset(&mHal, 0, sizeof(mHal));
+    mDimLOD = false;
 }
 
 void Type::preDestroy() const {
@@ -35,16 +34,15 @@
 }
 
 Type::~Type() {
-    if (mLODs) {
-        delete [] mLODs;
-        mLODs = NULL;
-    }
+    clear();
 }
 
 void Type::clear() {
-    if (mLODs) {
-        delete [] mLODs;
-        mLODs = NULL;
+    if (mHal.state.lodCount) {
+        delete [] mHal.state.lodDimX;
+        delete [] mHal.state.lodDimY;
+        delete [] mHal.state.lodDimZ;
+        delete [] mHal.state.lodOffset;
     }
     mElement.clear();
     memset(&mHal, 0, sizeof(mHal));
@@ -63,33 +61,39 @@
 }
 
 void Type::compute() {
-    uint32_t oldLODCount = mLODCount;
-    if (mHal.state.dimLOD) {
+    uint32_t oldLODCount = mHal.state.lodCount;
+    if (mDimLOD) {
         uint32_t l2x = rsFindHighBit(mHal.state.dimX) + 1;
         uint32_t l2y = rsFindHighBit(mHal.state.dimY) + 1;
         uint32_t l2z = rsFindHighBit(mHal.state.dimZ) + 1;
 
-        mLODCount = rsMax(l2x, l2y);
-        mLODCount = rsMax(mLODCount, l2z);
+        mHal.state.lodCount = rsMax(l2x, l2y);
+        mHal.state.lodCount = rsMax(mHal.state.lodCount, l2z);
     } else {
-        mLODCount = 1;
+        mHal.state.lodCount = 1;
     }
-    if (mLODCount != oldLODCount) {
-        if (mLODs){
-            delete [] mLODs;
+    if (mHal.state.lodCount != oldLODCount) {
+        if (oldLODCount) {
+            delete [] mHal.state.lodDimX;
+            delete [] mHal.state.lodDimY;
+            delete [] mHal.state.lodDimZ;
+            delete [] mHal.state.lodOffset;
         }
-        mLODs = new LOD[mLODCount];
+        mHal.state.lodDimX = new uint32_t[mHal.state.lodCount];
+        mHal.state.lodDimY = new uint32_t[mHal.state.lodCount];
+        mHal.state.lodDimZ = new uint32_t[mHal.state.lodCount];
+        mHal.state.lodOffset = new uint32_t[mHal.state.lodCount];
     }
 
     uint32_t tx = mHal.state.dimX;
     uint32_t ty = mHal.state.dimY;
     uint32_t tz = mHal.state.dimZ;
     size_t offset = 0;
-    for (uint32_t lod=0; lod < mLODCount; lod++) {
-        mLODs[lod].mX = tx;
-        mLODs[lod].mY = ty;
-        mLODs[lod].mZ = tz;
-        mLODs[lod].mOffset = offset;
+    for (uint32_t lod=0; lod < mHal.state.lodCount; lod++) {
+        mHal.state.lodDimX[lod] = tx;
+        mHal.state.lodDimY[lod] = ty;
+        mHal.state.lodDimZ[lod]  = tz;
+        mHal.state.lodOffset[lod] = offset;
         offset += tx * rsMax(ty, 1u) * rsMax(tz, 1u) * mElement->getSizeBytes();
         if (tx > 1) tx >>= 1;
         if (ty > 1) ty >>= 1;
@@ -107,27 +111,29 @@
 }
 
 uint32_t Type::getLODOffset(uint32_t lod, uint32_t x) const {
-    uint32_t offset = mLODs[lod].mOffset;
+    uint32_t offset = mHal.state.lodOffset[lod];
     offset += x * mElement->getSizeBytes();
     return offset;
 }
 
 uint32_t Type::getLODOffset(uint32_t lod, uint32_t x, uint32_t y) const {
-    uint32_t offset = mLODs[lod].mOffset;
-    offset += (x + y * mLODs[lod].mX) * mElement->getSizeBytes();
+    uint32_t offset = mHal.state.lodOffset[lod];
+    offset += (x + y * mHal.state.lodDimX[lod]) * mElement->getSizeBytes();
     return offset;
 }
 
 uint32_t Type::getLODOffset(uint32_t lod, uint32_t x, uint32_t y, uint32_t z) const {
-    uint32_t offset = mLODs[lod].mOffset;
-    offset += (x + y*mLODs[lod].mX + z*mLODs[lod].mX*mLODs[lod].mY) * mElement->getSizeBytes();
+    uint32_t offset = mHal.state.lodOffset[lod];
+    offset += (x +
+               y * mHal.state.lodDimX[lod] +
+               z * mHal.state.lodDimX[lod] * mHal.state.lodDimY[lod]) * mElement->getSizeBytes();
     return offset;
 }
 
 uint32_t Type::getLODFaceOffset(uint32_t lod, RsAllocationCubemapFace face,
                                 uint32_t x, uint32_t y) const {
-    uint32_t offset = mLODs[lod].mOffset;
-    offset += (x + y * mLODs[lod].mX) * mElement->getSizeBytes();
+    uint32_t offset = mHal.state.lodOffset[lod];
+    offset += (x + y * mHal.state.lodDimX[lod]) * mElement->getSizeBytes();
 
     if (face != 0) {
         uint32_t faceOffset = getSizeBytes() / 6;
@@ -143,7 +149,7 @@
                                                       mHal.state.dimX,
                                                       mHal.state.dimY,
                                                       mHal.state.dimZ,
-                                                      mHal.state.dimLOD,
+                                                      mHal.state.lodCount,
                                                       mHal.state.faces);
     snprintf(buf, sizeof(buf), "%s element: ", prefix);
     mElement->dumpLOGV(buf);
@@ -162,7 +168,7 @@
     stream->addU32(mHal.state.dimY);
     stream->addU32(mHal.state.dimZ);
 
-    stream->addU8((uint8_t)(mHal.state.dimLOD ? 1 : 0));
+    stream->addU8((uint8_t)(mHal.state.lodCount ? 1 : 0));
     stream->addU8((uint8_t)(mHal.state.faces ? 1 : 0));
 }
 
@@ -233,12 +239,12 @@
 
 
     Type *nt = new Type(rsc);
+    nt->mDimLOD = dimLOD;
     returnRef.set(nt);
     nt->mElement.set(e);
     nt->mHal.state.dimX = dimX;
     nt->mHal.state.dimY = dimY;
     nt->mHal.state.dimZ = dimZ;
-    nt->mHal.state.dimLOD = dimLOD;
     nt->mHal.state.faces = dimFaces;
     nt->compute();
 
@@ -251,14 +257,14 @@
 
 ObjectBaseRef<Type> Type::cloneAndResize1D(Context *rsc, uint32_t dimX) const {
     return getTypeRef(rsc, mElement.get(), dimX,
-                      mHal.state.dimY, mHal.state.dimZ, mHal.state.dimLOD, mHal.state.faces);
+                      mHal.state.dimY, mHal.state.dimZ, mHal.state.lodCount, mHal.state.faces);
 }
 
 ObjectBaseRef<Type> Type::cloneAndResize2D(Context *rsc,
                               uint32_t dimX,
                               uint32_t dimY) const {
     return getTypeRef(rsc, mElement.get(), dimX, dimY,
-                      mHal.state.dimZ, mHal.state.dimLOD, mHal.state.faces);
+                      mHal.state.dimZ, mHal.state.lodCount, mHal.state.faces);
 }
 
 
@@ -280,13 +286,13 @@
 void rsaTypeGetNativeData(RsContext con, RsType type, uint32_t *typeData, uint32_t typeDataSize) {
     rsAssert(typeDataSize == 6);
     // Pack the data in the follofing way mHal.state.dimX; mHal.state.dimY; mHal.state.dimZ;
-    // mHal.state.dimLOD; mHal.state.faces; mElement; into typeData
+    // mHal.state.lodCount; mHal.state.faces; mElement; into typeData
     Type *t = static_cast<Type *>(type);
 
     (*typeData++) = t->getDimX();
     (*typeData++) = t->getDimY();
     (*typeData++) = t->getDimZ();
-    (*typeData++) = t->getDimLOD();
+    (*typeData++) = t->getDimLOD() ? 1 : 0;
     (*typeData++) = t->getDimFaces() ? 1 : 0;
     (*typeData++) = (uint32_t)t->getElement();
     t->getElement()->incUserRef();
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 3878156..f1aeb0a 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -44,7 +44,11 @@
             uint32_t dimX;
             uint32_t dimY;
             uint32_t dimZ;
-            bool dimLOD;
+            uint32_t *lodDimX;
+            uint32_t *lodDimY;
+            uint32_t *lodDimZ;
+            uint32_t *lodOffset;
+            uint32_t lodCount;
             bool faces;
         };
         State state;
@@ -62,15 +66,24 @@
     uint32_t getDimX() const {return mHal.state.dimX;}
     uint32_t getDimY() const {return mHal.state.dimY;}
     uint32_t getDimZ() const {return mHal.state.dimZ;}
-    uint32_t getDimLOD() const {return mHal.state.dimLOD;}
+    bool getDimLOD() const {return mDimLOD;}
     bool getDimFaces() const {return mHal.state.faces;}
 
-    uint32_t getLODDimX(uint32_t lod) const {rsAssert(lod < mLODCount); return mLODs[lod].mX;}
-    uint32_t getLODDimY(uint32_t lod) const {rsAssert(lod < mLODCount); return mLODs[lod].mY;}
-    uint32_t getLODDimZ(uint32_t lod) const {rsAssert(lod < mLODCount); return mLODs[lod].mZ;}
-
+    uint32_t getLODDimX(uint32_t lod) const {
+        rsAssert(lod < mHal.state.lodCount);
+        return mHal.state.lodDimX[lod];
+    }
+    uint32_t getLODDimY(uint32_t lod) const {
+        rsAssert(lod < mHal.state.lodCount);
+        return mHal.state.lodDimY[lod];
+    }
+    uint32_t getLODDimZ(uint32_t lod) const {
+        rsAssert(lod < mHal.state.lodCount);
+        return mHal.state.lodDimZ[lod];
+    }
     uint32_t getLODOffset(uint32_t lod) const {
-        rsAssert(lod < mLODCount); return mLODs[lod].mOffset;
+        rsAssert(lod < mHal.state.lodCount);
+        return mHal.state.lodOffset[lod];
     }
     uint32_t getLODOffset(uint32_t lod, uint32_t x) const;
     uint32_t getLODOffset(uint32_t lod, uint32_t x, uint32_t y) const;
@@ -79,7 +92,7 @@
     uint32_t getLODFaceOffset(uint32_t lod, RsAllocationCubemapFace face,
                               uint32_t x, uint32_t y) const;
 
-    uint32_t getLODCount() const {return mLODCount;}
+    uint32_t getLODCount() const {return mHal.state.lodCount;}
     bool getIsNp2() const;
 
     void clear();
@@ -106,14 +119,8 @@
     }
 
 protected:
-    struct LOD {
-        size_t mX;
-        size_t mY;
-        size_t mZ;
-        size_t mOffset;
-    };
-
     void makeLODTable();
+    bool mDimLOD;
 
     // Internal structure from most to least significant.
     // * Array dimensions
@@ -127,9 +134,6 @@
 
     size_t mMipChainSizeBytes;
     size_t mTotalSizeBytes;
-    LOD *mLODs;
-    uint32_t mLODCount;
-
 protected:
     virtual void preDestroy() const;
     virtual ~Type();
diff --git a/libs/rs/rsUtils.h b/libs/rs/rsUtils.h
index a9a992a..cbbae6c 100644
--- a/libs/rs/rsUtils.h
+++ b/libs/rs/rsUtils.h
@@ -34,8 +34,6 @@
 
 #include <math.h>
 
-#include "rs.h"
-
 namespace android {
 namespace renderscript {
 
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 6ec5d20..f572f71 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -36,6 +36,7 @@
 #include "utils/String8.h"
 #include "android_media_Utils.h"
 
+#include "android_os_Parcel.h"
 #include "android_util_Binder.h"
 #include <binder/Parcel.h>
 #include <gui/ISurfaceTexture.h>
diff --git a/media/libmedia/AudioSystem.cpp b/media/libmedia/AudioSystem.cpp
index e0b186a..a1cbf0f 100644
--- a/media/libmedia/AudioSystem.cpp
+++ b/media/libmedia/AudioSystem.cpp
@@ -701,10 +701,10 @@
     return aps->getStrategyForStream(stream);
 }
 
-uint32_t AudioSystem::getDevicesForStream(audio_stream_type_t stream)
+audio_devices_t AudioSystem::getDevicesForStream(audio_stream_type_t stream)
 {
     const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
-    if (aps == 0) return 0;
+    if (aps == 0) return (audio_devices_t)0;
     return aps->getDevicesForStream(stream);
 }
 
diff --git a/media/libmedia/IAudioPolicyService.cpp b/media/libmedia/IAudioPolicyService.cpp
index 99385aa4..da7c124 100644
--- a/media/libmedia/IAudioPolicyService.cpp
+++ b/media/libmedia/IAudioPolicyService.cpp
@@ -267,13 +267,13 @@
         return reply.readInt32();
     }
 
-    virtual uint32_t getDevicesForStream(audio_stream_type_t stream)
+    virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
         data.writeInt32(static_cast <uint32_t>(stream));
         remote()->transact(GET_DEVICES_FOR_STREAM, data, &reply);
-        return (uint32_t) reply.readInt32();
+        return (audio_devices_t) reply.readInt32();
     }
 
     virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc)
diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp
index b21e86a..9e00bb3 100644
--- a/media/libstagefright/AwesomePlayer.cpp
+++ b/media/libstagefright/AwesomePlayer.cpp
@@ -2108,6 +2108,8 @@
 
         mWVMExtractor = new WVMExtractor(dataSource);
         mWVMExtractor->setAdaptiveStreamingMode(true);
+        if (mUIDValid)
+            mWVMExtractor->setUID(mUID);
         extractor = mWVMExtractor;
     } else {
         extractor = MediaExtractor::Create(
diff --git a/media/libstagefright/WVMExtractor.cpp b/media/libstagefright/WVMExtractor.cpp
index c7ad513..dac8106 100644
--- a/media/libstagefright/WVMExtractor.cpp
+++ b/media/libstagefright/WVMExtractor.cpp
@@ -123,6 +123,12 @@
     }
 }
 
+void WVMExtractor::setUID(uid_t uid) {
+    if (mImpl != NULL) {
+        mImpl->setUID(uid);
+    }
+}
+
 bool SniffWVM(
     const sp<DataSource> &source, String8 *mimeType, float *confidence,
         sp<AMessage> *) {
diff --git a/media/libstagefright/include/WVMExtractor.h b/media/libstagefright/include/WVMExtractor.h
index 9f763f9..3c3ca89 100644
--- a/media/libstagefright/include/WVMExtractor.h
+++ b/media/libstagefright/include/WVMExtractor.h
@@ -34,6 +34,7 @@
 
     virtual int64_t getCachedDurationUs(status_t *finalStatus) = 0;
     virtual void setAdaptiveStreamingMode(bool adaptive) = 0;
+    virtual void setUID(uid_t uid) = 0;
 };
 
 class WVMExtractor : public MediaExtractor {
@@ -60,6 +61,8 @@
     // is used.
     void setAdaptiveStreamingMode(bool adaptive);
 
+    void setUID(uid_t uid);
+
     static bool getVendorLibHandle();
 
 protected:
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 7c1cd18..1e82646 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -26,7 +26,7 @@
     <string name="status_bar_recent_remove_item_title" msgid="6026395868129852968">"Hapus dari daftar"</string>
     <string name="status_bar_recent_inspect_item_title" msgid="7793624864528818569">"Info apl"</string>
     <string name="status_bar_no_recent_apps" msgid="6576392951053994640">"Tidak ada apl terbaru"</string>
-    <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Singkirkan aplikasi terbaru"</string>
+    <string name="status_bar_accessibility_dismiss_recents" msgid="4576076075226540105">"Tutup aplikasi terbaru"</string>
   <plurals name="status_bar_accessibility_recent_apps">
     <item quantity="one" msgid="5854176083865845541">"1 apl terbaru"</item>
     <item quantity="other" msgid="1040784359794890744">"%d apl terbaru"</item>
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index fd7418c..f9b033b 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -1621,7 +1621,7 @@
         uint32_t strategy = AudioSystem::getStrategyForStream(streamType);
         for (size_t i = 0; i < mTracks.size(); ++i) {
             sp<Track> t = mTracks[i];
-            if (t != 0) {
+            if (t != 0 && !t->isOutputTrack()) {
                 uint32_t actual = AudioSystem::getStrategyForStream(t->streamType());
                 if (sessionId == t->sessionId() && strategy != actual) {
                     ALOGE("createTrack_l() mismatched strategy; expected %u but found %u",
diff --git a/services/audioflinger/AudioPolicyService.cpp b/services/audioflinger/AudioPolicyService.cpp
index 753b1d2..d57326b 100644
--- a/services/audioflinger/AudioPolicyService.cpp
+++ b/services/audioflinger/AudioPolicyService.cpp
@@ -298,8 +298,7 @@
     ssize_t idx = mInputs.indexOfKey(input);
     InputDesc *inputDesc;
     if (idx < 0) {
-        inputDesc = new InputDesc();
-        inputDesc->mSessionId = audioSession;
+        inputDesc = new InputDesc(audioSession);
         mInputs.add(input, inputDesc);
     } else {
         inputDesc = mInputs.valueAt(idx);
@@ -358,7 +357,6 @@
     }
     InputDesc *inputDesc = mInputs.valueAt(index);
     setPreProcessorEnabled(inputDesc, false);
-    inputDesc->mEffects.clear();
     delete inputDesc;
     mInputs.removeItemsAt(index);
 }
@@ -432,10 +430,12 @@
     return mpAudioPolicy->get_strategy_for_stream(mpAudioPolicy, stream);
 }
 
-uint32_t AudioPolicyService::getDevicesForStream(audio_stream_type_t stream)
+//audio policy: use audio_device_t appropriately
+
+audio_devices_t AudioPolicyService::getDevicesForStream(audio_stream_type_t stream)
 {
     if (mpAudioPolicy == NULL) {
-        return 0;
+        return (audio_devices_t)0;
     }
     return mpAudioPolicy->get_devices_for_stream(mpAudioPolicy, stream);
 }
@@ -600,9 +600,9 @@
     return NO_ERROR;
 }
 
-void AudioPolicyService::setPreProcessorEnabled(InputDesc *inputDesc, bool enabled)
+void AudioPolicyService::setPreProcessorEnabled(const InputDesc *inputDesc, bool enabled)
 {
-    Vector<sp<AudioEffect> > fxVector = inputDesc->mEffects;
+    const Vector<sp<AudioEffect> > &fxVector = inputDesc->mEffects;
     for (size_t i = 0; i < fxVector.size(); i++) {
         fxVector.itemAt(i)->setEnabled(enabled);
     }
diff --git a/services/audioflinger/AudioPolicyService.h b/services/audioflinger/AudioPolicyService.h
index 962c917..7119b90 100644
--- a/services/audioflinger/AudioPolicyService.h
+++ b/services/audioflinger/AudioPolicyService.h
@@ -95,7 +95,7 @@
                                           audio_devices_t device);
 
     virtual uint32_t getStrategyForStream(audio_stream_type_t stream);
-    virtual uint32_t getDevicesForStream(audio_stream_type_t stream);
+    virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream);
 
     virtual audio_io_handle_t getOutputForEffect(effect_descriptor_t *desc);
     virtual status_t registerEffect(effect_descriptor_t *desc,
@@ -279,15 +279,15 @@
 
     class InputDesc {
     public:
-        InputDesc() {}
-        virtual ~InputDesc() {}
-        int mSessionId;
+        InputDesc(int session) : mSessionId(session) {}
+        /*virtual*/ ~InputDesc() {}
+        const int mSessionId;
         Vector< sp<AudioEffect> >mEffects;
     };
 
     static const char * const kInputSourceNames[AUDIO_SOURCE_CNT -1];
 
-    void setPreProcessorEnabled(InputDesc *inputDesc, bool enabled);
+    void setPreProcessorEnabled(const InputDesc *inputDesc, bool enabled);
     status_t loadPreProcessorConfig(const char *path);
     status_t loadEffects(cnode *root, Vector <EffectDesc *>& effects);
     EffectDesc *loadEffect(cnode *root);
diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java
index 720b5fe..09d0698 100644
--- a/services/java/com/android/server/NetworkManagementService.java
+++ b/services/java/com/android/server/NetworkManagementService.java
@@ -880,7 +880,6 @@
         mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
         try {
             wifiFirmwareReload(wlanIface, "AP");
-            mConnector.execute("softap", "start", wlanIface);
             if (wifiConfig == null) {
                 mConnector.execute("softap", "set", wlanIface, softapIface);
             } else {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index ce597ad..fd968e0 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4297,7 +4297,7 @@
             try {
                 int uid = AppGlobals.getPackageManager()
                         .getPackageUid(rec.key.packageName);
-                if (uid != Binder.getCallingUid()) {
+                if (!UserId.isSameApp(uid, Binder.getCallingUid())) {
                     String msg = "Permission Denial: cancelIntentSender() from pid="
                         + Binder.getCallingPid()
                         + ", uid=" + Binder.getCallingUid()
@@ -4934,7 +4934,7 @@
     void grantUriPermissionFromIntentLocked(int callingUid,
             String targetPkg, Intent intent, UriPermissionOwner owner) {
         NeededUriGrants needed = checkGrantUriPermissionFromIntentLocked(callingUid, targetPkg,
-                intent, intent.getFlags(), null);
+                intent, intent != null ? intent.getFlags() : 0, null);
         if (needed == null) {
             return;
         }
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 19fe1bf..a4e573d 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -1821,6 +1821,10 @@
             if (below != null && below.finishing) {
                 continue;
             }
+            // Don't check any lower in the stack if we're crossing a user boundary.
+            if (below != null && below.userId != taskTop.userId) {
+                break;
+            }
             if (target == null) {
                 target = below;
                 targetI = i;
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 023f97d..18b51a7 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -6579,7 +6579,7 @@
         for (int i = count - 1; i >= 0; i--) {
             WindowState win = mWindows.get(i);
             try {
-                win.mClient.dispatchScreenStatus(on);
+                win.mClient.dispatchScreenState(on);
             } catch (RemoteException e) {
                 // Ignored
             }
@@ -9173,7 +9173,7 @@
 
     void scheduleAnimationLocked() {
         if (!mAnimationScheduled) {
-            mChoreographer.postAnimationCallback(mAnimationRunnable);
+            mChoreographer.postAnimationCallback(mAnimationRunnable, null);
             mAnimationScheduled = true;
         }
     }
diff --git a/tests/RenderScriptTests/SampleTest/res/drawable-nodpi/city.png b/tests/RenderScriptTests/SampleTest/res/drawable-nodpi/city.png
new file mode 100644
index 0000000..27c4618
--- /dev/null
+++ b/tests/RenderScriptTests/SampleTest/res/drawable-nodpi/city.png
Binary files differ
diff --git a/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/SampleRSActivity.java b/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/SampleRSActivity.java
index cd5d53f..77cbf84 100644
--- a/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/SampleRSActivity.java
+++ b/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/SampleRSActivity.java
@@ -18,6 +18,7 @@
 
 import android.app.Activity;
 import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.Canvas;
 import android.graphics.SurfaceTexture;
@@ -66,13 +67,14 @@
     }
 
     private final String TAG = "Img";
-    private Bitmap mBitmapIn;
-    private TextureView mDisplayView;
+    private Bitmap mBitmapTwoByTwo;
+    private Bitmap mBitmapCity;
 
     private TextView mBenchmarkResult;
 
     private RenderScript mRS;
-    private Allocation mInPixelsAllocation;
+    private Allocation mTwoByTwoAlloc;
+    private Allocation mCityAlloc;
     private ScriptC_sample mScript;
 
     public void onStartTrackingTouch(SeekBar seekBar) {
@@ -86,14 +88,18 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.rs);
 
-        mBitmapIn = loadBitmap(R.drawable.twobytwo);
-        mDisplayView = (TextureView) findViewById(R.id.display);
+        mBitmapTwoByTwo = loadBitmap(R.drawable.twobytwo);
+        mBitmapCity = loadBitmap(R.drawable.city);
 
         mBenchmarkResult = (TextView) findViewById(R.id.benchmarkText);
         mBenchmarkResult.setText("Result: not run");
 
         mRS = RenderScript.create(this);
-        mInPixelsAllocation = Allocation.createFromBitmap(mRS, mBitmapIn,
+        mTwoByTwoAlloc = Allocation.createFromBitmap(mRS, mBitmapTwoByTwo,
+                                                          Allocation.MipmapControl.MIPMAP_NONE,
+                                                          Allocation.USAGE_SCRIPT);
+
+        mCityAlloc = Allocation.createFromBitmap(mRS, mBitmapCity,
                                                           Allocation.MipmapControl.MIPMAP_NONE,
                                                           Allocation.USAGE_SCRIPT);
 
@@ -101,8 +107,8 @@
 
         int usage = Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT;
 
-        int outX = 32;
-        int outY = 32;
+        int outX = 256;
+        int outY = 256;
 
         // Wrap Linear
         Allocation outAlloc = Allocation.createTyped(mRS, b.setX(outX).setY(outY).create(), usage);
@@ -144,7 +150,7 @@
 
     private synchronized void filterAlloc(Allocation alloc, Sampler sampler) {
         long t = java.lang.System.currentTimeMillis();
-        mScript.invoke_setSampleData(alloc, mInPixelsAllocation, sampler);
+        mScript.invoke_setSampleData(alloc, mTwoByTwoAlloc, sampler);
         mScript.forEach_root(alloc);
         alloc.ioSendOutput();
         mRS.finish();
diff --git a/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/sample.rs b/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/sample.rs
index 8a027b2..0f3c0a7 100644
--- a/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/sample.rs
+++ b/tests/RenderScriptTests/SampleTest/src/com/android/rs/sample/sample.rs
@@ -38,39 +38,113 @@
     return max(0, min(coord, size - 1));
 }
 
-static float2 wrap(rs_sampler_value wrapS, rs_sampler_value wrapT, float2 coord) {
-    float2 wrappedCoord;
-    float temp;
-    if (wrapS == RS_SAMPLER_WRAP) {
-        wrappedCoord.x = fract(coord.x, &temp);
-        // Make sure that non zero integer uv's map to one
-        if (wrappedCoord.x == 0.0f && coord.x != 0.0f) {
-            wrappedCoord.x = 1.0f;
+#define convert_float(v) (float)v
+#define SAMPLE_1D_FUNC(vecsize)                                                                 \
+        static float##vecsize get1DSample##vecsize(rs_allocation a, float2 weights,             \
+                                                 int iPixel, int next) {                        \
+            uchar##vecsize *p0c = (uchar##vecsize*)rsGetElementAt(a, iPixel);                   \
+            uchar##vecsize *p1c = (uchar##vecsize*)rsGetElementAt(a, next);                     \
+            float##vecsize p0 = convert_float##vecsize(*p0c);                                   \
+            float##vecsize p1 = convert_float##vecsize(*p1c);                                   \
+            return p0 * weights.x + p1 * weights.y;                                             \
         }
-        if (wrappedCoord.x < 0.0f) {
-            wrappedCoord.x += 1.0f;
+#define SAMPLE_2D_FUNC(vecsize)                                                                 \
+        static float##vecsize get2DSample##vecsize(rs_allocation a, float4 weights,             \
+                                                 int2 iPixel, int nextX, int nextY) {           \
+            uchar##vecsize *p0c = (uchar##vecsize*)rsGetElementAt(a, iPixel.x, iPixel.y);       \
+            uchar##vecsize *p1c = (uchar##vecsize*)rsGetElementAt(a, nextX, iPixel.y);          \
+            uchar##vecsize *p2c = (uchar##vecsize*)rsGetElementAt(a, iPixel.x, nextY);          \
+            uchar##vecsize *p3c = (uchar##vecsize*)rsGetElementAt(a, nextX, nextY);             \
+            float##vecsize p0 = convert_float##vecsize(*p0c);                                   \
+            float##vecsize p1 = convert_float##vecsize(*p1c);                                   \
+            float##vecsize p2 = convert_float##vecsize(*p2c);                                   \
+            float##vecsize p3 = convert_float##vecsize(*p3c);                                   \
+            return p0 * weights.x + p1 * weights.y + p2 * weights.z + p3 * weights.w;           \
         }
-    } else {
-        wrappedCoord.x = max(0.0f, min(coord.x, 1.0f));
+
+SAMPLE_1D_FUNC()
+SAMPLE_1D_FUNC(2)
+SAMPLE_1D_FUNC(3)
+SAMPLE_1D_FUNC(4)
+
+SAMPLE_2D_FUNC()
+SAMPLE_2D_FUNC(2)
+SAMPLE_2D_FUNC(3)
+SAMPLE_2D_FUNC(4)
+
+static float4 getBilinearSample565(rs_allocation a, float4 weights,
+                                   int2 iPixel, int nextX, int nextY) {
+    float4 zero = {0.0f, 0.0f, 0.0f, 0.0f};
+    return zero;
+}
+
+static float4 getBilinearSample(rs_allocation a, float4 weights,
+                                int2 iPixel, int nextX, int nextY,
+                                uint32_t vecSize, rs_data_type dt) {
+    if (dt == RS_TYPE_UNSIGNED_5_6_5) {
+        return getBilinearSample565(a, weights, iPixel, nextX, nextY);
     }
 
-    if (wrapT == RS_SAMPLER_WRAP) {
-        wrappedCoord.y = fract(coord.y, &temp);
-        // Make sure that non zero integer uv's map to one
-        if (wrappedCoord.y == 0.0f && coord.y != 0.0f) {
-            wrappedCoord.y = 1.0f;
-        }
-        if (wrappedCoord.y < 0.0f) {
-            wrappedCoord.y += 1.0f;
-        }
-    } else {
-        wrappedCoord.y = max(0.0f, min(coord.y, 1.0f));
+    float4 result;
+    switch(vecSize) {
+    case 1:
+        result.x = get2DSample(a, weights, iPixel, nextX, nextY);
+        result.yzw = 0.0f;
+        break;
+    case 2:
+        result.xy = get2DSample2(a, weights, iPixel, nextX, nextY);
+        result.zw = 0.0f;
+        break;
+    case 3:
+        result.xyz = get2DSample3(a, weights, iPixel, nextX, nextY);
+        result.w = 0.0f;
+        break;
+    case 4:
+        result = get2DSample4(a, weights, iPixel, nextX, nextY);
+        break;
     }
-    return wrappedCoord;
+
+    return result;
 }
 
+static float4 getNearestSample(rs_allocation a, int2 iPixel, uint32_t vecSize, rs_data_type dt) {
+    if (dt == RS_TYPE_UNSIGNED_5_6_5) {
+        float4 zero = {0.0f, 0.0f, 0.0f, 0.0f};
+        return zero;
+    }
+
+    float4 result;
+    switch(vecSize) {
+    case 1:
+        result.x = convert_float(*((uchar*)rsGetElementAt(a, iPixel.x, iPixel.y)));
+        result.yzw = 0.0f;
+    case 2:
+        result.xy = convert_float2(*((uchar2*)rsGetElementAt(a, iPixel.x, iPixel.y)));
+        result.zw = 0.0f;
+    case 3:
+        result.xyz = convert_float3(*((uchar3*)rsGetElementAt(a, iPixel.x, iPixel.y)));
+        result.w = 0.0f;
+    case 4:
+        result = convert_float4(*((uchar4*)rsGetElementAt(a, iPixel.x, iPixel.y)));
+    }
+
+    return result;
+}
+
+
 // Naive implementation of texture filtering for prototyping purposes
 static float4 sample(rs_allocation a, rs_sampler s, float2 uv) {
+
+    // Find out what kind of input data we are sampling
+    rs_element elem = rsAllocationGetElement(a);
+    uint32_t vecSize = rsElementGetVectorSize(elem);
+    rs_data_kind dk = rsElementGetDataKind(elem);
+    rs_data_type dt = rsElementGetDataType(elem);
+
+    if (dk == RS_KIND_USER || (dt != RS_TYPE_UNSIGNED_8 && dt != RS_TYPE_UNSIGNED_5_6_5)) {
+        float4 zero = {0.0f, 0.0f, 0.0f, 0.0f};
+        return zero;
+    }
     //rsDebug("*****************************************", 0);
     rs_sampler_value wrapS = rsgSamplerGetWrapS(s);
     rs_sampler_value wrapT = rsgSamplerGetWrapT(s);
@@ -78,29 +152,20 @@
     rs_sampler_value sampleMin = rsgSamplerGetMinification(s);
     rs_sampler_value sampleMag = rsgSamplerGetMagnification(s);
 
-    uv = wrap(wrapS, wrapT, uv);
-
     int32_t sourceW = rsAllocationGetDimX(a);
     int32_t sourceH = rsAllocationGetDimY(a);
 
-    /*rsDebug("uv", uv);
-    rsDebug("sourceW", sourceW);
-    rsDebug("sourceH", sourceH);*/
-
     float2 dimF;
     dimF.x = (float)(sourceW);
     dimF.y = (float)(sourceH);
     float2 pixelUV = uv * dimF;
     int2 iPixel = convert_int2(pixelUV);
-    /*rsDebug("iPixelX initial", iPixel.x);
-    rsDebug("iPixelY initial", iPixel.y);*/
 
     if (sampleMin == RS_SAMPLER_NEAREST ||
         sampleMag == RS_SAMPLER_NEAREST) {
         iPixel.x = wrapI(wrapS, iPixel.x, sourceW);
         iPixel.y = wrapI(wrapT, iPixel.y, sourceH);
-        uchar4 *nearestSample = (uchar4*)rsGetElementAt(a, iPixel.x, iPixel.y);
-        return convert_float4(*nearestSample);
+        return getNearestSample(a, iPixel, vecSize, dt);
     }
 
     float2 frac = pixelUV - convert_float2(iPixel);
@@ -125,36 +190,12 @@
     weights.z = oneMinusFrac.x * frac.y;
     weights.w = frac.x * frac.y;
 
-    uint32_t nextX = wrapI(wrapS, iPixel.x + 1, sourceW);
-    uint32_t nextY = wrapI(wrapT, iPixel.y + 1, sourceH);
+    int32_t nextX = wrapI(wrapS, iPixel.x + 1, sourceW);
+    int32_t nextY = wrapI(wrapT, iPixel.y + 1, sourceH);
     iPixel.x = wrapI(wrapS, iPixel.x, sourceW);
     iPixel.y = wrapI(wrapT, iPixel.y, sourceH);
-    /*rsDebug("iPixelX wrapped", iPixel.x);
-    rsDebug("iPixelY wrapped", iPixel.y);*/
 
-    uchar4 *p0c = (uchar4*)rsGetElementAt(a, iPixel.x, iPixel.y);
-    uchar4 *p1c = (uchar4*)rsGetElementAt(a, nextX, iPixel.y);
-    uchar4 *p2c = (uchar4*)rsGetElementAt(a, iPixel.x, nextY);
-    uchar4 *p3c = (uchar4*)rsGetElementAt(a, nextX, nextY);
-
-    float4 p0 = convert_float4(*p0c);
-    float4 p1 = convert_float4(*p1c);
-    float4 p2 = convert_float4(*p2c);
-    float4 p3 = convert_float4(*p3c);
-
-    float4 result = p0 * weights.x + p1 * weights.y + p2 * weights.z + p3 * weights.w;
-
-    /*rsDebug("pixelUV", pixelUV);
-    rsDebug("frac", frac);
-    rsDebug("oneMinusFrac", oneMinusFrac);
-    rsDebug("p0", p0);
-    rsDebug("p1", p1);
-    rsDebug("p2", p2);
-    rsDebug("p3", p3);
-    rsDebug("w", weights);
-    rsDebug("result", result);*/
-
-    return result;
+    return getBilinearSample(a, weights, iPixel, nextX, nextY, vecSize, dt);
 }
 
 void root(uchar4 *out, uint32_t x, uint32_t y) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
index 965f553..7c683c9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java
@@ -53,7 +53,8 @@
     }
 
     @Override
-    public void dispatchScreenStatus(boolean on) throws RemoteException {
+    public void dispatchScreenState(boolean on) throws RemoteException {
+        // pass for now.
     }
 
     @Override
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 843620c..05a8ca7 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -1852,6 +1852,9 @@
                     replyToMessage(message, WifiManager.DISABLE_NETWORK_FAILED,
                             WifiManager.BUSY);
                     break;
+                case WifiWatchdogStateMachine.RSSI_FETCH:
+                    replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_FAILED);
+                    break;
                 default:
                     loge("Error! unhandled message" + message);
                     break;
@@ -2998,6 +3001,12 @@
                                 mRssiPollToken, 0), POLL_RSSI_INTERVAL_MSECS);
                     }
                     break;
+                case WifiWatchdogStateMachine.RSSI_FETCH:
+                    eventLoggingEnabled = false;
+                    fetchRssiAndLinkSpeedNative();
+                    replyToMessage(message, WifiWatchdogStateMachine.RSSI_FETCH_SUCCEEDED,
+                            mWifiInfo.getRssi());
+                    break;
                 default:
                     return NOT_HANDLED;
             }
diff --git a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
index a2f6343..5c9bef9 100644
--- a/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiWatchdogStateMachine.java
@@ -93,17 +93,36 @@
     private static final String TAG = "WifiWatchdogStateMachine";
     private static final String WALLED_GARDEN_NOTIFICATION_ID = "WifiWatchdog.walledgarden";
 
+    /* RSSI Levels as used by notification icon
+       Level 4  -55 <= RSSI
+       Level 3  -66 <= RSSI < -55
+       Level 2  -77 <= RSSI < -67
+       Level 1  -88 <= RSSI < -78
+       Level 0         RSSI < -88 */
+
     /* Wi-fi connection is considered poor below this
        RSSI level threshold and the watchdog report it
        to the WifiStateMachine */
-    private static final int RSSI_LEVEL_CUTOFF = 1;
+    private static final int RSSI_LEVEL_CUTOFF = 0;
     /* Wi-fi connection is monitored actively below this
        threshold */
-    private static final int RSSI_LEVEL_MONITOR = 2;
+    private static final int RSSI_LEVEL_MONITOR = 1;
+    /* RSSI threshold during monitoring below which network is avoided */
+    private static final int RSSI_MONITOR_THRESHOLD = -84;
+    /* Number of times RSSI is measured to be low before being avoided */
+    private static final int RSSI_MONITOR_COUNT = 5;
+    private int mRssiMonitorCount = 0;
+
+    /* Avoid flapping */
+    private static final int MIN_INTERVAL_AVOID_BSSID_MS = 60 * 1000;
+    private String mLastAvoidedBssid;
+    /* a -ve interval to allow avoidance at boot */
+    private long mLastBssidAvoidedTime = -MIN_INTERVAL_AVOID_BSSID_MS;
 
     private int mCurrentSignalLevel;
 
     private static final long DEFAULT_ARP_CHECK_INTERVAL_MS = 2 * 60 * 1000;
+    private static final long DEFAULT_RSSI_FETCH_INTERVAL_MS = 1000;
     private static final long DEFAULT_WALLED_GARDEN_INTERVAL_MS = 30 * 60 * 1000;
 
     private static final int DEFAULT_NUM_ARP_PINGS = 5;
@@ -143,10 +162,14 @@
     /* Internal messages */
     private static final int CMD_ARP_CHECK                          = BASE + 11;
     private static final int CMD_DELAYED_WALLED_GARDEN_CHECK        = BASE + 12;
+    private static final int CMD_RSSI_FETCH                         = BASE + 13;
 
     /* Notifications to WifiStateMachine */
     static final int POOR_LINK_DETECTED                             = BASE + 21;
     static final int GOOD_LINK_DETECTED                             = BASE + 22;
+    static final int RSSI_FETCH                                     = BASE + 23;
+    static final int RSSI_FETCH_SUCCEEDED                           = BASE + 24;
+    static final int RSSI_FETCH_FAILED                              = BASE + 25;
 
     private static final int SINGLE_ARP_CHECK = 0;
     private static final int FULL_ARP_CHECK   = 1;
@@ -167,11 +190,15 @@
     private WalledGardenCheckState mWalledGardenCheckState = new WalledGardenCheckState();
     /* Online and watching link connectivity */
     private OnlineWatchState mOnlineWatchState = new OnlineWatchState();
+    /* RSSI level is at RSSI_LEVEL_MONITOR and needs close monitoring */
+    private RssiMonitoringState mRssiMonitoringState = new RssiMonitoringState();
     /* Online and doing nothing */
     private OnlineState mOnlineState = new OnlineState();
 
     private int mArpToken = 0;
     private long mArpCheckIntervalMs;
+    private int mRssiFetchToken = 0;
+    private long mRssiFetchIntervalMs;
     private long mWalledGardenIntervalMs;
     private int mNumArpPings;
     private int mMinArpResponses;
@@ -219,6 +246,7 @@
                 addState(mConnectedState, mWatchdogEnabledState);
                     addState(mWalledGardenCheckState, mConnectedState);
                     addState(mOnlineWatchState, mConnectedState);
+                    addState(mRssiMonitoringState, mOnlineWatchState);
                     addState(mOnlineState, mConnectedState);
 
         if (isWatchdogEnabled()) {
@@ -239,6 +267,7 @@
         // Disable for wifi only devices.
         if (Settings.Secure.getString(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON) == null
                 && sWifiOnly) {
+            log("Disabling watchog for wi-fi only device");
             putSettingsBoolean(contentResolver, Settings.Secure.WIFI_WATCHDOG_ON, false);
         }
         WifiWatchdogStateMachine wwsm = new WifiWatchdogStateMachine(context);
@@ -361,6 +390,7 @@
         pw.println("mLinkProperties: [" + mLinkProperties + "]");
         pw.println("mCurrentSignalLevel: [" + mCurrentSignalLevel + "]");
         pw.println("mArpCheckIntervalMs: [" + mArpCheckIntervalMs+ "]");
+        pw.println("mRssiFetchIntervalMs: [" + mRssiFetchIntervalMs + "]");
         pw.println("mWalledGardenIntervalMs: [" + mWalledGardenIntervalMs + "]");
         pw.println("mNumArpPings: [" + mNumArpPings + "]");
         pw.println("mMinArpResponses: [" + mMinArpResponses + "]");
@@ -371,7 +401,9 @@
     }
 
     private boolean isWatchdogEnabled() {
-        return getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
+        boolean ret = getSettingsBoolean(mContentResolver, Settings.Secure.WIFI_WATCHDOG_ON, true);
+        if (DBG) log("watchdog enabled " + ret);
+        return ret;
     }
 
     private void updateSettings() {
@@ -380,6 +412,9 @@
         mArpCheckIntervalMs = Secure.getLong(mContentResolver,
                 Secure.WIFI_WATCHDOG_ARP_CHECK_INTERVAL_MS,
                 DEFAULT_ARP_CHECK_INTERVAL_MS);
+        mRssiFetchIntervalMs = Secure.getLong(mContentResolver,
+                Secure.WIFI_WATCHDOG_RSSI_FETCH_INTERVAL_MS,
+                DEFAULT_RSSI_FETCH_INTERVAL_MS);
         mNumArpPings = Secure.getInt(mContentResolver,
                 Secure.WIFI_WATCHDOG_NUM_ARP_PINGS,
                 DEFAULT_NUM_ARP_PINGS);
@@ -436,6 +471,11 @@
 
     class DefaultState extends State {
         @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+        }
+
+        @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_WATCHDOG_SETTINGS_CHANGE:
@@ -445,13 +485,15 @@
                     }
                     break;
                 case EVENT_RSSI_CHANGE:
-                    mCurrentSignalLevel = WifiManager.calculateSignalLevel(msg.arg1,
-                            WifiManager.RSSI_LEVELS);
+                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
                     break;
                 case EVENT_WIFI_RADIO_STATE_CHANGE:
                 case EVENT_NETWORK_STATE_CHANGE:
                 case CMD_ARP_CHECK:
                 case CMD_DELAYED_WALLED_GARDEN_CHECK:
+                case CMD_RSSI_FETCH:
+                case RSSI_FETCH_SUCCEEDED:
+                case RSSI_FETCH_FAILED:
                     //ignore
                     break;
                 default:
@@ -464,6 +506,11 @@
 
     class WatchdogDisabledState extends State {
         @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+        }
+
+        @Override
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_WATCHDOG_TOGGLED:
@@ -493,7 +540,7 @@
         @Override
         public void enter() {
             if (DBG) log("WifiWatchdogService enabled");
-       }
+        }
 
         @Override
         public boolean processMessage(Message msg) {
@@ -507,6 +554,8 @@
                     NetworkInfo networkInfo = (NetworkInfo)
                             intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
 
+                    if (DBG) log("network state change " + networkInfo.getDetailedState());
+
                     switch (networkInfo.getDetailedState()) {
                         case VERIFYING_POOR_LINK:
                             mLinkProperties = (LinkProperties) intent.getParcelableExtra(
@@ -557,6 +606,10 @@
     }
 
     class NotConnectedState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+        }
     }
 
     class VerifyingLinkState extends State {
@@ -568,7 +621,7 @@
         }
 
         private void handleRssiChange() {
-            if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
+            if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
                 //stay here
                 if (DBG) log("enter VerifyingLinkState, stay level: " + mCurrentSignalLevel);
             } else {
@@ -587,11 +640,7 @@
                     }
                     break;
                 case EVENT_RSSI_CHANGE:
-                    int signalLevel = WifiManager.calculateSignalLevel(msg.arg1,
-                            WifiManager.RSSI_LEVELS);
-                    if (DBG) log("RSSI change old: " + mCurrentSignalLevel + "new: " + signalLevel);
-                    mCurrentSignalLevel = signalLevel;
-
+                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
                     handleRssiChange();
                     break;
                 case CMD_ARP_CHECK:
@@ -680,11 +729,11 @@
 
         private void handleRssiChange() {
             if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
-                if (DBG) log("Transition out, below cut off level: " + mCurrentSignalLevel);
-                mWsmChannel.sendMessage(POOR_LINK_DETECTED);
+                sendPoorLinkDetected();
             } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
-                if (DBG) log("Start monitoring, level: " + mCurrentSignalLevel);
-                sendMessage(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0));
+                transitionTo(mRssiMonitoringState);
+            } else {
+                //stay here
             }
         }
 
@@ -692,30 +741,14 @@
         public boolean processMessage(Message msg) {
             switch (msg.what) {
                 case EVENT_RSSI_CHANGE:
-                    int signalLevel = WifiManager.calculateSignalLevel(msg.arg1,
-                            WifiManager.RSSI_LEVELS);
-                    if (DBG) log("RSSI change old: " + mCurrentSignalLevel + "new: " + signalLevel);
-                    mCurrentSignalLevel = signalLevel;
-
-                    handleRssiChange();
-
-                    break;
-                case CMD_ARP_CHECK:
-                    if (msg.arg1 == mArpToken) {
-                        if (doArpTest(SINGLE_ARP_CHECK) != true) {
-                            if (DBG) log("single ARP fail, full ARP check");
-                            //do a full test
-                            if (doArpTest(FULL_ARP_CHECK) != true) {
-                                if (DBG) log("notify full ARP fail, level: " + mCurrentSignalLevel);
-                                mWsmChannel.sendMessage(POOR_LINK_DETECTED);
-                            }
-                        }
-
-                        if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
-                            if (DBG) log("Continue ARP check, rssi level: " + mCurrentSignalLevel);
-                            sendMessageDelayed(obtainMessage(CMD_ARP_CHECK, ++mArpToken, 0),
-                                    mArpCheckIntervalMs);
-                        }
+                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+                    //Ready to avoid bssid again ?
+                    long time = android.os.SystemClock.elapsedRealtime();
+                    if (time - mLastBssidAvoidedTime  > MIN_INTERVAL_AVOID_BSSID_MS) {
+                        handleRssiChange();
+                    } else {
+                        if (DBG) log("Early to avoid " + mWifiInfo + " time: " + time +
+                                " last avoided: " + mLastBssidAvoidedTime);
                     }
                     break;
                 default:
@@ -725,10 +758,65 @@
         }
     }
 
+    class RssiMonitoringState extends State {
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+            sendMessage(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0));
+        }
+
+        public boolean processMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_RSSI_CHANGE:
+                    mCurrentSignalLevel = calculateSignalLevel(msg.arg1);
+                    if (mCurrentSignalLevel <= RSSI_LEVEL_CUTOFF) {
+                        sendPoorLinkDetected();
+                    } else if (mCurrentSignalLevel <= RSSI_LEVEL_MONITOR) {
+                        //stay here;
+                    } else {
+                        //We dont need frequent RSSI monitoring any more
+                        transitionTo(mOnlineWatchState);
+                    }
+                    break;
+                case CMD_RSSI_FETCH:
+                    if (msg.arg1 == mRssiFetchToken) {
+                        mWsmChannel.sendMessage(RSSI_FETCH);
+                        sendMessageDelayed(obtainMessage(CMD_RSSI_FETCH, ++mRssiFetchToken, 0),
+                                mRssiFetchIntervalMs);
+                    }
+                    break;
+                case RSSI_FETCH_SUCCEEDED:
+                    int rssi = msg.arg1;
+                    if (DBG) log("RSSI_FETCH_SUCCEEDED: " + rssi);
+                    if (msg.arg1 < RSSI_MONITOR_THRESHOLD) {
+                        mRssiMonitorCount++;
+                    } else {
+                        mRssiMonitorCount = 0;
+                    }
+
+                    if (mRssiMonitorCount > RSSI_MONITOR_COUNT) {
+                        sendPoorLinkDetected();
+                        ++mRssiFetchToken;
+                    }
+                    break;
+                case RSSI_FETCH_FAILED:
+                    //can happen if we are waiting to get a disconnect notification
+                    if (DBG) log("RSSI_FETCH_FAILED");
+                    break;
+                default:
+                    return NOT_HANDLED;
+            }
+            return HANDLED;
+        }
+   }
+
     /* Child state of ConnectedState indicating that we are online
      * and there is nothing to do
      */
     class OnlineState extends State {
+        @Override
+        public void enter() {
+            if (DBG) log(getName() + "\n");
+        }
     }
 
     private boolean shouldCheckWalledGarden() {
@@ -794,6 +882,20 @@
         return success;
     }
 
+    private int calculateSignalLevel(int rssi) {
+        int signalLevel = WifiManager.calculateSignalLevel(rssi,
+                WifiManager.RSSI_LEVELS);
+        if (DBG) log("RSSI current: " + mCurrentSignalLevel + "new: " + rssi + ", " + signalLevel);
+        return signalLevel;
+    }
+
+    private void sendPoorLinkDetected() {
+        if (DBG) log("send POOR_LINK_DETECTED " + mWifiInfo);
+        mWsmChannel.sendMessage(POOR_LINK_DETECTED);
+        mLastAvoidedBssid = mWifiInfo.getBSSID();
+        mLastBssidAvoidedTime = android.os.SystemClock.elapsedRealtime();
+    }
+
     /**
      * Convenience function for retrieving a single secure settings value
      * as a string with a default value.
@@ -844,11 +946,11 @@
         return Settings.Secure.putInt(cr, name, value ? 1 : 0);
     }
 
-    private void log(String s) {
+    private static void log(String s) {
         Log.d(TAG, s);
     }
 
-    private void loge(String s) {
+    private static void loge(String s) {
         Log.e(TAG, s);
     }
 }