Merge change Ibb245ea7 into eclair

* changes:
  Return an empty PluginList instead of null for the deprecated getPluginList() api.
diff --git a/camera/libcameraservice/CameraService.cpp b/camera/libcameraservice/CameraService.cpp
index e548524..df59dcf 100644
--- a/camera/libcameraservice/CameraService.cpp
+++ b/camera/libcameraservice/CameraService.cpp
@@ -683,22 +683,30 @@
 {
     LOGD("stopPreview (pid %d)", getCallingPid());
 
-    Mutex::Autolock lock(mLock);
-    if (checkPid() != NO_ERROR) return;
+    // hold main lock during state transition
+    {
+        Mutex::Autolock lock(mLock);
+        if (checkPid() != NO_ERROR) return;
 
-    if (mHardware == 0) {
-        LOGE("mHardware is NULL, returning.");
-        return;
+        if (mHardware == 0) {
+            LOGE("mHardware is NULL, returning.");
+            return;
+        }
+
+        mHardware->stopPreview();
+        mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
+        LOGD("stopPreview(), hardware stopped OK");
+
+        if (mSurface != 0 && !mUseOverlay) {
+            mSurface->unregisterBuffers();
+        }
     }
 
-    mHardware->stopPreview();
-    mHardware->disableMsgType(CAMERA_MSG_PREVIEW_FRAME);
-    LOGD("stopPreview(), hardware stopped OK");
-
-    if (mSurface != 0 && !mUseOverlay) {
-        mSurface->unregisterBuffers();
+    // hold preview buffer lock
+    {
+        Mutex::Autolock lock(mPreviewLock);
+        mPreviewBuffer.clear();
     }
-    mPreviewBuffer.clear();
 }
 
 // stop recording mode
@@ -706,24 +714,31 @@
 {
     LOGD("stopRecording (pid %d)", getCallingPid());
 
-    Mutex::Autolock lock(mLock);
-    if (checkPid() != NO_ERROR) return;
+    // hold main lock during state transition
+    {
+        Mutex::Autolock lock(mLock);
+        if (checkPid() != NO_ERROR) return;
 
-    if (mHardware == 0) {
-        LOGE("mHardware is NULL, returning.");
-        return;
+        if (mHardware == 0) {
+            LOGE("mHardware is NULL, returning.");
+            return;
+        }
+
+        if (mMediaPlayerBeep.get() != NULL) {
+            mMediaPlayerBeep->seekTo(0);
+            mMediaPlayerBeep->start();
+        }
+
+        mHardware->stopRecording();
+        mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME);
+        LOGD("stopRecording(), hardware stopped OK");
     }
 
-    if (mMediaPlayerBeep.get() != NULL) {
-        mMediaPlayerBeep->seekTo(0);
-        mMediaPlayerBeep->start();
+    // hold preview buffer lock
+    {
+        Mutex::Autolock lock(mPreviewLock);
+        mPreviewBuffer.clear();
     }
-
-    mHardware->stopRecording();
-    mHardware->disableMsgType(CAMERA_MSG_VIDEO_FRAME);
-    LOGD("stopRecording(), hardware stopped OK");
-
-    mPreviewBuffer.clear();
 }
 
 // release a recording frame
@@ -1216,10 +1231,10 @@
     // provided it's big enough. Don't allocate the memory or
     // perform the copy if there's no callback.
 
-    // hold the lock while we grab a reference to the preview buffer
+    // hold the preview lock while we grab a reference to the preview buffer
     sp<MemoryHeapBase> previewBuffer;
     {
-        Mutex::Autolock lock(mLock);
+        Mutex::Autolock lock(mPreviewLock);
         if (mPreviewBuffer == 0) {
             mPreviewBuffer = new MemoryHeapBase(size, 0, NULL);
         } else if (size > mPreviewBuffer->virtualSize()) {
diff --git a/camera/libcameraservice/CameraService.h b/camera/libcameraservice/CameraService.h
index 41c5d99..3e3e54f 100644
--- a/camera/libcameraservice/CameraService.h
+++ b/camera/libcameraservice/CameraService.h
@@ -181,7 +181,6 @@
         mutable     Condition                   mReady;
                     sp<CameraService>           mCameraService;
                     sp<ISurface>                mSurface;
-                    sp<MemoryHeapBase>          mPreviewBuffer;
                     int                         mPreviewCallbackFlag;
 
                     sp<MediaPlayer>             mMediaPlayerClick;
@@ -197,6 +196,9 @@
                     sp<OverlayRef>              mOverlayRef;
                     int                         mOverlayW;
                     int                         mOverlayH;
+
+        mutable     Mutex                       mPreviewLock;
+                    sp<MemoryHeapBase>          mPreviewBuffer;
     };
 
 // ----------------------------------------------------------------------------
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/pim/vcard/VCardComposer.java b/core/java/android/pim/vcard/VCardComposer.java
index 9638262..f9dce25 100644
--- a/core/java/android/pim/vcard/VCardComposer.java
+++ b/core/java/android/pim/vcard/VCardComposer.java
@@ -41,6 +41,8 @@
 import android.provider.ContactsContract.CommonDataKinds.StructuredName;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
 import android.provider.ContactsContract.CommonDataKinds.Website;
+import android.telephony.PhoneNumberUtils;
+import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
 import android.text.format.Time;
 import android.util.CharsetUtils;
@@ -761,27 +763,58 @@
         }
     }
 
+    private boolean containsNonEmptyName(ContentValues contentValues) {
+        final String familyName = contentValues.getAsString(StructuredName.FAMILY_NAME);
+        final String middleName = contentValues.getAsString(StructuredName.MIDDLE_NAME);
+        final String givenName = contentValues.getAsString(StructuredName.GIVEN_NAME);
+        final String prefix = contentValues.getAsString(StructuredName.PREFIX);
+        final String suffix = contentValues.getAsString(StructuredName.SUFFIX);
+        final String displayName = contentValues.getAsString(StructuredName.DISPLAY_NAME);
+        return !(TextUtils.isEmpty(familyName) && TextUtils.isEmpty(middleName) &&
+                TextUtils.isEmpty(givenName) && TextUtils.isEmpty(prefix) &&
+                TextUtils.isEmpty(suffix) && TextUtils.isEmpty(displayName));
+    }
+
     private void appendStructuredNamesInternal(final StringBuilder builder,
             final List<ContentValues> contentValuesList) {
         // For safety, we'll emit just one value around StructuredName, as external importers
         // may get confused with multiple "N", "FN", etc. properties, though it is valid in
         // vCard spec.
         ContentValues primaryContentValues = null;
+        ContentValues subprimaryContentValues = null;
         for (ContentValues contentValues : contentValuesList) {
+            if (contentValues == null){
+                continue;
+            }
             Integer isSuperPrimary = contentValues.getAsInteger(StructuredName.IS_SUPER_PRIMARY);
-            if (isSuperPrimary != null && isSuperPrimary != 0) {
+            if (isSuperPrimary != null && isSuperPrimary > 0) {
                 // We choose "super primary" ContentValues.
                 primaryContentValues = contentValues;
                 break;
-            } else if (primaryContentValues == null && contentValues != null) {
-                // We choose the first ContentValues if "super primary" ContentValues does not exist.
-                primaryContentValues = contentValues;
+            } else if (primaryContentValues == null) {
+                // We choose the first "primary" ContentValues
+                // if "super primary" ContentValues does not exist.
+                Integer isPrimary = contentValues.getAsInteger(StructuredName.IS_PRIMARY);
+                if (isPrimary != null && isPrimary > 0 &&
+                        containsNonEmptyName(contentValues)) {
+                    primaryContentValues = contentValues;
+                    // Do not break, since there may be ContentValues with "super primary"
+                    // afterword.
+                } else if (subprimaryContentValues == null &&
+                        containsNonEmptyName(contentValues)) {
+                    subprimaryContentValues = contentValues;
+                }
             }
         }
 
         if (primaryContentValues == null) {
-            Log.e(LOG_TAG, "All ContentValues given from database is empty.");
-            primaryContentValues = new ContentValues();
+            if (subprimaryContentValues != null) {
+                // We choose the first ContentValues if any "primary" ContentValues does not exist.
+                primaryContentValues = subprimaryContentValues;
+            } else {
+                Log.e(LOG_TAG, "All ContentValues given from database is empty.");
+                primaryContentValues = new ContentValues();
+            }
         }
 
         final String familyName = primaryContentValues
@@ -1140,13 +1173,39 @@
                 if (TextUtils.isEmpty(phoneNumber)) {
                     continue;
                 }
-                phoneLineExists = true;
                 int type = (typeAsObject != null ? typeAsObject : Phone.TYPE_HOME);
-                // TODO: Premature, since this allows two phone numbers which are
-                //        same from the view of phone number format (e.g. "100" v.s. "1-0-0")
-                if (!phoneSet.contains(phoneNumber)) {
-                    phoneSet.add(phoneNumber);
-                    appendVCardTelephoneLine(builder, type, label, phoneNumber);
+
+                phoneLineExists = true;
+                if (type == Phone.TYPE_PAGER) {
+                    phoneLineExists = true;
+                    if (!phoneSet.contains(phoneNumber)) {
+                        phoneSet.add(phoneNumber);
+                        appendVCardTelephoneLine(builder, type, label, phoneNumber);
+                    }
+                } else {
+                    // The entry "may" have several phone numbers when the contact entry is
+                    // corrupted because of its original source.
+                    //
+                    // e.g. I encountered the entry like the following.
+                    // "111-222-3333 (Miami)\n444-555-6666 (Broward; 305-653-6796 (Miami); ..."
+                    // This kind of entry is not able to be inserted via Android devices, but
+                    // possible if the source of the data is already corrupted.
+                    List<String> phoneNumberList = splitIfSeveralPhoneNumbersExist(phoneNumber);
+                    if (phoneNumberList.isEmpty()) {
+                        continue;
+                    }
+                    phoneLineExists = true;
+                    for (String actualPhoneNumber : phoneNumberList) {
+                        if (!phoneSet.contains(actualPhoneNumber)) {
+                            final int format = VCardUtils.getPhoneNumberFormat(mVCardType);
+                            SpannableStringBuilder tmpBuilder =
+                                new SpannableStringBuilder(actualPhoneNumber);
+                            PhoneNumberUtils.formatNumber(tmpBuilder, format);
+                            final String formattedPhoneNumber = tmpBuilder.toString();
+                            phoneSet.add(actualPhoneNumber);
+                            appendVCardTelephoneLine(builder, type, label, formattedPhoneNumber);
+                        }
+                    }
                 }
             }
         }
@@ -1156,6 +1215,27 @@
         }
     }
 
+    private List<String> splitIfSeveralPhoneNumbersExist(final String phoneNumber) {
+        List<String> phoneList = new ArrayList<String>();
+
+        StringBuilder builder = new StringBuilder();
+        final int length = phoneNumber.length();
+        for (int i = 0; i < length; i++) {
+            final char ch = phoneNumber.charAt(i);
+            if (Character.isDigit(ch)) {
+                builder.append(ch);
+            } else if ((ch == ';' || ch == '\n') && builder.length() > 0) {
+                phoneList.add(builder.toString());
+                builder = new StringBuilder();
+            }
+        }
+        if (builder.length() > 0) {
+            phoneList.add(builder.toString());
+        }
+
+        return phoneList;
+    }
+
     private void appendEmails(final StringBuilder builder,
             final Map<String, List<ContentValues>> contentValuesListMap) {
         final List<ContentValues> contentValuesList = contentValuesListMap
diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java
index 974fca8..11b3888 100644
--- a/core/java/android/pim/vcard/VCardParser_V21.java
+++ b/core/java/android/pim/vcard/VCardParser_V21.java
@@ -16,6 +16,8 @@
 package android.pim.vcard;
 
 import android.pim.vcard.exception.VCardException;
+import android.pim.vcard.exception.VCardInvalidCommentLineException;
+import android.pim.vcard.exception.VCardInvalidLineException;
 import android.pim.vcard.exception.VCardNestedException;
 import android.pim.vcard.exception.VCardNotSupportedException;
 import android.pim.vcard.exception.VCardVersionException;
@@ -52,7 +54,7 @@
             Arrays.asList("INLINE", "URL", "CONTENT-ID", "CID"));
         
     /** Store the property names available in vCard 2.1 */
-    private static final HashSet<String> sAvailablePropertyNameV21 =
+    private static final HashSet<String> sAvailablePropertyNameSetV21 =
         new HashSet<String>(Arrays.asList(
                 "BEGIN", "LOGO", "PHOTO", "LABEL", "FN", "TITLE", "SOUND",
                 "VERSION", "TEL", "EMAIL", "TZ", "GEO", "NOTE", "URL",
@@ -152,7 +154,7 @@
      * @return true when the propertyName is a valid property name.
      */
     protected boolean isValidPropertyName(String propertyName) {
-        if (!(sAvailablePropertyNameV21.contains(propertyName.toUpperCase()) ||
+        if (!(sAvailablePropertyNameSetV21.contains(propertyName.toUpperCase()) ||
                 propertyName.startsWith("X-")) && 
                 !mWarningValueMap.contains(propertyName)) {
             mWarningValueMap.add(propertyName);
@@ -342,7 +344,12 @@
                 mBuilder.startProperty();
                 mTimeStartProperty += System.currentTimeMillis() - start;
             }
-            ended = parseItem();
+            try {
+                ended = parseItem();
+            } catch (VCardInvalidCommentLineException e) {
+                Log.e(LOG_TAG, "Invalid line which looks like some comment was found. Ignored.");
+                ended = false;
+            }
             if (mBuilder != null && !ended) {
                 long start = System.currentTimeMillis();
                 mBuilder.endProperty();
@@ -369,7 +376,7 @@
             return true;
         }
         if (propertyNameAndValue.length != 2) {
-            throw new VCardException("Invalid line \"" + line + "\""); 
+            throw new VCardInvalidLineException("Invalid line \"" + line + "\"");
         }
         String propertyName = propertyNameAndValue[0].toUpperCase();
         String propertyValue = propertyNameAndValue[1];
@@ -418,7 +425,11 @@
         int nameIndex = 0;
 
         String[] propertyNameAndValue = new String[2];
-        
+
+        if (length > 0 && line.charAt(0) == '#') {
+            throw new VCardInvalidCommentLineException();
+        }
+
         for (int i = 0; i < length; i++) {
             char ch = line.charAt(i); 
             switch (state) {
@@ -483,7 +494,7 @@
             }
         }
         
-        throw new VCardException("Invalid line: \"" + line + "\"");
+        throw new VCardInvalidLineException("Invalid line: \"" + line + "\"");
     }
     
     
@@ -527,7 +538,7 @@
     /**
      * ptypeval = knowntype / "X-" word
      */
-    protected void handleType(String ptypeval) {
+    protected void handleType(final String ptypeval) {
         String upperTypeValue = ptypeval;
         if (!(sKnownTypeSet.contains(upperTypeValue) || upperTypeValue.startsWith("X-")) && 
                 !mWarningValueMap.contains(ptypeval)) {
@@ -543,7 +554,7 @@
     /**
      * pvalueval = "INLINE" / "URL" / "CONTENT-ID" / "CID" / "X-" word
      */
-    protected void handleValue(String pvalueval) throws VCardException {
+    protected void handleValue(final String pvalueval) throws VCardException {
         if (sKnownValueSet.contains(pvalueval.toUpperCase()) ||
                 pvalueval.startsWith("X-")) {
             if (mBuilder != null) {
diff --git a/core/java/android/pim/vcard/VCardUtils.java b/core/java/android/pim/vcard/VCardUtils.java
index 4f50103..dd44288 100644
--- a/core/java/android/pim/vcard/VCardUtils.java
+++ b/core/java/android/pim/vcard/VCardUtils.java
@@ -20,6 +20,7 @@
 import android.provider.ContactsContract.Data;
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
+import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 
 import java.util.Collection;
@@ -168,6 +169,14 @@
         return list;
     }
 
+    public static int getPhoneNumberFormat(final int vcardType) {
+        if (VCardConfig.isJapaneseDevice(vcardType)) {
+            return PhoneNumberUtils.FORMAT_JAPAN;
+        } else {
+            return PhoneNumberUtils.FORMAT_NANP;
+        }
+    }
+
     /**
      * Inserts postal data into the builder object.
      * 
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java
new file mode 100644
index 0000000..67db62c
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardInvalidCommentLineException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.pim.vcard.exception;
+
+/**
+ * Thrown when the vCard has some line starting with '#'. In the specification,
+ * both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
+ * such lines.
+ */
+public class VCardInvalidCommentLineException extends VCardInvalidLineException {
+    public VCardInvalidCommentLineException() {
+        super();
+    }
+
+    public VCardInvalidCommentLineException(final String message) {
+        super(message);
+    }
+}
diff --git a/core/java/android/pim/vcard/exception/VCardInvalidLineException.java b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
new file mode 100644
index 0000000..330153e
--- /dev/null
+++ b/core/java/android/pim/vcard/exception/VCardInvalidLineException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.pim.vcard.exception;
+
+/**
+ * Thrown when the vCard has some line starting with '#'. In the specification,
+ * both vCard 2.1 and vCard 3.0 does not allow such line, but some actual exporter emit
+ * such lines.
+ */
+public class VCardInvalidLineException extends VCardException {
+    public VCardInvalidLineException() {
+        super();
+    }
+
+    public VCardInvalidLineException(final String message) {
+        super(message);
+    }
+}
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/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
new file mode 100644
index 0000000..6bcecc3
--- /dev/null
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -0,0 +1,626 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.ImageView.ScaleType;
+import com.android.internal.R;
+
+/**
+ * A special widget containing two Sliders and a threshold for each.  Moving either slider beyond
+ * the threshold will cause the registered OnTriggerListener.onTrigger() to be called with
+ * {@link OnTriggerListener#LEFT_HANDLE} or {@link OnTriggerListener#RIGHT_HANDLE} to be called.
+ *
+ */
+public class SlidingTab extends ViewGroup {
+    private static final String LOG_TAG = "SlidingTab";
+    private static final boolean DBG = false;
+    private static final int HORIZONTAL = 0; // as defined in attrs.xml
+    private static final int VERTICAL = 1;
+    private static final int MSG_ANIMATE = 100;
+
+    // TODO: Make these configurable
+    private static final float TARGET_ZONE = 2.0f / 3.0f;
+    private static final long VIBRATE_SHORT = 30;
+    private static final long VIBRATE_LONG = 40;
+
+    private OnTriggerListener mOnTriggerListener;
+    private int mGrabbedState = OnTriggerListener.NO_HANDLE;
+    private boolean mTriggered = false;
+    private Vibrator mVibrator;
+    private float mDensity; // used to scale dimensions for bitmaps.
+
+    private final SlidingTabHandler mHandler = new SlidingTabHandler();
+
+    /**
+     * Either {@link #HORIZONTAL} or {@link #VERTICAL}.
+     */
+    private int mOrientation;
+
+    private Slider mLeftSlider;
+    private Slider mRightSlider;
+    private Slider mCurrentSlider;
+    private boolean mTracking;
+    private float mTargetZone;
+    private Slider mOtherSlider;
+    private boolean mAnimating;
+
+    /**
+     * Interface definition for a callback to be invoked when a tab is triggered
+     * by moving it beyond a target zone.
+     */
+    public interface OnTriggerListener {
+        /**
+         * The interface was triggered because the user let go of the handle without reaching the
+         * target zone.
+         */
+        public static final int NO_HANDLE = 0;
+
+        /**
+         * The interface was triggered because the user grabbed the left handle and moved it past
+         * the target zone.
+         */
+        public static final int LEFT_HANDLE = 1;
+
+        /**
+         * The interface was triggered because the user grabbed the right handle and moved it past
+         * the target zone.
+         */
+        public static final int RIGHT_HANDLE = 2;
+
+        /**
+         * Called when the user moves a handle beyond the target zone.
+         *
+         * @param v The view that was triggered.
+         * @param whichHandle  Which "dial handle" the user grabbed,
+         *        either {@link #LEFT_HANDLE}, {@link #RIGHT_HANDLE}.
+         */
+        void onTrigger(View v, int whichHandle);
+
+        /**
+         * Called when the "grabbed state" changes (i.e. when the user either grabs or releases
+         * one of the handles.)
+         *
+         * @param v the view that was triggered
+         * @param grabbedState the new state: {@link #NO_HANDLE}, {@link #LEFT_HANDLE},
+         * or {@link #RIGHT_HANDLE}.
+         */
+        void onGrabbedStateChange(View v, int grabbedState);
+    }
+
+    /**
+     * Simple container class for all things pertinent to a slider.  
+     * A slider consists of 3 Views:
+     * 
+     * {@link #tab} is the tab shown on the screen in the default state.
+     * {@link #text} is the view revealed as the user slides the tab out.
+     * {@link #target} is the target the user must drag the slider past to trigger the slider.
+     *
+     */
+    private static class Slider {
+        /**
+         * Tab alignment - determines which side the tab should be drawn on
+         */
+        public static final int ALIGN_LEFT = 0;
+        public static final int ALIGN_RIGHT = 1;
+        public static final int ALIGN_TOP = 2;
+        public static final int ALIGN_BOTTOM = 3;
+
+        /**
+         * States for the view.
+         */
+        private static final int STATE_NORMAL = 0;
+        private static final int STATE_PRESSED = 1;
+        private static final int STATE_ACTIVE = 2;
+
+        private final ImageView tab;
+        private final TextView text;
+        private final ImageView target;
+
+        /**
+         * Constructor
+         * 
+         * @param parent the container view of this one
+         * @param tabId drawable for the tab
+         * @param barId drawable for the bar
+         * @param targetId drawable for the target
+         */
+        Slider(ViewGroup parent, int tabId, int barId, int targetId) {
+            // Create tab
+            tab = new ImageView(parent.getContext());
+            tab.setBackgroundResource(tabId);
+            tab.setScaleType(ScaleType.CENTER);
+            tab.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                    LayoutParams.WRAP_CONTENT));
+
+            // Create hint TextView
+            text = new TextView(parent.getContext());
+            text.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                    LayoutParams.FILL_PARENT));
+            text.setBackgroundResource(barId);
+            text.setTextAppearance(parent.getContext(), R.style.TextAppearance_SlidingTabNormal);
+            // hint.setSingleLine();  // Hmm.. this causes the text to disappear off-screen
+
+            // Create target
+            target = new ImageView(parent.getContext());
+            target.setImageResource(targetId);
+            target.setScaleType(ScaleType.CENTER);
+            target.setLayoutParams(
+                    new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
+            target.setVisibility(View.INVISIBLE);
+
+            parent.addView(target); // this needs to be first - relies on painter's algorithm
+            parent.addView(tab);
+            parent.addView(text);
+        }
+
+        void setIcon(int iconId) {
+            tab.setImageResource(iconId);
+        }
+        
+        void setTabBackgroundResource(int tabId) {
+            tab.setBackgroundResource(tabId);
+        }
+        
+        void setBarBackgroundResource(int barId) {
+            text.setBackgroundResource(barId);
+        }
+        
+        void setHintText(int resId) {
+            // TODO: Text should be blank if widget is vertical
+            text.setText(resId); 
+        }
+
+        void hide() {
+            // TODO: Animate off the screen
+            text.setVisibility(View.INVISIBLE);
+            tab.setVisibility(View.INVISIBLE);
+            target.setVisibility(View.INVISIBLE);
+        }
+
+        void setState(int state) {
+            text.setPressed(state == STATE_PRESSED);
+            tab.setPressed(state == STATE_PRESSED);
+            if (state == STATE_ACTIVE) {
+                final int[] activeState = new int[] {com.android.internal.R.attr.state_active};
+                if (text.getBackground().isStateful()) {
+                    text.getBackground().setState(activeState);
+                }
+                if (tab.getBackground().isStateful()) {
+                    tab.getBackground().setState(activeState);
+                }
+                text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabActive);
+            } else {
+                text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
+            }
+        }
+
+        void showTarget() {
+            target.setVisibility(View.VISIBLE);
+        }
+
+        void reset() {
+            setState(STATE_NORMAL);
+            text.setVisibility(View.VISIBLE);
+            text.setTextAppearance(text.getContext(), R.style.TextAppearance_SlidingTabNormal);
+            tab.setVisibility(View.VISIBLE);
+            target.setVisibility(View.INVISIBLE);
+        }
+
+        void setTarget(int targetId) {
+            target.setImageResource(targetId);
+        }
+
+        /**
+         * Layout the given widgets within the parent.
+         *
+         * @param l the parent's left border
+         * @param t the parent's top border
+         * @param r the parent's right border
+         * @param b the parent's bottom border
+         * @param alignment which side to align the widget to
+         */
+        void layout(int l, int t, int r, int b, int alignment) {
+            final int handleWidth = tab.getBackground().getIntrinsicWidth();
+            final int handleHeight = tab.getBackground().getIntrinsicHeight();
+            final int targetWidth = target.getDrawable().getIntrinsicWidth();
+            final int targetHeight = target.getDrawable().getIntrinsicHeight();
+            final int parentWidth = r - l;
+            final int parentHeight = b - t;
+
+            final int leftTarget = (int) (TARGET_ZONE * parentWidth) - targetWidth + handleWidth / 2;
+            final int rightTarget = (int) ((1.0f - TARGET_ZONE) * parentWidth) - handleWidth / 2;
+            final int left = (parentWidth - handleWidth) / 2;
+            final int right = left + handleWidth;
+
+            if (alignment == ALIGN_LEFT || alignment == ALIGN_RIGHT) {
+                // horizontal
+                final int targetTop = (parentHeight - targetHeight) / 2;
+                final int targetBottom = targetTop + targetHeight;
+                final int top = (parentHeight - handleHeight) / 2;
+                final int bottom = (parentHeight + handleHeight) / 2;
+                if (alignment == ALIGN_LEFT) {
+                    tab.layout(0, top, handleWidth, bottom);
+                    text.layout(0 - parentWidth, top, 0, bottom);
+                    text.setGravity(Gravity.RIGHT);
+                    target.layout(leftTarget, targetTop, leftTarget + targetWidth, targetBottom);
+                } else {
+                    tab.layout(parentWidth - handleWidth, top, parentWidth, bottom);
+                    text.layout(parentWidth, top, parentWidth + parentWidth, bottom);
+                    target.layout(rightTarget, targetTop, rightTarget + targetWidth, targetBottom);
+                    text.setGravity(Gravity.TOP);
+                }
+            } else {
+                // vertical
+                final int targetLeft = (parentWidth - targetWidth) / 2;
+                final int targetRight = (parentWidth + targetWidth) / 2;
+                final int top = (int) (TARGET_ZONE * parentHeight) + handleHeight / 2 - targetHeight;
+                final int bottom = (int) ((1.0f - TARGET_ZONE) * parentHeight) - handleHeight / 2;
+                if (alignment == ALIGN_TOP) {
+                    tab.layout(left, 0, right, handleHeight);
+                    text.layout(left, 0 - parentHeight, right, 0);
+                    target.layout(targetLeft, top, targetRight, top + targetHeight);
+                } else {
+                    tab.layout(left, parentHeight - handleHeight, right, parentHeight);
+                    text.layout(left, parentHeight, right, parentHeight + parentHeight);
+                    target.layout(targetLeft, bottom, targetRight, bottom + targetHeight);
+                }
+            }
+        }
+
+        public int getTabWidth() {
+            return tab.getDrawable().getIntrinsicWidth();
+        }
+
+        public int getTabHeight() {
+            return tab.getDrawable().getIntrinsicHeight();
+        }
+    }
+
+    public SlidingTab(Context context) {
+        this(context, null);
+    }
+
+    /**
+     * Constructor used when this widget is created from a layout file.
+     */
+    public SlidingTab(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SlidingTab);
+        mOrientation = a.getInt(R.styleable.SlidingTab_orientation, HORIZONTAL);
+        a.recycle();
+
+        Resources r = getResources();
+        mDensity = r.getDisplayMetrics().density;
+        if (DBG) log("- Density: " + mDensity);
+
+        mLeftSlider = new Slider(this, 
+                R.drawable.jog_tab_left_generic, 
+                R.drawable.jog_tab_bar_left_generic,
+                R.drawable.jog_tab_target_gray);
+        mRightSlider = new Slider(this, 
+                R.drawable.jog_tab_right_generic, 
+                R.drawable.jog_tab_bar_right_generic,
+                R.drawable.jog_tab_target_gray);
+
+        // setBackgroundColor(0x80808080);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
+
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+
+        if (widthSpecMode == MeasureSpec.UNSPECIFIED || heightSpecMode == MeasureSpec.UNSPECIFIED) {
+            throw new RuntimeException(LOG_TAG + " cannot have UNSPECIFIED dimensions");
+        }
+
+        final int leftTabWidth = (int) (mDensity * mLeftSlider.getTabWidth() + 0.5f);
+        final int rightTabWidth = (int) (mDensity * mRightSlider.getTabWidth() + 0.5f);
+        final int leftTabHeight = (int) (mDensity * mLeftSlider.getTabHeight() + 0.5f);
+        final int rightTabHeight = (int) (mDensity * mRightSlider.getTabHeight() + 0.5f);
+        final int width;
+        final int height;
+        if (isHorizontal()) {
+            width = Math.max(widthSpecSize, leftTabWidth + rightTabWidth);
+            height = Math.max(leftTabHeight, rightTabHeight);
+        } else {
+            width = Math.max(leftTabWidth, rightTabHeight);
+            height = Math.max(heightSpecSize, leftTabHeight + rightTabHeight);
+        }
+        setMeasuredDimension(width, height);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        final int action = event.getAction();
+        final float x = event.getX();
+        final float y = event.getY();
+
+        final Rect frame = new Rect();
+
+        if (mAnimating) {
+            return false;
+        }
+
+        View leftHandle = mLeftSlider.tab;
+        leftHandle.getHitRect(frame);
+        boolean leftHit = frame.contains((int) x, (int) y);
+
+        View rightHandle = mRightSlider.tab;
+        rightHandle.getHitRect(frame);
+        boolean rightHit = frame.contains((int)x, (int) y);
+
+        if (!mTracking && !(leftHit || rightHit)) {
+            return false;
+        }
+
+        switch (action) {
+            case MotionEvent.ACTION_DOWN: {
+                mTracking = true;
+                mTriggered = false;
+                vibrate(VIBRATE_SHORT);
+                if (leftHit) {
+                    mCurrentSlider = mLeftSlider;
+                    mOtherSlider = mRightSlider;
+                    mTargetZone = isHorizontal() ? TARGET_ZONE : 1.0f - TARGET_ZONE;
+                    setGrabbedState(OnTriggerListener.LEFT_HANDLE);
+                } else {
+                    mCurrentSlider = mRightSlider;
+                    mOtherSlider = mLeftSlider;
+                    mTargetZone = isHorizontal() ? 1.0f - TARGET_ZONE : TARGET_ZONE;
+                    setGrabbedState(OnTriggerListener.RIGHT_HANDLE);
+                }
+                mCurrentSlider.setState(Slider.STATE_PRESSED);
+                mCurrentSlider.showTarget();
+                mOtherSlider.hide();
+                break;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mTracking) {
+            final int action = event.getAction();
+            final float x = event.getX();
+            final float y = event.getY();
+            final View handle = mCurrentSlider.tab;
+            switch (action) {
+                case MotionEvent.ACTION_MOVE:
+                    moveHandle(x, y);
+                    float position = isHorizontal() ? x : y;
+                    float target = mTargetZone * (isHorizontal() ? getWidth() : getHeight());
+                    boolean targetZoneReached;
+                    if (isHorizontal()) {
+                        targetZoneReached = mCurrentSlider == mLeftSlider ?
+                                position > target : position < target;
+                    } else {
+                        targetZoneReached = mCurrentSlider == mLeftSlider ?
+                                position < target : position > target;
+                    }
+                    if (!mTriggered && targetZoneReached) {
+                        mTriggered = true;
+                        mTracking = false;
+                        mCurrentSlider.setState(Slider.STATE_ACTIVE);
+                        dispatchTriggerEvent(mCurrentSlider == mLeftSlider ?
+                            OnTriggerListener.LEFT_HANDLE : OnTriggerListener.RIGHT_HANDLE);
+
+                        // TODO: This is a place holder for the real animation. It just holds
+                        // the screen for 500ms.
+                        mAnimating = true;
+                        mHandler.postDelayed(new Runnable() {
+                            public void run() {
+                                resetView();
+                                mAnimating = false;
+                            }
+                        }, 500);
+                    }
+
+                    if (isHorizontal() && (y <= handle.getBottom() && y >= handle.getTop()) ||
+                            !isHorizontal() && (x >= handle.getLeft() && x <= handle.getRight()) ) {
+                        break;
+                    }
+                    // Intentionally fall through - we're outside tracking rectangle
+
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mTracking = false;
+                    mTriggered = false;
+                    resetView();
+                    setGrabbedState(OnTriggerListener.NO_HANDLE);
+                    break;
+            }
+        }
+
+        return mTracking || super.onTouchEvent(event);
+    }
+
+    private boolean isHorizontal() {
+        return mOrientation == HORIZONTAL;
+    }
+
+    private void resetView() {
+        mLeftSlider.reset();
+        mRightSlider.reset();
+        onLayout(true, getLeft(), getTop(), getLeft() + getWidth(), getTop() + getHeight());
+    }
+
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        if (!changed) return;
+
+        // Center the widgets in the view
+        mLeftSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_LEFT : Slider.ALIGN_BOTTOM);
+        mRightSlider.layout(l, t, r, b, isHorizontal() ? Slider.ALIGN_RIGHT : Slider.ALIGN_TOP);
+
+        invalidate(); // TODO: be more conservative about what we're invalidating
+    }
+
+    private void moveHandle(float x, float y) {
+        final View handle = mCurrentSlider.tab;
+        final View content = mCurrentSlider.text;
+        if (isHorizontal()) {
+            int deltaX = (int) x - handle.getLeft() - (handle.getWidth() / 2);
+            handle.offsetLeftAndRight(deltaX);
+            content.offsetLeftAndRight(deltaX);
+        } else {
+            int deltaY = (int) y - handle.getTop() - (handle.getHeight() / 2);
+            handle.offsetTopAndBottom(deltaY);
+            content.offsetTopAndBottom(deltaY);
+        }
+        invalidate(); // TODO: be more conservative about what we're invalidating
+    }
+
+    /**
+     * Sets the left handle icon to a given resource.
+     *
+     * The resource should refer to a Drawable object, or use 0 to remove
+     * the icon.
+     *
+     * @param iconId the resource ID of the icon drawable
+     * @param targetId the resource of the target drawable
+     * @param barId the resource of the bar drawable (stateful)
+     * @param tabId the resource of the 
+     */
+    public void setLeftTabResources(int iconId, int targetId, int barId, int tabId) {
+        mLeftSlider.setIcon(iconId);
+        mLeftSlider.setTarget(targetId); 
+        mLeftSlider.setBarBackgroundResource(barId);
+        mLeftSlider.setTabBackgroundResource(tabId);
+    }
+
+    /**
+     * Sets the left handle hint text to a given resource string.
+     *
+     * @param resId
+     */
+    public void setLeftHintText(int resId) {
+        mLeftSlider.setHintText(resId);
+    }
+
+    /**
+     * Sets the right handle icon to a given resource.
+     *
+     * The resource should refer to a Drawable object, or use 0 to remove
+     * the icon.
+     *
+     * @param iconId the resource ID of the icon drawable
+     * @param targetId the resource of the target drawable
+     * @param barId the resource of the bar drawable (stateful)
+     * @param tabId the resource of the 
+     */
+    public void setRightTabResources(int iconId, int targetId, int barId, int tabId) {
+        mRightSlider.setIcon(iconId);
+        mRightSlider.setTarget(targetId); 
+        mRightSlider.setBarBackgroundResource(barId);
+        mRightSlider.setTabBackgroundResource(tabId);
+    }
+
+    /**
+     * Sets the left handle hint text to a given resource string.
+     *
+     * @param resId
+     */
+    public void setRightHintText(int resId) {
+        mRightSlider.setHintText(resId);
+    }
+
+    /**
+     * Triggers haptic feedback.
+     */
+    private synchronized void vibrate(long duration) {
+        if (mVibrator == null) {
+            mVibrator = (android.os.Vibrator)
+                    getContext().getSystemService(Context.VIBRATOR_SERVICE);
+        }
+        mVibrator.vibrate(duration);
+    }
+
+    /**
+     * Registers a callback to be invoked when the user triggers an event.
+     *
+     * @param listener the OnDialTriggerListener to attach to this view
+     */
+    public void setOnTriggerListener(OnTriggerListener listener) {
+        mOnTriggerListener = listener;
+    }
+
+    /**
+     * Dispatches a trigger event to listener. Ignored if a listener is not set.
+     * @param whichHandle the handle that triggered the event.
+     */
+    private void dispatchTriggerEvent(int whichHandle) {
+        vibrate(VIBRATE_LONG);
+        if (mOnTriggerListener != null) {
+            mOnTriggerListener.onTrigger(this, whichHandle);
+        }
+    }
+
+    /**
+     * Sets the current grabbed state, and dispatches a grabbed state change
+     * event to our listener.
+     */
+    private void setGrabbedState(int newState) {
+        if (newState != mGrabbedState) {
+            mGrabbedState = newState;
+            if (mOnTriggerListener != null) {
+                mOnTriggerListener.onGrabbedStateChange(this, mGrabbedState);
+            }
+        }
+    }
+
+    private class SlidingTabHandler extends Handler {
+        public void handleMessage(Message m) {
+            switch (m.what) {
+                case MSG_ANIMATE:
+                    doAnimation();
+                    break;
+            }
+        }
+    }
+
+    private void doAnimation() {
+        if (mAnimating) {
+
+        }
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_confirm_gray.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_gray.9.png
new file mode 100644
index 0000000..92db44f
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_confirm_green.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_green.9.png
new file mode 100644
index 0000000..0bed1a0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_confirm_red.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_red.9.png
new file mode 100644
index 0000000..81fbe5a
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_confirm_yellow.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_yellow.9.png
new file mode 100644
index 0000000..d9c33fb
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_normal.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_normal.9.png
new file mode 100644
index 0000000..1cf7f1c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_bar_pressed.9.png b/core/res/res/drawable-hdpi/jog_tab_bar_pressed.9.png
new file mode 100644
index 0000000..c7b367e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_bar_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png
new file mode 100644
index 0000000..3499208
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png
new file mode 100644
index 0000000..91eaec8
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png
new file mode 100644
index 0000000..8818b9e
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png b/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png
new file mode 100644
index 0000000..e5bc5f6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_left_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_normal.png b/core/res/res/drawable-hdpi/jog_tab_left_normal.png
new file mode 100644
index 0000000..5326c7c
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_left_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_left_pressed.png b/core/res/res/drawable-hdpi/jog_tab_left_pressed.png
new file mode 100644
index 0000000..7b906df
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_left_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png
new file mode 100644
index 0000000..ea8c315
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png
new file mode 100644
index 0000000..aa0ceb9
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png
new file mode 100644
index 0000000..d772fb6
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png b/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png
new file mode 100644
index 0000000..3cfeb67
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_right_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_normal.png b/core/res/res/drawable-hdpi/jog_tab_right_normal.png
new file mode 100644
index 0000000..da7726b
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_right_normal.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_right_pressed.png b/core/res/res/drawable-hdpi/jog_tab_right_pressed.png
new file mode 100644
index 0000000..450a325
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_right_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_target_gray.png b/core/res/res/drawable-hdpi/jog_tab_target_gray.png
new file mode 100644
index 0000000..e7ef129
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_target_gray.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_target_green.png b/core/res/res/drawable-hdpi/jog_tab_target_green.png
new file mode 100644
index 0000000..17f6b10
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_target_green.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_target_red.png b/core/res/res/drawable-hdpi/jog_tab_target_red.png
new file mode 100644
index 0000000..8db20bb
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_target_red.png
Binary files differ
diff --git a/core/res/res/drawable-hdpi/jog_tab_target_yellow.png b/core/res/res/drawable-hdpi/jog_tab_target_yellow.png
new file mode 100644
index 0000000..15045b0
--- /dev/null
+++ b/core/res/res/drawable-hdpi/jog_tab_target_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/btn_lock_normal.9.png b/core/res/res/drawable-land-hdpi/btn_lock_normal.9.png
new file mode 100644
index 0000000..f1dac62
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/btn_lock_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_off.png b/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_off.png
new file mode 100755
index 0000000..d73db48
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_off.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_on.png b/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_on.png
new file mode 100755
index 0000000..90da6e3
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/ic_jog_dial_sound_on.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/ic_jog_dial_unlock.png b/core/res/res/drawable-land-hdpi/ic_jog_dial_unlock.png
new file mode 100755
index 0000000..a9af1af
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/ic_jog_dial_unlock.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_gray.9.png b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_gray.9.png
new file mode 100644
index 0000000..c0f7706
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_gray.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_green.9.png b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_green.9.png
new file mode 100644
index 0000000..0f2ce13
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_green.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_red.9.png b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_red.9.png
new file mode 100644
index 0000000..a34eb7d
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_red.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_yellow.9.png b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_yellow.9.png
new file mode 100644
index 0000000..e143356
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_bar_confirm_yellow.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_bar_normal.9.png b/core/res/res/drawable-land-hdpi/jog_tab_bar_normal.9.png
new file mode 100644
index 0000000..b5837f7
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_bar_normal.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_bar_pressed.9.png b/core/res/res/drawable-land-hdpi/jog_tab_bar_pressed.9.png
new file mode 100644
index 0000000..79ad83d
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_bar_pressed.9.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_gray.png b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_gray.png
new file mode 100644
index 0000000..9c63b22
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_green.png b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_green.png
new file mode 100644
index 0000000..4f9877c
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_red.png b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_red.png
new file mode 100644
index 0000000..bdce97d
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_yellow.png b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_yellow.png
new file mode 100644
index 0000000..327fc2c
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_left_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_left_normal.png b/core/res/res/drawable-land-hdpi/jog_tab_left_normal.png
new file mode 100644
index 0000000..e69d91c
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_left_normal.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_left_pressed.png b/core/res/res/drawable-land-hdpi/jog_tab_left_pressed.png
new file mode 100644
index 0000000..b6153d8
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_left_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_gray.png b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_gray.png
new file mode 100644
index 0000000..6e3e00b
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_gray.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_green.png b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_green.png
new file mode 100644
index 0000000..dae9efc
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_green.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_red.png b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_red.png
new file mode 100644
index 0000000..9de3158
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_red.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_yellow.png b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_yellow.png
new file mode 100644
index 0000000..8c9f180
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_right_confirm_yellow.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_right_normal.png b/core/res/res/drawable-land-hdpi/jog_tab_right_normal.png
new file mode 100644
index 0000000..0c4faf2
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_right_normal.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_right_pressed.png b/core/res/res/drawable-land-hdpi/jog_tab_right_pressed.png
new file mode 100644
index 0000000..4ec7b56
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_right_pressed.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_target_gray.png b/core/res/res/drawable-land-hdpi/jog_tab_target_gray.png
new file mode 100644
index 0000000..4150007
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_target_gray.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_target_green.png b/core/res/res/drawable-land-hdpi/jog_tab_target_green.png
new file mode 100644
index 0000000..ef18b6c
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_target_green.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_target_red.png b/core/res/res/drawable-land-hdpi/jog_tab_target_red.png
new file mode 100644
index 0000000..5dfaa5f
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_target_red.png
Binary files differ
diff --git a/core/res/res/drawable-land-hdpi/jog_tab_target_yellow.png b/core/res/res/drawable-land-hdpi/jog_tab_target_yellow.png
new file mode 100644
index 0000000..d0509fa
--- /dev/null
+++ b/core/res/res/drawable-land-hdpi/jog_tab_target_yellow.png
Binary files differ
diff --git a/core/res/res/drawable/jog_tab_bar_left_generic.xml b/core/res/res/drawable/jog_tab_bar_left_generic.xml
new file mode 100644
index 0000000..de1a42f
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_bar_left_generic.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_bar_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_bar_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_bar_confirm_gray" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_bar_left_unlock.xml b/core/res/res/drawable/jog_tab_bar_left_unlock.xml
new file mode 100644
index 0000000..b1d7c31
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_bar_left_unlock.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_bar_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_bar_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_bar_confirm_green" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_bar_right_generic.xml b/core/res/res/drawable/jog_tab_bar_right_generic.xml
new file mode 100644
index 0000000..de1a42f
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_bar_right_generic.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_bar_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_bar_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_bar_confirm_gray" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_bar_right_sound_off.xml b/core/res/res/drawable/jog_tab_bar_right_sound_off.xml
new file mode 100644
index 0000000..de1a42f
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_bar_right_sound_off.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_bar_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_bar_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_bar_confirm_gray" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_bar_right_sound_on.xml b/core/res/res/drawable/jog_tab_bar_right_sound_on.xml
new file mode 100644
index 0000000..febe32a
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_bar_right_sound_on.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_bar_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_bar_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_bar_confirm_yellow" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_left_generic.xml b/core/res/res/drawable/jog_tab_left_generic.xml
new file mode 100644
index 0000000..ed6d98b
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_left_generic.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_left_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_left_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_left_confirm_gray" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_left_unlock.xml b/core/res/res/drawable/jog_tab_left_unlock.xml
new file mode 100644
index 0000000..18ec7fa
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_left_unlock.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_left_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_left_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_left_confirm_green" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_right_generic.xml b/core/res/res/drawable/jog_tab_right_generic.xml
new file mode 100644
index 0000000..e173f2a
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_right_generic.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_right_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_right_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_right_confirm_gray" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_right_sound_off.xml b/core/res/res/drawable/jog_tab_right_sound_off.xml
new file mode 100644
index 0000000..e173f2a
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_right_sound_off.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_right_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_right_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_right_confirm_gray" />
+        
+</selector>
diff --git a/core/res/res/drawable/jog_tab_right_sound_on.xml b/core/res/res/drawable/jog_tab_right_sound_on.xml
new file mode 100644
index 0000000..61f677c
--- /dev/null
+++ b/core/res/res/drawable/jog_tab_right_sound_on.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- StateListDrawable used for buttons in the in-call onscreen touch UI. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" 
+        android:drawable="@drawable/jog_tab_right_pressed" />
+
+    <item android:state_enabled="true"
+        android:drawable="@drawable/jog_tab_right_normal" />
+
+    <item android:state_active="true"
+        android:drawable="@drawable/jog_tab_right_confirm_yellow" />
+        
+</selector>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock.xml b/core/res/res/layout/keyguard_screen_tab_unlock.xml
new file mode 100644
index 0000000..84b5751
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_tab_unlock.xml
@@ -0,0 +1,129 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the general lock screen which shows information about the
+  state of the device, as well as instructions on how to get past it
+  depending on the state of the device.  It is the same for landscape
+  and portrait.-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:id="@+id/root">
+
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:background="#70000000"
+        android:gravity="center_horizontal">
+    
+        <TextView
+            android:id="@+id/carrier"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_marginTop="20dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            />
+    
+        <TextView
+            android:id="@+id/time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/carrier"
+            android:layout_marginTop="25dip"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textSize="55sp"
+            />
+    
+        <TextView
+            android:id="@+id/date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/time"
+            android:layout_marginTop="-12dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            />
+    
+        <View
+            android:id="@+id/divider"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="10dip"
+            android:layout_below="@id/date"
+            android:background="@android:drawable/divider_horizontal_dark"
+            />
+    
+        <TextView
+            android:id="@+id/status1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/divider"
+            android:layout_marginTop="6dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            android:drawablePadding="4dip"
+            />
+    
+        <TextView
+            android:id="@+id/status2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/status1"
+            android:layout_marginTop="6dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            android:drawablePadding="4dip"
+            />
+    
+        <TextView
+            android:id="@+id/screenLocked"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/status2"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            android:gravity="center"
+            android:layout_marginTop="12dip"
+            />
+    
+        <com.android.internal.widget.SlidingTab
+            android:id="@+id/tab_selector"
+            android:orientation="horizontal"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            android:layout_marginBottom="50dip" 
+            />
+    
+        <!-- emergency call button shown when sim is missing or PUKd -->
+        <Button
+            android:id="@+id/emergencyCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/screenLocked"
+            android:layout_marginTop="24dip"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="8dip"
+           />
+    
+    </RelativeLayout>
+
+</FrameLayout>
diff --git a/core/res/res/layout/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
new file mode 100644
index 0000000..6aed301
--- /dev/null
+++ b/core/res/res/layout/keyguard_screen_tab_unlock_land.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2009, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This is the general lock screen which shows information about the
+  state of the device, as well as instructions on how to get past it
+  depending on the state of the device.-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tabunlock="http://schemas.android.com/apk/res/com.android.tabunlock"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="horizontal"
+    android:background="#70000000"
+    android:id="@+id/root">
+     
+    <!-- left side -->
+    <RelativeLayout
+            android:layout_width="0dip"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:gravity="center_horizontal">
+
+        <TextView
+            android:id="@+id/carrier"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentTop="true"
+            android:layout_marginTop="20dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            />
+
+        <TextView
+            android:id="@+id/time"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/carrier"
+            android:layout_marginTop="25dip"
+            android:textAppearance="?android:attr/textAppearanceLarge"
+            android:textSize="55sp"
+            />
+
+        <TextView
+            android:id="@+id/date"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/time"
+            android:layout_marginTop="-12dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            />
+
+        <View
+            android:id="@+id/divider"
+            android:layout_width="fill_parent"
+            android:layout_height="1dip"
+            android:layout_marginTop="10dip"
+            android:layout_below="@id/date"
+            android:background="@android:drawable/divider_horizontal_dark"
+            />
+
+        <TextView
+            android:id="@+id/status1"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/divider"
+            android:layout_marginTop="6dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            android:drawablePadding="4dip"
+            />
+
+        <TextView
+            android:id="@+id/status2"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/status1"
+            android:layout_marginTop="6dip"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            android:drawablePadding="4dip"
+            />
+
+        <TextView
+            android:id="@+id/screenLocked"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/status2"
+            android:textAppearance="?android:attr/textAppearanceMedium"
+            android:textColor="?android:attr/textColorSecondary"
+            android:gravity="center"
+            android:layout_marginTop="12dip"
+            />
+
+        <!-- emergency call button shown when sim is missing or PUKd -->
+        <Button
+            android:id="@+id/emergencyCallButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/screenLocked"
+            android:layout_marginTop="24dip"
+            android:drawableLeft="@drawable/ic_emergency"
+            android:drawablePadding="8dip"
+           />
+    </RelativeLayout>
+
+    <!-- right side -->
+    <com.android.internal.widget.SlidingTab
+        android:id="@+id/tab_selector"
+        android:orientation="vertical"
+        android:layout_width="wrap_content"
+        android:layout_height="fill_parent"
+        android:layout_marginBottom="50dip"
+        />
+
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 81da739..50382b3 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3559,4 +3559,14 @@
         <attr name="detailSocialSummary" format="boolean" />
     </declare-styleable>
 
+    <!-- =============================== -->
+    <!-- TabSelector class attributes -->
+    <!-- =============================== -->
+    <eat-comment />
+
+    <declare-styleable name="SlidingTab">
+        <!-- Use "horizontal" for a row, "vertical" for a column.  The default is horizontal. -->
+        <attr name="orientation" />
+    </declare-styleable>
+
 </resources>
diff --git a/core/res/res/values/colors.xml b/core/res/res/values/colors.xml
index 5d3069b..3c0f0a4 100644
--- a/core/res/res/values/colors.xml
+++ b/core/res/res/values/colors.xml
@@ -84,6 +84,10 @@
     <color name="search_url_text_selected">@android:color/black</color>
     <color name="search_url_text_pressed">@android:color/black</color>
     <color name="search_widget_corpus_item_background">@android:color/lighter_gray</color>
+    
+    <!-- SlidingTab -->
+    <color name="sliding_tab_text_color_active">@android:color/black</color>
+    <color name="sliding_tab_text_color_shadow">@android:color/black</color>
 
 </resources>
 
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index aaa1d8b..822a59a 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -207,4 +207,11 @@
 
     <!-- Enables swipe versus poly-finger touch disambiguation in the KeyboardView -->
     <bool name="config_swipeDisambiguation">true</bool>
+
+    <!-- Enables special filtering code in the framework for raw touch events
+         from the touch driver.  This code exists for one particular device,
+         and should not be enabled for any others.  Hopefully in the future
+         it will be removed when the lower-level touch driver generates better
+         data. -->
+    <bool name="config_filterTouchEvents">false</bool>
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index de30fe7..d1ae571 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1434,6 +1434,10 @@
 
     <!-- Displayed in a progress dialog while a username and password are being checked. -->
     <string name="lockscreen_glogin_checking_password">Checking...</string>
+    <!-- Displayed on lock screen's left tab - unlock -->
+    <string name="lockscreen_unlock_label">Unlock</string>
+    <!-- Displayed on lock screen's right tab - mute/unmute -->
+    <string name="lockscreen_mute_unmute_label">Sound</string>
 
     <!-- A format string for 12-hour time of day, just the hour, not the minute, with lower-case "am" or "pm" (example: "3pm"). -->
     <string name="hour_ampm">"<xliff:g id="hour" example="3">%-l</xliff:g><xliff:g id="ampm" example="pm">%P</xliff:g>"</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 6e38138..a629bb2 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -676,6 +676,25 @@
     <style name="TextAppearance.Widget.TextView.SpinnerItem">
         <item name="android:textColor">@android:color/primary_text_light_disable_only</item>
     </style>
+    
+    <!-- @hide -->
+    <style name="TextAppearance.SlidingTabNormal" 
+        parent="@android:attr/textAppearanceMedium">
+        <item name="android:textColor">?android:attr/textColorTertiary</item>
+        <item name="android:textSize">28sp</item>
+        <item name="android:shadowColor">@android:color/sliding_tab_text_color_shadow</item>
+        <item name="android:shadowDx">0.0</item>
+        <item name="android:shadowDy">1.0</item>
+        <item name="android:shadowRadius">5.0</item>
+    </style>
+
+    <!-- @hide -->
+    <style name="TextAppearance.SlidingTabActive" 
+        parent="@android:attr/textAppearanceMedium">
+        <item name="android:textColor">@android:color/sliding_tab_text_color_active</item>
+        <item name="android:textSize">28sp</item>
+    </style>
+    
 
     <!-- @hide -->	
      <style name="TextAppearance.SearchResult">	
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/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index d68ccfa..a885df8 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -56,7 +56,7 @@
      * Turn on some hacks we have to improve the touch interaction with a
      * certain device whose screen currently is not all that good.
      */
-    static final boolean BAD_TOUCH_HACK = true;
+    static boolean BAD_TOUCH_HACK = false;
     
     private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
 
@@ -282,6 +282,9 @@
             lt = new LatencyTimer(100, 1000);
         }
 
+        BAD_TOUCH_HACK = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_filterTouchEvents);
+        
         mHapticFeedbackCallback = hapticFeedbackCallback;
         
         readExcludedDevices();
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());
+                        }
                     }
                 }
             }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index c16b04d..aee45bff 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -41,6 +41,7 @@
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
 import com.android.internal.telephony.SMSDispatcher;
+import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 import com.android.internal.telephony.cdma.SmsMessage;
 import com.android.internal.telephony.cdma.sms.SmsEnvelope;
 import com.android.internal.telephony.cdma.sms.UserData;
@@ -366,8 +367,19 @@
          */
 
         int refNumber = getNextConcatenatedRef() & 0x00FF;
+        int msgCount = parts.size();
+        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
 
-        for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
+        for (int i = 0; i < msgCount; i++) {
+            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
+            if (encoding != details.codeUnitSize
+                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
+                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
+                encoding = details.codeUnitSize;
+            }
+        }
+
+        for (int i = 0; i < msgCount; i++) {
             SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
             concatRef.refNumber = refNumber;
             concatRef.seqNumber = i + 1;  // 1-based sequence
@@ -389,6 +401,12 @@
             UserData uData = new UserData();
             uData.payloadStr = parts.get(i);
             uData.userDataHeader = smsHeader;
+            if (encoding == android.telephony.SmsMessage.ENCODING_7BIT) {
+                uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+            } else { // assume UTF-16
+                uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+            }
+            uData.msgEncodingSet = true;
 
             /* By setting the statusReportRequested bit only for the
              * last message fragment, this will result in only one
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
index 3051e2f..bb3f2a7 100644
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaServiceStateTracker.java
@@ -67,6 +67,15 @@
     CdmaCellLocation cellLoc;
     CdmaCellLocation newCellLoc;
 
+     /** if time between NTIZ updates is less than mNitzUpdateSpacing the update may be ignored. */
+    private static final int NITZ_UPDATE_SPACING_DEFAULT = 1000 * 60 * 10;
+    private int mNitzUpdateSpacing = SystemProperties.getInt("ro.nitz_update_spacing",
+            NITZ_UPDATE_SPACING_DEFAULT);
+
+    /** If mNitzUpdateSpacing hasn't been exceeded but update is > mNitzUpdate do the update */
+    private static final int NITZ_UPDATE_DIFF_DEFAULT = 2000;
+    private int mNitzUpdateDiff = SystemProperties.getInt("ro.nitz_update_diff",
+            NITZ_UPDATE_DIFF_DEFAULT);
     /**
      *  Values correspond to ServiceStateTracker.DATA_ACCESS_ definitions.
      */
@@ -1420,45 +1429,62 @@
             try {
                 mWakeLock.acquire();
 
+                /**
+                 * Correct the NITZ time by how long its taken to get here.
+                 */
+                long millisSinceNitzReceived
+                        = SystemClock.elapsedRealtime() - nitzReceiveTime;
+
+                if (millisSinceNitzReceived < 0) {
+                    // Sanity check: something is wrong
+                    Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled "
+                                        + "backwards since NITZ time was received, "
+                                        + nitz);
+                    return;
+                }
+
+                if (millisSinceNitzReceived > Integer.MAX_VALUE) {
+                    // If the time is this far off, something is wrong > 24 days!
+                    Log.i(LOG_TAG, "NITZ: not setting time, processing has taken "
+                                    + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
+                                    + " days");
+                    return;
+                }
+
+                // Note: with range checks above, cast to int is safe
+                c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
+
                 if (getAutoTime()) {
-                    long millisSinceNitzReceived
-                            = SystemClock.elapsedRealtime() - nitzReceiveTime;
+                    /**
+                     * Update system time automatically
+                     */
+                    long gained = c.getTimeInMillis() - System.currentTimeMillis();
+                    long timeSinceLastUpdate = SystemClock.elapsedRealtime() - mSavedAtTime;
 
-                    if (millisSinceNitzReceived < 0) {
-                        // Sanity check: something is wrong
-                        Log.i(LOG_TAG, "NITZ: not setting time, clock has rolled "
-                                            + "backwards since NITZ time was received, "
-                                            + nitz);
+                    if ((timeSinceLastUpdate > mNitzUpdateSpacing)
+                            || (Math.abs(gained) > mNitzUpdateDiff)) {
+                        Log.i(LOG_TAG, "NITZ: Auto updating time of day to " + c.getTime()
+                                + " NITZ receive delay=" + millisSinceNitzReceived
+                                + "ms gained=" + gained + "ms from " + nitz);
+
+                        setAndBroadcastNetworkSetTime(c.getTimeInMillis());
+                    } else {
+                        Log.i(LOG_TAG, "NITZ: ignore, a previous update was "
+                                + timeSinceLastUpdate + "ms ago and gained=" + gained + "ms");
                         return;
                     }
-
-                    if (millisSinceNitzReceived > Integer.MAX_VALUE) {
-                        // If the time is this far off, something is wrong > 24 days!
-                        Log.i(LOG_TAG, "NITZ: not setting time, processing has taken "
-                                        + (millisSinceNitzReceived / (1000 * 60 * 60 * 24))
-                                        + " days");
-                        return;
-                    }
-
-                    // Note: with range checks above, cast to int is safe
-                    c.add(Calendar.MILLISECOND, (int)millisSinceNitzReceived);
-
-                    Log.i(LOG_TAG, "NITZ: Setting time of day to " + c.getTime()
-                            + " NITZ receive delay(ms): " + millisSinceNitzReceived
-                        + " gained(ms): "
-                        + (c.getTimeInMillis() - System.currentTimeMillis())
-                            + " from " + nitz);
-
-                    setAndBroadcastNetworkSetTime(c.getTimeInMillis());
-                    Log.i(LOG_TAG, "NITZ: after Setting time of day");
                 }
+
+                /**
+                 * Update properties and save the time we did the update
+                 */
+                Log.i(LOG_TAG, "NITZ: update nitz time property");
                 SystemProperties.set("gsm.nitz.time", String.valueOf(c.getTimeInMillis()));
-                saveNitzTime(c.getTimeInMillis());
-                if (Config.LOGV) {
-                    long end = SystemClock.elapsedRealtime();
-                    Log.v(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start));
-                }
+                mSavedTime = c.getTimeInMillis();
+                mSavedAtTime = SystemClock.elapsedRealtime();
             } finally {
+                long end = SystemClock.elapsedRealtime();
+                Log.i(LOG_TAG, "NITZ: end=" + end + " dur=" + (end - start));
                 mWakeLock.release();
             }
         } catch (RuntimeException ex) {
@@ -1479,11 +1505,6 @@
         mSavedTimeZone = zoneId;
     }
 
-    private void saveNitzTime(long time) {
-        mSavedTime = time;
-        mSavedAtTime = SystemClock.elapsedRealtime();
-    }
-
     /**
      * Set the timezone and send out a sticky broadcast so the system can
      * determine if the timezone was set by the carrier.
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 0ca3148..6ae316d 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 
 import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
 import com.android.internal.telephony.gsm.SmsMessage;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.SMSDispatcher;
@@ -165,8 +166,19 @@
             ArrayList<PendingIntent> deliveryIntents) {
 
         int refNumber = getNextConcatenatedRef() & 0x00FF;
+        int msgCount = parts.size();
+        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
 
-        for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
+        for (int i = 0; i < msgCount; i++) {
+            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
+            if (encoding != details.codeUnitSize
+                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
+                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
+                encoding = details.codeUnitSize;
+            }
+        }
+
+        for (int i = 0; i < msgCount; i++) {
             SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
             concatRef.refNumber = refNumber;
             concatRef.seqNumber = i + 1;  // 1-based sequence
@@ -192,7 +204,8 @@
             }
 
             SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
-                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader));
+                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
+                    encoding);
 
             sendRawPdu(pdus.encodedScAddress, pdus.encodedMessage, sentIntent, deliveryIntent);
         }
@@ -242,8 +255,19 @@
         }
 
         int refNumber = getNextConcatenatedRef() & 0x00FF;
+        int msgCount = parts.size();
+        int encoding = android.telephony.SmsMessage.ENCODING_UNKNOWN;
 
-        for (int i = 0, msgCount = parts.size(); i < msgCount; i++) {
+        for (int i = 0; i < msgCount; i++) {
+            TextEncodingDetails details = SmsMessage.calculateLength(parts.get(i), false);
+            if (encoding != details.codeUnitSize
+                    && (encoding == android.telephony.SmsMessage.ENCODING_UNKNOWN
+                            || encoding == android.telephony.SmsMessage.ENCODING_7BIT)) {
+                encoding = details.codeUnitSize;
+            }
+        }
+
+        for (int i = 0; i < msgCount; i++) {
             SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
             concatRef.refNumber = refNumber;
             concatRef.seqNumber = i + 1;  // 1-based sequence
@@ -263,7 +287,8 @@
             }
 
             SmsMessage.SubmitPdu pdus = SmsMessage.getSubmitPdu(scAddress, destinationAddress,
-                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader));
+                    parts.get(i), deliveryIntent != null, SmsHeader.toByteArray(smsHeader),
+                    encoding);
 
             HashMap<String, Object> map = new HashMap<String, Object>();
             map.put("smsc", pdus.encodedScAddress);
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index 569cf25..ebd60a9 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -225,6 +225,25 @@
     public static SubmitPdu getSubmitPdu(String scAddress,
             String destinationAddress, String message,
             boolean statusReportRequested, byte[] header) {
+        return getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested, header,
+                ENCODING_UNKNOWN);
+    }
+
+
+    /**
+     * Get an SMS-SUBMIT PDU for a destination address and a message using the
+     * specified encoding.
+     *
+     * @param scAddress Service Centre address.  Null means use default.
+     * @param encoding Encoding defined by constants in android.telephony.SmsMessage.ENCODING_*
+     * @return a <code>SubmitPdu</code> containing the encoded SC
+     *         address, if applicable, and the encoded message.
+     *         Returns null on encode error.
+     * @hide
+     */
+    public static SubmitPdu getSubmitPdu(String scAddress,
+            String destinationAddress, String message,
+            boolean statusReportRequested, byte[] header, int encoding) {
 
         // Perform null parameter checks.
         if (message == null || destinationAddress == null) {
@@ -237,18 +256,43 @@
         ByteArrayOutputStream bo = getSubmitPduHead(
                 scAddress, destinationAddress, mtiByte,
                 statusReportRequested, ret);
-
-        try {
+        // User Data (and length)
+        byte[] userData;
+        if (encoding == ENCODING_UNKNOWN) {
             // First, try encoding it with the GSM alphabet
+            encoding = ENCODING_7BIT;
+        }
+        try {
+            if (encoding == ENCODING_7BIT) {
+                userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header);
+            } else { //assume UCS-2
+                try {
+                    userData = encodeUCS2(message, header);
+                } catch(UnsupportedEncodingException uex) {
+                    Log.e(LOG_TAG,
+                            "Implausible UnsupportedEncodingException ",
+                            uex);
+                    return null;
+                }
+            }
+        } catch (EncodeException ex) {
+            // Encoding to the 7-bit alphabet failed. Let's see if we can
+            // send it as a UCS-2 encoded message
+            try {
+                userData = encodeUCS2(message, header);
+            } catch(UnsupportedEncodingException uex) {
+                Log.e(LOG_TAG,
+                        "Implausible UnsupportedEncodingException ",
+                        uex);
+                return null;
+            }
+        }
 
-            // User Data (and length)
-            byte[] userData = GsmAlphabet.stringToGsm7BitPackedWithHeader(message, header);
-
+        if (encoding == ENCODING_7BIT) {
             if ((0xff & userData[0]) > MAX_USER_DATA_SEPTETS) {
                 // Message too long
                 return null;
             }
-
             // TP-Data-Coding-Scheme
             // Default encoding, uncompressed
             // To test writing messages to the SIM card, change this value 0x00
@@ -258,58 +302,51 @@
             // the receiver's SIM card. You can then send messages to yourself
             // (on a phone with this change) and they'll end up on the SIM card.
             bo.write(0x00);
-
-            // (no TP-Validity-Period)
-
-            bo.write(userData, 0, userData.length);
-        } catch (EncodeException ex) {
-            byte[] userData, textPart;
-            // Encoding to the 7-bit alphabet failed. Let's see if we can
-            // send it as a UCS-2 encoded message
-
-            try {
-                textPart = message.getBytes("utf-16be");
-            } catch (UnsupportedEncodingException uex) {
-                Log.e(LOG_TAG,
-                      "Implausible UnsupportedEncodingException ",
-                      uex);
-                return null;
-            }
-
-            if (header != null) {
-                // Need 1 byte for UDHL
-                userData = new byte[header.length + textPart.length + 1];
-
-                userData[0] = (byte)header.length;
-                System.arraycopy(header, 0, userData, 1, header.length);
-                System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
-            }
-            else {
-                userData = textPart;
-            }
-
-            if (userData.length > MAX_USER_DATA_BYTES) {
+        } else { //assume UCS-2
+            if ((0xff & userData[0]) > MAX_USER_DATA_BYTES) {
                 // Message too long
                 return null;
             }
-
             // TP-Data-Coding-Scheme
             // Class 3, UCS-2 encoding, uncompressed
             bo.write(0x0b);
-
-            // (no TP-Validity-Period)
-
-            // TP-UDL
-            bo.write(userData.length);
-
-            bo.write(userData, 0, userData.length);
         }
 
+        // (no TP-Validity-Period)
+        bo.write(userData, 0, userData.length);
         ret.encodedMessage = bo.toByteArray();
         return ret;
     }
 
     /**
+     * Packs header and UCS-2 encoded message. Includes TP-UDL & TP-UDHL if necessary
+     *
+     * @return
+     * @throws UnsupportedEncodingException
+     */
+    private static byte[] encodeUCS2(String message, byte[] header)
+        throws UnsupportedEncodingException {
+        byte[] userData, textPart;
+        textPart = message.getBytes("utf-16be");
+
+        if (header != null) {
+            // Need 1 byte for UDHL
+            userData = new byte[header.length + textPart.length + 1];
+
+            userData[0] = (byte)header.length;
+            System.arraycopy(header, 0, userData, 1, header.length);
+            System.arraycopy(textPart, 0, userData, header.length + 1, textPart.length);
+        }
+        else {
+            userData = textPart;
+        }
+        byte[] ret = new byte[userData.length+1];
+        ret[0] = (byte) (userData.length & 0xff );
+        System.arraycopy(userData, 0, ret, 1, userData.length);
+        return ret;
+    }
+
+    /**
      * Get an SMS-SUBMIT PDU for a destination address and a message
      *
      * @param scAddress Service Centre address.  Null means use default.