Merge change I4db11d50 into eclair

* changes:
  Backport the change I30b141a2 from MR2 to MR1. Do not merge.
diff --git a/core/java/android/accounts/AccountAuthenticatorCache.java b/core/java/android/accounts/AccountAuthenticatorCache.java
index ce063a7..d6c76a2 100644
--- a/core/java/android/accounts/AccountAuthenticatorCache.java
+++ b/core/java/android/accounts/AccountAuthenticatorCache.java
@@ -18,10 +18,16 @@
 
 import android.content.pm.PackageManager;
 import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
 import android.content.res.TypedArray;
 import android.content.Context;
 import android.util.AttributeSet;
 import android.text.TextUtils;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 
 /**
  * A cache of services that export the {@link IAccountAuthenticator} interface. This cache
@@ -33,10 +39,12 @@
 /* package private */ class AccountAuthenticatorCache
         extends RegisteredServicesCache<AuthenticatorDescription> {
     private static final String TAG = "Account";
+    private static final MySerializer sSerializer = new MySerializer();
 
     public AccountAuthenticatorCache(Context context) {
         super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
-                AccountManager.AUTHENTICATOR_META_DATA_NAME, AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME);
+                AccountManager.AUTHENTICATOR_META_DATA_NAME,
+                AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
     }
 
     public AuthenticatorDescription parseServiceAttributes(String packageName, AttributeSet attrs) {
@@ -62,4 +70,16 @@
             sa.recycle();
         }
     }
+
+    private static class MySerializer implements XmlSerializerAndParser<AuthenticatorDescription> {
+        public void writeAsXml(AuthenticatorDescription item, XmlSerializer out)
+                throws IOException {
+            out.attribute(null, "type", item.type);
+        }
+
+        public AuthenticatorDescription createFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            return AuthenticatorDescription.newKey(parser.getAttributeValue(null, "type"));
+        }
+    }
 }
diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java
index 4f59c4e..800ad749 100644
--- a/core/java/android/accounts/AccountManagerService.java
+++ b/core/java/android/accounts/AccountManagerService.java
@@ -72,7 +72,7 @@
  */
 public class AccountManagerService
         extends IAccountManager.Stub
-        implements RegisteredServicesCacheListener {
+        implements RegisteredServicesCacheListener<AuthenticatorDescription> {
     private static final String GOOGLE_ACCOUNT_TYPE = "com.google";
 
     private static final String NO_BROADCAST_FLAG = "nobroadcast";
@@ -220,34 +220,29 @@
         mMessageHandler = new MessageHandler(mMessageThread.getLooper());
 
         mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
-        mAuthenticatorCache.setListener(this);
+        mAuthenticatorCache.setListener(this, null /* Handler */);
         mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
                 MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
 
         mSimWatcher = new SimWatcher(mContext);
         sThis.set(this);
-
-        onRegisteredServicesCacheChanged();
     }
 
-    public void onRegisteredServicesCacheChanged() {
+    public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
         boolean accountDeleted = false;
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor cursor = db.query(TABLE_ACCOUNTS,
                 new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
-                null, null, null, null, null);
+                ACCOUNTS_TYPE + "=?", new String[]{desc.type}, null, null, null);
         try {
             while (cursor.moveToNext()) {
                 final long accountId = cursor.getLong(0);
                 final String accountType = cursor.getString(1);
                 final String accountName = cursor.getString(2);
-                if (mAuthenticatorCache.getServiceInfo(AuthenticatorDescription.newKey(accountType))
-                        == null) {
-                    Log.d(TAG, "deleting account " + accountName + " because type "
-                            + accountType + " no longer has a registered authenticator");
-                    db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
-                    accountDeleted= true;
-                }
+                Log.d(TAG, "deleting account " + accountName + " because type "
+                        + accountType + " no longer has a registered authenticator");
+                db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
+                accountDeleted = true;
             }
         } finally {
             cursor.close();
diff --git a/core/java/android/accounts/AuthenticatorDescription.java b/core/java/android/accounts/AuthenticatorDescription.java
index e642700..91c94e6 100644
--- a/core/java/android/accounts/AuthenticatorDescription.java
+++ b/core/java/android/accounts/AuthenticatorDescription.java
@@ -87,6 +87,10 @@
         return type.equals(other.type);
     }
 
+    public String toString() {
+        return "AuthenticatorDescription {type=" + type + "}";
+    }
+
     /** @inhericDoc */
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeString(type);
diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java
index eba8715..fbe3548 100644
--- a/core/java/android/content/AbstractSyncableContentProvider.java
+++ b/core/java/android/content/AbstractSyncableContentProvider.java
@@ -135,6 +135,8 @@
         public void onCreate(SQLiteDatabase db) {
             bootstrapDatabase(db);
             mSyncState.createDatabase(db);
+            ContentResolver.requestSync(null /* all accounts */,
+                    mContentUri.getAuthority(), new Bundle());
         }
 
         @Override
diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java
index 7d9f1de..6ade837 100644
--- a/core/java/android/content/SyncAdaptersCache.java
+++ b/core/java/android/content/SyncAdaptersCache.java
@@ -17,9 +17,14 @@
 package android.content;
 
 import android.content.pm.RegisteredServicesCache;
+import android.content.pm.XmlSerializerAndParser;
 import android.content.res.TypedArray;
-import android.content.Context;
 import android.util.AttributeSet;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 
 /**
  * A cache of services that export the {@link android.content.ISyncAdapter} interface.
@@ -31,9 +36,10 @@
     private static final String SERVICE_INTERFACE = "android.content.SyncAdapter";
     private static final String SERVICE_META_DATA = "android.content.SyncAdapter";
     private static final String ATTRIBUTES_NAME = "sync-adapter";
+    private static final MySerializer sSerializer = new MySerializer();
 
     SyncAdaptersCache(Context context) {
-        super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME);
+        super(context, SERVICE_INTERFACE, SERVICE_META_DATA, ATTRIBUTES_NAME, sSerializer);
     }
 
     public SyncAdapterType parseServiceAttributes(String packageName, AttributeSet attrs) {
@@ -57,4 +63,18 @@
             sa.recycle();
         }
     }
+
+    static class MySerializer implements XmlSerializerAndParser<SyncAdapterType> {
+        public void writeAsXml(SyncAdapterType item, XmlSerializer out) throws IOException {
+            out.attribute(null, "authority", item.authority);
+            out.attribute(null, "accountType", item.accountType);
+        }
+    
+        public SyncAdapterType createFromXml(XmlPullParser parser)
+                throws IOException, XmlPullParserException {
+            final String authority = parser.getAttributeValue(null, "authority");
+            final String accountType = parser.getAttributeValue(null, "accountType");
+            return SyncAdapterType.newKey(authority, accountType);
+        }
+    }
 }
\ No newline at end of file
diff --git a/core/java/android/content/pm/RegisteredServicesCache.java b/core/java/android/content/pm/RegisteredServicesCache.java
index 538373f..b39a67d 100644
--- a/core/java/android/content/pm/RegisteredServicesCache.java
+++ b/core/java/android/content/pm/RegisteredServicesCache.java
@@ -22,6 +22,8 @@
 import android.content.IntentFilter;
 import android.content.ComponentName;
 import android.content.res.XmlResourceParser;
+import android.os.Environment;
+import android.os.Handler;
 import android.util.Log;
 import android.util.AttributeSet;
 import android.util.Xml;
@@ -29,14 +31,26 @@
 import java.util.Map;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.ArrayList;
+import java.util.concurrent.atomic.AtomicReference;
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.IOException;
+import java.io.FileInputStream;
+
+import com.android.internal.os.AtomicFile;
+import com.android.internal.util.FastXmlSerializer;
 
 import com.google.android.collect.Maps;
+import com.google.android.collect.Lists;
+
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
 
 /**
  * A cache of registered services. This cache
@@ -52,75 +66,104 @@
     private final String mInterfaceName;
     private final String mMetaDataName;
     private final String mAttributesName;
+    private final XmlSerializerAndParser<V> mSerializerAndParser;
+    private final AtomicReference<BroadcastReceiver> mReceiver;
 
-    public RegisteredServicesCacheListener getListener() {
-        return mListener;
-    }
+    private final Object mServicesLock = new Object();
+    // synchronized on mServicesLock
+    private HashMap<V, Integer> mPersistentServices;
+    // synchronized on mServicesLock
+    private Map<V, ServiceInfo<V>> mServices;
+    // synchronized on mServicesLock
+    private boolean mPersistentServicesFileDidNotExist;
 
-    public void setListener(RegisteredServicesCacheListener listener) {
-        mListener = listener;
-    }
+    /**
+     * This file contains the list of known services. We would like to maintain this forever
+     * so we store it as an XML file.
+     */
+    private final AtomicFile mPersistentServicesFile;
 
-    private volatile RegisteredServicesCacheListener mListener;
-
-    // no need to be synchronized since the map is never changed once mService is written
-    volatile Map<V, ServiceInfo<V>> mServices;
-
-    // synchronized on "this"
-    private BroadcastReceiver mReceiver = null;
+    // the listener and handler are synchronized on "this" and must be updated together
+    private RegisteredServicesCacheListener<V> mListener;
+    private Handler mHandler;
 
     public RegisteredServicesCache(Context context, String interfaceName, String metaDataName,
-            String attributeName) {
+            String attributeName, XmlSerializerAndParser<V> serializerAndParser) {
         mContext = context;
         mInterfaceName = interfaceName;
         mMetaDataName = metaDataName;
         mAttributesName = attributeName;
+        mSerializerAndParser = serializerAndParser;
+
+        File dataDir = Environment.getDataDirectory();
+        File systemDir = new File(dataDir, "system");
+        File syncDir = new File(systemDir, "registered_services");
+        mPersistentServicesFile = new AtomicFile(new File(syncDir, interfaceName + ".xml"));
+
+        generateServicesMap();
+
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context1, Intent intent) {
+                generateServicesMap();
+            }
+        };
+        mReceiver = new AtomicReference<BroadcastReceiver>(receiver);
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        intentFilter.addDataScheme("package");
+        mContext.registerReceiver(receiver, intentFilter);
     }
 
     public void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
-        getAllServices();
-        Map<V, ServiceInfo<V>> services = mServices;
+        Map<V, ServiceInfo<V>> services;
+        synchronized (mServicesLock) {
+            services = mServices;
+        }
         fout.println("RegisteredServicesCache: " + services.size() + " services");
         for (ServiceInfo info : services.values()) {
             fout.println("  " + info);
         }
     }
 
-    private boolean maybeRegisterForPackageChanges() {
+    public RegisteredServicesCacheListener<V> getListener() {
         synchronized (this) {
-            if (mReceiver == null) {
-                synchronized (this) {
-                    mReceiver = new BroadcastReceiver() {
-                        @Override
-                        public void onReceive(Context context, Intent intent) {
-                            mServices = generateServicesMap();
-                            RegisteredServicesCacheListener listener = mListener;
-                            if (listener != null) {
-                                listener.onRegisteredServicesCacheChanged();
-                            }
-                        }
-                    };
-                }
-
-                IntentFilter intentFilter = new IntentFilter();
-                intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-                intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-                intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-                intentFilter.addDataScheme("package");
-                mContext.registerReceiver(mReceiver, intentFilter);
-                return true;
-            }
-            return false;
+            return mListener;
         }
     }
 
-    private void maybeUnregisterForPackageChanges() {
-        synchronized (this) {
-            if (mReceiver != null) {
-                mContext.unregisterReceiver(mReceiver);
-                mReceiver = null;
-            }
+    public void setListener(RegisteredServicesCacheListener<V> listener, Handler handler) {
+        if (handler == null) {
+            handler = new Handler(mContext.getMainLooper());
         }
+        synchronized (this) {
+            mHandler = handler;
+            mListener = listener;
+        }
+    }
+
+    private void notifyListener(final V type, final boolean removed) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.d(TAG, "notifyListener: " + type + " is " + (removed ? "removed" : "added"));
+        }
+        RegisteredServicesCacheListener<V> listener;
+        Handler handler; 
+        synchronized (this) {
+            listener = mListener;
+            handler = mHandler;
+        }
+        if (listener == null) {
+            return;
+        }
+        
+        final RegisteredServicesCacheListener<V> listener2 = listener;
+        handler.post(new Runnable() {
+            public void run() {
+                listener2.onServiceChanged(type, removed);
+            }
+        });
     }
 
     /**
@@ -140,7 +183,7 @@
 
         @Override
         public String toString() {
-            return "ServiceInfo: " + type + ", " + componentName;
+            return "ServiceInfo: " + type + ", " + componentName + ", uid " + uid;
         }
     }
 
@@ -150,11 +193,9 @@
      * @return the AuthenticatorInfo that matches the account type or null if none is present
      */
     public ServiceInfo<V> getServiceInfo(V type) {
-        if (mServices == null) {
-            maybeRegisterForPackageChanges();
-            mServices = generateServicesMap();
+        synchronized (mServicesLock) {
+            return mServices.get(type);
         }
-        return mServices.get(type);
     }
 
     /**
@@ -162,54 +203,171 @@
      * registered authenticators.
      */
     public Collection<ServiceInfo<V>> getAllServices() {
-        if (mServices == null) {
-            maybeRegisterForPackageChanges();
-            mServices = generateServicesMap();
+        synchronized (mServicesLock) {
+            return Collections.unmodifiableCollection(mServices.values());
         }
-        return Collections.unmodifiableCollection(mServices.values());
     }
 
     /**
      * Stops the monitoring of package additions, removals and changes.
      */
     public void close() {
-        maybeUnregisterForPackageChanges();
+        final BroadcastReceiver receiver = mReceiver.getAndSet(null);
+        if (receiver != null) {
+            mContext.unregisterReceiver(receiver);
+        }
     }
 
     @Override
     protected void finalize() throws Throwable {
-        synchronized (this) {
-            if (mReceiver != null) {
-                Log.e(TAG, "RegisteredServicesCache finalized without being closed");
-            }
+        if (mReceiver.get() != null) {
+            Log.e(TAG, "RegisteredServicesCache finalized without being closed");
         }
         close();
         super.finalize();
     }
 
-    Map<V, ServiceInfo<V>> generateServicesMap() {
-        Map<V, ServiceInfo<V>> services = Maps.newHashMap();
+    private boolean inSystemImage(int callerUid) {
+        String[] packages = mContext.getPackageManager().getPackagesForUid(callerUid);
+        for (String name : packages) {
+            try {
+                PackageInfo packageInfo =
+                        mContext.getPackageManager().getPackageInfo(name, 0 /* flags */);
+                if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                    return true;
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    void generateServicesMap() {
         PackageManager pm = mContext.getPackageManager();
-
-        List<ResolveInfo> resolveInfos =
-                pm.queryIntentServices(new Intent(mInterfaceName), PackageManager.GET_META_DATA);
-
+        ArrayList<ServiceInfo<V>> serviceInfos = new ArrayList<ServiceInfo<V>>();
+        List<ResolveInfo> resolveInfos = pm.queryIntentServices(new Intent(mInterfaceName),
+                PackageManager.GET_META_DATA);
         for (ResolveInfo resolveInfo : resolveInfos) {
             try {
                 ServiceInfo<V> info = parseServiceInfo(resolveInfo);
-                if (info != null) {
-                    services.put(info.type, info);
-                } else {
-                    Log.w(TAG, "Unable to load input method " + resolveInfo.toString());
+                if (info == null) {
+                    Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
+                    continue;
                 }
+                serviceInfos.add(info);
             } catch (XmlPullParserException e) {
-                Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
+                Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
             } catch (IOException e) {
-                Log.w(TAG, "Unable to load input method " + resolveInfo.toString(), e);
+                Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
             }
         }
 
-        return services;
+        synchronized (mServicesLock) {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.d(TAG, "generateServicesMap: " + mInterfaceName);
+            }
+            if (mPersistentServices == null) {
+                readPersistentServicesLocked();
+            }
+            mServices = Maps.newHashMap();
+            boolean changed = false;
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.d(TAG, "found " + serviceInfos.size() + " services");
+            }
+            for (ServiceInfo<V> info : serviceInfos) {
+                // four cases:
+                // - doesn't exist yet
+                //   - add, notify user that it was added
+                // - exists and the UID is the same
+                //   - replace, don't notify user
+                // - exists, the UID is different, and the new one is not a system package
+                //   - ignore
+                // - exists, the UID is different, and the new one is a system package
+                //   - add, notify user that it was added
+                Integer previousUid = mPersistentServices.get(info.type);
+                if (previousUid == null) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.d(TAG, "encountered new type: " + info);
+                    }
+                    changed = true;
+                    mServices.put(info.type, info);
+                    mPersistentServices.put(info.type, info.uid);
+                    if (!mPersistentServicesFileDidNotExist) {
+                        notifyListener(info.type, false /* removed */);
+                    }
+                } else if (previousUid == info.uid) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.d(TAG, "encountered existing type with the same uid: " + info);
+                    }
+                    mServices.put(info.type, info);
+                } else if (inSystemImage(info.uid)
+                        || !containsTypeAndUid(serviceInfos, info.type, previousUid)) {
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        if (inSystemImage(info.uid)) {
+                            Log.d(TAG, "encountered existing type with a new uid but from"
+                                    + " the system: " + info);
+                        } else {
+                            Log.d(TAG, "encountered existing type with a new uid but existing was"
+                                    + " removed: " + info);
+                        }
+                    }
+                    changed = true;
+                    mServices.put(info.type, info);
+                    mPersistentServices.put(info.type, info.uid);
+                    notifyListener(info.type, false /* removed */);
+                } else {
+                    // ignore
+                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                        Log.d(TAG, "encountered existing type with a new uid, ignoring: " + info);
+                    }
+                }
+            }
+
+            ArrayList<V> toBeRemoved = Lists.newArrayList();
+            for (V v1 : mPersistentServices.keySet()) {
+                if (!containsType(serviceInfos, v1)) {
+                    toBeRemoved.add(v1);
+                }
+            }
+            for (V v1 : toBeRemoved) {
+                mPersistentServices.remove(v1);
+                changed = true;
+                notifyListener(v1, true /* removed */);
+            }
+            if (changed) {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.d(TAG, "writing updated list of persistent services");
+                }
+                writePersistentServicesLocked();
+            } else {
+                if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                    Log.d(TAG, "persistent services did not change, so not writing anything");
+                }
+            }
+            mPersistentServicesFileDidNotExist = false;
+        }
+    }
+
+    private boolean containsType(ArrayList<ServiceInfo<V>> serviceInfos, V type) {
+        for (int i = 0, N = serviceInfos.size(); i < N; i++) {
+            if (serviceInfos.get(i).type.equals(type)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    private boolean containsTypeAndUid(ArrayList<ServiceInfo<V>> serviceInfos, V type, int uid) {
+        for (int i = 0, N = serviceInfos.size(); i < N; i++) {
+            final ServiceInfo<V> serviceInfo = serviceInfos.get(i);
+            if (serviceInfo.type.equals(type) && serviceInfo.uid == uid) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     private ServiceInfo<V> parseServiceInfo(ResolveInfo service)
@@ -252,5 +410,89 @@
         }
     }
 
+    /**
+     * Read all sync status back in to the initial engine state.
+     */
+    private void readPersistentServicesLocked() {
+        mPersistentServices = Maps.newHashMap();
+        if (mSerializerAndParser == null) {
+            return;
+        }
+        FileInputStream fis = null;
+        try {
+            mPersistentServicesFileDidNotExist = !mPersistentServicesFile.getBaseFile().exists();
+            if (mPersistentServicesFileDidNotExist) {
+                return;
+            }
+            fis = mPersistentServicesFile.openRead();
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, null);
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG) {
+                eventType = parser.next();
+            }
+            String tagName = parser.getName();
+            if ("services".equals(tagName)) {
+                eventType = parser.next();
+                do {
+                    if (eventType == XmlPullParser.START_TAG && parser.getDepth() == 2) {
+                        tagName = parser.getName();
+                        if ("service".equals(tagName)) {
+                            V service = mSerializerAndParser.createFromXml(parser);
+                            if (service == null) {
+                                break;
+                            }
+                            String uidString = parser.getAttributeValue(null, "uid");
+                            int uid = Integer.parseInt(uidString);
+                            mPersistentServices.put(service, uid);
+                        }
+                    }
+                    eventType = parser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            }
+        } catch (Exception e) {
+            Log.w(TAG, "Error reading persistent services, starting from scratch", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (java.io.IOException e1) {
+                }
+            }
+        }
+    }
+
+    /**
+     * Write all sync status to the sync status file.
+     */
+    private void writePersistentServicesLocked() {
+        if (mSerializerAndParser == null) {
+            return;
+        }
+        FileOutputStream fos = null;
+        try {
+            fos = mPersistentServicesFile.startWrite();
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, "utf-8");
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            out.startTag(null, "services");
+            for (Map.Entry<V, Integer> service : mPersistentServices.entrySet()) {
+                out.startTag(null, "service");
+                out.attribute(null, "uid", Integer.toString(service.getValue()));
+                mSerializerAndParser.writeAsXml(service.getKey(), out);
+                out.endTag(null, "service");
+            }
+            out.endTag(null, "services");
+            out.endDocument();
+            mPersistentServicesFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Log.w(TAG, "Error writing accounts", e1);
+            if (fos != null) {
+                mPersistentServicesFile.failWrite(fos);
+            }
+        }
+    }
+
     public abstract V parseServiceAttributes(String packageName, AttributeSet attrs);
 }
diff --git a/core/java/android/content/pm/RegisteredServicesCacheListener.java b/core/java/android/content/pm/RegisteredServicesCacheListener.java
index c92c86e..2bc0942 100644
--- a/core/java/android/content/pm/RegisteredServicesCacheListener.java
+++ b/core/java/android/content/pm/RegisteredServicesCacheListener.java
@@ -1,12 +1,16 @@
 package android.content.pm;
 
+import android.os.Parcelable;
+
 /**
  * Listener for changes to the set of registered services managed by a RegisteredServicesCache.
  * @hide
  */
-public interface RegisteredServicesCacheListener {
+public interface RegisteredServicesCacheListener<V> {
     /**
-     * Invoked when the registered services cache changes.
+     * Invoked when a service is registered or changed.
+     * @param type the type of registered service
+     * @param removed true if the service was removed
      */
-    void onRegisteredServicesCacheChanged();
+    void onServiceChanged(V type, boolean removed);
 }
diff --git a/core/java/android/content/pm/XmlSerializerAndParser.java b/core/java/android/content/pm/XmlSerializerAndParser.java
new file mode 100644
index 0000000..33598f0
--- /dev/null
+++ b/core/java/android/content/pm/XmlSerializerAndParser.java
@@ -0,0 +1,14 @@
+package android.content.pm;
+
+import org.xmlpull.v1.XmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import android.os.Parcel;
+
+import java.io.IOException;
+
+/** @hide */
+public interface XmlSerializerAndParser<T> {
+    void writeAsXml(T item, XmlSerializer out) throws IOException;
+    T createFromXml(XmlPullParser parser) throws IOException, XmlPullParserException;
+}
diff --git a/core/java/android/service/wallpaper/IWallpaperEngine.aidl b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
index bbd9dde..37e6133 100644
--- a/core/java/android/service/wallpaper/IWallpaperEngine.aidl
+++ b/core/java/android/service/wallpaper/IWallpaperEngine.aidl
@@ -16,11 +16,14 @@
 
 package android.service.wallpaper;
 
+import android.view.MotionEvent;
+
 /**
  * @hide
  */
 oneway interface IWallpaperEngine {
     void setDesiredSize(int width, int height);
     void setVisibility(boolean visible);
+    void dispatchPointer(in MotionEvent event);
 	void destroy();
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index e79832b..b29d837 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -746,6 +746,12 @@
             mCaller.sendMessage(msg);
         }
 
+        public void dispatchPointer(MotionEvent event) {
+            if (mEngine != null) {
+                mEngine.mWindow.onDispatchPointer(event, event.getEventTime(), false);
+            }
+        }
+        
         public void destroy() {
             Message msg = mCaller.obtainMessage(DO_DETACH);
             mCaller.sendMessage(msg);
@@ -805,6 +811,7 @@
                             mEngine.mPendingMove = null;
                         }
                     }
+                    if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
                     mEngine.onTouchEvent(ev);
                     ev.recycle();
                 } break;
diff --git a/obex/javax/obex/ClientOperation.java b/obex/javax/obex/ClientOperation.java
index 65663b1..05b498c 100644
--- a/obex/javax/obex/ClientOperation.java
+++ b/obex/javax/obex/ClientOperation.java
@@ -269,7 +269,7 @@
 
         if (mPrivateOutput == null) {
             // there are 3 bytes operation headers and 3 bytes body headers //
-            mPrivateOutput = new PrivateOutputStream(this, mMaxPacketSize - 6);
+            mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
         }
 
         mPrivateOutputOpen = true;
@@ -278,7 +278,13 @@
     }
 
     public int getMaxPacketSize() {
-        return mMaxPacketSize - 6;
+        return mMaxPacketSize - 6 - getHeaderLength();
+    }
+
+    public int getHeaderLength() {
+        // OPP may need it
+        byte[] headerArray = ObexHelper.createHeader(mRequestHeader, false);
+        return headerArray.length;
     }
 
     /**
diff --git a/obex/javax/obex/Operation.java b/obex/javax/obex/Operation.java
index 20653f2..25656ed 100644
--- a/obex/javax/obex/Operation.java
+++ b/obex/javax/obex/Operation.java
@@ -163,6 +163,8 @@
 
     long getLength();
 
+    int getHeaderLength();
+
     String getType();
 
     InputStream openInputStream() throws IOException;
diff --git a/obex/javax/obex/ServerOperation.java b/obex/javax/obex/ServerOperation.java
index 504fe35..07a3a53 100644
--- a/obex/javax/obex/ServerOperation.java
+++ b/obex/javax/obex/ServerOperation.java
@@ -124,21 +124,30 @@
              * It is a PUT request.
              */
             mGetOperation = false;
-        } else {
+
+            /*
+             * Determine if the final bit is set
+             */
+            if ((request & 0x80) == 0) {
+                finalBitSet = false;
+            } else {
+                finalBitSet = true;
+                mRequestFinished = true;
+            }
+        } else if ((request == 0x03) || (request == 0x83)) {
             /*
              * It is a GET request.
              */
             mGetOperation = true;
-        }
 
-        /*
-         * Determine if the final bit is set
-         */
-        if ((request & 0x80) == 0) {
+            // For Get request, final bit set is decided by server side logic
             finalBitSet = false;
+
+            if (request == 0x83) {
+                mRequestFinished = true;
+            }
         } else {
-            finalBitSet = true;
-            mRequestFinished = true;
+            throw new IOException("ServerOperation can not handle such request");
         }
 
         int length = in.read();
@@ -216,12 +225,9 @@
         }
 
         // wait for get request finished !!!!
-        while (mGetOperation && !finalBitSet) {
+        while (mGetOperation && !mRequestFinished) {
             sendReply(ResponseCodes.OBEX_HTTP_CONTINUE);
         }
-        if (finalBitSet && mGetOperation) {
-            mRequestFinished = true;
-        }
     }
 
     public boolean isValidBody() {
@@ -333,6 +339,12 @@
             out.write(headerArray);
         }
 
+        // For Get operation: if response code is OBEX_HTTP_OK, then this is the
+        // last packet; so set finalBitSet to true.
+        if (mGetOperation && type == ResponseCodes.OBEX_HTTP_OK) {
+            finalBitSet = true;
+        }
+
         if ((finalBitSet) || (headerArray.length < (mMaxPacketLength - 20))) {
             if (bodyLength > 0) {
                 /*
@@ -410,9 +422,10 @@
                 }
             } else {
 
-                if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)
-                        || (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL)) {
+                if ((headerID == ObexHelper.OBEX_OPCODE_PUT_FINAL)) {
                     finalBitSet = true;
+                } else if (headerID == ObexHelper.OBEX_OPCODE_GET_FINAL) {
+                    mRequestFinished = true;
                 }
 
                 /*
@@ -584,7 +597,20 @@
     }
 
     public int getMaxPacketSize() {
-        return mMaxPacketLength - 6;
+        return mMaxPacketLength - 6 - getHeaderLength();
+    }
+
+    public int getHeaderLength() {
+        long id = mListener.getConnectionId();
+        if (id == -1) {
+            replyHeader.mConnectionID = null;
+        } else {
+            replyHeader.mConnectionID = ObexHelper.convertToByteArray(id);
+        }
+
+        byte[] headerArray = ObexHelper.createHeader(replyHeader, false);
+
+        return headerArray.length;
     }
 
     /**
@@ -623,7 +649,7 @@
         }
 
         if (mPrivateOutput == null) {
-            mPrivateOutput = new PrivateOutputStream(this, mMaxPacketLength - 6);
+            mPrivateOutput = new PrivateOutputStream(this, getMaxPacketSize());
         }
         mPrivateOutputOpen = true;
         return mPrivateOutput;
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 0330163..9ca57ba 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -18,7 +18,6 @@
 
 import java.io.Writer;
 import java.util.ArrayList;
-import java.util.concurrent.Semaphore;
 
 import javax.microedition.khronos.egl.EGL10;
 import javax.microedition.khronos.egl.EGL11;
@@ -943,6 +942,9 @@
      * to a Renderer instance to do the actual drawing. Can be configured to
      * render continuously or on request.
      *
+     * All potentially blocking synchronization is done through the
+     * sGLThreadManager object. This avoids multiple-lock ordering issues.
+     *
      */
     class GLThread extends Thread {
         GLThread(Renderer renderer) {
@@ -962,51 +964,31 @@
                 Log.i("GLThread", "starting tid=" + getId());
             }
 
-            /*
-             * When the android framework launches a second instance of
-             * an activity, the new instance's onCreate() method may be
-             * called before the first instance returns from onDestroy().
-             *
-             * This semaphore ensures that only one instance at a time
-             * accesses EGL.
-             */
             try {
                 guardedRun();
             } catch (InterruptedException e) {
                 // fall thru and exit normally
             } finally {
-                synchronized(this) {
-                    if (LOG_THREADS) {
-                        Log.i("GLThread", "exiting tid=" +  getId());
-                    }
-                    mDone = true;
-                    notifyAll();
-                }
+                sGLThreadManager.threadExiting(this);
             }
         }
 
-        private void startEgl() throws InterruptedException {
-            if (! mHaveEgl) {
-                mHaveEgl = true;
-                sGLThreadManager.start(this);
-                mEglHelper.start();
-            }
-        }
-
-        private void stopEgl() {
+        /*
+         * This private method should only be called inside a
+         * synchronized(sGLThreadManager) block.
+         */
+        private void stopEglLocked() {
             if (mHaveEgl) {
                 mHaveEgl = false;
                 mEglHelper.destroySurface();
                 mEglHelper.finish();
-                sGLThreadManager.end(this);
+                sGLThreadManager.releaseEglSurface(this);
             }
         }
 
         private void guardedRun() throws InterruptedException {
             mEglHelper = new EglHelper();
             try {
-                startEgl();
-
                 GL10 gl = null;
                 boolean tellRendererSurfaceCreated = true;
                 boolean tellRendererSurfaceChanged = true;
@@ -1015,63 +997,97 @@
                  * This is our main activity thread's loop, we go until
                  * asked to quit.
                  */
-                while (!mDone) {
-
+                while (!isDone()) {
                     /*
                      *  Update the asynchronous state (window size)
                      */
-                    int w, h;
-                    boolean changed;
+                    int w = 0;
+                    int h = 0;
+                    boolean changed = false;
                     boolean needStart = false;
-                    synchronized (this) {
+                    boolean eventsWaiting = false;
+
+                    synchronized (sGLThreadManager) {
+                        while (true) {
+                            // Manage acquiring and releasing the SurfaceView
+                            // surface and the EGL surface.
+                            if (mPaused) {
+                                stopEglLocked();
+                            }
+                            if (!mHasSurface) {
+                                if (!mWaitingForSurface) {
+                                    stopEglLocked();
+                                    mWaitingForSurface = true;
+                                    sGLThreadManager.notifyAll();
+                                }
+                            } else {
+                                if (!mHaveEgl) {
+                                    if (sGLThreadManager.tryAcquireEglSurface(this)) {
+                                        mHaveEgl = true;
+                                        mEglHelper.start();
+                                        mRequestRender = true;
+                                        needStart = true;
+                                    }
+                                }
+                            }
+
+                            // Check if we need to wait. If not, update any state
+                            // that needs to be updated, copy any state that
+                            // needs to be copied, and use "break" to exit the
+                            // wait loop.
+
+                            if (mDone) {
+                                return;
+                            }
+
+                            if (mEventsWaiting) {
+                                eventsWaiting = true;
+                                mEventsWaiting = false;
+                                break;
+                            }
+
+                            if ( (! mPaused) && mHasSurface && mHaveEgl
+                                    && (mWidth > 0) && (mHeight > 0)
+                                    && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))
+                                    ) {
+                                changed = mSizeChanged;
+                                w = mWidth;
+                                h = mHeight;
+                                mSizeChanged = false;
+                                mRequestRender = false;
+                                if (mHasSurface && mWaitingForSurface) {
+                                    changed = true;
+                                    mWaitingForSurface = false;
+                                    sGLThreadManager.notifyAll();
+                                }
+                                break;
+                            }
+
+                            // By design, this is the only place where we wait().
+
+                            if (LOG_THREADS) {
+                                Log.i("GLThread", "waiting tid=" + getId());
+                            }
+                            sGLThreadManager.wait();
+                        }
+                    } // end of synchronized(sGLThreadManager)
+
+                    /*
+                     * Handle queued events
+                     */
+                    if (eventsWaiting) {
                         Runnable r;
                         while ((r = getEvent()) != null) {
                             r.run();
-                        }
-                        if (mPaused) {
-                            stopEgl();
-                            needStart = true;
-                        }
-                        while(true) {
-                            if (!mHasSurface) {
-                                if (!mWaitingForSurface) {
-                                    stopEgl();
-                                    mWaitingForSurface = true;
-                                    notifyAll();
-                                }
-                            } else {
-                                boolean shouldHaveEgl = sGLThreadManager.shouldHaveEgl(this);
-                                if (mHaveEgl && (!shouldHaveEgl)) {
-                                    stopEgl();
-                                } else if ((!mHaveEgl) && shouldHaveEgl) {
-                                    startEgl();
-                                    needStart = true;
-                                }
+                            if (isDone()) {
+                                return;
                             }
-                            if (!needToWait()) {
-                                break;
-                            }
-                            if (LOG_THREADS) {
-                                Log.i("GLThread", "needToWait tid=" + getId());
-                            }
-                            wait();
                         }
-                        if (mDone) {
-                            break;
-                        }
-                        changed = mSizeChanged;
-                        w = mWidth;
-                        h = mHeight;
-                        mSizeChanged = false;
-                        mRequestRender = false;
-                        if (mHasSurface && mWaitingForSurface) {
-                            changed = true;
-                            mWaitingForSurface = false;
-                            notifyAll();
-                        }
+                        // Go back and see if we need to wait to render.
+                        continue;
                     }
+
                     if (needStart) {
-                        startEgl();
                         tellRendererSurfaceCreated = true;
                         changed = true;
                     }
@@ -1102,71 +1118,63 @@
                 /*
                  * clean-up everything...
                  */
-                stopEgl();
+                synchronized (sGLThreadManager) {
+                    stopEglLocked();
+                }
             }
         }
 
-        private boolean needToWait() {
-            if (mDone) {
-                return false;
+        private boolean isDone() {
+            synchronized (sGLThreadManager) {
+                return mDone;
             }
-
-            if (mPaused || (! mHasSurface) || (! mHaveEgl)) {
-                return true;
-            }
-
-            if ((mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) {
-                return false;
-            }
-
-            return true;
         }
 
         public void setRenderMode(int renderMode) {
             if ( !((RENDERMODE_WHEN_DIRTY <= renderMode) && (renderMode <= RENDERMODE_CONTINUOUSLY)) ) {
                 throw new IllegalArgumentException("renderMode");
             }
-            synchronized(this) {
+            synchronized(sGLThreadManager) {
                 mRenderMode = renderMode;
                 if (renderMode == RENDERMODE_CONTINUOUSLY) {
-                    notifyAll();
+                    sGLThreadManager.notifyAll();
                 }
             }
         }
 
         public int getRenderMode() {
-            synchronized(this) {
+            synchronized(sGLThreadManager) {
                 return mRenderMode;
             }
         }
 
         public void requestRender() {
-            synchronized(this) {
+            synchronized(sGLThreadManager) {
                 mRequestRender = true;
-                notifyAll();
+                sGLThreadManager.notifyAll();
             }
         }
 
         public void surfaceCreated() {
-            synchronized(this) {
+            synchronized(sGLThreadManager) {
                 if (LOG_THREADS) {
                     Log.i("GLThread", "surfaceCreated tid=" + getId());
                 }
                 mHasSurface = true;
-                notifyAll();
+                sGLThreadManager.notifyAll();
             }
         }
 
         public void surfaceDestroyed() {
-            synchronized(this) {
+            synchronized(sGLThreadManager) {
                 if (LOG_THREADS) {
                     Log.i("GLThread", "surfaceDestroyed tid=" + getId());
                 }
                 mHasSurface = false;
-                notifyAll();
+                sGLThreadManager.notifyAll();
                 while(!mWaitingForSurface && isAlive() && ! mDone) {
                     try {
-                        wait();
+                        sGLThreadManager.wait();
                     } catch (InterruptedException e) {
                         Thread.currentThread().interrupt();
                     }
@@ -1175,35 +1183,35 @@
         }
 
         public void onPause() {
-            synchronized (this) {
+            synchronized (sGLThreadManager) {
                 mPaused = true;
-                notifyAll();
+                sGLThreadManager.notifyAll();
             }
         }
 
         public void onResume() {
-            synchronized (this) {
+            synchronized (sGLThreadManager) {
                 mPaused = false;
                 mRequestRender = true;
-                notifyAll();
+                sGLThreadManager.notifyAll();
             }
         }
 
         public void onWindowResize(int w, int h) {
-            synchronized (this) {
+            synchronized (sGLThreadManager) {
                 mWidth = w;
                 mHeight = h;
                 mSizeChanged = true;
-                notifyAll();
+                sGLThreadManager.notifyAll();
             }
         }
 
         public void requestExitAndWait() {
             // don't call this from GLThread thread or it is a guaranteed
             // deadlock!
-            synchronized(this) {
+            synchronized(sGLThreadManager) {
                 mDone = true;
-                notifyAll();
+                sGLThreadManager.notifyAll();
             }
             try {
                 join();
@@ -1219,6 +1227,10 @@
         public void queueEvent(Runnable r) {
             synchronized(this) {
                 mEventQueue.add(r);
+                synchronized(sGLThreadManager) {
+                    mEventsWaiting = true;
+                    sGLThreadManager.notifyAll();
+                }
             }
         }
 
@@ -1232,6 +1244,8 @@
             return null;
         }
 
+        // Once the thread is started, all accesses to the following member
+        // variables are protected by the sGLThreadManager monitor
         private boolean mDone;
         private boolean mPaused;
         private boolean mHasSurface;
@@ -1241,6 +1255,9 @@
         private int mHeight;
         private int mRenderMode;
         private boolean mRequestRender;
+        private boolean mEventsWaiting;
+        // End of member variables protected by the sGLThreadManager monitor.
+
         private Renderer mRenderer;
         private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>();
         private EglHelper mEglHelper;
@@ -1286,37 +1303,43 @@
         }
     }
 
-    static class GLThreadManager {
-        public boolean shouldHaveEgl(GLThread thread) {
-            synchronized(this) {
-                return thread == mMostRecentGLThread || mMostRecentGLThread == null;
+    private static class GLThreadManager {
+
+        public synchronized void threadExiting(GLThread thread) {
+            if (LOG_THREADS) {
+                Log.i("GLThread", "exiting tid=" +  thread.getId());
             }
+            thread.mDone = true;
+            if (mEglOwner == thread) {
+                mEglOwner = null;
+            }
+            notifyAll();
         }
-        public void start(GLThread thread) throws InterruptedException {
-            GLThread oldThread = null;
-            synchronized(this) {
-                oldThread = mMostRecentGLThread;
-                mMostRecentGLThread = thread;
+
+        /*
+         * Tries once to acquire the right to use an EGL
+         * surface. Does not block.
+         * @return true if the right to use an EGL surface was acquired.
+         */
+        public synchronized boolean tryAcquireEglSurface(GLThread thread) {
+            if (mEglOwner == thread || mEglOwner == null) {
+                mEglOwner = thread;
+                notifyAll();
+                return true;
             }
-            if (oldThread != null) {
-                synchronized(oldThread) {
-                    oldThread.notifyAll();
-                }
-            }
-            sEglSemaphore.acquire();
+            return false;
         }
-        public void end(GLThread thread) {
-            sEglSemaphore.release();
-            synchronized(this) {
-                if (mMostRecentGLThread == thread) {
-                    mMostRecentGLThread = null;
-                }
+
+        public synchronized void releaseEglSurface(GLThread thread) {
+            if (mEglOwner == thread) {
+                mEglOwner = null;
             }
+            notifyAll();
         }
-        private GLThread mMostRecentGLThread;
+
+        private GLThread mEglOwner;
     }
 
-    private static final Semaphore sEglSemaphore = new Semaphore(1);
     private static final GLThreadManager sGLThreadManager = new GLThreadManager();
     private boolean mSizeChanged = true;
 
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 69f4b89..327cd72 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -435,6 +435,7 @@
     float mLastWallpaperY = -1;
     float mLastWallpaperXStep = -1;
     float mLastWallpaperYStep = -1;
+    boolean mSendingPointersToWallpaper = false;
     // This is set when we are waiting for a wallpaper to tell us it is done
     // changing its scroll position.
     WindowState mWaitingOnWallpaper;
@@ -1749,8 +1750,20 @@
                 }
                 try {
                     MotionEvent ev = MotionEvent.obtainNoHistory(pointer);
-                    ev.offsetLocation(srcWin.mFrame.left-wallpaper.mFrame.left,
-                            srcWin.mFrame.top-wallpaper.mFrame.top);
+                    if (srcWin != null) {
+                        ev.offsetLocation(srcWin.mFrame.left-wallpaper.mFrame.left,
+                                srcWin.mFrame.top-wallpaper.mFrame.top);
+                    } else {
+                        ev.offsetLocation(-wallpaper.mFrame.left, -wallpaper.mFrame.top);
+                    }
+                    switch (pointer.getAction()) {
+                        case MotionEvent.ACTION_DOWN:
+                            mSendingPointersToWallpaper = true;
+                            break;
+                        case MotionEvent.ACTION_UP:
+                            mSendingPointersToWallpaper = false;
+                            break;
+                    }
                     wallpaper.mClient.dispatchPointer(ev, eventTime, false);
                 } catch (RemoteException e) {
                     Log.w(TAG, "Failure sending pointer to wallpaper", e);
@@ -4836,6 +4849,12 @@
             if (action != MotionEvent.ACTION_MOVE) {
                 Log.w(TAG, "No window to dispatch pointer action " + ev.getAction());
             }
+            synchronized (mWindowMap) {
+                if (mSendingPointersToWallpaper) {
+                    Log.i(TAG, "Sending skipped pointer to wallpaper!");
+                    sendPointerToWallpaperLocked(null, ev, ev.getEventTime());
+                }
+            }
             if (qev != null) {
                 mQueue.recycleEvent(qev);
             }
@@ -4843,6 +4862,12 @@
             return INJECT_FAILED;
         }
         if (targetObj == mKeyWaiter.CONSUMED_EVENT_TOKEN) {
+            synchronized (mWindowMap) {
+                if (mSendingPointersToWallpaper) {
+                    Log.i(TAG, "Sending skipped pointer to wallpaper!");
+                    sendPointerToWallpaperLocked(null, ev, ev.getEventTime());
+                }
+            }
             if (qev != null) {
                 mQueue.recycleEvent(qev);
             }
@@ -4963,6 +4988,19 @@
         }
 
         synchronized(mWindowMap) {
+            if (!target.isVisibleLw()) {
+                // During this motion dispatch, the target window has become
+                // invisible.
+                if (mSendingPointersToWallpaper) {
+                    sendPointerToWallpaperLocked(null, ev, eventTime);
+                }
+                if (qev != null) {
+                    mQueue.recycleEvent(qev);
+                }
+                ev.recycle();
+                return INJECT_SUCCEEDED;
+            }
+            
             if (qev != null && action == MotionEvent.ACTION_MOVE) {
                 mKeyWaiter.bindTargetWindowLocked(target,
                         KeyWaiter.RETURN_PENDING_POINTER, qev);
@@ -4987,15 +5025,16 @@
                         mKeyWaiter.mOutsideTouchTargets = null;
                     }
                 }
-                final Rect frame = target.mFrame;
-                ev.offsetLocation(-(float)frame.left, -(float)frame.top);
-                mKeyWaiter.bindTargetWindowLocked(target);
                 
                 // If we are on top of the wallpaper, then the wallpaper also
                 // gets to see this movement.
-                if (mWallpaperTarget == target) {
-                    sendPointerToWallpaperLocked(target, ev, eventTime);
+                if (mWallpaperTarget == target || mSendingPointersToWallpaper) {
+                    sendPointerToWallpaperLocked(null, ev, eventTime);
                 }
+                
+                final Rect frame = target.mFrame;
+                ev.offsetLocation(-(float)frame.left, -(float)frame.top);
+                mKeyWaiter.bindTargetWindowLocked(target);
             }
         }
 
@@ -5918,12 +5957,12 @@
                         res.offsetLocation(-win.mFrame.left, -win.mFrame.top);
                     }
                 }
-            }
-            
-            if (res != null && returnWhat == RETURN_PENDING_POINTER) {
-                synchronized (mWindowMap) {
-                    if (mWallpaperTarget == win) {
-                        sendPointerToWallpaperLocked(win, res, res.getEventTime());
+                
+                if (res != null && returnWhat == RETURN_PENDING_POINTER) {
+                    synchronized (mWindowMap) {
+                        if (mWallpaperTarget == win || mSendingPointersToWallpaper) {
+                            sendPointerToWallpaperLocked(win, res, res.getEventTime());
+                        }
                     }
                 }
             }