Merge "Adding support for dynamically updated test status"
diff --git a/api/current.xml b/api/current.xml
index 5e57d80..80428bd 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -21741,6 +21741,17 @@
  visibility="public"
 >
 </method>
+<method name="removeAllUpdateListeners"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</method>
 <method name="removeUpdateListener"
  return="void"
  abstract="false"
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index f901bfb..4dee350 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -19,6 +19,7 @@
 package com.android.commands.am;
 
 import android.app.ActivityManagerNative;
+import android.app.IActivityController;
 import android.app.IActivityManager;
 import android.app.IInstrumentationWatcher;
 import android.app.Instrumentation;
@@ -28,12 +29,18 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.AndroidException;
 import android.view.IWindowManager;
 
+import java.io.BufferedReader;
+import java.io.DataInputStream;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.io.PrintStream;
 import java.net.URISyntaxException;
 import java.util.Iterator;
@@ -99,6 +106,8 @@
             runProfile();
         } else if (op.equals("dumpheap")) {
             runDumpHeap();
+        } else if (op.equals("monitor")) {
+            runMonitor();
         } else {
             throw new IllegalArgumentException("Unknown command: " + op);
         }
@@ -472,6 +481,210 @@
         }
     }
 
+    class MyActivityController extends IActivityController.Stub {
+        static final int STATE_NORMAL = 0;
+        static final int STATE_CRASHED = 1;
+        static final int STATE_EARLY_ANR = 2;
+        static final int STATE_ANR = 3;
+
+        int mState;
+
+        static final int RESULT_DEFAULT = 0;
+
+        static final int RESULT_CRASH_DIALOG = 0;
+        static final int RESULT_CRASH_KILL = 1;
+
+        static final int RESULT_EARLY_ANR_CONTINUE = 0;
+        static final int RESULT_EARLY_ANR_KILL = 1;
+
+        static final int RESULT_ANR_DIALOG = 0;
+        static final int RESULT_ANR_KILL = 1;
+        static final int RESULT_ANR_WAIT = 1;
+
+        int mResult;
+
+        @Override
+        public boolean activityResuming(String pkg) throws RemoteException {
+            synchronized (this) {
+                System.out.println("** Activity resuming: " + pkg);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean activityStarting(Intent intent, String pkg) throws RemoteException {
+            synchronized (this) {
+                System.out.println("** Activity starting: " + pkg);
+            }
+            return true;
+        }
+
+        @Override
+        public boolean appCrashed(String processName, int pid, String shortMsg, String longMsg,
+                long timeMillis, String stackTrace) throws RemoteException {
+            synchronized (this) {
+                System.out.println("** ERROR: PROCESS CRASHED");
+                System.out.println("processName: " + processName);
+                System.out.println("processPid: " + pid);
+                System.out.println("shortMsg: " + shortMsg);
+                System.out.println("longMsg: " + longMsg);
+                System.out.println("timeMillis: " + timeMillis);
+                System.out.println("stack:");
+                System.out.print(stackTrace);
+                System.out.println("#");
+                int result = waitControllerLocked(STATE_CRASHED);
+                return result == RESULT_CRASH_KILL ? false : true;
+            }
+        }
+
+        @Override
+        public int appEarlyNotResponding(String processName, int pid, String annotation)
+                throws RemoteException {
+            synchronized (this) {
+                System.out.println("** ERROR: EARLY PROCESS NOT RESPONDING");
+                System.out.println("processName: " + processName);
+                System.out.println("processPid: " + pid);
+                System.out.println("annotation: " + annotation);
+                int result = waitControllerLocked(STATE_EARLY_ANR);
+                if (result == RESULT_EARLY_ANR_KILL) return -1;
+                return 0;
+            }
+        }
+
+        @Override
+        public int appNotResponding(String processName, int pid, String processStats)
+                throws RemoteException {
+            synchronized (this) {
+                System.out.println("** ERROR: PROCESS NOT RESPONDING");
+                System.out.println("processName: " + processName);
+                System.out.println("processPid: " + pid);
+                System.out.println("processStats:");
+                System.out.print(processStats);
+                System.out.println("#");
+                int result = waitControllerLocked(STATE_ANR);
+                if (result == RESULT_ANR_KILL) return -1;
+                if (result == RESULT_ANR_WAIT) return 1;
+                return 0;
+            }
+        }
+
+        int waitControllerLocked(int state) {
+            mState = state;
+            System.out.println("");
+            printMessageForState();
+
+            while (mState != STATE_NORMAL) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                }
+            }
+
+            return mResult;
+        }
+
+        void resumeController(int result) {
+            synchronized (this) {
+                mState = STATE_NORMAL;
+                mResult = result;
+                notifyAll();
+            }
+        }
+
+        void printMessageForState() {
+            switch (mState) {
+                case STATE_NORMAL:
+                    System.out.println("Monitoring activity manager...  available commands:");
+                    break;
+                case STATE_CRASHED:
+                    System.out.println("Waiting after crash...  available commands:");
+                    System.out.println("(c)ontinue: show crash dialog");
+                    System.out.println("(k)ill: immediately kill app");
+                    break;
+                case STATE_EARLY_ANR:
+                    System.out.println("Waiting after early ANR...  available commands:");
+                    System.out.println("(c)ontinue: standard ANR processing");
+                    System.out.println("(k)ill: immediately kill app");
+                    break;
+                case STATE_ANR:
+                    System.out.println("Waiting after ANR...  available commands:");
+                    System.out.println("(c)ontinue: show ANR dialog");
+                    System.out.println("(k)ill: immediately kill app");
+                    System.out.println("(w)ait: wait some more");
+                    break;
+            }
+            System.out.println("(q)uit: finish monitoring");
+        }
+
+        void run() throws RemoteException {
+            try {
+                printMessageForState();
+
+                mAm.setActivityController(this);
+                mState = STATE_NORMAL;
+
+                InputStreamReader converter = new InputStreamReader(System.in);
+                BufferedReader in = new BufferedReader(converter);
+                String line;
+
+                while ((line = in.readLine()) != null) {
+                    boolean addNewline = true;
+                    if (line.length() <= 0) {
+                        addNewline = false;
+                    } else if ("q".equals(line) || "quit".equals(line)) {
+                        resumeController(RESULT_DEFAULT);
+                        break;
+                    } else if (mState == STATE_CRASHED) {
+                        if ("c".equals(line) || "continue".equals(line)) {
+                            resumeController(RESULT_CRASH_DIALOG);
+                        } else if ("k".equals(line) || "kill".equals(line)) {
+                            resumeController(RESULT_CRASH_KILL);
+                        } else {
+                            System.out.println("Invalid command: " + line);
+                        }
+                    } else if (mState == STATE_ANR) {
+                        if ("c".equals(line) || "continue".equals(line)) {
+                            resumeController(RESULT_ANR_DIALOG);
+                        } else if ("k".equals(line) || "kill".equals(line)) {
+                            resumeController(RESULT_ANR_KILL);
+                        } else if ("w".equals(line) || "wait".equals(line)) {
+                            resumeController(RESULT_ANR_WAIT);
+                        } else {
+                            System.out.println("Invalid command: " + line);
+                        }
+                    } else if (mState == STATE_EARLY_ANR) {
+                        if ("c".equals(line) || "continue".equals(line)) {
+                            resumeController(RESULT_EARLY_ANR_CONTINUE);
+                        } else if ("k".equals(line) || "kill".equals(line)) {
+                            resumeController(RESULT_EARLY_ANR_KILL);
+                        } else {
+                            System.out.println("Invalid command: " + line);
+                        }
+                    } else {
+                        System.out.println("Invalid command: " + line);
+                    }
+
+                    synchronized (this) {
+                        if (addNewline) {
+                            System.out.println("");
+                        }
+                        printMessageForState();
+                    }
+                }
+
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                mAm.setActivityController(null);
+            }
+        }
+    }
+
+    private void runMonitor() throws Exception {
+        MyActivityController controller = new MyActivityController();
+        controller.run();
+    }
+
     private class IntentReceiver extends IIntentReceiver.Stub {
         private boolean mFinished = false;
 
@@ -649,6 +862,8 @@
                 "    dump heap: am dumpheap [flags] <PROCESS> <FILE>\n" +
                 "        -n: dump native heap instead of managed heap\n" +
                 "\n" +
+                "    start monitoring: am monitor\n" +
+                "\n" +
                 "    <INTENT> specifications include these flags:\n" +
                 "        [-a <ACTION>] [-d <DATA_URI>] [-t <MIME_TYPE>]\n" +
                 "        [-c <CATEGORY> [-c <CATEGORY>] ...]\n" +
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index 8ab94ad..eb4b733 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+//#define LOG_NDEBUG 0
+#define LOG_TAG "stagefright"
+#include <media/stagefright/foundation/ADebug.h>
+
 #include <sys/time.h>
 
 #include <stdlib.h>
@@ -30,7 +34,6 @@
 #include <media/stagefright/AudioPlayer.h>
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/JPEGSource.h>
-#include <media/stagefright/MediaDebug.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/MediaExtractor.h>
@@ -43,6 +46,8 @@
 #include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/MPEG4Writer.h>
 
+#include <fcntl.h>
+
 using namespace android;
 
 static long gNumRepetitions;
@@ -120,7 +125,7 @@
 
             bool shouldSeek = false;
             if (err == INFO_FORMAT_CHANGED) {
-                CHECK_EQ(buffer, NULL);
+                CHECK(buffer == NULL);
 
                 printf("format changed.\n");
                 continue;
@@ -206,7 +211,7 @@
             options.clearSeekTo();
 
             if (err != OK) {
-                CHECK_EQ(buffer, NULL);
+                CHECK(buffer == NULL);
 
                 if (err == INFO_FORMAT_CHANGED) {
                     printf("format changed.\n");
@@ -267,14 +272,98 @@
     }
 }
 
-static void writeSourceToMP4(const sp<MediaSource> &source) {
+////////////////////////////////////////////////////////////////////////////////
+
+struct DetectSyncSource : public MediaSource {
+    DetectSyncSource(const sp<MediaSource> &source);
+
+    virtual status_t start(MetaData *params = NULL);
+    virtual status_t stop();
+    virtual sp<MetaData> getFormat();
+
+    virtual status_t read(
+            MediaBuffer **buffer, const ReadOptions *options);
+
+private:
+    sp<MediaSource> mSource;
+    bool mIsAVC;
+
+    DISALLOW_EVIL_CONSTRUCTORS(DetectSyncSource);
+};
+
+DetectSyncSource::DetectSyncSource(const sp<MediaSource> &source)
+    : mSource(source),
+      mIsAVC(false) {
+    const char *mime;
+    CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime));
+
+    mIsAVC = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+}
+
+status_t DetectSyncSource::start(MetaData *params) {
+    return mSource->start(params);
+}
+
+status_t DetectSyncSource::stop() {
+    return mSource->stop();
+}
+
+sp<MetaData> DetectSyncSource::getFormat() {
+    return mSource->getFormat();
+}
+
+static bool isIDRFrame(MediaBuffer *buffer) {
+    const uint8_t *data =
+        (const uint8_t *)buffer->data() + buffer->range_offset();
+    size_t size = buffer->range_length();
+    for (size_t i = 0; i + 3 < size; ++i) {
+        if (!memcmp("\x00\x00\x01", &data[i], 3)) {
+            uint8_t nalType = data[i + 3] & 0x1f;
+            if (nalType == 5) {
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+status_t DetectSyncSource::read(
+        MediaBuffer **buffer, const ReadOptions *options) {
+    status_t err = mSource->read(buffer, options);
+
+    if (err != OK) {
+        return err;
+    }
+
+    if (mIsAVC && isIDRFrame(*buffer)) {
+        (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, true);
+    } else {
+        (*buffer)->meta_data()->setInt32(kKeyIsSyncFrame, false);
+    }
+
+    return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+static void writeSourceToMP4(
+        sp<MediaSource> &source, bool syncInfoPresent) {
+    if (!syncInfoPresent) {
+        source = new DetectSyncSource(source);
+    }
+
     sp<MPEG4Writer> writer =
         new MPEG4Writer(gWriteMP4Filename.string());
 
-    CHECK_EQ(writer->addSource(source), OK);
+    // at most one minute.
+    writer->setMaxFileDuration(60000000ll);
+
+    CHECK_EQ(writer->addSource(source), (status_t)OK);
 
     sp<MetaData> params = new MetaData;
-    CHECK_EQ(writer->start(), OK);
+    params->setInt32(kKeyNotRealTime, true);
+    CHECK_EQ(writer->start(params.get()), (status_t)OK);
 
     while (!writer->reachedEOS()) {
         usleep(100000);
@@ -283,7 +372,7 @@
 }
 
 static void performSeekTest(const sp<MediaSource> &source) {
-    CHECK_EQ(OK, source->start());
+    CHECK_EQ((status_t)OK, source->start());
 
     int64_t durationUs;
     CHECK(source->getFormat()->findInt64(kKeyDuration, &durationUs));
@@ -335,7 +424,7 @@
         }
     }
 
-    CHECK_EQ(OK, source->stop());
+    CHECK_EQ((status_t)OK, source->stop());
 }
 
 static void usage(const char *me) {
@@ -481,10 +570,10 @@
         for (int k = 0; k < argc; ++k) {
             const char *filename = argv[k];
 
-            CHECK_EQ(retriever->setDataSource(filename), OK);
+            CHECK_EQ(retriever->setDataSource(filename), (status_t)OK);
             CHECK_EQ(retriever->setMode(
                         METADATA_MODE_FRAME_CAPTURE_AND_METADATA_RETRIEVAL),
-                     OK);
+                     (status_t)OK);
 
             sp<IMemory> mem = retriever->captureFrame();
 
@@ -530,7 +619,7 @@
             Vector<CodecCapabilities> results;
             CHECK_EQ(QueryCodecs(omx, kMimeTypes[k],
                                  true, // queryDecoders
-                                 &results), OK);
+                                 &results), (status_t)OK);
 
             for (size_t i = 0; i < results.size(); ++i) {
                 printf("  decoder '%s' supports ",
@@ -579,6 +668,8 @@
     status_t err = client.connect();
 
     for (int k = 0; k < argc; ++k) {
+        bool syncInfoPresent = true;
+
         const char *filename = argv[k];
 
         sp<DataSource> dataSource = DataSource::CreateFromURI(filename);
@@ -625,6 +716,8 @@
                 }
 
                 extractor = rtspController.get();
+
+                syncInfoPresent = false;
             } else {
                 extractor = MediaExtractor::Create(dataSource);
                 if (extractor == NULL) {
@@ -674,7 +767,7 @@
         }
 
         if (gWriteMP4) {
-            writeSourceToMP4(mediaSource);
+            writeSourceToMP4(mediaSource, syncInfoPresent);
         } else if (seekTest) {
             performSeekTest(mediaSource);
         } else {
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 1e2bbcc..02b2dce 100755
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -614,6 +614,17 @@
     }
 
     /**
+     * Removes all listeners from the set listening to frame updates for this animation.
+     */
+    public void removeAllUpdateListeners() {
+        if (mUpdateListeners == null) {
+            return;
+        }
+        mUpdateListeners.clear();
+        mUpdateListeners = null;
+    }
+
+    /**
      * Removes a listener from the set listening to frame updates for this animation.
      *
      * @param listener the listener to be removed from the current set of update listeners
@@ -685,7 +696,15 @@
      */
     private void start(boolean playBackwards) {
         mPlayingBackwards = playBackwards;
-        if ((mStartDelay == 0) && (Thread.currentThread() == Looper.getMainLooper().getThread())) {
+        Looper looper = Looper.getMainLooper();
+        final boolean isUiThread;
+        if (looper != null) {
+            isUiThread = Thread.currentThread() == looper.getThread();
+        } else {
+            // ignore check if we don't have a Looper (this isn't an Activity)
+            isUiThread = true;
+        }
+        if ((mStartDelay == 0) && isUiThread) {
             if (mListeners != null) {
                 ArrayList<AnimatorListener> tmpListeners =
                         (ArrayList<AnimatorListener>) mListeners.clone();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 3f74904..2ff88da 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -61,6 +61,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
+import android.util.LogPrinter;
 import android.util.Slog;
 import android.view.Display;
 import android.view.HardwareRenderer;
@@ -121,6 +122,7 @@
     private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
     private static final boolean DEBUG = false;
     static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV;
+    static final boolean DEBUG_MESSAGES = false;
     static final boolean DEBUG_BROADCAST = false;
     private static final boolean DEBUG_RESULTS = false;
     private static final boolean DEBUG_BACKUP = false;
@@ -913,7 +915,7 @@
         public static final int DUMP_HEAP               = 135;
         public static final int DUMP_ACTIVITY           = 136;
         String codeToString(int code) {
-            if (localLOGV) {
+            if (DEBUG_MESSAGES) {
                 switch (code) {
                     case LAUNCH_ACTIVITY: return "LAUNCH_ACTIVITY";
                     case PAUSE_ACTIVITY: return "PAUSE_ACTIVITY";
@@ -957,6 +959,7 @@
             return "(unknown)";
         }
         public void handleMessage(Message msg) {
+            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + msg.what);
             switch (msg.what) {
                 case LAUNCH_ACTIVITY: {
                     ActivityClientRecord r = (ActivityClientRecord)msg.obj;
@@ -1084,6 +1087,7 @@
                     handleDumpActivity((DumpComponentInfo)msg.obj);
                     break;
             }
+            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + msg.what);
         }
 
         void maybeSnapshot() {
@@ -1550,7 +1554,7 @@
 
     private final void queueOrSendMessage(int what, Object obj, int arg1, int arg2) {
         synchronized (this) {
-            if (localLOGV) Slog.v(
+            if (DEBUG_MESSAGES) Slog.v(
                 TAG, "SCHEDULE " + what + " " + mH.codeToString(what)
                 + ": " + arg1 + " / " + obj);
             Message msg = Message.obtain();
@@ -3699,6 +3703,11 @@
         ActivityThread thread = new ActivityThread();
         thread.attach(false);
 
+        if (false) {
+            Looper.myLooper().setMessageLogging(new
+                    LogPrinter(Log.DEBUG, "ActivityThread"));
+        }
+
         Looper.loop();
 
         if (Process.supportsProcesses()) {
diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl
index c76a517..aca8305 100644
--- a/core/java/android/app/IActivityController.aidl
+++ b/core/java/android/app/IActivityController.aidl
@@ -48,6 +48,11 @@
             long timeMillis, String stackTrace);
     
     /**
+     * Early call as soon as an ANR is detected.
+     */
+    int appEarlyNotResponding(String processName, int pid, String annotation);
+
+    /**
      * An application process is not responding.  Return 0 to show the "app
      * not responding" dialog, 1 to continue waiting, or -1 to kill it
      * immediately.
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 3df1790..4cb7026 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -2428,73 +2428,73 @@
      */
     /* package */ static ArrayList<DbStats> getDbStats() {
         ArrayList<DbStats> dbStatsList = new ArrayList<DbStats>();
-        // make a local copy of mActiveDatabases - so that this method is not competing
-        // for synchronization lock on mActiveDatabases
-        ArrayList<WeakReference<SQLiteDatabase>> tempList;
-        synchronized(mActiveDatabases) {
-            tempList = (ArrayList<WeakReference<SQLiteDatabase>>)mActiveDatabases.clone();
-        }
-        for (WeakReference<SQLiteDatabase> w : tempList) {
-            SQLiteDatabase db = w.get();
-            if (db == null || !db.isOpen()) {
-                continue;
-            }
-
-            synchronized (db) {
-                try {
-                    // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
-                    int lookasideUsed = db.native_getDbLookaside();
-
-                    // get the lastnode of the dbname
-                    String path = db.getPath();
-                    int indx = path.lastIndexOf("/");
-                    String lastnode = path.substring((indx != -1) ? ++indx : 0);
-
-                    // get list of attached dbs and for each db, get its size and pagesize
-                    ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs();
-                    if (attachedDbs == null) {
-                        continue;
-                    }
-                    for (int i = 0; i < attachedDbs.size(); i++) {
-                        Pair<String, String> p = attachedDbs.get(i);
-                        long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first
-                                + ".page_count;", null);
-
-                        // first entry in the attached db list is always the main database
-                        // don't worry about prefixing the dbname with "main"
-                        String dbName;
-                        if (i == 0) {
-                            dbName = lastnode;
-                        } else {
-                            // lookaside is only relevant for the main db
-                            lookasideUsed = 0;
-                            dbName = "  (attached) " + p.first;
-                            // if the attached db has a path, attach the lastnode from the path to above
-                            if (p.second.trim().length() > 0) {
-                                int idx = p.second.lastIndexOf("/");
-                                dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
-                            }
-                        }
-                        if (pageCount > 0) {
-                            dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
-                                    lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
-                                    db.getCachesize()));
-                        }
-                    }
-                    // if there are pooled connections, return the cache stats for them also.
-                    if (db.mConnectionPool != null) {
-                        for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) {
-                            dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
-                                    + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
-                                    pDb.getCacheMissNum(), pDb.getCachesize()));
-                        }
-                    }
-                } catch (SQLiteException e) {
-                    // ignore. we don't care about exceptions when we are taking adb
-                    // bugreport!
-                }
-            }
-        }
+//        // make a local copy of mActiveDatabases - so that this method is not competing
+//        // for synchronization lock on mActiveDatabases
+//        ArrayList<WeakReference<SQLiteDatabase>> tempList;
+//        synchronized(mActiveDatabases) {
+//            tempList = (ArrayList<WeakReference<SQLiteDatabase>>)mActiveDatabases.clone();
+//        }
+//        for (WeakReference<SQLiteDatabase> w : tempList) {
+//            SQLiteDatabase db = w.get();
+//            if (db == null || !db.isOpen()) {
+//                continue;
+//            }
+//
+//            synchronized (db) {
+//                try {
+//                    // get SQLITE_DBSTATUS_LOOKASIDE_USED for the db
+//                    int lookasideUsed = db.native_getDbLookaside();
+//
+//                    // get the lastnode of the dbname
+//                    String path = db.getPath();
+//                    int indx = path.lastIndexOf("/");
+//                    String lastnode = path.substring((indx != -1) ? ++indx : 0);
+//
+//                    // get list of attached dbs and for each db, get its size and pagesize
+//                    ArrayList<Pair<String, String>> attachedDbs = db.getAttachedDbs();
+//                    if (attachedDbs == null) {
+//                        continue;
+//                    }
+//                    for (int i = 0; i < attachedDbs.size(); i++) {
+//                        Pair<String, String> p = attachedDbs.get(i);
+//                        long pageCount = DatabaseUtils.longForQuery(db, "PRAGMA " + p.first
+//                                + ".page_count;", null);
+//
+//                        // first entry in the attached db list is always the main database
+//                        // don't worry about prefixing the dbname with "main"
+//                        String dbName;
+//                        if (i == 0) {
+//                            dbName = lastnode;
+//                        } else {
+//                            // lookaside is only relevant for the main db
+//                            lookasideUsed = 0;
+//                            dbName = "  (attached) " + p.first;
+//                            // if the attached db has a path, attach the lastnode from the path to above
+//                            if (p.second.trim().length() > 0) {
+//                                int idx = p.second.lastIndexOf("/");
+//                                dbName += " : " + p.second.substring((idx != -1) ? ++idx : 0);
+//                            }
+//                        }
+//                        if (pageCount > 0) {
+//                            dbStatsList.add(new DbStats(dbName, pageCount, db.getPageSize(),
+//                                    lookasideUsed, db.getCacheHitNum(), db.getCacheMissNum(),
+//                                    db.getCachesize()));
+//                        }
+//                    }
+//                    // if there are pooled connections, return the cache stats for them also.
+//                    if (db.mConnectionPool != null) {
+//                        for (SQLiteDatabase pDb : db.mConnectionPool.getConnectionList()) {
+//                            dbStatsList.add(new DbStats("(pooled # " + pDb.mConnectionNum + ") "
+//                                    + lastnode, 0, 0, 0, pDb.getCacheHitNum(),
+//                                    pDb.getCacheMissNum(), pDb.getCachesize()));
+//                        }
+//                    }
+//                } catch (SQLiteException e) {
+//                    // ignore. we don't care about exceptions when we are taking adb
+//                    // bugreport!
+//                }
+//            }
+//        }
         return dbStatsList;
     }
 
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index ba8014f2..d49c8be 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -301,7 +301,11 @@
          */
         public static abstract class Proc {
 
-            public static class ExcessiveWake {
+            public static class ExcessivePower {
+                public static final int TYPE_WAKE = 1;
+                public static final int TYPE_CPU = 2;
+
+                public int type;
                 public long overTime;
                 public long usedTime;
             }
@@ -343,9 +347,9 @@
              */
             public abstract long getTimeAtCpuSpeedStep(int speedStep, int which);
 
-            public abstract int countExcessiveWakes();
+            public abstract int countExcessivePowers();
 
-            public abstract ExcessiveWake getExcessiveWake(int i);
+            public abstract ExcessivePower getExcessivePower(int i);
         }
 
         /**
@@ -1593,7 +1597,7 @@
                     systemTime = ps.getSystemTime(which);
                     starts = ps.getStarts(which);
                     numExcessive = which == STATS_SINCE_CHARGED
-                            ? ps.countExcessiveWakes() : 0;
+                            ? ps.countExcessivePowers() : 0;
 
                     if (userTime != 0 || systemTime != 0 || starts != 0
                             || numExcessive != 0) {
@@ -1609,9 +1613,17 @@
                         }
                         pw.println(sb.toString());
                         for (int e=0; e<numExcessive; e++) {
-                            Uid.Proc.ExcessiveWake ew = ps.getExcessiveWake(e);
+                            Uid.Proc.ExcessivePower ew = ps.getExcessivePower(e);
                             if (ew != null) {
-                                pw.print(prefix); pw.print("      * Killed for wake lock use: ");
+                                pw.print(prefix); pw.print("      * Killed for ");
+                                        if (ew.type == Uid.Proc.ExcessivePower.TYPE_WAKE) {
+                                            pw.print("wake lock");
+                                        } else if (ew.type == Uid.Proc.ExcessivePower.TYPE_CPU) {
+                                            pw.print("cpu");
+                                        } else {
+                                            pw.print("unknown");
+                                        }
+                                        pw.print(" use: ");
                                         TimeUtils.formatDuration(ew.usedTime, pw);
                                         pw.print(" over ");
                                         TimeUtils.formatDuration(ew.overTime, pw);
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index f695dbb..1235b4d 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -86,6 +86,12 @@
     public static final int WIFI_UID = 1010;
 
     /**
+     * Defines the GID for the group that allows write access to the SD card.
+     * @hide
+     */
+    public static final int SDCARD_RW_GID = 1015;
+
+    /**
      * Defines the start of a range of UIDs (and GIDs), going from this
      * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning
      * to applications.
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 4e61ddf..66149ac 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -67,7 +67,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS' 
 
     // Current on-disk Parcel version
-    private static final int VERSION = 51;
+    private static final int VERSION = 52;
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS = 2000;
@@ -85,7 +85,7 @@
 
     static final int MSG_UPDATE_WAKELOCKS = 1;
     static final int MSG_REPORT_POWER_CHANGE = 2;
-    static final long DELAY_UPDATE_WAKELOCKS = 15*1000;
+    static final long DELAY_UPDATE_WAKELOCKS = 5*1000;
 
     public interface BatteryCallback {
         public void batteryNeedsCpuUpdate();
@@ -1476,6 +1476,13 @@
         }
     }
 
+    public void reportExcessiveCpuLocked(int uid, String proc, long overTime, long usedTime) {
+        Uid u = mUidStats.get(uid);
+        if (u != null) {
+            u.reportExcessiveCpuLocked(proc, overTime, usedTime);
+        }
+    }
+
     int mSensorNesting;
 
     public void noteStartSensorLocked(int uid, int sensor) {
@@ -2977,7 +2984,7 @@
 
             SamplingCounter[] mSpeedBins;
 
-            ArrayList<ExcessiveWake> mExcessiveWake;
+            ArrayList<ExcessivePower> mExcessivePower;
 
             Proc() {
                 mUnpluggables.add(this);
@@ -3005,55 +3012,69 @@
                 }
             }
             
-            public int countExcessiveWakes() {
-                return mExcessiveWake != null ? mExcessiveWake.size() : 0;
+            public int countExcessivePowers() {
+                return mExcessivePower != null ? mExcessivePower.size() : 0;
             }
 
-            public ExcessiveWake getExcessiveWake(int i) {
-                if (mExcessiveWake != null) {
-                    return mExcessiveWake.get(i);
+            public ExcessivePower getExcessivePower(int i) {
+                if (mExcessivePower != null) {
+                    return mExcessivePower.get(i);
                 }
                 return null;
             }
 
             public void addExcessiveWake(long overTime, long usedTime) {
-                if (mExcessiveWake == null) {
-                    mExcessiveWake = new ArrayList<ExcessiveWake>();
+                if (mExcessivePower == null) {
+                    mExcessivePower = new ArrayList<ExcessivePower>();
                 }
-                ExcessiveWake ew = new ExcessiveWake();
+                ExcessivePower ew = new ExcessivePower();
+                ew.type = ExcessivePower.TYPE_WAKE;
                 ew.overTime = overTime;
                 ew.usedTime = usedTime;
-                mExcessiveWake.add(ew);
+                mExcessivePower.add(ew);
             }
 
-            void writeExcessiveWakeToParcelLocked(Parcel out) {
-                if (mExcessiveWake == null) {
+            public void addExcessiveCpu(long overTime, long usedTime) {
+                if (mExcessivePower == null) {
+                    mExcessivePower = new ArrayList<ExcessivePower>();
+                }
+                ExcessivePower ew = new ExcessivePower();
+                ew.type = ExcessivePower.TYPE_CPU;
+                ew.overTime = overTime;
+                ew.usedTime = usedTime;
+                mExcessivePower.add(ew);
+            }
+
+            void writeExcessivePowerToParcelLocked(Parcel out) {
+                if (mExcessivePower == null) {
                     out.writeInt(0);
                     return;
                 }
 
-                final int N = mExcessiveWake.size();
+                final int N = mExcessivePower.size();
                 out.writeInt(N);
                 for (int i=0; i<N; i++) {
-                    ExcessiveWake ew = mExcessiveWake.get(i);
+                    ExcessivePower ew = mExcessivePower.get(i);
+                    out.writeInt(ew.type);
                     out.writeLong(ew.overTime);
                     out.writeLong(ew.usedTime);
                 }
             }
 
-            void readExcessiveWakeFromParcelLocked(Parcel in) {
+            void readExcessivePowerFromParcelLocked(Parcel in) {
                 final int N = in.readInt();
                 if (N == 0) {
-                    mExcessiveWake = null;
+                    mExcessivePower = null;
                     return;
                 }
 
-                mExcessiveWake = new ArrayList<ExcessiveWake>();
+                mExcessivePower = new ArrayList<ExcessivePower>();
                 for (int i=0; i<N; i++) {
-                    ExcessiveWake ew = new ExcessiveWake();
+                    ExcessivePower ew = new ExcessivePower();
+                    ew.type = in.readInt();
                     ew.overTime = in.readLong();
                     ew.usedTime = in.readLong();
-                    mExcessiveWake.add(ew);
+                    mExcessivePower.add(ew);
                 }
             }
 
@@ -3082,7 +3103,7 @@
                     }
                 }
 
-                writeExcessiveWakeToParcelLocked(out);
+                writeExcessivePowerToParcelLocked(out);
             }
 
             void readFromParcelLocked(Parcel in) {
@@ -3112,7 +3133,7 @@
                     }
                 }
 
-                readExcessiveWakeFromParcelLocked(in);
+                readExcessivePowerFromParcelLocked(in);
             }
 
             public BatteryStatsImpl getBatteryStats() {
@@ -3746,6 +3767,13 @@
             }
         }
         
+        public void reportExcessiveCpuLocked(String proc, long overTime, long usedTime) {
+            Proc p = getProcessStatsLocked(proc);
+            if (p != null) {
+                p.addExcessiveCpu(overTime, usedTime);
+            }
+        }
+
         public void noteStartSensor(int sensor) {
             StopwatchTimer t = getSensorTimerLocked(sensor, true);
             if (t != null) {
@@ -4688,7 +4716,7 @@
                         p.mSpeedBins[i].readSummaryFromParcelLocked(in);
                     }
                 }
-                p.readExcessiveWakeFromParcelLocked(in);
+                p.readExcessivePowerFromParcelLocked(in);
             }
 
             NP = in.readInt();
@@ -4887,7 +4915,7 @@
                             out.writeInt(0);
                         }
                     }
-                    ps.writeExcessiveWakeToParcelLocked(out);
+                    ps.writeExcessivePowerToParcelLocked(out);
                 }
             }
 
diff --git a/core/java/com/android/internal/widget/DrawableHolder.java b/core/java/com/android/internal/widget/DrawableHolder.java
new file mode 100644
index 0000000..d53860c
--- /dev/null
+++ b/core/java/com/android/internal/widget/DrawableHolder.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import java.util.ArrayList;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.animation.Animator.AnimatorListener;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.Log;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This class is a container for a Drawable with multiple animated properties.
+ *
+ */
+public class DrawableHolder implements AnimatorListener {
+    public static final DecelerateInterpolator EASE_OUT_INTERPOLATOR = new DecelerateInterpolator();
+    private static final String TAG = "DrawableHolder";
+    private static final boolean DBG = false;
+    private float mX = 0.0f;
+    private float mY = 0.0f;
+    private float mScaleX = 1.0f;
+    private float mScaleY = 1.0f;
+    private BitmapDrawable mDrawable;
+    private float mAlpha = 1f;
+    private ArrayList<ObjectAnimator<Float>> mAnimators = new ArrayList<ObjectAnimator<Float>>();
+    private ArrayList<ObjectAnimator<Float>> mNeedToStart = new ArrayList<ObjectAnimator<Float>>();
+
+    public DrawableHolder(BitmapDrawable drawable) {
+        this(drawable, 0.0f, 0.0f);
+    }
+
+    public DrawableHolder(BitmapDrawable drawable, float x, float y) {
+        mDrawable = drawable;
+        mX = x;
+        mY = y;
+        mDrawable.getPaint().setAntiAlias(true); // Force AA
+        mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight());
+    }
+
+    /**
+     *
+     * Adds an animation that interpolates given property from its current value
+     * to the given value.
+     *
+     * @param duration the duration, in ms.
+     * @param delay the delay to start the animation, in ms.
+     * @param property the property to animate
+     * @param toValue the target value
+     * @param replace if true, replace the current animation with this one.
+     */
+    public ObjectAnimator<Float> addAnimTo(long duration, long delay,
+            String property, float toValue, boolean replace) {
+
+        if (replace) removeAnimationFor(property);
+
+        ObjectAnimator<Float> anim = new ObjectAnimator<Float>(duration, this, property, toValue);
+        anim.setStartDelay(delay);
+        anim.setInterpolator(EASE_OUT_INTERPOLATOR);
+        this.addAnimation(anim, replace);
+        if (DBG) Log.v(TAG, "animationCount = " + mAnimators.size());
+        return anim;
+    }
+
+    /**
+     * Stops all animations for the given property and removes it from the list.
+     *
+     * @param property
+     */
+    public void removeAnimationFor(String property) {
+        ArrayList<ObjectAnimator<Float>> removalList = new ArrayList<ObjectAnimator<Float>>();
+        for (ObjectAnimator<Float> currentAnim : mAnimators) {
+            if (property.equals(currentAnim.getPropertyName())) {
+                currentAnim.cancel();
+                removalList.add(currentAnim);
+            }
+        }
+        if (DBG) Log.v(TAG, "Remove list size: " + removalList.size());
+        mAnimators.removeAll(removalList);
+    }
+
+    /**
+     * Stops all animations and removes them from the list.
+     */
+    public void clearAnimations() {
+        for (ObjectAnimator<Float> currentAnim : mAnimators) {
+            currentAnim.cancel();
+        }
+        mAnimators.clear();
+    }
+
+    /**
+     * Adds the given animation to the list of animations for this object.
+     *
+     * @param anim
+     * @param overwrite
+     * @return
+     */
+    private DrawableHolder addAnimation(ObjectAnimator<Float> anim, boolean overwrite) {
+        if (anim != null)
+            mAnimators.add(anim);
+        mNeedToStart.add(anim);
+        return this;
+    }
+
+    /**
+     * Draw this object to the canvas using the properties defined in this class.
+     *
+     * @param canvas canvas to draw into
+     */
+    public void draw(Canvas canvas) {
+        final float threshold = 1.0f / 256.0f; // contribution less than 1 LSB of RGB byte
+        if (mAlpha <= threshold) // don't bother if it won't show up
+            return;
+        canvas.save(Canvas.MATRIX_SAVE_FLAG);
+        canvas.translate(mX, mY);
+        canvas.scale(mScaleX, mScaleY);
+        canvas.translate(-0.5f*getWidth(), -0.5f*getHeight());
+        mDrawable.setAlpha((int) Math.round(mAlpha * 255f));
+        mDrawable.draw(canvas);
+        canvas.restore();
+    }
+
+    /**
+     * Starts all animations added since the last call to this function.  Used to synchronize
+     * animations.
+     *
+     * @param listener an optional listener to add to the animations. Typically used to know when
+     * to invalidate the surface these are being drawn to.
+     */
+    public void startAnimations(ValueAnimator.AnimatorUpdateListener listener) {
+        for (int i = 0; i < mNeedToStart.size(); i++) {
+            ObjectAnimator<Float> anim = mNeedToStart.get(i);
+            anim.addUpdateListener(listener);
+            anim.addListener(this);
+            anim.start();
+        }
+        mNeedToStart.clear();
+    }
+
+
+    public DrawableHolder setX(float value) {
+        mX = value;
+        return this;
+    }
+
+    public DrawableHolder setY(float value) {
+        mY = value;
+        return this;
+    }
+
+    public DrawableHolder setScaleX(float value) {
+        mScaleX = value;
+        return this;
+    }
+
+    public DrawableHolder setScaleY(float value) {
+        mScaleY = value;
+        return this;
+    }
+
+    public DrawableHolder setAlpha(float alpha) {
+        mAlpha = alpha;
+        return this;
+    }
+
+    public float getX() {
+        return mX;
+    }
+
+    public float getY() {
+        return mY;
+    }
+
+    public float getScaleX() {
+        return mScaleX;
+    }
+
+    public float getScaleY() {
+        return mScaleY;
+    }
+
+    public float getAlpha() {
+        return mAlpha;
+    }
+
+    public BitmapDrawable getDrawable() {
+        return mDrawable;
+    }
+
+    public int getWidth() {
+        return mDrawable.getIntrinsicWidth();
+    }
+
+    public int getHeight() {
+        return mDrawable.getIntrinsicHeight();
+    }
+
+    public void onAnimationCancel(Animator animation) {
+
+    }
+
+    public void onAnimationEnd(Animator animation) {
+        mAnimators.remove(animation);
+    }
+
+    public void onAnimationRepeat(Animator animation) {
+
+    }
+
+    public void onAnimationStart(Animator animation) {
+
+    }
+}
diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
new file mode 100644
index 0000000..f4ee7ee
--- /dev/null
+++ b/core/java/com/android/internal/widget/WaveView.java
@@ -0,0 +1,573 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import java.util.ArrayList;
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.os.Vibrator;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+import com.android.internal.R;
+
+/**
+ * A special widget containing a center and outer ring. Moving the center ring to the outer ring
+ * causes an event that can be caught by implementing OnTriggerListener.
+ */
+public class WaveView extends View implements ValueAnimator.AnimatorUpdateListener {
+    private static final String TAG = "WaveView";
+    private static final boolean DBG = false;
+    private static final int WAVE_COUNT = 5; // default wave count
+    private static final long VIBRATE_SHORT = 20;  // msec
+    private static final long VIBRATE_LONG = 20;  // msec
+
+    // Lock state machine states
+    private static final int STATE_RESET_LOCK = 0;
+    private static final int STATE_READY = 1;
+    private static final int STATE_START_ATTEMPT = 2;
+    private static final int STATE_ATTEMPTING = 3;
+    private static final int STATE_UNLOCK_ATTEMPT = 4;
+    private static final int STATE_UNLOCK_SUCCESS = 5;
+
+    // Animation properties.
+    private static final long DURATION = 500; // duration of transitional animations
+    private static final long FINAL_DELAY = 1300; // delay for final animations
+    private static final long SHORT_DELAY = 100; // for starting one animation after another.
+    private static final long WAVE_DURATION = 2000; // amount of time for way to expand/decay
+    private static final long RESET_TIMEOUT = 3000; // elapsed time of inactivity before we reset
+    private static final long DELAY_INCREMENT = 15; // increment per wave while tracking motion
+    private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking
+
+    private Vibrator mVibrator;
+    private OnTriggerListener mOnTriggerListener;
+    private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3);
+    private ArrayList<DrawableHolder> mLightWaves = new ArrayList<DrawableHolder>(WAVE_COUNT);
+    private boolean mFingerDown = false;
+    private float mRingRadius = 182.0f; // Radius of bitmap ring. Used to snap halo to it
+    private int mSnapRadius = 136; // minimum threshold for drag unlock
+    private int mWaveDelay = 240; // time to delay
+    private int mWaveCount = WAVE_COUNT;  // number of waves
+    private long mWaveTimerDelay = mWaveDelay;
+    private int mCurrentWave = 0;
+    private float mLockCenterX; // center of widget as dictated by widget size
+    private float mLockCenterY;
+    private float mMouseX; // current mouse position as of last touch event
+    private float mMouseY;
+    private DrawableHolder mUnlockRing;
+    private DrawableHolder mUnlockDefault;
+    private DrawableHolder mUnlockHalo;
+    private int mLockState = STATE_RESET_LOCK;
+    private int mGrabbedState = OnTriggerListener.NO_HANDLE;
+
+    public WaveView(Context context) {
+        this(context, null);
+    }
+
+    public WaveView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
+        // mOrientation = a.getInt(R.styleable.WaveView_orientation, HORIZONTAL);
+        // a.recycle();
+
+        initDrawables();
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mLockCenterX = 0.5f * w;
+        mLockCenterY = 0.5f * h;
+        super.onSizeChanged(w, h, oldw, oldh);
+    }
+
+    @Override
+    protected int getSuggestedMinimumWidth() {
+        // View should be large enough to contain the unlock ring + halo
+        return mUnlockRing.getWidth() + mUnlockHalo.getWidth();
+    }
+
+    @Override
+    protected int getSuggestedMinimumHeight() {
+        // View should be large enough to contain the unlock ring + halo
+        return mUnlockRing.getHeight() + mUnlockHalo.getHeight();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
+        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
+        int widthSpecSize =  MeasureSpec.getSize(widthMeasureSpec);
+        int heightSpecSize =  MeasureSpec.getSize(heightMeasureSpec);
+        int width;
+        int height;
+
+        if (widthSpecMode == MeasureSpec.AT_MOST) {
+            width = Math.min(widthSpecSize, getSuggestedMinimumWidth());
+        } else if (widthSpecMode == MeasureSpec.EXACTLY) {
+            width = widthSpecSize;
+        } else {
+            width = getSuggestedMinimumWidth();
+        }
+
+        if (heightSpecMode == MeasureSpec.AT_MOST) {
+            height = Math.min(heightSpecSize, getSuggestedMinimumWidth());
+        } else if (heightSpecMode == MeasureSpec.EXACTLY) {
+            height = heightSpecSize;
+        } else {
+            height = getSuggestedMinimumHeight();
+        }
+
+        setMeasuredDimension(width, height);
+    }
+
+    private void initDrawables() {
+        mUnlockRing = new DrawableHolder(createDrawable(R.drawable.unlock_ring))
+            .setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f).setAlpha(0.0f);
+        mDrawables.add(mUnlockRing);
+
+        mUnlockDefault = new DrawableHolder(createDrawable(R.drawable.unlock_default))
+            .setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f).setAlpha(0.0f);
+        mDrawables.add(mUnlockDefault);
+
+        mUnlockHalo = new DrawableHolder(createDrawable(R.drawable.unlock_halo))
+            .setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f).setAlpha(0.0f);
+        mDrawables.add(mUnlockHalo);
+
+        BitmapDrawable wave = createDrawable(R.drawable.unlock_wave);
+        for (int i = 0; i < mWaveCount; i++) {
+            DrawableHolder holder = new DrawableHolder(wave);
+            mLightWaves.add(holder);
+            holder.setAlpha(0.0f);
+        }
+    }
+
+    private void waveUpdateFrame(float mouseX, float mouseY, boolean fingerDown) {
+        double distX = mouseX - mLockCenterX;
+        double distY = mouseY - mLockCenterY;
+        int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
+        double touchA = Math.atan2(distX, distY);
+        float ringX = (float) (mLockCenterX + mRingRadius * Math.sin(touchA));
+        float ringY = (float) (mLockCenterY + mRingRadius * Math.cos(touchA));
+
+        switch (mLockState) {
+            case STATE_RESET_LOCK:
+                if (DBG) Log.v(TAG, "State RESET_LOCK");
+                mWaveTimerDelay = mWaveDelay;
+                for (int i = 0; i < mLightWaves.size(); i++) {
+                    //TweenMax.to(mLightWave.get(i), .3, {alpha:0, ease:Quint.easeOut});
+                    DrawableHolder holder = mLightWaves.get(i);
+                    holder.addAnimTo(300, 0, "alpha", 0.0f, false);
+                }
+                for (int i = 0; i < mLightWaves.size(); i++) {
+                    mLightWaves.get(i).startAnimations(this);
+                }
+
+                //TweenMax.to(unlockRing, .5, { x: lockX, y: lockY, scaleX: .1, scaleY: .1,
+                // alpha: 0, overwrite: true, ease:Quint.easeOut   });
+                mUnlockRing.addAnimTo(DURATION, 0, "x", mLockCenterX, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "y", mLockCenterY, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, true);
+
+                //TweenMax.to(unlockDefault, 0, { x: lockX, y: lockY, scaleX: .1, scaleY: .1,
+                // alpha: 0  , overwrite: true                       });
+                mUnlockDefault.removeAnimationFor("x");
+                mUnlockDefault.removeAnimationFor("y");
+                mUnlockDefault.removeAnimationFor("scaleX");
+                mUnlockDefault.removeAnimationFor("scaleY");
+                mUnlockDefault.removeAnimationFor("alpha");
+                mUnlockDefault.setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f)
+                        .setAlpha(0.0f);
+
+                //TweenMax.to(unlockDefault, .5, { delay: .1, scaleX: 1, scaleY: 1,
+                // alpha: 1, overwrite: true, ease:Quint.easeOut   });
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
+
+                //TweenMax.to(unlockHalo, 0, { x: lockX, y: lockY, scaleX:.1, scaleY: .1,
+                // alpha: 0  , overwrite: true                       });
+                mUnlockHalo.removeAnimationFor("x");
+                mUnlockHalo.removeAnimationFor("y");
+                mUnlockHalo.removeAnimationFor("scaleX");
+                mUnlockHalo.removeAnimationFor("scaleY");
+                mUnlockHalo.removeAnimationFor("alpha");
+                mUnlockHalo.setX(mLockCenterX).setY(mLockCenterY).setScaleX(0.1f).setScaleY(0.1f)
+                        .setAlpha(0.0f);
+
+                //TweenMax.to(unlockHalo, .5, { x: lockX, y: lockY, scaleX: 1, scaleY: 1,
+                // alpha: 1  , overwrite: true, ease:Quint.easeOut   });
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "x", mLockCenterX, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "y", mLockCenterY, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, true);
+                mUnlockHalo.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, true);
+
+                //lockTimer.stop();
+                removeCallbacks(mLockTimerActions);
+
+                mLockState = STATE_READY;
+                break;
+
+            case STATE_READY:
+                if (DBG) Log.v(TAG, "State READY");
+                break;
+
+            case STATE_START_ATTEMPT:
+                if (DBG) Log.v(TAG, "State START_ATTEMPT");
+                //TweenMax.to(unlockDefault, 0, {scaleX: .1, scaleY:.1, alpha: 0,
+                // x:lockX +182, y: lockY  , overwrite: true   });
+                mUnlockDefault.removeAnimationFor("x");
+                mUnlockDefault.removeAnimationFor("y");
+                mUnlockDefault.removeAnimationFor("scaleX");
+                mUnlockDefault.removeAnimationFor("scaleY");
+                mUnlockDefault.removeAnimationFor("alpha");
+                mUnlockDefault.setX(mLockCenterX + 182).setY(mLockCenterY).setScaleX(0.1f)
+                        .setScaleY(0.1f).setAlpha(0.0f);
+
+                //TweenMax.to(unlockDefault, 0.5, { delay: .1   , scaleX: 1, scaleY: 1,
+                // alpha: 1, ease:Quint.easeOut                        });
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleX", 1.0f, false);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "scaleY", 1.0f, false);
+                mUnlockDefault.addAnimTo(DURATION, SHORT_DELAY, "alpha", 1.0f, false);
+
+                //TweenMax.to(unlockRing, 0.5, {scaleX: 1, scaleY: 1,
+                // alpha: 1, ease:Quint.easeOut, overwrite: true   });
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
+                mUnlockRing.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
+
+                postDelayed(mAddWaveAction, mWaveTimerDelay);
+
+                mLockState = STATE_ATTEMPTING;
+                break;
+
+            case STATE_ATTEMPTING:
+                if (DBG) Log.v(TAG, "State ATTEMPTING");
+                //TweenMax.to(unlockHalo, 0.4, { x:mouseX, y:mouseY, scaleX:1, scaleY:1,
+                // alpha: 1, ease:Quint.easeOut });
+                if (dragDistance > mSnapRadius) {
+                    if (fingerDown) {
+                        //TweenMax.to(unlockHalo, 0.4, {x:ringX, y:ringY, scaleX:1, scaleY:1,
+                        // alpha: 1 , ease:Quint.easeOut    , overwrite: true });
+                        mUnlockHalo.addAnimTo(0, 0, "x", ringX, true);
+                        mUnlockHalo.addAnimTo(0, 0, "y", ringY, true);
+                        mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
+                        mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
+                        mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
+                    }  else {
+                        mLockState = STATE_UNLOCK_ATTEMPT;
+                    }
+                } else {
+                    mUnlockHalo.addAnimTo(0, 0, "x", mouseX, true);
+                    mUnlockHalo.addAnimTo(0, 0, "y", mouseY, true);
+                    mUnlockHalo.addAnimTo(0, 0, "scaleX", 1.0f, true);
+                    mUnlockHalo.addAnimTo(0, 0, "scaleY", 1.0f, true);
+                    mUnlockHalo.addAnimTo(0, 0, "alpha", 1.0f, true);
+                }
+                break;
+
+            case STATE_UNLOCK_ATTEMPT:
+                if (DBG) Log.v(TAG, "State UNLOCK_ATTEMPT");
+                if (dragDistance > mSnapRadius) {
+                    for (int n = 0; n < mLightWaves.size(); n++) {
+                        //TweenMax.to(this["lightWave"+n], .5,{alpha:0, delay: (6+n-currentWave)*.1,
+                        // x:ringX, y:ringY, scaleX: .1, scaleY: .1, ease:Quint.easeOut});
+                        DrawableHolder wave = mLightWaves.get(n);
+                        long delay = 1000L*(6 + n - mCurrentWave)/10L;
+                        wave.addAnimTo(DURATION, delay, "x", ringX, true);
+                        wave.addAnimTo(DURATION, delay, "y", ringY, true);
+                        wave.addAnimTo(DURATION, delay, "scaleX", 0.1f, true);
+                        wave.addAnimTo(DURATION, delay, "scaleY", 0.1f, true);
+                        wave.addAnimTo(DURATION, delay, "alpha", 0.0f, true);
+                    }
+                    for (int i = 0; i < mLightWaves.size(); i++) {
+                        mLightWaves.get(i).startAnimations(this);
+                    }
+
+                    //TweenMax.to(unlockRing, .5, {x:ringX, y: ringY, scaleX: .1, scaleY: .1,
+                    // alpha: 0, ease: Quint.easeOut   });
+                    mUnlockRing.addAnimTo(DURATION, 0, "x", ringX, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "y", ringY, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "scaleX", 0.1f, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "scaleY", 0.1f, false);
+                    mUnlockRing.addAnimTo(DURATION, 0, "alpha", 0.0f, false);
+
+                    //TweenMax.to(unlockRing, .5, { delay: 1.3, alpha: 0  , ease: Quint.easeOut });
+                    mUnlockRing.addAnimTo(DURATION, FINAL_DELAY, "alpha", 0.0f, false);
+
+                    //TweenMax.to(unlockDefault, 0, { x:ringX, y: ringY, scaleX: .1, scaleY: .1,
+                    // alpha: 0  , overwrite: true });
+                    mUnlockDefault.removeAnimationFor("x");
+                    mUnlockDefault.removeAnimationFor("y");
+                    mUnlockDefault.removeAnimationFor("scaleX");
+                    mUnlockDefault.removeAnimationFor("scaleY");
+                    mUnlockDefault.removeAnimationFor("alpha");
+                    mUnlockDefault.setX(ringX).setY(ringY).setScaleX(0.1f).setScaleY(0.1f)
+                            .setAlpha(0.0f);
+
+                    //TweenMax.to(unlockDefault, .5, { x:ringX, y: ringY, scaleX: 1, scaleY: 1,
+                    // alpha: 1  , ease: Quint.easeOut  , overwrite: true });
+                    mUnlockDefault.addAnimTo(DURATION, 0, "x", ringX, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "y", ringY, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "scaleX", 1.0f, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "scaleY", 1.0f, true);
+                    mUnlockDefault.addAnimTo(DURATION, 0, "alpha", 1.0f, true);
+
+                    //TweenMax.to(unlockDefault, .5, { delay: 1.3, scaleX: 3, scaleY: 3,
+                    // alpha: 1, ease: Quint.easeOut });
+                    mUnlockDefault.addAnimTo(DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
+                    mUnlockDefault.addAnimTo(DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
+                    mUnlockDefault.addAnimTo(DURATION, FINAL_DELAY, "alpha", 1.0f, false);
+
+                    //TweenMax.to(unlockHalo, .5, { x:ringX, y: ringY , ease: Back.easeOut    });
+                    mUnlockHalo.addAnimTo(DURATION, 0, "x", ringX, false);
+                    mUnlockHalo.addAnimTo(DURATION, 0, "y", ringY, false);
+
+                    //TweenMax.to(unlockHalo, .5, { delay: 1.3, scaleX: 3, scaleY: 3,
+                    // alpha: 1, ease: Quint.easeOut   });
+                    mUnlockHalo.addAnimTo(DURATION, FINAL_DELAY, "scaleX", 3.0f, false);
+                    mUnlockHalo.addAnimTo(DURATION, FINAL_DELAY, "scaleY", 3.0f, false);
+                    mUnlockHalo.addAnimTo(DURATION, FINAL_DELAY, "alpha", 1.0f, false);
+
+                    removeCallbacks(mLockTimerActions);
+
+                    postDelayed(mLockTimerActions, RESET_TIMEOUT);
+
+                    dispatchTriggerEvent(OnTriggerListener.CENTER_HANDLE);
+                    mLockState = STATE_UNLOCK_SUCCESS;
+                } else {
+                    mLockState = STATE_RESET_LOCK;
+                }
+                break;
+
+            case STATE_UNLOCK_SUCCESS:
+                if (DBG) Log.v(TAG, "State UNLOCK_SUCCESS");
+                removeCallbacks(mAddWaveAction);
+                break;
+
+            default:
+                if (DBG) Log.v(TAG, "Unknown state " + mLockState);
+                break;
+        }
+        mUnlockDefault.startAnimations(this);
+        mUnlockHalo.startAnimations(this);
+        mUnlockRing.startAnimations(this);
+    }
+
+    BitmapDrawable createDrawable(int resId) {
+        Resources res = getResources();
+        Bitmap bitmap = BitmapFactory.decodeResource(res, resId);
+        return new BitmapDrawable(res, bitmap);
+    }
+
+    @Override
+    protected void onDraw(Canvas canvas) {
+        waveUpdateFrame(mMouseX, mMouseY, mFingerDown);
+        for (int i = 0; i < mDrawables.size(); ++i) {
+            mDrawables.get(i).draw(canvas);
+        }
+        for (int i = 0; i < mLightWaves.size(); ++i) {
+            mLightWaves.get(i).draw(canvas);
+        }
+    }
+
+    private final Runnable mLockTimerActions = new Runnable() {
+        public void run() {
+            if (DBG) Log.v(TAG, "LockTimerActions");
+            // reset lock after inactivity
+            if (mLockState == STATE_ATTEMPTING) {
+                mLockState = STATE_RESET_LOCK;
+            }
+            // for prototype, reset after successful unlock
+            if (mLockState == STATE_UNLOCK_SUCCESS) {
+                mLockState = STATE_RESET_LOCK;
+            }
+            invalidate();
+        }
+    };
+
+    private final Runnable mAddWaveAction = new Runnable() {
+        public void run() {
+            double distX = mMouseX - mLockCenterX;
+            double distY = mMouseY - mLockCenterY;
+            int dragDistance = (int) Math.ceil(Math.hypot(distX, distY));
+            if (mLockState == STATE_ATTEMPTING && dragDistance < mSnapRadius
+                    && mWaveTimerDelay >= mWaveDelay) {
+                mWaveTimerDelay = Math.min(WAVE_DURATION, mWaveTimerDelay + DELAY_INCREMENT);
+
+                DrawableHolder wave = mLightWaves.get(mCurrentWave);
+                wave.setAlpha(0.0f);
+                wave.setScaleX(0.2f);
+                wave.setScaleY(0.2f);
+                wave.setX(mMouseX);
+                wave.setY(mMouseY);
+
+                //TweenMax.to(this["lightWave"+currentWave], 2, { x:lockX , y:lockY, alpha: 1.5,
+                // scaleX: 1, scaleY:1, ease:Cubic.easeOut});
+                wave.addAnimTo(WAVE_DURATION, 0, "x", mLockCenterX, true);
+                wave.addAnimTo(WAVE_DURATION, 0, "y", mLockCenterY, true);
+                wave.addAnimTo(WAVE_DURATION*2/3, 0, "alpha", 1.0f, true);
+                wave.addAnimTo(WAVE_DURATION, 0, "scaleX", 1.0f, true);
+                wave.addAnimTo(WAVE_DURATION, 0, "scaleY", 1.0f, true);
+
+                //TweenMax.to(this["lightWave"+currentWave], 1, { delay: 1.3
+                // , alpha: 0  , ease:Quint.easeOut});
+                wave.addAnimTo(1000, FINAL_DELAY, "alpha", 0.0f, false);
+                wave.startAnimations(WaveView.this);
+
+                mCurrentWave = (mCurrentWave+1) % mWaveCount;
+                if (DBG) Log.v(TAG, "WaveTimerDelay: start new wave in " + mWaveTimerDelay);
+                postDelayed(mAddWaveAction, mWaveTimerDelay);
+            } else {
+                mWaveTimerDelay += DELAY_INCREMENT2;
+            }
+        }
+    };
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        final int action = event.getAction();
+        mMouseX = event.getX();
+        mMouseY = event.getY();
+        boolean handled = false;
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                removeCallbacks(mLockTimerActions);
+                mFingerDown = true;
+                setGrabbedState(OnTriggerListener.CENTER_HANDLE);
+                {
+                    float x = mMouseX - mUnlockHalo.getX();
+                    float y = mMouseY - mUnlockHalo.getY();
+                    float dist = (float) Math.hypot(x, y);
+                    if (dist < mUnlockHalo.getWidth()*0.5f) {
+                        if (mLockState == STATE_READY) {
+                            mLockState = STATE_START_ATTEMPT;
+                        }
+                    }
+                }
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_MOVE:
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_UP:
+                mFingerDown = false;
+                postDelayed(mLockTimerActions, RESET_TIMEOUT);
+                setGrabbedState(OnTriggerListener.NO_HANDLE);
+                handled = true;
+                break;
+
+            case MotionEvent.ACTION_CANCEL:
+                mFingerDown = false;
+                handled = true;
+                break;
+        }
+        invalidate();
+        return handled ? true : super.onTouchEvent(event);
+    }
+
+    /**
+     * 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);
+            }
+        }
+    }
+
+    public interface OnTriggerListener {
+        /**
+         * Sent when the user releases the handle.
+         */
+        public static final int NO_HANDLE = 0;
+
+        /**
+         * Sent when the user grabs the center handle
+         */
+        public static final int CENTER_HANDLE = 10;
+
+        /**
+         * Called when the user drags the center ring beyond a threshold.
+         */
+        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 #CENTER_HANDLE},
+         */
+        void onGrabbedStateChange(View v, int grabbedState);
+    }
+
+    public void onAnimationUpdate(ValueAnimator animation) {
+        invalidate();
+    }
+
+    public void reset() {
+        mLockState = STATE_RESET_LOCK;
+    }
+}
diff --git a/core/res/res/drawable-xlarge/unlock_default.png b/core/res/res/drawable-xlarge/unlock_default.png
new file mode 100644
index 0000000..0a441c0
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_default.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_halo.png b/core/res/res/drawable-xlarge/unlock_halo.png
new file mode 100644
index 0000000..09b0526
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_halo.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_ring.png b/core/res/res/drawable-xlarge/unlock_ring.png
new file mode 100644
index 0000000..1ac6d54
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_ring.png
Binary files differ
diff --git a/core/res/res/drawable-xlarge/unlock_wave.png b/core/res/res/drawable-xlarge/unlock_wave.png
new file mode 100644
index 0000000..21bfa24
--- /dev/null
+++ b/core/res/res/drawable-xlarge/unlock_wave.png
Binary files differ
diff --git a/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml
index 4761800..b3645aa 100644
--- a/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml
+++ b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock.xml
@@ -57,13 +57,10 @@
             android:drawablePadding="4dip"
             />
 
-        <com.android.internal.widget.SlidingTab
-            android:id="@+id/tab_selector"
-            android:orientation="horizontal"
-            android:layout_width="match_parent"
+        <com.android.internal.widget.WaveView
+            android:id="@+id/wave_view"
+            android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            android:layout_marginBottom="80dip"
             />
 
         <!-- "emergency calls only" shown when sim is missing or PUKd -->
diff --git a/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml
index bb398f6..6c99ccac 100644
--- a/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml
+++ b/core/res/res/layout-xlarge/keyguard_screen_tab_unlock_land.xml
@@ -58,15 +58,14 @@
                 android:drawablePadding="4dip"
                 />
 
-        <com.android.internal.widget.SlidingTab
-            android:id="@+id/tab_selector"
-            android:orientation="vertical"
+        <com.android.internal.widget.WaveView
+            android:id="@+id/wave_view"
             android:layout_width="wrap_content"
             android:layout_height="match_parent"
             android:layout_marginRight="0dip"
             android:layout_weight="1.0"
             />
-            
+
         <!-- "emergency calls only" shown when sim is missing or PUKd -->
         <TextView
             android:id="@+id/emergencyCallText"
diff --git a/data/fonts/Ahem.ttf b/data/fonts/Ahem.ttf
deleted file mode 100644
index 17e6c60..0000000
--- a/data/fonts/Ahem.ttf
+++ /dev/null
Binary files differ
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 6bcaaaf..1fd7bba 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -44,5 +44,5 @@
         <name>monaco</name>
     </font>
     <fallback ttf="DroidSansFallback" />
-    <fallback ttf="DroidSansJapanese" />
-</fonts>
\ No newline at end of file
+    <fallback ttf="MTLmr3m" />
+</fonts>
diff --git a/graphics/java/android/renderscript/Program.java b/graphics/java/android/renderscript/Program.java
index c6ed72a..ffcdbbc5 100644
--- a/graphics/java/android/renderscript/Program.java
+++ b/graphics/java/android/renderscript/Program.java
@@ -154,6 +154,13 @@
             mOutputs[mOutputCount++] = e;
         }
 
+        void resetConstant() {
+            mConstantCount = 0;
+            for(int i = 0; i < MAX_CONSTANT; i ++) {
+                mConstants[i] = null;
+            }
+        }
+
         public int addConstant(Type t) throws IllegalStateException {
             // Should check for consistant and non-conflicting names...
             if(mConstantCount >= MAX_CONSTANT) {
@@ -165,7 +172,7 @@
 
         public BaseProgramBuilder setTextureCount(int count) throws IllegalArgumentException {
             // Should check for consistant and non-conflicting names...
-            if(count >= MAX_CONSTANT) {
+            if(count >= MAX_TEXTURE) {
                 throw new IllegalArgumentException("Max texture count exceeded.");
             }
             mTextureCount = count;
diff --git a/graphics/java/android/renderscript/ProgramFragment.java b/graphics/java/android/renderscript/ProgramFragment.java
index 00c5cf1..c1d6428 100644
--- a/graphics/java/android/renderscript/ProgramFragment.java
+++ b/graphics/java/android/renderscript/ProgramFragment.java
@@ -55,7 +55,7 @@
             tmp[idx++] = 3;
             tmp[idx++] = mTextureCount;
 
-            int id = mRS.nProgramFragmentCreate2(mShader, tmp);
+            int id = mRS.nProgramFragmentCreate(mShader, tmp);
             ProgramFragment pf = new ProgramFragment(id, mRS);
             initProgram(pf);
             return pf;
@@ -199,6 +199,7 @@
                     mNumTextures ++;
                 }
             }
+            resetConstant();
             buildShaderString();
             Type constType = null;
             if (!mVaryingColorEnable) {
diff --git a/graphics/java/android/renderscript/ProgramVertex.java b/graphics/java/android/renderscript/ProgramVertex.java
index 119db69..63e2598 100644
--- a/graphics/java/android/renderscript/ProgramVertex.java
+++ b/graphics/java/android/renderscript/ProgramVertex.java
@@ -64,7 +64,7 @@
             tmp[idx++] = 3;
             tmp[idx++] = mTextureCount;
 
-            int id = mRS.nProgramVertexCreate2(mShader, tmp);
+            int id = mRS.nProgramVertexCreate(mShader, tmp);
             ProgramVertex pv = new ProgramVertex(id, mRS);
             initProgram(pv);
             return pv;
diff --git a/graphics/java/android/renderscript/RenderScript.java b/graphics/java/android/renderscript/RenderScript.java
index 159e070..2aa3e84 100644
--- a/graphics/java/android/renderscript/RenderScript.java
+++ b/graphics/java/android/renderscript/RenderScript.java
@@ -447,26 +447,15 @@
     synchronized void nProgramBindSampler(int vpf, int slot, int s) {
         rsnProgramBindSampler(mContext, vpf, slot, s);
     }
-
-    native int  rsnProgramFragmentCreate(int con, int[] params);
-    synchronized int nProgramFragmentCreate(int[] params) {
-        return rsnProgramFragmentCreate(mContext, params);
+    native int  rsnProgramFragmentCreate(int con, String shader, int[] params);
+    synchronized int nProgramFragmentCreate(String shader, int[] params) {
+        return rsnProgramFragmentCreate(mContext, shader, params);
     }
-    native int  rsnProgramFragmentCreate2(int con, String shader, int[] params);
-    synchronized int nProgramFragmentCreate2(String shader, int[] params) {
-        return rsnProgramFragmentCreate2(mContext, shader, params);
+    native int  rsnProgramVertexCreate(int con, String shader, int[] params);
+    synchronized int nProgramVertexCreate(String shader, int[] params) {
+        return rsnProgramVertexCreate(mContext, shader, params);
     }
 
-    native int  rsnProgramVertexCreate(int con, boolean texMat);
-    synchronized int nProgramVertexCreate(boolean texMat) {
-        return rsnProgramVertexCreate(mContext, texMat);
-    }
-    native int  rsnProgramVertexCreate2(int con, String shader, int[] params);
-    synchronized int nProgramVertexCreate2(String shader, int[] params) {
-        return rsnProgramVertexCreate2(mContext, shader, params);
-    }
-
-
     native int  rsnMeshCreate(int con, int vtxCount, int indexCount);
     synchronized int nMeshCreate(int vtxCount, int indexCount) {
         return rsnMeshCreate(mContext, vtxCount, indexCount);
diff --git a/graphics/jni/android_renderscript_RenderScript.cpp b/graphics/jni/android_renderscript_RenderScript.cpp
index ee2080e..6aed11b 100644
--- a/graphics/jni/android_renderscript_RenderScript.cpp
+++ b/graphics/jni/android_renderscript_RenderScript.cpp
@@ -983,29 +983,16 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jintArray params)
-{
-    jint *paramPtr = _env->GetIntArrayElements(params, NULL);
-    jint paramLen = _env->GetArrayLength(params);
-
-    LOG_API("nProgramFragmentCreate, con(%p), paramLen(%i)", con, paramLen);
-
-    jint ret = (jint)rsProgramFragmentCreate(con, (uint32_t *)paramPtr, paramLen);
-    _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
-    return ret;
-}
-
-static jint
-nProgramFragmentCreate2(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
+nProgramFragmentCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
 {
     const char* shaderUTF = _env->GetStringUTFChars(shader, NULL);
     jint shaderLen = _env->GetStringUTFLength(shader);
     jint *paramPtr = _env->GetIntArrayElements(params, NULL);
     jint paramLen = _env->GetArrayLength(params);
 
-    LOG_API("nProgramFragmentCreate2, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
+    LOG_API("nProgramFragmentCreate, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
 
-    jint ret = (jint)rsProgramFragmentCreate2(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
+    jint ret = (jint)rsProgramFragmentCreate(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
     _env->ReleaseStringUTFChars(shader, shaderUTF);
     _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
     return ret;
@@ -1015,23 +1002,16 @@
 // ---------------------------------------------------------------------------
 
 static jint
-nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jboolean texMat)
-{
-    LOG_API("nProgramVertexCreate, con(%p), texMat(%i)", con, texMat);
-    return (jint)rsProgramVertexCreate(con, texMat);
-}
-
-static jint
-nProgramVertexCreate2(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
+nProgramVertexCreate(JNIEnv *_env, jobject _this, RsContext con, jstring shader, jintArray params)
 {
     const char* shaderUTF = _env->GetStringUTFChars(shader, NULL);
     jint shaderLen = _env->GetStringUTFLength(shader);
     jint *paramPtr = _env->GetIntArrayElements(params, NULL);
     jint paramLen = _env->GetArrayLength(params);
 
-    LOG_API("nProgramVertexCreate2, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
+    LOG_API("nProgramVertexCreate, con(%p), shaderLen(%i), paramLen(%i)", con, shaderLen, paramLen);
 
-    jint ret = (jint)rsProgramVertexCreate2(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
+    jint ret = (jint)rsProgramVertexCreate(con, shaderUTF, shaderLen, (uint32_t *)paramPtr, paramLen);
     _env->ReleaseStringUTFChars(shader, shaderUTF);
     _env->ReleaseIntArrayElements(params, paramPtr, JNI_ABORT);
     return ret;
@@ -1217,36 +1197,36 @@
 
 
 // All methods below are thread protected in java.
-{"rsnContextCreate",                 "(II)I",                                (void*)nContextCreate },
-{"rsnContextCreateGL",               "(IIZ)I",                               (void*)nContextCreateGL },
+{"rsnContextCreate",                 "(II)I",                                 (void*)nContextCreate },
+{"rsnContextCreateGL",               "(IIZ)I",                                (void*)nContextCreateGL },
 {"rsnContextFinish",                 "(I)V",                                  (void*)nContextFinish },
 {"rsnContextSetPriority",            "(II)V",                                 (void*)nContextSetPriority },
 {"rsnContextSetSurface",             "(IIILandroid/view/Surface;)V",          (void*)nContextSetSurface },
-{"rsnContextDestroy",                "(I)V",                                 (void*)nContextDestroy },
+{"rsnContextDestroy",                "(I)V",                                  (void*)nContextDestroy },
 {"rsnContextDump",                   "(II)V",                                 (void*)nContextDump },
 {"rsnContextPause",                  "(I)V",                                  (void*)nContextPause },
 {"rsnContextResume",                 "(I)V",                                  (void*)nContextResume },
 {"rsnAssignName",                    "(II[B)V",                               (void*)nAssignName },
-{"rsnGetName",                       "(II)Ljava/lang/String;",               (void*)nGetName },
+{"rsnGetName",                       "(II)Ljava/lang/String;",                (void*)nGetName },
 {"rsnObjDestroy",                    "(II)V",                                 (void*)nObjDestroy },
 
 {"rsnFileOpen",                      "(I[B)I",                                (void*)nFileOpen },
 {"rsnFileA3DCreateFromAssetStream",  "(II)I",                                 (void*)nFileA3DCreateFromAssetStream },
 {"rsnFileA3DGetNumIndexEntries",     "(II)I",                                 (void*)nFileA3DGetNumIndexEntries },
-{"rsnFileA3DGetIndexEntries",        "(III[I[Ljava/lang/String;)V",          (void*)nFileA3DGetIndexEntries },
+{"rsnFileA3DGetIndexEntries",        "(III[I[Ljava/lang/String;)V",           (void*)nFileA3DGetIndexEntries },
 {"rsnFileA3DGetEntryByIndex",        "(III)I",                                (void*)nFileA3DGetEntryByIndex },
 
-{"rsnFontCreateFromFile",            "(ILjava/lang/String;II)I",             (void*)nFontCreateFromFile },
+{"rsnFontCreateFromFile",            "(ILjava/lang/String;II)I",              (void*)nFontCreateFromFile },
 
 {"rsnElementCreate",                 "(IIIZI)I",                              (void*)nElementCreate },
 {"rsnElementCreate2",                "(I[I[Ljava/lang/String;[I)I",           (void*)nElementCreate2 },
 {"rsnElementGetNativeData",          "(II[I)V",                               (void*)nElementGetNativeData },
-{"rsnElementGetSubElements",         "(II[I[Ljava/lang/String;)V",           (void*)nElementGetSubElements },
+{"rsnElementGetSubElements",         "(II[I[Ljava/lang/String;)V",            (void*)nElementGetSubElements },
 
 {"rsnTypeBegin",                     "(II)V",                                 (void*)nTypeBegin },
 {"rsnTypeAdd",                       "(III)V",                                (void*)nTypeAdd },
 {"rsnTypeCreate",                    "(I)I",                                  (void*)nTypeCreate },
-{"rsnTypeGetNativeData",             "(II[I)V",                                (void*)nTypeGetNativeData },
+{"rsnTypeGetNativeData",             "(II[I)V",                               (void*)nTypeGetNativeData },
 
 {"rsnAllocationCreateTyped",         "(II)I",                                 (void*)nAllocationCreateTyped },
 {"rsnAllocationCreateFromBitmap",    "(IIZLandroid/graphics/Bitmap;)I",       (void*)nAllocationCreateFromBitmap },
@@ -1307,18 +1287,16 @@
 {"rsnProgramBindTexture",            "(IIII)V",                               (void*)nProgramBindTexture },
 {"rsnProgramBindSampler",            "(IIII)V",                               (void*)nProgramBindSampler },
 
-{"rsnProgramFragmentCreate",         "(I[I)I",                                (void*)nProgramFragmentCreate },
-{"rsnProgramFragmentCreate2",        "(ILjava/lang/String;[I)I",              (void*)nProgramFragmentCreate2 },
+{"rsnProgramFragmentCreate",        "(ILjava/lang/String;[I)I",               (void*)nProgramFragmentCreate },
 
-{"rsnProgramRasterCreate",           "(IZZZ)I",                             (void*)nProgramRasterCreate },
+{"rsnProgramRasterCreate",           "(IZZZ)I",                               (void*)nProgramRasterCreate },
 {"rsnProgramRasterSetLineWidth",     "(IIF)V",                                (void*)nProgramRasterSetLineWidth },
 {"rsnProgramRasterSetCullMode",      "(III)V",                                (void*)nProgramRasterSetCullMode },
 
-{"rsnProgramVertexCreate",           "(IZ)I",                                 (void*)nProgramVertexCreate },
-{"rsnProgramVertexCreate2",          "(ILjava/lang/String;[I)I",              (void*)nProgramVertexCreate2 },
+{"rsnProgramVertexCreate",          "(ILjava/lang/String;[I)I",               (void*)nProgramVertexCreate },
 
 {"rsnContextBindRootScript",         "(II)V",                                 (void*)nContextBindRootScript },
-{"rsnContextBindProgramStore",       "(II)V",                                (void*)nContextBindProgramStore },
+{"rsnContextBindProgramStore",       "(II)V",                                 (void*)nContextBindProgramStore },
 {"rsnContextBindProgramFragment",    "(II)V",                                 (void*)nContextBindProgramFragment },
 {"rsnContextBindProgramVertex",      "(II)V",                                 (void*)nContextBindProgramVertex },
 {"rsnContextBindProgramRaster",      "(II)V",                                 (void*)nContextBindProgramRaster },
@@ -1333,7 +1311,7 @@
 
 {"rsnMeshGetVertexBufferCount",      "(II)I",                                 (void*)nMeshGetVertexBufferCount },
 {"rsnMeshGetIndexCount",             "(II)I",                                 (void*)nMeshGetIndexCount },
-{"rsnMeshGetVertices",               "(II[II)V",                             (void*)nMeshGetVertices },
+{"rsnMeshGetVertices",               "(II[II)V",                              (void*)nMeshGetVertices },
 {"rsnMeshGetIndices",                "(II[I[II)V",                            (void*)nMeshGetIndices },
 
 };
diff --git a/libs/rs/java/Samples/res/raw/multitexf.glsl b/libs/rs/java/Samples/res/raw/multitexf.glsl
new file mode 100644
index 0000000..91151ad
--- /dev/null
+++ b/libs/rs/java/Samples/res/raw/multitexf.glsl
@@ -0,0 +1,12 @@
+varying vec4 varTex0;
+
+void main() {
+   vec2 t0 = varTex0.xy;
+   lowp vec4 col0 = texture2D(UNI_Tex0, t0).rgba;
+   lowp vec4 col1 = texture2D(UNI_Tex1, t0*4.0).rgba;
+   lowp vec4 col2 = texture2D(UNI_Tex2, t0).rgba;
+   col0.xyz = col0.xyz*col1.xyz*1.5;
+   col0.xyz = mix(col0.xyz, col2.xyz, col2.w);
+   gl_FragColor = col0;
+}
+
diff --git a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
index 18b94d9..d4e83d3 100644
--- a/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
+++ b/libs/rs/java/Samples/src/com/android/samples/RsRenderStatesRS.java
@@ -42,7 +42,7 @@
         mOptionsARGB.inScaled = false;
         mOptionsARGB.inPreferredConfig = Bitmap.Config.ARGB_8888;
         mMode = 0;
-        mMaxModes = 7;
+        mMaxModes = 8;
         initRS();
     }
 
@@ -68,6 +68,7 @@
     // Custom shaders
     private ProgramVertex mProgVertexCustom;
     private ProgramFragment mProgFragmentCustom;
+    private ProgramFragment mProgFragmentMultitex;
     private ScriptField_VertexShaderConstants_s mVSConst;
     private ScriptField_FragentShaderConstants_s mFSConst;
 
@@ -214,8 +215,14 @@
         // Bind the source of constant data
         mProgFragmentCustom.bindConstants(mFSConst.getAllocation(), 0);
 
+        pfbCustom = new ProgramFragment.ShaderBuilder(mRS);
+        pfbCustom.setShader(mRes, R.raw.multitexf);
+        pfbCustom.setTextureCount(3);
+        mProgFragmentMultitex = pfbCustom.create();
+
         mScript.set_gProgVertexCustom(mProgVertexCustom);
         mScript.set_gProgFragmentCustom(mProgFragmentCustom);
+        mScript.set_gProgFragmentMultitex(mProgFragmentMultitex);
     }
 
     private Allocation loadTextureRGB(int id) {
diff --git a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
index c7bea93..659e1e4 100644
--- a/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
+++ b/libs/rs/java/Samples/src/com/android/samples/rsrenderstates.rs
@@ -60,6 +60,7 @@
 // Custom shaders we use for lighting
 rs_program_vertex gProgVertexCustom;
 rs_program_fragment gProgFragmentCustom;
+rs_program_fragment gProgFragmentMultitex;
 
 #pragma rs export_var(gProgVertex, gProgFragmentColor, gProgFragmentTexture)
 #pragma rs export_var(gProgStoreBlendNoneDepth, gProgStoreBlendNone, gProgStoreBlendAlpha, gProgStoreBlendAdd)
@@ -68,7 +69,7 @@
 #pragma rs export_var(gFontSans, gFontSerif, gFontSerifBold, gFontSerifItalic, gFontSerifBoldItalic, gFontMono)
 #pragma rs export_var(gLinearClamp, gLinearWrap, gMipLinearWrap, gNearestClamp)
 #pragma rs export_var(gCullBack, gCullFront)
-#pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom)
+#pragma rs export_var(gVSConstants, gFSConstants, gVSInputs, gProgVertexCustom, gProgFragmentCustom, gProgFragmentMultitex)
 
 //What we are showing
 #pragma rs export_var(gDisplayMode)
@@ -414,9 +415,38 @@
 
     rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
     rsgBindFont(gFontMono);
-    //rsgDrawText("Custom shader sample", 10, rsgGetHeight() - 10);
+    rsgDrawText("Custom shader sample", 10, rsgGetHeight() - 10);
 }
 
+void displayMultitextureSample() {
+    bindProgramVertexOrtho();
+    rs_matrix4x4 matrix;
+    rsMatrixLoadIdentity(&matrix);
+    rsgProgramVertexLoadModelMatrix(&matrix);
+
+    // Fragment shader with texture
+    rsgBindProgramStore(gProgStoreBlendNone);
+    rsgBindProgramFragment(gProgFragmentMultitex);
+    rsgBindSampler(gProgFragmentMultitex, 0, gLinearClamp);
+    rsgBindSampler(gProgFragmentMultitex, 1, gLinearWrap);
+    rsgBindSampler(gProgFragmentMultitex, 2, gLinearClamp);
+    rsgBindTexture(gProgFragmentMultitex, 0, gTexOpaque);
+    rsgBindTexture(gProgFragmentMultitex, 1, gTexTorus);
+    rsgBindTexture(gProgFragmentMultitex, 2, gTexTransparent);
+
+    float startX = 0, startY = 0;
+    float width = 256, height = 256;
+    rsgDrawQuadTexCoords(startX, startY, 0, 0, 0,
+                         startX, startY + height, 0, 0, 1,
+                         startX + width, startY + height, 0, 1, 1,
+                         startX + width, startY, 0, 1, 0);
+
+    rsgFontColor(1.0f, 1.0f, 1.0f, 1.0f);
+    rsgBindFont(gFontMono);
+    rsgDrawText("Custom shader with multitexturing", 10, 280);
+}
+
+
 int root(int launchID) {
 
     gDt = rsGetDt();
@@ -446,6 +476,9 @@
     case 6:
         displayCustomShaderSamples();
         break;
+    case 7:
+        displayMultitextureSample();
+        break;
     }
 
     return 10;
diff --git a/libs/rs/rs.spec b/libs/rs/rs.spec
index 21cbc50..2b7928f 100644
--- a/libs/rs/rs.spec
+++ b/libs/rs/rs.spec
@@ -434,12 +434,6 @@
 	}
 
 ProgramFragmentCreate {
-	param const uint32_t * params
-	param uint32_t paramLength
-	ret RsProgramFragment
-	}
-
-ProgramFragmentCreate2 {
 	param const char * shaderText
 	param uint32_t shaderLength
 	param const uint32_t * params
@@ -448,11 +442,6 @@
 	}
 
 ProgramVertexCreate {
-	param bool texMat
-	ret RsProgramVertex
-	}
-
-ProgramVertexCreate2 {
 	param const char * shaderText
 	param uint32_t shaderLength
 	param const uint32_t * params
diff --git a/libs/rs/rsContext.cpp b/libs/rs/rsContext.cpp
index 3dbdbfb..3681bc2 100644
--- a/libs/rs/rsContext.cpp
+++ b/libs/rs/rsContext.cpp
@@ -163,11 +163,6 @@
     uint32_t ret = runScript(mRootScript.get());
 
     checkError("runRootScript");
-    if (mError != RS_ERROR_NONE) {
-        // If we have an error condition we stop rendering until
-        // somthing changes that might fix it.
-        ret = 0;
-    }
     return ret;
 }
 
diff --git a/libs/rs/rsContext.h b/libs/rs/rsContext.h
index e38ba55..bce9c13 100644
--- a/libs/rs/rsContext.h
+++ b/libs/rs/rsContext.h
@@ -169,6 +169,9 @@
     mutable const ObjectBase * mObjHead;
 
     bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+    uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
+    uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
+    uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
 
     void launchThreads(WorkerCallback_t cbk, void *data);
     uint32_t getWorkerPoolSize() const {return (uint32_t)mWorkers.mRunningCount;}
diff --git a/libs/rs/rsContextHostStub.h b/libs/rs/rsContextHostStub.h
index c437606..f30915e 100644
--- a/libs/rs/rsContextHostStub.h
+++ b/libs/rs/rsContextHostStub.h
@@ -120,6 +120,9 @@
     mutable const ObjectBase * mObjHead;
 
     bool ext_OES_texture_npot() const {return mGL.OES_texture_npot;}
+    uint32_t getMaxFragmentTextures() const {return mGL.mMaxFragmentTextureImageUnits;}
+    uint32_t getMaxFragmentUniformVectors() const {return mGL.mMaxFragmentUniformVectors;}
+    uint32_t getMaxVertexUniformVectors() const {return mGL.mMaxVertexUniformVectors;}
 
 protected:
 
diff --git a/libs/rs/rsElement.cpp b/libs/rs/rsElement.cpp
index d0909c8..0b9e28c 100644
--- a/libs/rs/rsElement.cpp
+++ b/libs/rs/rsElement.cpp
@@ -138,37 +138,10 @@
     // We need to check if this already exists
     for (uint32_t ct=0; ct < rsc->mStateElement.mElements.size(); ct++) {
         Element *ee = rsc->mStateElement.mElements[ct];
-
-        if (!ee->getFieldCount() ) {
-
-            if((ee->getComponent().getType() == elem->getComponent().getType()) &&
-               (ee->getComponent().getKind() == elem->getComponent().getKind()) &&
-               (ee->getComponent().getIsNormalized() == elem->getComponent().getIsNormalized()) &&
-               (ee->getComponent().getVectorSize() == elem->getComponent().getVectorSize())) {
-                // Match
-                delete elem;
-                ee->incUserRef();
-                return ee;
-            }
-
-        } else if (ee->getFieldCount() == elem->mFieldCount) {
-
-            bool match = true;
-            for (uint32_t i=0; i < elem->mFieldCount; i++) {
-                if ((ee->mFields[i].e.get() != elem->mFields[i].e.get()) ||
-                    (ee->mFields[i].name.length() != elem->mFields[i].name.length()) ||
-                    (ee->mFields[i].name != elem->mFields[i].name) ||
-                    (ee->mFields[i].arraySize != elem->mFields[i].arraySize)) {
-                    match = false;
-                    break;
-                }
-            }
-            if (match) {
-                delete elem;
-                ee->incUserRef();
-                return ee;
-            }
-
+        if(ee->isEqual(elem)) {
+            delete elem;
+            ee->incUserRef();
+            return ee;
         }
     }
 
@@ -176,6 +149,32 @@
     return elem;
 }
 
+bool Element::isEqual(const Element *other) const {
+    if(other == NULL) {
+        return false;
+    }
+    if (!other->getFieldCount() && !mFieldCount) {
+        if((other->getType() == getType()) &&
+           (other->getKind() == getKind()) &&
+           (other->getComponent().getIsNormalized() == getComponent().getIsNormalized()) &&
+           (other->getComponent().getVectorSize() == getComponent().getVectorSize())) {
+            return true;
+        }
+        return false;
+    }
+    if (other->getFieldCount() == mFieldCount) {
+        for (uint32_t i=0; i < mFieldCount; i++) {
+            if ((!other->mFields[i].e->isEqual(mFields[i].e.get())) ||
+                (other->mFields[i].name.length() != mFields[i].name.length()) ||
+                (other->mFields[i].name != mFields[i].name) ||
+                (other->mFields[i].arraySize != mFields[i].arraySize)) {
+                return false;
+            }
+        }
+        return true;
+    }
+    return false;
+}
 
 const Element * Element::create(Context *rsc, RsDataType dt, RsDataKind dk,
                             bool isNorm, uint32_t vecSize)
diff --git a/libs/rs/rsElement.h b/libs/rs/rsElement.h
index ae6a6cc..50bca85 100644
--- a/libs/rs/rsElement.h
+++ b/libs/rs/rsElement.h
@@ -72,6 +72,8 @@
     void decRefs(const void *) const;
     bool getHasReferences() const {return mHasReference;}
 
+    bool isEqual(const Element *other) const;
+
 protected:
     // deallocate any components that are part of this element.
     void clear();
diff --git a/libs/rs/rsFileA3D.cpp b/libs/rs/rsFileA3D.cpp
index 5709f2a..893598f 100644
--- a/libs/rs/rsFileA3D.cpp
+++ b/libs/rs/rsFileA3D.cpp
@@ -68,13 +68,11 @@
     uint32_t flags = headerStream->loadU32();
     mUse64BitOffsets = (flags & 1) != 0;
 
-    LOGE("file open 64bit = %i", mUse64BitOffsets);
-
     uint32_t numIndexEntries = headerStream->loadU32();
     for(uint32_t i = 0; i < numIndexEntries; i ++) {
         A3DIndexEntry *entry = new A3DIndexEntry();
         headerStream->loadString(&entry->mObjectName);
-        LOGE("Header data, entry name = %s", entry->mObjectName.string());
+        LOGV("Header data, entry name = %s", entry->mObjectName.string());
         entry->mType = (RsA3DClassID)headerStream->loadU32();
         if(mUse64BitOffsets){
             entry->mOffset = headerStream->loadOffset();
@@ -91,7 +89,6 @@
 
 bool FileA3D::load(const void *data, size_t length)
 {
-    LOGE("Loading data. Size: %u", length);
     const uint8_t *localData = (const uint8_t *)data;
 
     size_t lengthRemaining = length;
@@ -114,8 +111,6 @@
     localData += sizeof(headerSize);
     lengthRemaining -= sizeof(headerSize);
 
-    LOGE("Loading data, headerSize = %lli", headerSize);
-
     if(lengthRemaining < headerSize) {
         return false;
     }
@@ -145,8 +140,6 @@
     localData += sizeof(mDataSize);
     lengthRemaining -= sizeof(mDataSize);
 
-    LOGE("Loading data, mDataSize = %lli", mDataSize);
-
     if(lengthRemaining < mDataSize) {
         return false;
     }
@@ -169,7 +162,7 @@
     char magicString[12];
     size_t len;
 
-    LOGE("file open 1");
+    LOGV("file open 1");
     len = fread(magicString, 1, 12, f);
     if ((len != 12) ||
         memcmp(magicString, "Android3D_ff", 12)) {
@@ -205,7 +198,7 @@
         return false;
     }
 
-    LOGE("file open size = %lli", mDataSize);
+    LOGV("file open size = %lli", mDataSize);
 
     // We should know enough to read the file in at this point.
     mAlloc = malloc(mDataSize);
@@ -220,7 +213,7 @@
 
     mReadStream = new IStream(mData, mUse64BitOffsets);
 
-    LOGE("Header is read an stream initialized");
+    LOGV("Header is read an stream initialized");
     return true;
 }
 
@@ -437,7 +430,7 @@
     }
 
     ObjectBase *obj = fa3d->initializeFromEntry(index);
-    LOGE("Returning object with name %s", obj->getName());
+    LOGV("Returning object with name %s", obj->getName());
 
     return obj;
 }
diff --git a/libs/rs/rsFont.cpp b/libs/rs/rsFont.cpp
index 0f815a2..bd5713e 100644
--- a/libs/rs/rsFont.cpp
+++ b/libs/rs/rsFont.cpp
@@ -372,7 +372,7 @@
     // This will dirty the texture and the shader so next time
     // we draw it will upload the data
     mTextTexture->deferedUploadToTexture(mRSC, false, 0);
-    mFontShaderF->bindTexture(0, mTextTexture.get());
+    mFontShaderF->bindTexture(mRSC, 0, mTextTexture.get());
 
     // Some debug code
     /*for(uint32_t i = 0; i < mCacheLines.size(); i ++) {
@@ -414,12 +414,12 @@
     ProgramFragment *pf = new ProgramFragment(mRSC, shaderString.string(),
                                               shaderString.length(), tmp, 4);
     mFontShaderF.set(pf);
-    mFontShaderF->bindAllocation(mFontShaderFConstant.get(), 0);
+    mFontShaderF->bindAllocation(mRSC, mFontShaderFConstant.get(), 0);
 
     Sampler *sampler = new Sampler(mRSC, RS_SAMPLER_NEAREST, RS_SAMPLER_NEAREST,
                                       RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP, RS_SAMPLER_CLAMP);
     mFontSampler.set(sampler);
-    mFontShaderF->bindSampler(0, sampler);
+    mFontShaderF->bindSampler(mRSC, 0, sampler);
 
     ProgramStore *fontStore = new ProgramStore(mRSC);
     mFontProgramStore.set(fontStore);
diff --git a/libs/rs/rsProgram.cpp b/libs/rs/rsProgram.cpp
index 2441491..9c66462 100644
--- a/libs/rs/rsProgram.cpp
+++ b/libs/rs/rsProgram.cpp
@@ -29,7 +29,6 @@
 using namespace android;
 using namespace android::renderscript;
 
-
 Program::Program(Context *rsc) : ObjectBase(rsc)
 {
     mAllocFile = __FILE__;
@@ -38,7 +37,10 @@
     mShaderID = 0;
     mAttribCount = 0;
     mUniformCount = 0;
+    mTextureCount = 0;
 
+    mTextures = NULL;
+    mSamplers = NULL;
     mInputElements = NULL;
     mOutputElements = NULL;
     mConstantTypes = NULL;
@@ -80,6 +82,8 @@
         }
     }
 
+    mTextures = new ObjectBaseRef<Allocation>[mTextureCount];
+    mSamplers = new ObjectBaseRef<Sampler>[mTextureCount];
     mInputElements = new ObjectBaseRef<Element>[mInputCount];
     mOutputElements = new ObjectBaseRef<Element>[mOutputCount];
     mConstantTypes = new ObjectBaseRef<Type>[mConstantCount];
@@ -112,9 +116,15 @@
 Program::~Program()
 {
     for (uint32_t ct=0; ct < MAX_UNIFORMS; ct++) {
-        bindAllocation(NULL, ct);
+        bindAllocation(NULL, NULL, ct);
     }
 
+    for (uint32_t ct=0; ct < mTextureCount; ct++) {
+        bindTexture(NULL, ct, NULL);
+        bindSampler(NULL, ct, NULL);
+    }
+    delete[] mTextures;
+    delete[] mSamplers;
     delete[] mInputElements;
     delete[] mOutputElements;
     delete[] mConstantTypes;
@@ -124,8 +134,22 @@
 }
 
 
-void Program::bindAllocation(Allocation *alloc, uint32_t slot)
+void Program::bindAllocation(Context *rsc, Allocation *alloc, uint32_t slot)
 {
+    if (alloc != NULL) {
+        if (slot >= mConstantCount) {
+            LOGE("Attempt to bind alloc at slot %u, on shader id %u, but const count is %u",
+                 slot, (uint32_t)this, mConstantCount);
+            rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind allocation");
+            return;
+        }
+        if (!alloc->getType()->isEqual(mConstantTypes[slot].get())) {
+            LOGE("Attempt to bind alloc at slot %u, on shader id %u, but types mismatch",
+                 slot, (uint32_t)this);
+            rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind allocation");
+            return;
+        }
+    }
     if (mConstants[slot].get() == alloc) {
         return;
     }
@@ -139,10 +163,11 @@
     mDirty = true;
 }
 
-void Program::bindTexture(uint32_t slot, Allocation *a)
+void Program::bindTexture(Context *rsc, uint32_t slot, Allocation *a)
 {
-    if (slot >= MAX_TEXTURE) {
-        LOGE("Attempt to bind a texture to a slot > MAX_TEXTURE");
+    if (slot >= mTextureCount) {
+        LOGE("Attempt to bind texture to slot %u but tex count is %u", slot, mTextureCount);
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind texture");
         return;
     }
 
@@ -151,10 +176,11 @@
     mDirty = true;
 }
 
-void Program::bindSampler(uint32_t slot, Sampler *s)
+void Program::bindSampler(Context *rsc, uint32_t slot, Sampler *s)
 {
-    if (slot >= MAX_TEXTURE) {
-        LOGE("Attempt to bind a Sampler to a slot > MAX_TEXTURE");
+    if (slot >= mTextureCount) {
+        LOGE("Attempt to bind sampler to slot %u but tex count is %u", slot, mTextureCount);
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind sampler");
         return;
     }
 
@@ -289,11 +315,13 @@
     }
 }
 
-void Program::setupUserConstants(ShaderCache *sc, bool isFragment) {
+void Program::setupUserConstants(Context *rsc, ShaderCache *sc, bool isFragment) {
     uint32_t uidx = 0;
     for (uint32_t ct=0; ct < mConstantCount; ct++) {
         Allocation *alloc = mConstants[ct].get();
         if (!alloc) {
+            LOGE("Attempting to set constants on shader id %u, but alloc at slot %u is not set", (uint32_t)this, ct);
+            rsc->setError(RS_ERROR_BAD_SHADER, "No constant allocation bound");
             continue;
         }
 
@@ -384,19 +412,19 @@
 void rsi_ProgramBindConstants(Context *rsc, RsProgram vp, uint32_t slot, RsAllocation constants)
 {
     Program *p = static_cast<Program *>(vp);
-    p->bindAllocation(static_cast<Allocation *>(constants), slot);
+    p->bindAllocation(rsc, static_cast<Allocation *>(constants), slot);
 }
 
 void rsi_ProgramBindTexture(Context *rsc, RsProgram vpf, uint32_t slot, RsAllocation a)
 {
     Program *p = static_cast<Program *>(vpf);
-    p->bindTexture(slot, static_cast<Allocation *>(a));
+    p->bindTexture(rsc, slot, static_cast<Allocation *>(a));
 }
 
 void rsi_ProgramBindSampler(Context *rsc, RsProgram vpf, uint32_t slot, RsSampler s)
 {
     Program *p = static_cast<Program *>(vpf);
-    p->bindSampler(slot, static_cast<Sampler *>(s));
+    p->bindSampler(rsc, slot, static_cast<Sampler *>(s));
 }
 
 }
diff --git a/libs/rs/rsProgram.h b/libs/rs/rsProgram.h
index e7329c2..a8f34c3 100644
--- a/libs/rs/rsProgram.h
+++ b/libs/rs/rsProgram.h
@@ -32,20 +32,19 @@
 public:
     const static uint32_t MAX_ATTRIBS = 8;
     const static uint32_t MAX_UNIFORMS = 16;
-    const static uint32_t MAX_TEXTURE = 2;
 
     Program(Context *);
     Program(Context *, const char * shaderText, uint32_t shaderLength,
                        const uint32_t * params, uint32_t paramLength);
     virtual ~Program();
 
-    void bindAllocation(Allocation *, uint32_t slot);
+    void bindAllocation(Context *, Allocation *, uint32_t slot);
     virtual void createShader();
 
     bool isUserProgram() const {return !mIsInternal;}
 
-    void bindTexture(uint32_t slot, Allocation *);
-    void bindSampler(uint32_t slot, Sampler *);
+    void bindTexture(Context *, uint32_t slot, Allocation *);
+    void bindSampler(Context *, uint32_t slot, Sampler *);
 
     uint32_t getShaderID() const {return mShaderID;}
     void setShader(const char *, uint32_t len);
@@ -75,7 +74,7 @@
 
     // Applies to vertex and fragment shaders only
     void appendUserConstants();
-    void setupUserConstants(ShaderCache *sc, bool isFragment);
+    void setupUserConstants(Context *rsc, ShaderCache *sc, bool isFragment);
     void initAddUserElement(const Element *e, String8 *names, uint32_t *count, const char *prefix);
 
     ObjectBaseRef<Allocation> mConstants[MAX_UNIFORMS];
@@ -97,8 +96,8 @@
     // and filtered.
     //
     // Constants are strictly accessed by programetic loads.
-    ObjectBaseRef<Allocation> mTextures[MAX_TEXTURE];
-    ObjectBaseRef<Sampler> mSamplers[MAX_TEXTURE];
+    ObjectBaseRef<Allocation> *mTextures;
+    ObjectBaseRef<Sampler> *mSamplers;
 
     bool loadShader(Context *, uint32_t type);
 
diff --git a/libs/rs/rsProgramFragment.cpp b/libs/rs/rsProgramFragment.cpp
index 8f5c653..d511d21 100644
--- a/libs/rs/rsProgramFragment.cpp
+++ b/libs/rs/rsProgramFragment.cpp
@@ -31,40 +31,6 @@
 using namespace android;
 using namespace android::renderscript;
 
-
-ProgramFragment::ProgramFragment(Context *rsc, const uint32_t * params,
-                                 uint32_t paramLength) :
-    Program(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-    rsAssert(paramLength == 6);
-
-    mConstantColor[0] = 1.f;
-    mConstantColor[1] = 1.f;
-    mConstantColor[2] = 1.f;
-    mConstantColor[3] = 1.f;
-
-    mEnvModes[0] = (RsTexEnvMode)params[0];
-    mTextureFormats[0] = params[1];
-    mEnvModes[1] = (RsTexEnvMode)params[2];
-    mTextureFormats[1] = params[3];
-    mPointSpriteEnable = params[4] != 0;
-    mVaryingColor = false;
-    if (paramLength > 5)
-        mVaryingColor = params[5] != 0;
-
-    mTextureEnableMask = 0;
-    if (mEnvModes[0]) {
-        mTextureEnableMask |= 1;
-    }
-    if (mEnvModes[1]) {
-        mTextureEnableMask |= 2;
-    }
-
-    init(rsc);
-}
-
 ProgramFragment::ProgramFragment(Context *rsc, const char * shaderText,
                                  uint32_t shaderLength, const uint32_t * params,
                                  uint32_t paramLength) :
@@ -78,19 +44,23 @@
     mConstantColor[2] = 1.f;
     mConstantColor[3] = 1.f;
 
-    mTextureEnableMask = (1 << mTextureCount) -1;
-
     init(rsc);
 }
 
-
 ProgramFragment::~ProgramFragment()
 {
 }
 
-void ProgramFragment::setConstantColor(float r, float g, float b, float a)
+void ProgramFragment::setConstantColor(Context *rsc, float r, float g, float b, float a)
 {
     if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation color on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot  set fixed function emulation color on user program");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation color because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Unable to set fixed function emulation color because allocation is missing");
         return;
     }
     mConstantColor[0] = r;
@@ -101,7 +71,7 @@
     mDirty = true;
 }
 
-void ProgramFragment::setupGL2(const Context *rsc, ProgramFragmentState *state, ShaderCache *sc)
+void ProgramFragment::setupGL2(Context *rsc, ProgramFragmentState *state, ShaderCache *sc)
 {
     //LOGE("sgl2 frag1 %x", glGetError());
     if ((state->mLast.get() == this) && !mDirty) {
@@ -112,11 +82,22 @@
     rsc->checkError("ProgramFragment::setupGL2 start");
 
     rsc->checkError("ProgramFragment::setupGL2 begin uniforms");
-    setupUserConstants(sc, true);
+    setupUserConstants(rsc, sc, true);
 
-    for (uint32_t ct=0; ct < MAX_TEXTURE; ct++) {
+    uint32_t numTexturesToBind = mTextureCount;
+    uint32_t numTexturesAvailable = rsc->getMaxFragmentTextures();
+    if(numTexturesToBind >= numTexturesAvailable) {
+        LOGE("Attempting to bind %u textures on shader id %u, but only %u are available",
+             mTextureCount, (uint32_t)this, numTexturesAvailable);
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot bind more textuers than available");
+        numTexturesToBind = numTexturesAvailable;
+    }
+
+    for (uint32_t ct=0; ct < numTexturesToBind; ct++) {
         glActiveTexture(GL_TEXTURE0 + ct);
-        if (!(mTextureEnableMask & (1 << ct)) || !mTextures[ct].get()) {
+        if (!mTextures[ct].get()) {
+            LOGE("No texture bound for shader id %u, texture unit %u", (uint)this, ct);
+            rsc->setError(RS_ERROR_BAD_SHADER, "No texture bound");
             continue;
         }
 
@@ -151,8 +132,8 @@
     if (mUserShader.length() > 1) {
         mShader.append("precision mediump float;\n");
         appendUserConstants();
+        char buf[256];
         for (uint32_t ct=0; ct < mTextureCount; ct++) {
-            char buf[256];
             sprintf(buf, "uniform sampler2D UNI_Tex%i;\n", ct);
             mShader.append(buf);
         }
@@ -172,8 +153,11 @@
         }
     }
     mTextureUniformIndexStart = mUniformCount;
-    mUniformNames[mUniformCount++].setTo("UNI_Tex0");
-    mUniformNames[mUniformCount++].setTo("UNI_Tex1");
+    char buf[256];
+    for (uint32_t ct=0; ct < mTextureCount; ct++) {
+        sprintf(buf, "UNI_Tex%i", ct);
+        mUniformNames[mUniformCount++].setTo(buf);
+    }
 
     createShader();
 }
@@ -228,8 +212,8 @@
     Allocation *constAlloc = new Allocation(rsc, inputType);
     ProgramFragment *pf = new ProgramFragment(rsc, shaderString.string(),
                                               shaderString.length(), tmp, 4);
-    pf->bindAllocation(constAlloc, 0);
-    pf->setConstantColor(1.0f, 1.0f, 1.0f, 1.0f);
+    pf->bindAllocation(rsc, constAlloc, 0);
+    pf->setConstantColor(rsc, 1.0f, 1.0f, 1.0f, 1.0f);
 
     mDefault.set(pf);
 }
@@ -244,23 +228,13 @@
 namespace android {
 namespace renderscript {
 
-RsProgramFragment rsi_ProgramFragmentCreate(Context *rsc,
-                                            const uint32_t * params,
-                                            uint32_t paramLength)
-{
-    ProgramFragment *pf = new ProgramFragment(rsc, params, paramLength);
-    pf->incUserRef();
-    //LOGE("rsi_ProgramFragmentCreate %p", pf);
-    return pf;
-}
-
-RsProgramFragment rsi_ProgramFragmentCreate2(Context *rsc, const char * shaderText,
+RsProgramFragment rsi_ProgramFragmentCreate(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength)
 {
     ProgramFragment *pf = new ProgramFragment(rsc, shaderText, shaderLength, params, paramLength);
     pf->incUserRef();
-    //LOGE("rsi_ProgramFragmentCreate2 %p", pf);
+    //LOGE("rsi_ProgramFragmentCreate %p", pf);
     return pf;
 }
 
diff --git a/libs/rs/rsProgramFragment.h b/libs/rs/rsProgramFragment.h
index fb78b3f..1cf9ca7 100644
--- a/libs/rs/rsProgramFragment.h
+++ b/libs/rs/rsProgramFragment.h
@@ -28,13 +28,12 @@
 class ProgramFragment : public Program
 {
 public:
-    ProgramFragment(Context *, const uint32_t * params, uint32_t paramLength);
     ProgramFragment(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength);
     virtual ~ProgramFragment();
 
-    virtual void setupGL2(const Context *, ProgramFragmentState *, ShaderCache *sc);
+    virtual void setupGL2(Context *, ProgramFragmentState *, ShaderCache *sc);
 
     virtual void createShader();
     virtual void loadShader(Context *rsc);
@@ -43,19 +42,10 @@
     virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_FRAGMENT; }
     static ProgramFragment *createFromStream(Context *rsc, IStream *stream);
 
-    void setConstantColor(float, float, float, float);
+    void setConstantColor(Context *, float, float, float, float);
 
 protected:
-    // Hacks to create a program for now
-    uint32_t mTextureFormats[MAX_TEXTURE];
-    uint32_t mTextureDimensions[MAX_TEXTURE];
-    RsTexEnvMode mEnvModes[MAX_TEXTURE];
-    uint32_t mTextureEnableMask;
-    bool mPointSpriteEnable;
-    bool mVaryingColor;
-
     float mConstantColor[4];
-    int32_t mConstantColorUniformIndex;
     int32_t mTextureUniformIndexStart;
 };
 
@@ -69,7 +59,6 @@
     void init(Context *rsc);
     void deinit(Context *rsc);
 
-    ObjectBaseRef<Type> mTextureTypes[ProgramFragment::MAX_TEXTURE];
     ObjectBaseRef<ProgramFragment> mDefault;
     Vector<ProgramFragment *> mPrograms;
 
diff --git a/libs/rs/rsProgramVertex.cpp b/libs/rs/rsProgramVertex.cpp
index 6446b55..c3ef356 100644
--- a/libs/rs/rsProgramVertex.cpp
+++ b/libs/rs/rsProgramVertex.cpp
@@ -32,16 +32,6 @@
 using namespace android::renderscript;
 
 
-ProgramVertex::ProgramVertex(Context *rsc, bool texMat) :
-    Program(rsc)
-{
-    mAllocFile = __FILE__;
-    mAllocLine = __LINE__;
-    mTextureMatrixEnable = texMat;
-    mLightCount = 0;
-    init(rsc);
-}
-
 ProgramVertex::ProgramVertex(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength) :
@@ -49,8 +39,6 @@
 {
     mAllocFile = __FILE__;
     mAllocLine = __LINE__;
-    mTextureMatrixEnable = false;
-    mLightCount = 0;
 
     init(rsc);
 }
@@ -110,7 +98,7 @@
     }
 }
 
-void ProgramVertex::setupGL2(const Context *rsc, ProgramVertexState *state, ShaderCache *sc)
+void ProgramVertex::setupGL2(Context *rsc, ProgramVertexState *state, ShaderCache *sc)
 {
     //LOGE("sgl2 vtx1 %x", glGetError());
     if ((state->mLast.get() == this) && !mDirty) {
@@ -132,23 +120,21 @@
     }
 
     rsc->checkError("ProgramVertex::setupGL2 begin uniforms");
-    setupUserConstants(sc, false);
+    setupUserConstants(rsc, sc, false);
 
     state->mLast.set(this);
     rsc->checkError("ProgramVertex::setupGL2");
 }
 
-void ProgramVertex::addLight(const Light *l)
-{
-    if (mLightCount < MAX_LIGHTS) {
-        mLights[mLightCount].set(l);
-        mLightCount++;
-    }
-}
-
-void ProgramVertex::setProjectionMatrix(const rsc_Matrix *m) const
+void ProgramVertex::setProjectionMatrix(Context *rsc, const rsc_Matrix *m) const
 {
     if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation matrix projection on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation matrix projection because allocation is missing");
         return;
     }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
@@ -156,9 +142,16 @@
     mDirty = true;
 }
 
-void ProgramVertex::setModelviewMatrix(const rsc_Matrix *m) const
+void ProgramVertex::setModelviewMatrix(Context *rsc, const rsc_Matrix *m) const
 {
     if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation matrix modelview on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation matrix modelview because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
         return;
     }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
@@ -166,9 +159,16 @@
     mDirty = true;
 }
 
-void ProgramVertex::setTextureMatrix(const rsc_Matrix *m) const
+void ProgramVertex::setTextureMatrix(Context *rsc, const rsc_Matrix *m) const
 {
     if(isUserProgram()) {
+        LOGE("Attempting to set fixed function emulation matrix texture on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot set emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to set fixed function emulation matrix texture because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
         return;
     }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
@@ -176,16 +176,23 @@
     mDirty = true;
 }
 
-void ProgramVertex::getProjectionMatrix(rsc_Matrix *m) const
+void ProgramVertex::getProjectionMatrix(Context *rsc, rsc_Matrix *m) const
 {
     if(isUserProgram()) {
+        LOGE("Attempting to get fixed function emulation matrix projection on user program");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Cannot get emulation matrix on user shader");
+        return;
+    }
+    if(mConstants[0].get() == NULL) {
+        LOGE("Unable to get fixed function emulation matrix projection because allocation is missing");
+        rsc->setError(RS_ERROR_BAD_SHADER, "Fixed function allocation missing");
         return;
     }
     float *f = static_cast<float *>(mConstants[0]->getPtr());
     memcpy(m, &f[RS_PROGRAM_VERTEX_PROJECTION_OFFSET], sizeof(rsc_Matrix));
 }
 
-void ProgramVertex::transformToScreen(const Context *rsc, float *v4out, const float *v3in) const
+void ProgramVertex::transformToScreen(Context *rsc, float *v4out, const float *v3in) const
 {
     if(isUserProgram()) {
         return;
@@ -280,11 +287,10 @@
     ProgramVertex *pv = new ProgramVertex(rsc, shaderString.string(),
                                           shaderString.length(), tmp, 6);
     Allocation *alloc = new Allocation(rsc, inputType);
-    pv->bindAllocation(alloc, 0);
+    pv->bindAllocation(rsc, alloc, 0);
 
     mDefaultAlloc.set(alloc);
     mDefault.set(pv);
-    pv->bindAllocation(alloc, 0);
 
     updateSize(rsc);
 
@@ -315,15 +321,7 @@
 namespace android {
 namespace renderscript {
 
-
-RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, bool texMat)
-{
-    ProgramVertex *pv = new ProgramVertex(rsc, texMat);
-    pv->incUserRef();
-    return pv;
-}
-
-RsProgramVertex rsi_ProgramVertexCreate2(Context *rsc, const char * shaderText,
+RsProgramVertex rsi_ProgramVertexCreate(Context *rsc, const char * shaderText,
                              uint32_t shaderLength, const uint32_t * params,
                              uint32_t paramLength)
 {
diff --git a/libs/rs/rsProgramVertex.h b/libs/rs/rsProgramVertex.h
index 65ce541..355df2b 100644
--- a/libs/rs/rsProgramVertex.h
+++ b/libs/rs/rsProgramVertex.h
@@ -28,25 +28,18 @@
 class ProgramVertex : public Program
 {
 public:
-    const static uint32_t MAX_LIGHTS = 8;
-
     ProgramVertex(Context *,const char * shaderText, uint32_t shaderLength,
                   const uint32_t * params, uint32_t paramLength);
-    ProgramVertex(Context *, bool texMat);
     virtual ~ProgramVertex();
 
-    virtual void setupGL2(const Context *rsc, ProgramVertexState *state, ShaderCache *sc);
+    virtual void setupGL2(Context *rsc, ProgramVertexState *state, ShaderCache *sc);
 
+    void setProjectionMatrix(Context *, const rsc_Matrix *) const;
+    void getProjectionMatrix(Context *, rsc_Matrix *) const;
+    void setModelviewMatrix(Context *, const rsc_Matrix *) const;
+    void setTextureMatrix(Context *, const rsc_Matrix *) const;
 
-    void setTextureMatrixEnable(bool e) {mTextureMatrixEnable = e;}
-    void addLight(const Light *);
-
-    void setProjectionMatrix(const rsc_Matrix *) const;
-    void getProjectionMatrix(rsc_Matrix *) const;
-    void setModelviewMatrix(const rsc_Matrix *) const;
-    void setTextureMatrix(const rsc_Matrix *) const;
-
-    void transformToScreen(const Context *, float *v4out, const float *v3in) const;
+    void transformToScreen(Context *, float *v4out, const float *v3in) const;
 
     virtual void createShader();
     virtual void loadShader(Context *);
@@ -55,13 +48,6 @@
     virtual void serialize(OStream *stream) const;
     virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_PROGRAM_VERTEX; }
     static ProgramVertex *createFromStream(Context *rsc, IStream *stream);
-
-protected:
-    uint32_t mLightCount;
-    ObjectBaseRef<const Light> mLights[MAX_LIGHTS];
-
-    // Hacks to create a program for now
-    bool mTextureMatrixEnable;
 };
 
 
diff --git a/libs/rs/rsScriptC_LibGL.cpp b/libs/rs/rsScriptC_LibGL.cpp
index 2f8f79a..4be5059 100644
--- a/libs/rs/rsScriptC_LibGL.cpp
+++ b/libs/rs/rsScriptC_LibGL.cpp
@@ -93,33 +93,33 @@
 static void SC_vpLoadProjectionMatrix(const rsc_Matrix *m)
 {
     GET_TLS();
-    rsc->getVertex()->setProjectionMatrix(m);
+    rsc->getVertex()->setProjectionMatrix(rsc, m);
 }
 
 static void SC_vpLoadModelMatrix(const rsc_Matrix *m)
 {
     GET_TLS();
-    rsc->getVertex()->setModelviewMatrix(m);
+    rsc->getVertex()->setModelviewMatrix(rsc, m);
 }
 
 static void SC_vpLoadTextureMatrix(const rsc_Matrix *m)
 {
     GET_TLS();
-    rsc->getVertex()->setTextureMatrix(m);
+    rsc->getVertex()->setTextureMatrix(rsc, m);
 }
 
 
 static void SC_pfConstantColor(RsProgramFragment vpf, float r, float g, float b, float a)
 {
-    //GET_TLS();
+    GET_TLS();
     ProgramFragment *pf = static_cast<ProgramFragment *>(vpf);
-    pf->setConstantColor(r, g, b, a);
+    pf->setConstantColor(rsc, r, g, b, a);
 }
 
 static void SC_vpGetProjectionMatrix(rsc_Matrix *m)
 {
     GET_TLS();
-    rsc->getVertex()->getProjectionMatrix(m);
+    rsc->getVertex()->getProjectionMatrix(rsc, m);
 }
 
 
@@ -280,7 +280,7 @@
 {
     GET_TLS();
     ProgramFragment *pf = (ProgramFragment *)rsc->getFragment();
-    pf->setConstantColor(r, g, b, a);
+    pf->setConstantColor(rsc, r, g, b, a);
 }
 
 static void SC_uploadToTexture2(RsAllocation va, uint32_t baseMipLevel)
diff --git a/libs/rs/rsType.cpp b/libs/rs/rsType.cpp
index 79cfd41..478cc95 100644
--- a/libs/rs/rsType.cpp
+++ b/libs/rs/rsType.cpp
@@ -254,6 +254,20 @@
     return false;
 }
 
+bool Type::isEqual(const Type *other) const {
+    if(other == NULL) {
+        return false;
+    }
+    if (other->getElement()->isEqual(getElement()) &&
+        other->getDimX() == mDimX &&
+        other->getDimY() == mDimY &&
+        other->getDimZ() == mDimZ &&
+        other->getDimLOD() == mDimLOD &&
+        other->getDimFaces() == mFaces) {
+        return true;
+    }
+    return false;
+}
 
 //////////////////////////////////////////////////
 //
diff --git a/libs/rs/rsType.h b/libs/rs/rsType.h
index 5b51e20..33faa87 100644
--- a/libs/rs/rsType.h
+++ b/libs/rs/rsType.h
@@ -77,6 +77,8 @@
     virtual RsA3DClassID getClassId() const { return RS_A3D_CLASS_ID_TYPE; }
     static Type *createFromStream(Context *rsc, IStream *stream);
 
+    bool isEqual(const Type *other) const;
+
 protected:
     struct LOD {
         size_t mX;
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index f72eaa4..bc5f9fa 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -644,9 +644,8 @@
             } else if (MediaFile.isAudioFileType(mFileType)) {
                 map.put(Audio.Media.ARTIST, (mArtist != null && mArtist.length() > 0) ?
                         mArtist : MediaStore.UNKNOWN_STRING);
-// disable album artist support until MediaProvider really supports it
-//                map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
-//                        mAlbumArtist.length() > 0) ? mAlbumArtist : null);
+                map.put(Audio.Media.ALBUM_ARTIST, (mAlbumArtist != null &&
+                        mAlbumArtist.length() > 0) ? mAlbumArtist : null);
                 map.put(Audio.Media.ALBUM, (mAlbum != null && mAlbum.length() > 0) ?
                         mAlbum : MediaStore.UNKNOWN_STRING);
                 map.put(Audio.Media.COMPOSER, mComposer);
diff --git a/media/java/android/media/MtpDatabase.java b/media/java/android/media/MtpDatabase.java
index b64299a..403ed58 100644
--- a/media/java/android/media/MtpDatabase.java
+++ b/media/java/android/media/MtpDatabase.java
@@ -246,8 +246,30 @@
         return new int[] {
             // allow transfering arbitrary files
             MtpConstants.FORMAT_UNDEFINED,
+
             MtpConstants.FORMAT_ASSOCIATION,
+            MtpConstants.FORMAT_TEXT,
+            MtpConstants.FORMAT_HTML,
+            MtpConstants.FORMAT_WAV,
+            MtpConstants.FORMAT_MP3,
+            MtpConstants.FORMAT_MPEG,
+            MtpConstants.FORMAT_EXIF_JPEG,
+            MtpConstants.FORMAT_TIFF_EP,
+            MtpConstants.FORMAT_GIF,
+            MtpConstants.FORMAT_JFIF,
+            MtpConstants.FORMAT_PNG,
+            MtpConstants.FORMAT_TIFF,
+            MtpConstants.FORMAT_WMA,
+            MtpConstants.FORMAT_OGG,
+            MtpConstants.FORMAT_AAC,
+            MtpConstants.FORMAT_MP4_CONTAINER,
+            MtpConstants.FORMAT_MP2,
+            MtpConstants.FORMAT_3GP_CONTAINER,
             MtpConstants.FORMAT_ABSTRACT_AV_PLAYLIST,
+            MtpConstants.FORMAT_WPL_PLAYLIST,
+            MtpConstants.FORMAT_M3U_PLAYLIST,
+            MtpConstants.FORMAT_PLS_PLAYLIST,
+            MtpConstants.FORMAT_XML_DOCUMENT,
         };
     }
 
@@ -260,9 +282,13 @@
         return new int[] {
             MtpConstants.PROPERTY_STORAGE_ID,
             MtpConstants.PROPERTY_OBJECT_FORMAT,
+            MtpConstants.PROPERTY_PROTECTION_STATUS,
             MtpConstants.PROPERTY_OBJECT_SIZE,
             MtpConstants.PROPERTY_OBJECT_FILE_NAME,
+            MtpConstants.PROPERTY_DATE_MODIFIED,
             MtpConstants.PROPERTY_PARENT_OBJECT,
+            MtpConstants.PROPERTY_PERSISTENT_UID,
+            MtpConstants.PROPERTY_NAME,
         };
     }
 
@@ -279,6 +305,11 @@
         String column = null;
         boolean isString = false;
 
+        // temporary hack
+        if (property == MtpConstants.PROPERTY_NAME) {
+            property = MtpConstants.PROPERTY_OBJECT_FILE_NAME;
+        }
+
         switch (property) {
             case MtpConstants.PROPERTY_STORAGE_ID:
                 outIntValue[0] = mStorageID;
diff --git a/media/jni/android_media_MtpDatabase.cpp b/media/jni/android_media_MtpDatabase.cpp
index 1842cb2..227ee92 100644
--- a/media/jni/android_media_MtpDatabase.cpp
+++ b/media/jni/android_media_MtpDatabase.cpp
@@ -672,12 +672,15 @@
 };
 
 static const PropertyTableEntry   kObjectPropertyTable[] = {
-    {   MTP_PROPERTY_PARENT_OBJECT,     MTP_TYPE_UINT32 },
-    {   MTP_PROPERTY_STORAGE_ID,        MTP_TYPE_UINT32 },
-    {   MTP_PROPERTY_OBJECT_FORMAT,     MTP_TYPE_UINT16 },
-    {   MTP_PROPERTY_OBJECT_FILE_NAME,  MTP_TYPE_STR    },
-    {   MTP_PROPERTY_OBJECT_SIZE,       MTP_TYPE_UINT64 },
-    {   MTP_PROPERTY_DATE_MODIFIED,     MTP_TYPE_STR    },
+    {   MTP_PROPERTY_STORAGE_ID,        MTP_TYPE_UINT32     },
+    {   MTP_PROPERTY_OBJECT_FORMAT,     MTP_TYPE_UINT16     },
+    {   MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16     },
+    {   MTP_PROPERTY_OBJECT_SIZE,       MTP_TYPE_UINT64     },
+    {   MTP_PROPERTY_OBJECT_FILE_NAME,  MTP_TYPE_STR        },
+    {   MTP_PROPERTY_DATE_MODIFIED,     MTP_TYPE_STR        },
+    {   MTP_PROPERTY_PARENT_OBJECT,     MTP_TYPE_UINT32     },
+    {   MTP_PROPERTY_PERSISTENT_UID,    MTP_TYPE_UINT128    },
+    {   MTP_PROPERTY_NAME,              MTP_TYPE_STR        },
 };
 
 static const PropertyTableEntry   kDevicePropertyTable[] = {
@@ -764,6 +767,7 @@
         case MTP_PROPERTY_PERSISTENT_UID:
             result = new MtpProperty(property, MTP_TYPE_UINT128);
             break;
+        case MTP_PROPERTY_NAME:
         case MTP_PROPERTY_OBJECT_FILE_NAME:
         case MTP_PROPERTY_DATE_MODIFIED:
             result = new MtpProperty(property, MTP_TYPE_STR);
@@ -780,6 +784,7 @@
         case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME:
             // writeable string properties
             result = new MtpProperty(property, MTP_TYPE_STR, true);
+            // FIXME - set current value here!
             break;
     }
 
diff --git a/media/libstagefright/OMXCodec.cpp b/media/libstagefright/OMXCodec.cpp
index 7a29bd2..4d69dd3 100644
--- a/media/libstagefright/OMXCodec.cpp
+++ b/media/libstagefright/OMXCodec.cpp
@@ -205,12 +205,14 @@
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.7x30.video.encoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.qcom.video.encoder.mpeg4" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.TI.Video.encoder" },
+    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.Nvidia.mp4.encoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.SEC.MPEG4.Encoder" },
     { MEDIA_MIMETYPE_VIDEO_MPEG4, "M4vH263Encoder" },
 //    { MEDIA_MIMETYPE_VIDEO_MPEG4, "OMX.PV.mpeg4enc" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.7x30.video.encoder.h263" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.qcom.video.encoder.h263" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.TI.Video.encoder" },
+    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.Nvidia.h263.encoder" },
     { MEDIA_MIMETYPE_VIDEO_H263, "OMX.SEC.H263.Encoder" },
     { MEDIA_MIMETYPE_VIDEO_H263, "M4vH263Encoder" },
 //    { MEDIA_MIMETYPE_VIDEO_H263, "OMX.PV.h263enc" },
diff --git a/media/mtp/MtpCursor.cpp b/media/mtp/MtpCursor.cpp
index 8c964b4..865a294 100644
--- a/media/mtp/MtpCursor.cpp
+++ b/media/mtp/MtpCursor.cpp
@@ -66,7 +66,8 @@
 #define OBJECT_THUMB                221
 
 MtpCursor::MtpCursor(MtpClient* client, int queryType, int deviceID,
-                int storageID, int objectID, int columnCount, int* columns)
+                MtpStorageID storageID, MtpObjectHandle objectID,
+                int columnCount, int* columns)
         :   mClient(client),
             mQueryType(queryType),
             mDeviceID(deviceID),
@@ -427,7 +428,8 @@
     return true;
 }
 
-bool MtpCursor::putThumbnail(CursorWindow* window, int objectID, int format, int row, int column) {
+bool MtpCursor::putThumbnail(CursorWindow* window, MtpObjectHandle objectID,
+                            MtpObjectFormat format, int row, int column) {
     MtpDevice* device = mClient->getDevice(mDeviceID);
     void* thumbnail;
     int size, offset;
diff --git a/media/mtp/MtpCursor.h b/media/mtp/MtpCursor.h
index 3f84753..9e9833f 100644
--- a/media/mtp/MtpCursor.h
+++ b/media/mtp/MtpCursor.h
@@ -36,17 +36,18 @@
         OBJECT_CHILDREN     = 8,
     };
 
-    MtpClient*  mClient;
-    int         mQueryType;
-    int         mDeviceID;
-    int         mStorageID;
-    int         mQbjectID;
-    int         mColumnCount;
-    int*        mColumns;
+    MtpClient*      mClient;
+    int             mQueryType;
+    int             mDeviceID;
+    MtpStorageID    mStorageID;
+    MtpObjectHandle mQbjectID;
+    int             mColumnCount;
+    int*            mColumns;
 
 public:
                 MtpCursor(MtpClient* client, int queryType, int deviceID,
-                        int storageID, int objectID, int columnCount, int* columns);
+                        MtpStorageID storageID, MtpObjectHandle objectID,
+                        int columnCount, int* columns);
     virtual     ~MtpCursor();
 
     int         fillWindow(CursorWindow* window, int startPos);
@@ -68,7 +69,8 @@
     bool        prepareRow(CursorWindow* window);
     bool        putLong(CursorWindow* window, int value, int row, int column);
     bool        putString(CursorWindow* window, const char* text, int row, int column);
-    bool        putThumbnail(CursorWindow* window, int objectID, int format, int row, int column);
+    bool        putThumbnail(CursorWindow* window, MtpObjectHandle objectID,
+                            MtpObjectFormat format, int row, int column);
 };
 
 }; // namespace android
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 6332b4e..84a3e2c 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -26,6 +26,8 @@
 
 #include <cutils/properties.h>
 
+#define LOG_TAG "MtpServer"
+
 #include "MtpDebug.h"
 #include "MtpDatabase.h"
 #include "MtpProperty.h"
@@ -68,8 +70,8 @@
 //    MTP_OPERATION_INITIATE_OPEN_CAPTURE,
     MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
     MTP_OPERATION_GET_OBJECT_PROP_DESC,
-//    MTP_OPERATION_GET_OBJECT_PROP_VALUE,
-//    MTP_OPERATION_SET_OBJECT_PROP_VALUE,
+    MTP_OPERATION_GET_OBJECT_PROP_VALUE,
+    MTP_OPERATION_SET_OBJECT_PROP_VALUE,
     MTP_OPERATION_GET_OBJECT_REFERENCES,
     MTP_OPERATION_SET_OBJECT_REFERENCES,
 //    MTP_OPERATION_SKIP,
@@ -294,6 +296,7 @@
             response = doGetDevicePropDesc();
             break;
         default:
+            LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
             response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
             break;
     }
diff --git a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
index 5899bc1..9f2f98e 100644
--- a/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
+++ b/media/tests/CameraBrowser/src/com/android/camerabrowser/ObjectViewer.java
@@ -200,9 +200,8 @@
                 }
 
                 // temporary workaround until we straighten out permissions in /data/media
-                // 1015 is AID_SDCARD_RW
-                FileUtils.setPermissions(destDir.getPath(), 0775, Process.myUid(), 1015);
-                FileUtils.setPermissions(destFile.getPath(), 0664, Process.myUid(), 1015);
+                FileUtils.setPermissions(destDir.getPath(), 0775, Process.myUid(), Process.SDCARD_RW_GID);
+                FileUtils.setPermissions(destFile.getPath(), 0664, Process.myUid(), Process.SDCARD_RW_GID);
 
                 success = true;
             }
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index d99fc1e..bc944a0 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -444,19 +444,14 @@
 
 // ----------------------------------------------------------------------------
 
-static void gl_no_context() {
+static int gl_no_context() {
     tls_t* tls = getTLS();
     if (tls->logCallWithNoContext == EGL_TRUE) {
         tls->logCallWithNoContext = EGL_FALSE;
         LOGE("call to OpenGL ES API with no current context "
              "(logged once per thread)");
     }
-}
-
-// Always return GL_INVALID_OPERATION from glGetError() when called from
-// a thread without a bound context.
-static GLenum gl_no_context_glGetError() {
-    return GL_INVALID_OPERATION;
+    return 0;
 }
 
 static void early_egl_init(void) 
@@ -470,8 +465,6 @@
             addr, 
             sizeof(gHooksNoContext));
 
-    gHooksNoContext.gl.glGetError = gl_no_context_glGetError;
-
     setGlThreadSpecific(&gHooksNoContext);
 }
 
diff --git a/opengl/libs/GLES2/gl2.cpp b/opengl/libs/GLES2/gl2.cpp
index a12edf2..fee4609 100644
--- a/opengl/libs/GLES2/gl2.cpp
+++ b/opengl/libs/GLES2/gl2.cpp
@@ -60,6 +60,7 @@
             "ldr   r12, [r12, %[tls]] \n"                       \
             "cmp   r12, #0            \n"                       \
             "ldrne pc,  [r12, %[api]] \n"                       \
+            "mov   r0, #0             \n"                       \
             "bx    lr                 \n"                       \
             :                                                   \
             : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
diff --git a/opengl/libs/GLES_CM/gl.cpp b/opengl/libs/GLES_CM/gl.cpp
index d71ff76..ee29f12 100644
--- a/opengl/libs/GLES_CM/gl.cpp
+++ b/opengl/libs/GLES_CM/gl.cpp
@@ -114,6 +114,7 @@
             "ldr   r12, [r12, %[tls]] \n"                       \
             "cmp   r12, #0            \n"                       \
             "ldrne pc,  [r12, %[api]] \n"                       \
+            "mov   r0, #0             \n"                       \
             "bx    lr                 \n"                       \
             :                                                   \
             : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index d98bd7d..0ca0572 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -22,6 +22,7 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.Canvas;
 import android.util.Slog;
+import android.util.Log;
 import android.view.ViewDebug;
 import android.widget.FrameLayout;
 
@@ -124,4 +125,10 @@
     public StatusBarIcon getStatusBarIcon() {
         return mIcon;
     }
+
+    protected void debug(int depth) {
+        super.debug(depth);
+        Log.d("View", debugIndent(depth) + "slot=" + mSlot);
+        Log.d("View", debugIndent(depth) + "icon=" + mIcon);
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/LockScreen.java b/policy/src/com/android/internal/policy/impl/LockScreen.java
index f8c0aba..3583ab9 100644
--- a/policy/src/com/android/internal/policy/impl/LockScreen.java
+++ b/policy/src/com/android/internal/policy/impl/LockScreen.java
@@ -20,6 +20,8 @@
 import com.android.internal.telephony.IccCard;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.internal.widget.SlidingTab;
+import com.android.internal.widget.WaveView;
+import com.android.internal.widget.WaveView.OnTriggerListener;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -46,8 +48,9 @@
  * information about the device depending on its state, and how to get
  * past it, as applicable.
  */
-class LockScreen extends LinearLayout implements KeyguardScreen, KeyguardUpdateMonitor.InfoCallback,
-        KeyguardUpdateMonitor.SimStateCallback, SlidingTab.OnTriggerListener {
+class LockScreen extends LinearLayout implements KeyguardScreen,
+        KeyguardUpdateMonitor.InfoCallback,
+        KeyguardUpdateMonitor.SimStateCallback {
 
     private static final boolean DBG = false;
     private static final String TAG = "LockScreen";
@@ -59,7 +62,7 @@
     private final KeyguardUpdateMonitor mUpdateMonitor;
     private final KeyguardScreenCallback mCallback;
 
-    private SlidingTab mSelector;
+    private SlidingTab mSlidingTab;
     private TextView mScreenLocked;
     private TextView mEmergencyCallText;
     private Button mEmergencyCallButton;
@@ -89,6 +92,9 @@
     private boolean mEnableMenuKeyInLockScreen;
 
     private StatusView mStatusView;
+    private WaveView mEnergyWave;
+    private SlidingTabMethods mSlidingTabMethods;
+    private WaveViewMethods mWaveViewMethods;
 
     /**
      * The status of this lock screen.
@@ -141,6 +147,91 @@
         }
     }
 
+    class SlidingTabMethods implements SlidingTab.OnTriggerListener {
+
+        private void updateRightTabResources() {
+            boolean vibe = mSilentMode
+                && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);
+
+            mSlidingTab.setRightTabResources(
+                    mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on
+                                         : R.drawable.ic_jog_dial_sound_off )
+                                : R.drawable.ic_jog_dial_sound_on,
+                    mSilentMode ? R.drawable.jog_tab_target_yellow
+                                : R.drawable.jog_tab_target_gray,
+                    mSilentMode ? R.drawable.jog_tab_bar_right_sound_on
+                                : R.drawable.jog_tab_bar_right_sound_off,
+                    mSilentMode ? R.drawable.jog_tab_right_sound_on
+                                : R.drawable.jog_tab_right_sound_off);
+        }
+
+        /** {@inheritDoc} */
+        public void onTrigger(View v, int whichHandle) {
+            if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) {
+                mCallback.goToUnlockScreen();
+            } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
+                // toggle silent mode
+                mSilentMode = !mSilentMode;
+                if (mSilentMode) {
+                    final boolean vibe = (Settings.System.getInt(
+                        getContext().getContentResolver(),
+                        Settings.System.VIBRATE_IN_SILENT, 1) == 1);
+
+                    mAudioManager.setRingerMode(vibe
+                        ? AudioManager.RINGER_MODE_VIBRATE
+                        : AudioManager.RINGER_MODE_SILENT);
+                } else {
+                    mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
+                }
+
+                updateRightTabResources();
+
+                String message = mSilentMode ?
+                        getContext().getString(R.string.global_action_silent_mode_on_status) :
+                        getContext().getString(R.string.global_action_silent_mode_off_status);
+
+                final int toastIcon = mSilentMode
+                    ? R.drawable.ic_lock_ringer_off
+                    : R.drawable.ic_lock_ringer_on;
+
+                final int toastColor = mSilentMode
+                    ? getContext().getResources().getColor(R.color.keyguard_text_color_soundoff)
+                    : getContext().getResources().getColor(R.color.keyguard_text_color_soundon);
+                toastMessage(mScreenLocked, message, toastColor, toastIcon);
+                mCallback.pokeWakelock();
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void onGrabbedStateChange(View v, int grabbedState) {
+            if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
+                mSilentMode = isSilentMode();
+                mSlidingTab.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label
+                        : R.string.lockscreen_sound_off_label);
+            }
+            mCallback.pokeWakelock();
+        }
+    }
+
+    class WaveViewMethods implements WaveView.OnTriggerListener {
+        /** {@inheritDoc} */
+        public void onTrigger(View v, int whichHandle) {
+            if (whichHandle == WaveView.OnTriggerListener.CENTER_HANDLE) {
+                // Delay hiding lock screen long enough for animation to finish
+                postDelayed(new Runnable() {
+                    public void run() {
+                        mCallback.goToUnlockScreen();
+                    }
+                }, 500);
+            }
+        }
+
+        /** {@inheritDoc} */
+        public void onGrabbedStateChange(View v, int grabbedState) {
+            mCallback.pokeWakelock();
+        }
+    }
+
     /**
      * In general, we enable unlocking the insecure key guard with the menu key. However, there are
      * some cases where we wish to disable it, notably when the menu button placement or technology
@@ -195,9 +286,6 @@
         mStatusView = new StatusView(this, mUpdateMonitor, mLockPatternUtils);
 
         mScreenLocked = (TextView) findViewById(R.id.screenLocked);
-        mSelector = (SlidingTab) findViewById(R.id.tab_selector);
-        mSelector.setHoldAfterTrigger(true, false);
-        mSelector.setLeftHintText(R.string.lockscreen_unlock_label);
 
         mEmergencyCallText = (TextView) findViewById(R.id.emergencyCallText);
         mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton);
@@ -220,15 +308,25 @@
         mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
         mSilentMode = isSilentMode();
 
-        mSelector.setLeftTabResources(
-                R.drawable.ic_jog_dial_unlock,
-                R.drawable.jog_tab_target_green,
-                R.drawable.jog_tab_bar_left_unlock,
-                R.drawable.jog_tab_left_unlock);
-
-        updateRightTabResources();
-
-        mSelector.setOnTriggerListener(this);
+        mSlidingTab = (SlidingTab) findViewById(R.id.tab_selector);
+        mEnergyWave = (WaveView) findViewById(R.id.wave_view);
+        if (mSlidingTab != null) {
+            mSlidingTab.setHoldAfterTrigger(true, false);
+            mSlidingTab.setLeftHintText(R.string.lockscreen_unlock_label);
+            mSlidingTab.setLeftTabResources(
+                    R.drawable.ic_jog_dial_unlock,
+                    R.drawable.jog_tab_target_green,
+                    R.drawable.jog_tab_bar_left_unlock,
+                    R.drawable.jog_tab_left_unlock);
+            mSlidingTabMethods = new SlidingTabMethods();
+            mSlidingTab.setOnTriggerListener(mSlidingTabMethods);
+            mSlidingTabMethods.updateRightTabResources();
+        } else if (mEnergyWave != null) {
+            mWaveViewMethods = new WaveViewMethods();
+            mEnergyWave.setOnTriggerListener(mWaveViewMethods);
+        } else {
+            throw new IllegalStateException("Must have either SlidingTab or WaveView defined");
+        }
 
         resetStatusInfo(updateMonitor);
     }
@@ -237,22 +335,6 @@
         return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL;
     }
 
-    private void updateRightTabResources() {
-        boolean vibe = mSilentMode
-            && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE);
-
-        mSelector.setRightTabResources(
-                mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on
-                                     : R.drawable.ic_jog_dial_sound_off )
-                            : R.drawable.ic_jog_dial_sound_on,
-                mSilentMode ? R.drawable.jog_tab_target_yellow
-                            : R.drawable.jog_tab_target_gray,
-                mSilentMode ? R.drawable.jog_tab_bar_right_sound_on
-                            : R.drawable.jog_tab_bar_right_sound_off,
-                mSilentMode ? R.drawable.jog_tab_right_sound_on
-                            : R.drawable.jog_tab_right_sound_off);
-    }
-
     private void resetStatusInfo(KeyguardUpdateMonitor updateMonitor) {
         mShowingBatteryInfo = updateMonitor.shouldShowBatteryInfo();
         mPluggedIn = updateMonitor.isDevicePluggedIn();
@@ -278,53 +360,6 @@
         return false;
     }
 
-    /** {@inheritDoc} */
-    public void onTrigger(View v, int whichHandle) {
-        if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) {
-            mCallback.goToUnlockScreen();
-        } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
-            // toggle silent mode
-            mSilentMode = !mSilentMode;
-            if (mSilentMode) {
-                final boolean vibe = (Settings.System.getInt(
-                    getContext().getContentResolver(),
-                    Settings.System.VIBRATE_IN_SILENT, 1) == 1);
-
-                mAudioManager.setRingerMode(vibe
-                    ? AudioManager.RINGER_MODE_VIBRATE
-                    : AudioManager.RINGER_MODE_SILENT);
-            } else {
-                mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
-            }
-
-            updateRightTabResources();
-
-            String message = mSilentMode ?
-                    getContext().getString(R.string.global_action_silent_mode_on_status) :
-                    getContext().getString(R.string.global_action_silent_mode_off_status);
-
-            final int toastIcon = mSilentMode
-                ? R.drawable.ic_lock_ringer_off
-                : R.drawable.ic_lock_ringer_on;
-
-            final int toastColor = mSilentMode
-                ? getContext().getResources().getColor(R.color.keyguard_text_color_soundoff)
-                : getContext().getResources().getColor(R.color.keyguard_text_color_soundon);
-            toastMessage(mScreenLocked, message, toastColor, toastIcon);
-            mCallback.pokeWakelock();
-        }
-    }
-
-    /** {@inheritDoc} */
-    public void onGrabbedStateChange(View v, int grabbedState) {
-        if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) {
-            mSilentMode = isSilentMode();
-            mSelector.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label
-                    : R.string.lockscreen_sound_off_label);
-        }
-        mCallback.pokeWakelock();
-    }
-
     /**
      * Displays a message in a text view and then restores the previous text.
      * @param textView The text view.
@@ -460,6 +495,22 @@
     }
 
     /**
+     * Enables unlocking of this screen. Typically just shows the unlock widget.
+     */
+    private void enableUnlock() {
+        if (mEnergyWave != null) mEnergyWave.setVisibility(View.VISIBLE);
+        if (mSlidingTab != null) mSlidingTab.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Disable unlocking of this screen. Typically just hides the unlock widget.
+     */
+    private void disableUnlock() {
+        if (mEnergyWave != null) mEnergyWave.setVisibility(View.GONE);
+        if (mSlidingTab != null) mSlidingTab.setVisibility(View.GONE);
+    }
+
+    /**
      * Update the layout to match the current status.
      */
     private void updateLayout(Status status) {
@@ -481,9 +532,10 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.INVISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
+                enableUnlock();
                 break;
+
             case NetworkLocked:
                 // The carrier string shows both sim card status (i.e. No Sim Card) and
                 // carrier's name and/or "Emergency Calls Only" status
@@ -495,9 +547,10 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
+                enableUnlock();
                 break;
+
             case SimMissing:
                 // text
                 mStatusView.setCarrierText(R.string.lockscreen_missing_sim_message_short);
@@ -505,10 +558,10 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.VISIBLE);
-                // do not need to show the e-call button; user may unlock
+                enableUnlock(); // do not need to show the e-call button; user may unlock
                 break;
+
             case SimMissingLocked:
                 // text
                 mStatusView.setCarrierText(
@@ -519,10 +572,11 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.GONE); // cannot unlock
                 mEmergencyCallText.setVisibility(View.VISIBLE);
                 mEmergencyCallButton.setVisibility(View.VISIBLE);
+                disableUnlock();
                 break;
+
             case SimLocked:
                 // text
                 mStatusView.setCarrierText(
@@ -532,9 +586,10 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.INVISIBLE);
-                mSelector.setVisibility(View.VISIBLE);
                 mEmergencyCallText.setVisibility(View.GONE);
+                enableUnlock();
                 break;
+
             case SimPukLocked:
                 // text
                 mStatusView.setCarrierText(
@@ -545,9 +600,9 @@
 
                 // layout
                 mScreenLocked.setVisibility(View.VISIBLE);
-                mSelector.setVisibility(View.GONE); // cannot unlock
                 mEmergencyCallText.setVisibility(View.VISIBLE);
                 mEmergencyCallButton.setVisibility(View.VISIBLE);
+                disableUnlock();
                 break;
         }
     }
@@ -614,7 +669,9 @@
 
     /** {@inheritDoc} */
     public void onPause() {
-
+        if (mEnergyWave != null) {
+            mEnergyWave.reset();
+        }
     }
 
     /** {@inheritDoc} */
@@ -632,7 +689,7 @@
         boolean silent = AudioManager.RINGER_MODE_NORMAL != state;
         if (silent != mSilentMode) {
             mSilentMode = silent;
-            updateRightTabResources();
+            if (mSlidingTabMethods != null) mSlidingTabMethods.updateRightTabResources();
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index bd774ce..c047522 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -290,7 +290,8 @@
     // (See Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR.)
     int mIncallPowerBehavior;
 
-    int mLandscapeRotation = -1;
+    int mLandscapeRotation = -1; // default landscape rotation
+    int mSeascapeRotation = -1; // "other" landscape rotation, 180 degrees from mLandscapeRotation
     int mPortraitRotation = -1;
 
     // Nothing to see here, move along...
@@ -363,9 +364,12 @@
             return true;
         }
         // The user preference says we can rotate, and the app is willing to rotate.
+        // Note we include SCREEN_ORIENTATION_LANDSCAPE since we can use the sensor to choose
+        // between the two possible landscape rotations.
         if (mAccelerometerDefault != 0 &&
                 (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
-                 || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)) {
+                 || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
+                 || appOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
             return true;
         }
         // We're in a dock that has a rotation affinity, an the app is willing to rotate.
@@ -374,7 +378,8 @@
             // Note we override the nosensor flag here.
             if (appOrientation == ActivityInfo.SCREEN_ORIENTATION_USER
                     || appOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
-                    || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) {
+                    || appOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR
+                    || appOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
                 return true;
             }
         }
@@ -2120,20 +2125,20 @@
             if (d.getWidth() > d.getHeight()) {
                 mPortraitRotation = Surface.ROTATION_90;
                 mLandscapeRotation = Surface.ROTATION_0;
+                mSeascapeRotation = Surface.ROTATION_180;
             } else {
                 mPortraitRotation = Surface.ROTATION_0;
                 mLandscapeRotation = Surface.ROTATION_90;
+                mSeascapeRotation = Surface.ROTATION_270;
             }
         }
 
         synchronized (mLock) {
-            switch (orientation) {
-                case ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE:
-                    //always return landscape if orientation set to landscape
-                    return mLandscapeRotation;
-                case ActivityInfo.SCREEN_ORIENTATION_PORTRAIT:
-                    //always return portrait if orientation set to portrait
-                    return mPortraitRotation;
+            if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) {
+                //always return portrait if orientation set to portrait
+                return mPortraitRotation;
+            } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
+                return getCurrentLandscapeRotation(lastRotation);
             }
             // case for nosensor meaning ignore sensor and consider only lid
             // or orientation sensor disabled
@@ -2153,6 +2158,26 @@
         }
     }
 
+    private int getCurrentLandscapeRotation(int lastRotation) {
+        // landscape-only apps can take either landscape rotation
+        if (useSensorForOrientationLp(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)) {
+            int sensorRotation = mOrientationListener.getCurrentRotation(lastRotation);
+            if (isLandscapeOrSeascape(sensorRotation)) {
+                return sensorRotation;
+            }
+        }
+        // try to preserve the old rotation if it was landscape
+        if (isLandscapeOrSeascape(lastRotation)) {
+            return lastRotation;
+        }
+        // default to one of the two landscape rotations
+        return mLandscapeRotation;
+    }
+
+    private boolean isLandscapeOrSeascape(int sensorRotation) {
+        return sensorRotation == mLandscapeRotation || sensorRotation == mSeascapeRotation;
+    }
+
     public boolean detectSafeMode() {
         try {
             int menuState = mWindowManager.getKeycodeState(KeyEvent.KEYCODE_MENU);
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 56de765..8a732ed 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -4653,29 +4653,44 @@
         goto Exit;
     }
 
+    // check audio settings permission for global effects
+    if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && !settingsAllowed()) {
+        lStatus = PERMISSION_DENIED;
+        goto Exit;
+    }
+
+    // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects
+    // that can only be created by audio policy manager (running in same process)
+    if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && getpid() != pid) {
+        lStatus = PERMISSION_DENIED;
+        goto Exit;
+    }
+
+    // check recording permission for visualizer
+    if ((memcmp(&pDesc->type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0 ||
+         memcmp(&pDesc->uuid, &VISUALIZATION_UUID_, sizeof(effect_uuid_t)) == 0) &&
+        !recordingAllowed()) {
+        lStatus = PERMISSION_DENIED;
+        goto Exit;
+    }
+
+    if (output == 0) {
+        if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE) {
+            // output must be specified by AudioPolicyManager when using session
+            // AudioSystem::SESSION_OUTPUT_STAGE
+            lStatus = BAD_VALUE;
+            goto Exit;
+        } else if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) {
+            // if the output returned by getOutputForEffect() is removed before we lock the
+            // mutex below, the call to checkPlaybackThread_l(output) below will detect it
+            // and we will exit safely
+            output = AudioSystem::getOutputForEffect(&desc);
+        }
+    }
+
     {
         Mutex::Autolock _l(mLock);
 
-        // check audio settings permission for global effects
-        if (sessionId == AudioSystem::SESSION_OUTPUT_MIX && !settingsAllowed()) {
-            lStatus = PERMISSION_DENIED;
-            goto Exit;
-        }
-
-        // Session AudioSystem::SESSION_OUTPUT_STAGE is reserved for output stage effects
-        // that can only be created by audio policy manager (running in same process)
-        if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE && getpid() != pid) {
-            lStatus = PERMISSION_DENIED;
-            goto Exit;
-        }
-
-        // check recording permission for visualizer
-        if ((memcmp(&pDesc->type, SL_IID_VISUALIZATION, sizeof(effect_uuid_t)) == 0 ||
-             memcmp(&pDesc->uuid, &VISUALIZATION_UUID_, sizeof(effect_uuid_t)) == 0) &&
-            !recordingAllowed()) {
-            lStatus = PERMISSION_DENIED;
-            goto Exit;
-        }
 
         if (!EffectIsNullUuid(&pDesc->uuid)) {
             // if uuid is specified, request effect descriptor
@@ -4744,32 +4759,24 @@
 
         // If output is not specified try to find a matching audio session ID in one of the
         // output threads.
-        // TODO: allow attachment of effect to inputs
+        // If output is 0 here, sessionId is neither SESSION_OUTPUT_STAGE nor SESSION_OUTPUT_MIX
+        // because of code checking output when entering the function.
         if (output == 0) {
-            if (sessionId == AudioSystem::SESSION_OUTPUT_STAGE) {
-                // output must be specified by AudioPolicyManager when using session
-                // AudioSystem::SESSION_OUTPUT_STAGE
-                lStatus = BAD_VALUE;
-                goto Exit;
-            } else if (sessionId == AudioSystem::SESSION_OUTPUT_MIX) {
-                output = AudioSystem::getOutputForEffect(&desc);
-                LOGV("createEffect() got output %d for effect %s", output, desc.name);
-            } else {
-                 // look for the thread where the specified audio session is present
-                for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
-                    if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId) != 0) {
-                        output = mPlaybackThreads.keyAt(i);
-                        break;
-                    }
-                }
-                // If no output thread contains the requested session ID, default to
-                // first output. The effect chain will be moved to the correct output
-                // thread when a track with the same session ID is created
-                if (output == 0 && mPlaybackThreads.size()) {
-                    output = mPlaybackThreads.keyAt(0);
+             // look for the thread where the specified audio session is present
+            for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
+                if (mPlaybackThreads.valueAt(i)->hasAudioSession(sessionId) != 0) {
+                    output = mPlaybackThreads.keyAt(i);
+                    break;
                 }
             }
+            // If no output thread contains the requested session ID, default to
+            // first output. The effect chain will be moved to the correct output
+            // thread when a track with the same session ID is created
+            if (output == 0 && mPlaybackThreads.size()) {
+                output = mPlaybackThreads.keyAt(0);
+            }
         }
+        LOGV("createEffect() got output %d for effect %s", output, desc.name);
         PlaybackThread *thread = checkPlaybackThread_l(output);
         if (thread == NULL) {
             LOGE("createEffect() unknown output thread");
@@ -4777,6 +4784,8 @@
             goto Exit;
         }
 
+        // TODO: allow attachment of effect to inputs
+
         wclient = mClients.valueFor(pid);
 
         if (wclient != NULL) {
diff --git a/services/java/com/android/server/ProcessStats.java b/services/java/com/android/server/ProcessStats.java
index 5bdadcc..43dbcc0 100644
--- a/services/java/com/android/server/ProcessStats.java
+++ b/services/java/com/android/server/ProcessStats.java
@@ -80,16 +80,24 @@
         PROC_SPACE_TERM|PROC_OUT_LONG,                  // 11: major faults
         PROC_SPACE_TERM,
         PROC_SPACE_TERM|PROC_OUT_LONG,                  // 13: utime
-        PROC_SPACE_TERM|PROC_OUT_LONG                   // 14: stime
+        PROC_SPACE_TERM|PROC_OUT_LONG,                  // 14: stime
+        PROC_SPACE_TERM,
+        PROC_SPACE_TERM,
+        PROC_SPACE_TERM,
+        PROC_SPACE_TERM,
+        PROC_SPACE_TERM,
+        PROC_SPACE_TERM,
+        PROC_SPACE_TERM|PROC_OUT_LONG,                  // 21: vsize
     };
 
     static final int PROCESS_FULL_STAT_MINOR_FAULTS = 1;
     static final int PROCESS_FULL_STAT_MAJOR_FAULTS = 2;
     static final int PROCESS_FULL_STAT_UTIME = 3;
     static final int PROCESS_FULL_STAT_STIME = 4;
+    static final int PROCESS_FULL_STAT_VSIZE = 5;
 
-    private final String[] mProcessFullStatsStringData = new String[5];
-    private final long[] mProcessFullStatsData = new long[5];
+    private final String[] mProcessFullStatsStringData = new String[6];
+    private final long[] mProcessFullStatsData = new long[6];
 
     private static final int[] SYSTEM_CPU_FORMAT = new int[] {
         PROC_SPACE_TERM|PROC_COMBINE,
@@ -171,6 +179,8 @@
         final ArrayList<Stats> threadStats;
         final ArrayList<Stats> workingThreads;
         
+        public boolean interesting;
+
         public String baseName;
         public String name;
         int nameWidth;
@@ -349,59 +359,62 @@
                         + (parentPid < 0 ? "process" : "thread")
                         + " pid " + pid + ": " + st);
 
-                final long uptime = SystemClock.uptimeMillis();
+                if (st.interesting) {
+                    final long uptime = SystemClock.uptimeMillis();
 
-                final long[] procStats = mProcessStatsData;
-                if (!Process.readProcFile(st.statFile.toString(),
-                        PROCESS_STATS_FORMAT, null, procStats, null)) {
-                    continue;
-                }
-                
-                final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS];
-                final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS];
-                final long utime = procStats[PROCESS_STAT_UTIME];
-                final long stime = procStats[PROCESS_STAT_STIME];
-
-                if (utime == st.base_utime && stime == st.base_stime) {
-                    st.rel_utime = 0;
-                    st.rel_stime = 0;
-                    st.rel_minfaults = 0;
-                    st.rel_majfaults = 0;
-                    if (st.active) {
-                        st.active = false;
+                    final long[] procStats = mProcessStatsData;
+                    if (!Process.readProcFile(st.statFile.toString(),
+                            PROCESS_STATS_FORMAT, null, procStats, null)) {
+                        continue;
                     }
-                    continue;
-                }
                     
-                if (!st.active) {
-                    st.active = true;
-                }
+                    final long minfaults = procStats[PROCESS_STAT_MINOR_FAULTS];
+                    final long majfaults = procStats[PROCESS_STAT_MAJOR_FAULTS];
+                    final long utime = procStats[PROCESS_STAT_UTIME];
+                    final long stime = procStats[PROCESS_STAT_STIME];
 
-                if (parentPid < 0) {
-                    getName(st, st.cmdlineFile);
-                    if (st.threadStats != null) {
-                        mCurThreadPids = collectStats(st.threadsDir, pid, false,
-                                mCurThreadPids, st.threadStats);
+                    if (utime == st.base_utime && stime == st.base_stime) {
+                        st.rel_utime = 0;
+                        st.rel_stime = 0;
+                        st.rel_minfaults = 0;
+                        st.rel_majfaults = 0;
+                        if (st.active) {
+                            st.active = false;
+                        }
+                        continue;
                     }
+
+                    if (!st.active) {
+                        st.active = true;
+                    }
+
+                    if (parentPid < 0) {
+                        getName(st, st.cmdlineFile);
+                        if (st.threadStats != null) {
+                            mCurThreadPids = collectStats(st.threadsDir, pid, false,
+                                    mCurThreadPids, st.threadStats);
+                        }
+                    }
+
+                    if (DEBUG) Slog.v("Load", "Stats changed " + st.name + " pid=" + st.pid
+                            + " utime=" + utime + "-" + st.base_utime
+                            + " stime=" + stime + "-" + st.base_stime
+                            + " minfaults=" + minfaults + "-" + st.base_minfaults
+                            + " majfaults=" + majfaults + "-" + st.base_majfaults);
+
+                    st.rel_uptime = uptime - st.base_uptime;
+                    st.base_uptime = uptime;
+                    st.rel_utime = (int)(utime - st.base_utime);
+                    st.rel_stime = (int)(stime - st.base_stime);
+                    st.base_utime = utime;
+                    st.base_stime = stime;
+                    st.rel_minfaults = (int)(minfaults - st.base_minfaults);
+                    st.rel_majfaults = (int)(majfaults - st.base_majfaults);
+                    st.base_minfaults = minfaults;
+                    st.base_majfaults = majfaults;
+                    st.working = true;
                 }
 
-                if (DEBUG) Slog.v("Load", "Stats changed " + st.name + " pid=" + st.pid
-                        + " utime=" + utime + "-" + st.base_utime
-                        + " stime=" + stime + "-" + st.base_stime
-                        + " minfaults=" + minfaults + "-" + st.base_minfaults
-                        + " majfaults=" + majfaults + "-" + st.base_majfaults);
-
-                st.rel_uptime = uptime - st.base_uptime;
-                st.base_uptime = uptime;
-                st.rel_utime = (int)(utime - st.base_utime);
-                st.rel_stime = (int)(stime - st.base_stime);
-                st.base_utime = utime;
-                st.base_stime = stime;
-                st.rel_minfaults = (int)(minfaults - st.base_minfaults);
-                st.rel_majfaults = (int)(majfaults - st.base_majfaults);
-                st.base_minfaults = minfaults;
-                st.base_majfaults = majfaults;
-                st.working = true;
                 continue;
             }
             
@@ -421,12 +434,24 @@
                 if (Process.readProcFile(st.statFile.toString(),
                         PROCESS_FULL_STATS_FORMAT, procStatsString,
                         procStats, null)) {
-                    st.baseName = procStatsString[0];
-                    st.base_minfaults = procStats[PROCESS_FULL_STAT_MINOR_FAULTS];
-                    st.base_majfaults = procStats[PROCESS_FULL_STAT_MAJOR_FAULTS];
-                    st.base_utime = procStats[PROCESS_FULL_STAT_UTIME];
-                    st.base_stime = procStats[PROCESS_FULL_STAT_STIME];
+                    // This is a possible way to filter out processes that
+                    // are actually kernel threads...  do we want to?  Some
+                    // of them do use CPU, but there can be a *lot* that are
+                    // not doing anything.
+                    if (true || procStats[PROCESS_FULL_STAT_VSIZE] != 0) {
+                        st.interesting = true;
+                        st.baseName = procStatsString[0];
+                        st.base_minfaults = procStats[PROCESS_FULL_STAT_MINOR_FAULTS];
+                        st.base_majfaults = procStats[PROCESS_FULL_STAT_MAJOR_FAULTS];
+                        st.base_utime = procStats[PROCESS_FULL_STAT_UTIME];
+                        st.base_stime = procStats[PROCESS_FULL_STAT_STIME];
+                    } else {
+                        Slog.i(TAG, "Skipping kernel process pid " + pid
+                                + " name " + procStatsString[0]);
+                        st.baseName = procStatsString[0];
+                    }
                 } else {
+                    Slog.w(TAG, "Skipping unknown process pid " + pid);
                     st.baseName = "<unknown>";
                     st.base_utime = st.base_stime = 0;
                     st.base_minfaults = st.base_majfaults = 0;
@@ -438,7 +463,7 @@
                         mCurThreadPids = collectStats(st.threadsDir, pid, true,
                                 mCurThreadPids, st.threadStats);
                     }
-                } else {
+                } else if (st.interesting) {
                     st.name = st.baseName;
                     st.nameWidth = onMeasureProcessName(st.name);
                 }
@@ -452,7 +477,7 @@
                 st.rel_minfaults = 0;
                 st.rel_majfaults = 0;
                 st.added = true;
-                if (!first) {
+                if (!first && st.interesting) {
                     st.working = true;
                 }
                 continue;
@@ -624,6 +649,14 @@
         }
     }
 
+    final public int countStats() {
+        return mProcStats.size();
+    }
+
+    final public Stats getStats(int index) {
+        return mProcStats.get(index);
+    }
+
     final public int countWorkingStats() {
         buildWorkingProcs();
         return mWorkingProcs.size();
@@ -788,7 +821,8 @@
 
     private void getName(Stats st, String cmdlineFile) {
         String newName = st.name;
-        if (st.name == null || st.name.equals("app_process")) {
+        if (st.name == null || st.name.equals("app_process")
+                || st.name.equals("<pre-initialized>")) {
             String cmdName = readFile(cmdlineFile, '\0');
             if (cmdName != null && cmdName.length() > 1) {
                 newName = cmdName;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6b5b3bd..8e22652 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -122,6 +122,8 @@
 import java.lang.IllegalStateException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -145,6 +147,7 @@
     static final boolean DEBUG_BROADCAST = localLOGV || false;
     static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
     static final boolean DEBUG_SERVICE = localLOGV || false;
+    static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false;
     static final boolean DEBUG_VISBILITY = localLOGV || false;
     static final boolean DEBUG_PROCESSES = localLOGV || false;
     static final boolean DEBUG_PROVIDER = localLOGV || false;
@@ -153,6 +156,8 @@
     static final boolean DEBUG_RESULTS = localLOGV || false;
     static final boolean DEBUG_BACKUP = localLOGV || false;
     static final boolean DEBUG_CONFIGURATION = localLOGV || false;
+    static final boolean DEBUG_POWER = localLOGV || false;
+    static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false;
     static final boolean VALIDATE_TOKENS = false;
     static final boolean SHOW_ACTIVITY_START_TIME = true;
     
@@ -198,8 +203,16 @@
     // The minimum amount of time between successive GC requests for a process.
     static final int GC_MIN_INTERVAL = 60*1000;
 
-    // The rate at which we check for apps using excessive wake locks -- 15 mins.
-    static final int WAKE_LOCK_CHECK_DELAY = 15*60*1000;
+    // The rate at which we check for apps using excessive power -- 15 mins.
+    static final int POWER_CHECK_DELAY = (DEBUG_POWER_QUICK ? 2 : 15) * 60*1000;
+
+    // The minimum sample duration we will allow before deciding we have
+    // enough data on wake locks to start killing things.
+    static final int WAKE_LOCK_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;
+
+    // The minimum sample duration we will allow before deciding we have
+    // enough data on CPU usage to start killing things.
+    static final int CPU_MIN_CHECK_DURATION = (DEBUG_POWER_QUICK ? 1 : 5) * 60*1000;
 
     // How long we allow a receiver to run before giving up on it.
     static final int BROADCAST_TIMEOUT = 10*1000;
@@ -780,9 +793,14 @@
     boolean mDidAppSwitch;
     
     /**
-     * Last time (in realtime) at which we checked for wake lock usage.
+     * Last time (in realtime) at which we checked for power usage.
      */
-    long mLastWakeLockCheckTime;
+    long mLastPowerCheckRealtime;
+
+    /**
+     * Last time (in uptime) at which we checked for power usage.
+     */
+    long mLastPowerCheckUptime;
 
     /**
      * Set while we are wanting to sleep, to prevent any
@@ -1195,12 +1213,10 @@
             } break;
             case CHECK_EXCESSIVE_WAKE_LOCKS_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    checkExcessiveWakeLocksLocked(true);
+                    checkExcessivePowerUsageLocked(true);
                     removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
-                    if (mSleeping) {
-                        Message nmsg = obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
-                        sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY);
-                    }
+                    Message nmsg = obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
+                    sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
                 }
             } break;
             }
@@ -1395,7 +1411,8 @@
                 systemDir, "batterystats.bin").toString());
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.getActiveStatistics().writeAsyncLocked();
-        mOnBattery = mBatteryStatsService.getActiveStatistics().getIsOnBattery();
+        mOnBattery = DEBUG_POWER ? true
+                : mBatteryStatsService.getActiveStatistics().getIsOnBattery();
         mBatteryStatsService.getActiveStatistics().setCallback(this);
         
         mUsageStatsService = new UsageStatsService(new File(
@@ -1515,10 +1532,12 @@
                             int perc = bstats.startAddingCpuLocked();
                             int totalUTime = 0;
                             int totalSTime = 0;
-                            final int N = mProcessStats.countWorkingStats();
+                            final int N = mProcessStats.countStats();
                             for (int i=0; i<N; i++) {
-                                ProcessStats.Stats st
-                                        = mProcessStats.getWorkingStats(i);
+                                ProcessStats.Stats st = mProcessStats.getStats(i);
+                                if (!st.working) {
+                                    continue;
+                                }
                                 ProcessRecord pr = mPidsSelfLocked.get(st.pid);
                                 int otherUTime = (st.rel_utime*perc)/100;
                                 int otherSTime = (st.rel_stime*perc)/100;
@@ -1529,6 +1548,7 @@
                                     ps.addCpuTimeLocked(st.rel_utime-otherUTime,
                                             st.rel_stime-otherSTime);
                                     ps.addSpeedStepTimes(cpuSpeedTimes);
+                                    pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10;
                                 } else {
                                     BatteryStatsImpl.Uid.Proc ps =
                                             bstats.getProcessStatsLocked(st.name, st.pid);
@@ -1565,7 +1585,7 @@
         updateCpuStatsNow();
         synchronized (this) {
             synchronized(mPidsSelfLocked) {
-                mOnBattery = onBattery;
+                mOnBattery = DEBUG_POWER ? true : onBattery;
             }
         }
     }
@@ -2795,6 +2815,16 @@
         ArrayList<Integer> firstPids = new ArrayList<Integer>(5);
         SparseArray<Boolean> lastPids = new SparseArray<Boolean>(20);
 
+        if (mController != null) {
+            try {
+                // 0 == continue, -1 = kill process immediately
+                int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation);
+                if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid);
+            } catch (RemoteException e) {
+                mController = null;
+            }
+        }
+
         long anrTime = SystemClock.uptimeMillis();
         if (MONITOR_CPU_USAGE) {
             updateCpuStatsNow();
@@ -2845,10 +2875,6 @@
             }
         }
 
-        final ProcessStats processStats = new ProcessStats(true);
-
-        File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids);
-
         // Log the ANR to the main log.
         StringBuilder info = mStringBuilder;
         info.setLength(0);
@@ -2864,6 +2890,10 @@
             info.append("Parent: ").append(parent.shortComponentName).append("\n");
         }
 
+        final ProcessStats processStats = new ProcessStats(true);
+
+        File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids);
+
         String cpuInfo = null;
         if (MONITOR_CPU_USAGE) {
             updateCpuStatsNow();
@@ -3742,7 +3772,7 @@
             if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
                 // Start looking for apps that are abusing wake locks.
                 Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
-                mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY);
+                mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
                 // Tell anyone interested that we are done booting!
                 SystemProperties.set("sys.boot_completed", "1");
                 broadcastIntentLocked(null, null,
@@ -5655,10 +5685,10 @@
             }
 
             // Initialize the wake times of all processes.
-            checkExcessiveWakeLocksLocked(false);
+            checkExcessivePowerUsageLocked(false);
             mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
             Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
-            mHandler.sendMessageDelayed(nmsg, WAKE_LOCK_CHECK_DELAY);
+            mHandler.sendMessageDelayed(nmsg, POWER_CHECK_DELAY);
         }
     }
 
@@ -5708,7 +5738,6 @@
             mWindowManager.setEventDispatching(true);
             mSleeping = false;
             mMainStack.resumeTopActivityLocked(null);
-            mHandler.removeMessages(CHECK_EXCESSIVE_WAKE_LOCKS_MSG);
         }
     }
 
@@ -7055,12 +7084,13 @@
                 pw.println("Activity manager dump options:");
                 pw.println("  [-a] [-h] [cmd] ...");
                 pw.println("  cmd may be one of:");
-                pw.println("    activities: activity stack state");
-                pw.println("    broadcasts: broadcast state");
-                pw.println("    intents: pending intent state");
-                pw.println("    processes: process state");
-                pw.println("    providers: content provider state");
-                pw.println("    services: service state");
+                pw.println("    a[ctivities]: activity stack state");
+                pw.println("    b[roadcasts]: broadcast state");
+                pw.println("    i[ntents]: pending intent state");
+                pw.println("    p[rocesses]: process state");
+                pw.println("    o[om]: out of memory management");
+                pw.println("    prov[iders]: content provider state");
+                pw.println("    s[ervices]: service state");
                 pw.println("    service [name]: service client-side state");
                 return;
             } else {
@@ -7092,6 +7122,11 @@
                     dumpProcessesLocked(fd, pw, args, opti, true);
                 }
                 return;
+            } else if ("oom".equals(cmd) || "o".equals(cmd)) {
+                synchronized (this) {
+                    dumpOomLocked(fd, pw, args, opti, true);
+                }
+                return;
             } else if ("providers".equals(cmd) || "prov".equals(cmd)) {
                 synchronized (this) {
                     dumpProvidersLocked(fd, pw, args, opti, true);
@@ -7213,7 +7248,7 @@
         
         return true;
     }
-        
+
     boolean dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
             int opti, boolean dumpAll) {
         boolean needSep = false;
@@ -7243,8 +7278,8 @@
             if (needSep) pw.println(" ");
             needSep = true;
             pw.println("  Running processes (most recent first):");
-            dumpProcessList(pw, this, mLruProcesses, "    ",
-                    "Proc", "PERS", true);
+            dumpProcessOomList(pw, this, mLruProcesses, "    ",
+                    "Proc", "PERS", false);
             needSep = true;
         }
 
@@ -7275,7 +7310,7 @@
             needSep = true;
             pw.println("  Persisent processes that are starting:");
             dumpProcessList(pw, this, mPersistentStartingProcesses, "    ",
-                    "Starting Norm", "Restarting PERS", false);
+                    "Starting Norm", "Restarting PERS");
         }
 
         if (mStartingProcesses.size() > 0) {
@@ -7283,7 +7318,7 @@
             needSep = true;
             pw.println("  Processes that are starting:");
             dumpProcessList(pw, this, mStartingProcesses, "    ",
-                    "Starting Norm", "Starting PERS", false);
+                    "Starting Norm", "Starting PERS");
         }
 
         if (mRemovedProcesses.size() > 0) {
@@ -7291,7 +7326,7 @@
             needSep = true;
             pw.println("  Processes that are being removed:");
             dumpProcessList(pw, this, mRemovedProcesses, "    ",
-                    "Removed Norm", "Removed PERS", false);
+                    "Removed Norm", "Removed PERS");
         }
         
         if (mProcessesOnHold.size() > 0) {
@@ -7299,26 +7334,10 @@
             needSep = true;
             pw.println("  Processes that are on old until the system is ready:");
             dumpProcessList(pw, this, mProcessesOnHold, "    ",
-                    "OnHold Norm", "OnHold PERS", false);
+                    "OnHold Norm", "OnHold PERS");
         }
 
-        if (mProcessesToGc.size() > 0) {
-            if (needSep) pw.println(" ");
-            needSep = true;
-            pw.println("  Processes that are waiting to GC:");
-            long now = SystemClock.uptimeMillis();
-            for (int i=0; i<mProcessesToGc.size(); i++) {
-                ProcessRecord proc = mProcessesToGc.get(i);
-                pw.print("    Process "); pw.println(proc);
-                pw.print("      lowMem="); pw.print(proc.reportLowMemory);
-                        pw.print(", last gced=");
-                        pw.print(now-proc.lastRequestedGc);
-                        pw.print(" ms ago, last lowMem=");
-                        pw.print(now-proc.lastLowMemory);
-                        pw.println(" ms ago");
-                
-            }
-        }
+        needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll);
         
         if (mProcessCrashTimes.getMap().size() > 0) {
             if (needSep) pw.println(" ");
@@ -7382,6 +7401,12 @@
             pw.println("  mBooting=" + mBooting
                     + " mBooted=" + mBooted
                     + " mFactoryTest=" + mFactoryTest);
+            pw.print("  mLastPowerCheckRealtime=");
+                    TimeUtils.formatDuration(mLastPowerCheckRealtime, pw);
+                    pw.println("");
+            pw.print("  mLastPowerCheckUptime=");
+                    TimeUtils.formatDuration(mLastPowerCheckUptime, pw);
+                    pw.println("");
             pw.println("  mGoingToSleep=" + mMainStack.mGoingToSleep);
             pw.println("  mLaunchingActivity=" + mMainStack.mLaunchingActivity);
             pw.println("  mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq);
@@ -7390,6 +7415,75 @@
         return true;
     }
 
+    boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean needSep, boolean dumpAll) {
+        if (mProcessesToGc.size() > 0) {
+            if (needSep) pw.println(" ");
+            needSep = true;
+            pw.println("  Processes that are waiting to GC:");
+            long now = SystemClock.uptimeMillis();
+            for (int i=0; i<mProcessesToGc.size(); i++) {
+                ProcessRecord proc = mProcessesToGc.get(i);
+                pw.print("    Process "); pw.println(proc);
+                pw.print("      lowMem="); pw.print(proc.reportLowMemory);
+                        pw.print(", last gced=");
+                        pw.print(now-proc.lastRequestedGc);
+                        pw.print(" ms ago, last lowMem=");
+                        pw.print(now-proc.lastLowMemory);
+                        pw.println(" ms ago");
+
+            }
+        }
+        return needSep;
+    }
+
+    boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+            int opti, boolean dumpAll) {
+        boolean needSep = false;
+
+        if (mLruProcesses.size() > 0) {
+            ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(mLruProcesses);
+
+            Comparator<ProcessRecord> comparator = new Comparator<ProcessRecord>() {
+                @Override
+                public int compare(ProcessRecord object1, ProcessRecord object2) {
+                    if (object1.setAdj != object2.setAdj) {
+                        return object1.setAdj > object2.setAdj ? -1 : 1;
+                    }
+                    if (object1.setSchedGroup != object2.setSchedGroup) {
+                        return object1.setSchedGroup > object2.setSchedGroup ? -1 : 1;
+                    }
+                    if (object1.keeping != object2.keeping) {
+                        return object1.keeping ? -1 : 1;
+                    }
+                    if (object1.pid != object2.pid) {
+                        return object1.pid > object2.pid ? -1 : 1;
+                    }
+                    return 0;
+                }
+            };
+
+            Collections.sort(procs, comparator);
+
+            if (needSep) pw.println(" ");
+            needSep = true;
+            pw.println("  Process OOM control:");
+            dumpProcessOomList(pw, this, procs, "    ",
+                    "Proc", "PERS", true);
+            needSep = true;
+        }
+
+        needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll);
+
+        pw.println(" ");
+        pw.println("  mHomeProcess: " + mHomeProcess);
+        if (mHeavyWeightProcess != null) {
+            pw.println("  mHeavyWeightProcess: " + mHeavyWeightProcess);
+        }
+
+        return true;
+    }
+
     /**
      * There are three ways to call this:
      *  - no service specified: dump all the services
@@ -7833,84 +7927,14 @@
     
     private static final int dumpProcessList(PrintWriter pw,
             ActivityManagerService service, List list,
-            String prefix, String normalLabel, String persistentLabel,
-            boolean inclOomAdj) {
+            String prefix, String normalLabel, String persistentLabel) {
         int numPers = 0;
         final int N = list.size()-1;
         for (int i=N; i>=0; i--) {
             ProcessRecord r = (ProcessRecord)list.get(i);
-            if (false) {
-                pw.println(prefix + (r.persistent ? persistentLabel : normalLabel)
-                      + " #" + i + ":");
-                r.dump(pw, prefix + "  ");
-            } else if (inclOomAdj) {
-                String oomAdj;
-                if (r.setAdj >= EMPTY_APP_ADJ) {
-                    oomAdj = buildOomTag("empty", null, r.setAdj, EMPTY_APP_ADJ);
-                } else if (r.setAdj >= HIDDEN_APP_MIN_ADJ) {
-                    oomAdj = buildOomTag("bak", "  ", r.setAdj, HIDDEN_APP_MIN_ADJ);
-                } else if (r.setAdj >= HOME_APP_ADJ) {
-                    oomAdj = buildOomTag("home ", null, r.setAdj, HOME_APP_ADJ);
-                } else if (r.setAdj >= SECONDARY_SERVER_ADJ) {
-                    oomAdj = buildOomTag("svc", "  ", r.setAdj, SECONDARY_SERVER_ADJ);
-                } else if (r.setAdj >= BACKUP_APP_ADJ) {
-                    oomAdj = buildOomTag("bckup", null, r.setAdj, BACKUP_APP_ADJ);
-                } else if (r.setAdj >= HEAVY_WEIGHT_APP_ADJ) {
-                    oomAdj = buildOomTag("hvy  ", null, r.setAdj, HEAVY_WEIGHT_APP_ADJ);
-                } else if (r.setAdj >= PERCEPTIBLE_APP_ADJ) {
-                    oomAdj = buildOomTag("prcp ", null, r.setAdj, PERCEPTIBLE_APP_ADJ);
-                } else if (r.setAdj >= VISIBLE_APP_ADJ) {
-                    oomAdj = buildOomTag("vis  ", null, r.setAdj, VISIBLE_APP_ADJ);
-                } else if (r.setAdj >= FOREGROUND_APP_ADJ) {
-                    oomAdj = buildOomTag("fore ", null, r.setAdj, FOREGROUND_APP_ADJ);
-                } else if (r.setAdj >= CORE_SERVER_ADJ) {
-                    oomAdj = buildOomTag("core ", null, r.setAdj, CORE_SERVER_ADJ);
-                } else if (r.setAdj >= SYSTEM_ADJ) {
-                    oomAdj = buildOomTag("sys  ", null, r.setAdj, SYSTEM_ADJ);
-                } else {
-                    oomAdj = Integer.toString(r.setAdj);
-                }
-                String schedGroup;
-                switch (r.setSchedGroup) {
-                    case Process.THREAD_GROUP_BG_NONINTERACTIVE:
-                        schedGroup = "B";
-                        break;
-                    case Process.THREAD_GROUP_DEFAULT:
-                        schedGroup = "F";
-                        break;
-                    default:
-                        schedGroup = Integer.toString(r.setSchedGroup);
-                        break;
-                }
-                pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)",
-                        prefix, (r.persistent ? persistentLabel : normalLabel),
-                        N-i, oomAdj, schedGroup, r.toShortString(), r.adjType));
-                if (r.adjSource != null || r.adjTarget != null) {
-                    pw.print(prefix);
-                    pw.print("          ");
-                    if (r.adjTarget instanceof ComponentName) {
-                        pw.print(((ComponentName)r.adjTarget).flattenToShortString());
-                    } else if (r.adjTarget != null) {
-                        pw.print(r.adjTarget.toString());
-                    } else {
-                        pw.print("{null}");
-                    }
-                    pw.print("<=");
-                    if (r.adjSource instanceof ProcessRecord) {
-                        pw.print("Proc{");
-                        pw.print(((ProcessRecord)r.adjSource).toShortString());
-                        pw.println("}");
-                    } else if (r.adjSource != null) {
-                        pw.println(r.adjSource.toString());
-                    } else {
-                        pw.println("{null}");
-                    }
-                }
-            } else {
-                pw.println(String.format("%s%s #%2d: %s",
-                        prefix, (r.persistent ? persistentLabel : normalLabel),
-                        i, r.toString()));
-            }
+            pw.println(String.format("%s%s #%2d: %s",
+                    prefix, (r.persistent ? persistentLabel : normalLabel),
+                    i, r.toString()));
             if (r.persistent) {
                 numPers++;
             }
@@ -7918,6 +7942,132 @@
         return numPers;
     }
 
+    private static final void dumpProcessOomList(PrintWriter pw,
+            ActivityManagerService service, List<ProcessRecord> list,
+            String prefix, String normalLabel, String persistentLabel,
+            boolean inclDetails) {
+
+        final long curRealtime = SystemClock.elapsedRealtime();
+        final long realtimeSince = curRealtime - service.mLastPowerCheckRealtime;
+        final long curUptime = SystemClock.uptimeMillis();
+        final long uptimeSince = curUptime - service.mLastPowerCheckUptime;
+
+        final int N = list.size()-1;
+        for (int i=N; i>=0; i--) {
+            ProcessRecord r = list.get(i);
+            String oomAdj;
+            if (r.setAdj >= EMPTY_APP_ADJ) {
+                oomAdj = buildOomTag("empty", null, r.setAdj, EMPTY_APP_ADJ);
+            } else if (r.setAdj >= HIDDEN_APP_MIN_ADJ) {
+                oomAdj = buildOomTag("bak", "  ", r.setAdj, HIDDEN_APP_MIN_ADJ);
+            } else if (r.setAdj >= HOME_APP_ADJ) {
+                oomAdj = buildOomTag("home ", null, r.setAdj, HOME_APP_ADJ);
+            } else if (r.setAdj >= SECONDARY_SERVER_ADJ) {
+                oomAdj = buildOomTag("svc", "  ", r.setAdj, SECONDARY_SERVER_ADJ);
+            } else if (r.setAdj >= BACKUP_APP_ADJ) {
+                oomAdj = buildOomTag("bckup", null, r.setAdj, BACKUP_APP_ADJ);
+            } else if (r.setAdj >= HEAVY_WEIGHT_APP_ADJ) {
+                oomAdj = buildOomTag("hvy  ", null, r.setAdj, HEAVY_WEIGHT_APP_ADJ);
+            } else if (r.setAdj >= PERCEPTIBLE_APP_ADJ) {
+                oomAdj = buildOomTag("prcp ", null, r.setAdj, PERCEPTIBLE_APP_ADJ);
+            } else if (r.setAdj >= VISIBLE_APP_ADJ) {
+                oomAdj = buildOomTag("vis  ", null, r.setAdj, VISIBLE_APP_ADJ);
+            } else if (r.setAdj >= FOREGROUND_APP_ADJ) {
+                oomAdj = buildOomTag("fore ", null, r.setAdj, FOREGROUND_APP_ADJ);
+            } else if (r.setAdj >= CORE_SERVER_ADJ) {
+                oomAdj = buildOomTag("core ", null, r.setAdj, CORE_SERVER_ADJ);
+            } else if (r.setAdj >= SYSTEM_ADJ) {
+                oomAdj = buildOomTag("sys  ", null, r.setAdj, SYSTEM_ADJ);
+            } else {
+                oomAdj = Integer.toString(r.setAdj);
+            }
+            String schedGroup;
+            switch (r.setSchedGroup) {
+                case Process.THREAD_GROUP_BG_NONINTERACTIVE:
+                    schedGroup = "B";
+                    break;
+                case Process.THREAD_GROUP_DEFAULT:
+                    schedGroup = "F";
+                    break;
+                default:
+                    schedGroup = Integer.toString(r.setSchedGroup);
+                    break;
+            }
+            pw.println(String.format("%s%s #%2d: adj=%s/%s %s (%s)",
+                    prefix, (r.persistent ? persistentLabel : normalLabel),
+                    N-i, oomAdj, schedGroup, r.toShortString(), r.adjType));
+            if (r.adjSource != null || r.adjTarget != null) {
+                pw.print(prefix);
+                pw.print("          ");
+                if (r.adjTarget instanceof ComponentName) {
+                    pw.print(((ComponentName)r.adjTarget).flattenToShortString());
+                } else if (r.adjTarget != null) {
+                    pw.print(r.adjTarget.toString());
+                } else {
+                    pw.print("{null}");
+                }
+                pw.print("<=");
+                if (r.adjSource instanceof ProcessRecord) {
+                    pw.print("Proc{");
+                    pw.print(((ProcessRecord)r.adjSource).toShortString());
+                    pw.println("}");
+                } else if (r.adjSource != null) {
+                    pw.println(r.adjSource.toString());
+                } else {
+                    pw.println("{null}");
+                }
+            }
+            if (inclDetails) {
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("oom: max="); pw.print(r.maxAdj);
+                pw.print(" hidden="); pw.print(r.hiddenAdj);
+                pw.print(" curRaw="); pw.print(r.curRawAdj);
+                pw.print(" setRaw="); pw.print(r.setRawAdj);
+                pw.print(" cur="); pw.print(r.curAdj);
+                pw.print(" set="); pw.println(r.setAdj);
+                pw.print(prefix);
+                pw.print("    ");
+                pw.print("keeping="); pw.print(r.keeping);
+                pw.print(" hidden="); pw.print(r.hidden);
+                pw.print(" empty="); pw.println(r.empty);
+
+                if (!r.keeping) {
+                    if (r.lastWakeTime != 0) {
+                        long wtime;
+                        BatteryStatsImpl stats = service.mBatteryStatsService.getActiveStatistics();
+                        synchronized (stats) {
+                            wtime = stats.getProcessWakeTime(r.info.uid,
+                                    r.pid, curRealtime);
+                        }
+                        long timeUsed = wtime - r.lastWakeTime;
+                        pw.print(prefix);
+                        pw.print("    ");
+                        pw.print("keep awake over ");
+                        TimeUtils.formatDuration(realtimeSince, pw);
+                        pw.print(" used ");
+                        TimeUtils.formatDuration(timeUsed, pw);
+                        pw.print(" (");
+                        pw.print((timeUsed*100)/realtimeSince);
+                        pw.println("%)");
+                    }
+                    if (r.lastCpuTime != 0) {
+                        long timeUsed = r.curCpuTime - r.lastCpuTime;
+                        pw.print(prefix);
+                        pw.print("    ");
+                        pw.print("run cpu over ");
+                        TimeUtils.formatDuration(uptimeSince, pw);
+                        pw.print(" used ");
+                        TimeUtils.formatDuration(timeUsed, pw);
+                        pw.print(" (");
+                        pw.print((timeUsed*100)/uptimeSince);
+                        pw.println("%)");
+                    }
+                }
+            }
+        }
+    }
+
     static final void dumpApplicationMemoryUsage(FileDescriptor fd,
             PrintWriter pw, List list, String prefix, String[] args) {
         final boolean isCheckinRequest = scanArgs(args, "--checkin");
@@ -8544,7 +8694,11 @@
         return null;
     }
 
-    private final void bumpServiceExecutingLocked(ServiceRecord r) {
+    private final void bumpServiceExecutingLocked(ServiceRecord r, String why) {
+        if (DEBUG_SERVICE) Log.v(TAG, ">>> EXECUTING "
+                + why + " of " + r + " in app " + r.app);
+        else if (DEBUG_SERVICE_EXECUTING) Log.v(TAG, ">>> EXECUTING "
+                + why + " of " + r.shortName);
         long now = SystemClock.uptimeMillis();
         if (r.executeNesting == 0 && r.app != null) {
             if (r.app.executingServices.size() == 0) {
@@ -8582,8 +8736,7 @@
                     grantUriPermissionUncheckedFromIntentLocked(si.targetPermissionUid,
                             r.packageName, si.intent, si.getUriPermissionsLocked());
                 }
-                if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING start of " + r);
-                bumpServiceExecutingLocked(r);
+                bumpServiceExecutingLocked(r, "start");
                 if (!oomAdjusted) {
                     oomAdjusted = true;
                     updateOomAdjLocked(r.app);
@@ -8616,9 +8769,7 @@
         }
         if ((!i.requested || rebind) && i.apps.size() > 0) {
             try {
-                if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING bind of " + r
-                        + " in " + i + ": shouldUnbind=" + i.hasBound);
-                bumpServiceExecutingLocked(r);
+                bumpServiceExecutingLocked(r, "bind");
                 r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind);
                 if (!rebind) {
                     i.requested = true;
@@ -8653,8 +8804,7 @@
         r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
 
         app.services.add(r);
-        if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING create of " + r + " " + r.intent);
-        bumpServiceExecutingLocked(r);
+        bumpServiceExecutingLocked(r, "create");
         updateLruProcessLocked(app, true, true);
 
         boolean created = false;
@@ -8906,9 +9056,7 @@
                         + ": hasBound=" + ibr.hasBound);
                 if (r.app != null && r.app.thread != null && ibr.hasBound) {
                     try {
-                        if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING bring down unbind of " + r
-                                + " for " + ibr);
-                        bumpServiceExecutingLocked(r);
+                        bumpServiceExecutingLocked(r, "bring down unbind");
                         updateOomAdjLocked(r.app);
                         ibr.hasBound = false;
                         r.app.thread.scheduleUnbindService(r,
@@ -8959,12 +9107,7 @@
             r.app.services.remove(r);
             if (r.app.thread != null) {
                 try {
-                    if (DEBUG_SERVICE) {
-                        RuntimeException here = new RuntimeException();
-                        here.fillInStackTrace();
-                        Slog.v(TAG, ">>> EXECUTING stop of " + r, here);
-                    }
-                    bumpServiceExecutingLocked(r);
+                    bumpServiceExecutingLocked(r, "stop");
                     mStoppingServices.add(r);
                     updateOomAdjLocked(r.app);
                     r.app.thread.scheduleStopService(r);
@@ -9420,9 +9563,7 @@
         if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0
                 && b.intent.hasBound) {
             try {
-                if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING unbind of " + s
-                        + " from " + b);
-                bumpServiceExecutingLocked(s);
+                bumpServiceExecutingLocked(s, "unbind");
                 updateOomAdjLocked(s.app);
                 b.intent.hasBound = false;
                 // Assume the client doesn't want to know about a rebind;
@@ -9643,14 +9784,20 @@
         if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r
                 + ": nesting=" + r.executeNesting
                 + ", inStopping=" + inStopping + ", app=" + r.app);
+        else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName);
         r.executeNesting--;
         if (r.executeNesting <= 0 && r.app != null) {
+            if (DEBUG_SERVICE) Slog.v(TAG,
+                    "Nesting at 0 of " + r.shortName);
             r.app.executingServices.remove(r);
             if (r.app.executingServices.size() == 0) {
+                if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG,
+                        "No more executingServices of " + r.shortName);
                 mHandler.removeMessages(SERVICE_TIMEOUT_MSG, r.app);
             }
             if (inStopping) {
-                if (DEBUG_SERVICE) Slog.v(TAG, "doneExecuting remove stopping " + r);
+                if (DEBUG_SERVICE) Slog.v(TAG,
+                        "doneExecuting remove stopping " + r);
                 mStoppingServices.remove(r);
             }
             updateOomAdjLocked(r.app);
@@ -11326,6 +11473,7 @@
             app.adjType = "fixed";
             app.adjSeq = mAdjSeq;
             app.curRawAdj = app.maxAdj;
+            app.keeping = true;
             app.curSchedGroup = Process.THREAD_GROUP_DEFAULT;
             return (app.curAdj=app.maxAdj);
        }
@@ -11333,6 +11481,7 @@
         app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN;
         app.adjSource = null;
         app.adjTarget = null;
+        app.keeping = false;
         app.empty = false;
         app.hidden = false;
 
@@ -11462,6 +11611,9 @@
                     if (adj > SECONDARY_SERVER_ADJ) {
                         app.adjType = "started-bg-services";
                     }
+                    // Don't kill this process because it is doing work; it
+                    // has said it is doing work.
+                    app.keeping = true;
                 }
                 if (s.connections.size() > 0 && (adj > FOREGROUND_APP_ADJ
                         || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
@@ -11495,6 +11647,9 @@
                                     if (!client.hidden) {
                                         app.hidden = false;
                                     }
+                                    if (client.keeping) {
+                                        app.keeping = true;
+                                    }
                                     app.adjType = "service";
                                     app.adjTypeCode = ActivityManager.RunningAppProcessInfo
                                             .REASON_SERVICE_IN_USE;
@@ -11528,7 +11683,7 @@
                 }
             }
             
-            // Finally, f this process has active services running in it, we
+            // Finally, if this process has active services running in it, we
             // would like to avoid killing it unless it would prevent the current
             // application from running.  By default we put the process in
             // with the rest of the background processes; as we scan through
@@ -11570,6 +11725,9 @@
                             if (!client.hidden) {
                                 app.hidden = false;
                             }
+                            if (client.keeping) {
+                                app.keeping = true;
+                            }
                             app.adjType = "provider";
                             app.adjTypeCode = ActivityManager.RunningAppProcessInfo
                                     .REASON_PROVIDER_IN_USE;
@@ -11589,6 +11747,7 @@
                         adj = FOREGROUND_APP_ADJ;
                         schedGroup = Process.THREAD_GROUP_DEFAULT;
                         app.hidden = false;
+                        app.keeping = true;
                         app.adjType = "provider";
                         app.adjTarget = cpr.name;
                     }
@@ -11606,6 +11765,9 @@
                 schedGroup = Process.THREAD_GROUP_DEFAULT;
             }
         }
+        if (adj < HIDDEN_APP_MIN_ADJ) {
+            app.keeping = true;
+        }
 
         app.curAdj = adj;
         app.curSchedGroup = schedGroup;
@@ -11743,57 +11905,99 @@
         }
     }
 
-    final void checkExcessiveWakeLocksLocked(boolean doKills) {
+    final void checkExcessivePowerUsageLocked(boolean doKills) {
+        updateCpuStatsNow();
+
         BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
-        if (mLastWakeLockCheckTime == 0) {
-            doKills = false;
+        boolean doWakeKills = doKills;
+        boolean doCpuKills = doKills;
+        if (mLastPowerCheckRealtime == 0) {
+            doWakeKills = false;
+        }
+        if (mLastPowerCheckUptime == 0) {
+            doCpuKills = false;
         }
         if (stats.isScreenOn()) {
-            doKills = false;
+            doWakeKills = false;
         }
         final long curRealtime = SystemClock.elapsedRealtime();
-        final long timeSince = curRealtime - mLastWakeLockCheckTime;
-        mLastWakeLockCheckTime = curRealtime;
-        if (timeSince < (WAKE_LOCK_CHECK_DELAY/3)) {
-            doKills = false;
+        final long realtimeSince = curRealtime - mLastPowerCheckRealtime;
+        final long curUptime = SystemClock.uptimeMillis();
+        final long uptimeSince = curUptime - mLastPowerCheckUptime;
+        mLastPowerCheckRealtime = curRealtime;
+        mLastPowerCheckUptime = curUptime;
+        if (realtimeSince < WAKE_LOCK_MIN_CHECK_DURATION) {
+            doWakeKills = false;
+        }
+        if (uptimeSince < CPU_MIN_CHECK_DURATION) {
+            doCpuKills = false;
         }
         int i = mLruProcesses.size();
         while (i > 0) {
             i--;
             ProcessRecord app = mLruProcesses.get(i);
-            if (app.curAdj >= HIDDEN_APP_MIN_ADJ) {
+            if (!app.keeping) {
                 long wtime;
                 synchronized (stats) {
                     wtime = stats.getProcessWakeTime(app.info.uid,
                             app.pid, curRealtime);
                 }
-                long timeUsed = wtime - app.lastWakeTime;
-                if (false) {
+                long wtimeUsed = wtime - app.lastWakeTime;
+                long cputimeUsed = app.curCpuTime - app.lastCpuTime;
+                if (DEBUG_POWER) {
                     StringBuilder sb = new StringBuilder(128);
                     sb.append("Wake for ");
                     app.toShortString(sb);
                     sb.append(": over ");
-                    TimeUtils.formatDuration(timeSince, sb);
+                    TimeUtils.formatDuration(realtimeSince, sb);
                     sb.append(" used ");
-                    TimeUtils.formatDuration(timeUsed, sb);
+                    TimeUtils.formatDuration(wtimeUsed, sb);
                     sb.append(" (");
-                    sb.append((timeUsed*100)/timeSince);
+                    sb.append((wtimeUsed*100)/realtimeSince);
+                    sb.append("%)");
+                    Slog.i(TAG, sb.toString());
+                    sb.setLength(0);
+                    sb.append("CPU for ");
+                    app.toShortString(sb);
+                    sb.append(": over ");
+                    TimeUtils.formatDuration(uptimeSince, sb);
+                    sb.append(" used ");
+                    TimeUtils.formatDuration(cputimeUsed, sb);
+                    sb.append(" (");
+                    sb.append((cputimeUsed*100)/uptimeSince);
                     sb.append("%)");
                     Slog.i(TAG, sb.toString());
                 }
                 // If a process has held a wake lock for more
                 // than 50% of the time during this period,
                 // that sounds pad.  Kill!
-                if (doKills && timeSince > 0
-                        && ((timeUsed*100)/timeSince) >= 50) {
-                    Slog.i(TAG, "Excessive wake lock in " + app.processName
-                            + " (pid " + app.pid + "): held " + timeUsed
-                            + " during " + timeSince);
+                if (doWakeKills && realtimeSince > 0
+                        && ((wtimeUsed*100)/realtimeSince) >= 50) {
+                    synchronized (stats) {
+                        stats.reportExcessiveWakeLocked(app.info.uid, app.processName,
+                                realtimeSince, wtimeUsed);
+                    }
+                    Slog.w(TAG, "Excessive wake lock in " + app.processName
+                            + " (pid " + app.pid + "): held " + wtimeUsed
+                            + " during " + realtimeSince);
                     EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
                             app.processName, app.setAdj, "excessive wake lock");
                     Process.killProcessQuiet(app.pid);
+                } else if (doCpuKills && uptimeSince > 0
+                        && ((cputimeUsed*100)/uptimeSince) >= 50) {
+                    synchronized (stats) {
+                        stats.reportExcessiveCpuLocked(app.info.uid, app.processName,
+                                uptimeSince, cputimeUsed);
+                    }
+                    Slog.w(TAG, "Excessive CPU in " + app.processName
+                            + " (pid " + app.pid + "): used " + cputimeUsed
+                            + " during " + uptimeSince);
+                    EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
+                            app.processName, app.setAdj, "excessive cpu");
+                    Process.killProcessQuiet(app.pid);
                 } else {
                     app.lastWakeTime = wtime;
+                    app.lastCpuTime = app.curCpuTime;
                 }
             }
         }
@@ -11807,6 +12011,8 @@
             return true;
         }
 
+        final boolean wasKeeping = app.keeping;
+
         int adj = computeOomAdjLocked(app, hiddenAdj, TOP_APP, false);
 
         if ((app.pid != 0 && app.pid != MY_PID) || Process.supportsProcesses()) {
@@ -11821,13 +12027,20 @@
                     // Likewise do a gc when an app is moving in to the
                     // background (such as a service stopping).
                     scheduleAppGcLocked(app);
-                    // And note its current wake lock time.
+                }
+
+                if (wasKeeping && !app.keeping) {
+                    // This app is no longer something we want to keep.  Note
+                    // its current wake lock time to later know to kill it if
+                    // it is not behaving well.
                     BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics();
                     synchronized (stats) {
                         app.lastWakeTime = stats.getProcessWakeTime(app.info.uid,
                                 app.pid, SystemClock.elapsedRealtime());
                     }
+                    app.lastCpuTime = app.curCpuTime;
                 }
+
                 app.setRawAdj = app.curRawAdj;
             }
             if (adj != app.setAdj) {
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 67df707..404c6be 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -60,6 +60,7 @@
     int setAdj;                 // Last set OOM adjustment for this process
     int curSchedGroup;          // Currently desired scheduling class
     int setSchedGroup;          // Last set to background scheduling class
+    boolean keeping;            // Actively running code so don't kill due to that?
     boolean setIsForeground;    // Running foreground UI when last set?
     boolean foregroundServices; // Running any services that are foreground?
     boolean bad;                // True if disabled in the bad process list
@@ -75,6 +76,8 @@
     ComponentName instrumentationResultClass;// copy of instrumentationClass
     BroadcastRecord curReceiver;// receiver currently running in the app
     long lastWakeTime;          // How long proc held wake lock at last check
+    long lastCpuTime;           // How long proc has run CPU at last check
+    long curCpuTime;            // How long proc has run CPU most recently
     long lastRequestedGc;       // When we last asked the app to do a gc
     long lastLowMemory;         // When we last told the app that memory is low
     boolean reportLowMemory;    // Set to true when waiting to report low mem
@@ -131,13 +134,6 @@
     void dump(PrintWriter pw, String prefix) {
         final long now = SystemClock.uptimeMillis();
 
-        long wtime;
-        synchronized (batteryStats.getBatteryStats()) {
-            wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid,
-                    pid, SystemClock.elapsedRealtime());
-        }
-        long timeUsed = wtime - lastWakeTime;
-
         if (info.className != null) {
             pw.print(prefix); pw.print("class="); pw.println(info.className);
         }
@@ -170,6 +166,7 @@
         pw.print(prefix); pw.print("lastActivityTime=");
                 TimeUtils.formatDuration(lastActivityTime, now, pw);
                 pw.print(" lruWeight="); pw.print(lruWeight);
+                pw.print(" keeping="); pw.print(keeping);
                 pw.print(" hidden="); pw.print(hidden);
                 pw.print(" empty="); pw.println(empty);
         pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj);
@@ -188,9 +185,20 @@
                 pw.print(" persistentActivities="); pw.println(persistentActivities);
         pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq);
                 pw.print(" lruSeq="); pw.println(lruSeq);
-        pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
-                pw.print(" time used=");
-                TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+        if (!keeping) {
+            long wtime;
+            synchronized (batteryStats.getBatteryStats()) {
+                wtime = batteryStats.getBatteryStats().getProcessWakeTime(info.uid,
+                        pid, SystemClock.elapsedRealtime());
+            }
+            long timeUsed = wtime - lastWakeTime;
+            pw.print(prefix); pw.print("lastWakeTime="); pw.print(lastWakeTime);
+                    pw.print(" time used=");
+                    TimeUtils.formatDuration(timeUsed, pw); pw.println("");
+            pw.print(prefix); pw.print("lastCpuTime="); pw.print(lastCpuTime);
+                    pw.print(" time used=");
+                    TimeUtils.formatDuration(curCpuTime-lastCpuTime, pw); pw.println("");
+        }
         pw.print(prefix); pw.print("lastRequestedGc=");
                 TimeUtils.formatDuration(lastRequestedGc, now, pw);
                 pw.print(" lastLowMemory=");
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index f35a68e..e5aceb4 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -347,7 +347,9 @@
                         // If it gave us a garbage notification, it doesn't
                         // get to be foreground.
                         ams.setServiceForeground(name, ServiceRecord.this,
-                                localForegroundId, null, true);
+                                0, null, true);
+                        ams.crashApplication(appUid, appPid, localPackageName,
+                                "Bad notification for startForeground: " + e);
                     }
                 }
             });
diff --git a/services/java/com/android/server/sip/SipService.java b/services/java/com/android/server/sip/SipService.java
index f1dcd5a..3f43e1c 100644
--- a/services/java/com/android/server/sip/SipService.java
+++ b/services/java/com/android/server/sip/SipService.java
@@ -30,8 +30,8 @@
 import android.net.sip.SipErrorCode;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
+import android.net.sip.SipSession;
 import android.net.sip.SipSessionAdapter;
-import android.net.sip.SipSessionState;
 import android.net.wifi.WifiManager;
 import android.os.Binder;
 import android.os.Bundle;
@@ -143,7 +143,7 @@
     }
 
     private void openToReceiveCalls(SipProfile localProfile) {
-        open3(localProfile, SipManager.SIP_INCOMING_CALL_ACTION, null);
+        open3(localProfile, SipManager.ACTION_SIP_INCOMING_CALL, null);
     }
 
     public synchronized void open3(SipProfile localProfile,
@@ -255,15 +255,15 @@
 
     private void notifyProfileAdded(SipProfile localProfile) {
         if (DEBUG) Log.d(TAG, "notify: profile added: " + localProfile);
-        Intent intent = new Intent(SipManager.SIP_ADD_PHONE_ACTION);
-        intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+        Intent intent = new Intent(SipManager.ACTION_SIP_ADD_PHONE);
+        intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
         mContext.sendBroadcast(intent);
     }
 
     private void notifyProfileRemoved(SipProfile localProfile) {
         if (DEBUG) Log.d(TAG, "notify: profile removed: " + localProfile);
-        Intent intent = new Intent(SipManager.SIP_REMOVE_PHONE_ACTION);
-        intent.putExtra(SipManager.LOCAL_URI_KEY, localProfile.getUriString());
+        Intent intent = new Intent(SipManager.ACTION_SIP_REMOVE_PHONE);
+        intent.putExtra(SipManager.EXTRA_LOCAL_URI, localProfile.getUriString());
         mContext.sendBroadcast(intent);
     }
 
@@ -474,8 +474,8 @@
                     // send out incoming call broadcast
                     addPendingSession(session);
                     Intent intent = SipManager.createIncomingCallBroadcast(
-                            mIncomingCallBroadcastAction, session.getCallId(),
-                            sessionDescription);
+                            session.getCallId(), sessionDescription)
+                            .setAction(mIncomingCallBroadcastAction);
                     if (DEBUG) Log.d(TAG, " ringing~~ " + getUri() + ": "
                             + caller.getUri() + ": " + session.getCallId()
                             + " " + mIncomingCallBroadcastAction);
@@ -613,10 +613,10 @@
 
                 try {
                     int state = (mSession == null)
-                            ? SipSessionState.READY_TO_CALL
+                            ? SipSession.State.READY_TO_CALL
                             : mSession.getState();
-                    if ((state == SipSessionState.REGISTERING)
-                            || (state == SipSessionState.DEREGISTERING)) {
+                    if ((state == SipSession.State.REGISTERING)
+                            || (state == SipSession.State.DEREGISTERING)) {
                         mProxy.onRegistering(mSession);
                     } else if (mRegistered) {
                         int duration = (int)
@@ -1138,7 +1138,8 @@
                 event.mTriggerTime += event.mPeriod;
 
                 // run the callback in a new thread to prevent deadlock
-                new Thread(event.mCallback).start();
+                new Thread(event.mCallback, "SipServiceTimerCallbackThread")
+                        .start();
             }
             if (DEBUG_TIMER) {
                 Log.d(TAG, "after timeout execution");
diff --git a/services/java/com/android/server/sip/SipSessionGroup.java b/services/java/com/android/server/sip/SipSessionGroup.java
index b4c2241..66a2c05 100644
--- a/services/java/com/android/server/sip/SipSessionGroup.java
+++ b/services/java/com/android/server/sip/SipSessionGroup.java
@@ -25,11 +25,10 @@
 
 import android.net.sip.ISipSession;
 import android.net.sip.ISipSessionListener;
-import android.net.sip.SessionDescription;
 import android.net.sip.SipErrorCode;
 import android.net.sip.SipProfile;
+import android.net.sip.SipSession;
 import android.net.sip.SipSessionAdapter;
-import android.net.sip.SipSessionState;
 import android.text.TextUtils;
 import android.util.Log;
 
@@ -121,7 +120,7 @@
         reset(localIp);
     }
 
-    void reset(String localIp) throws SipException, IOException {
+    synchronized void reset(String localIp) throws SipException, IOException {
         mLocalIp = localIp;
         if (localIp == null) return;
 
@@ -301,7 +300,7 @@
             boolean processed = (session != null) && session.process(event);
             if (isLoggable && processed) {
                 Log.d(TAG, "new state after: "
-                        + SipSessionState.toString(session.mState));
+                        + SipSession.State.toString(session.mState));
             }
         } catch (Throwable e) {
             Log.w(TAG, "event process error: " + event, e);
@@ -332,7 +331,7 @@
 
         public boolean process(EventObject evt) throws SipException {
             if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
-                    + SipSessionState.toString(mState) + ": processing "
+                    + SipSession.State.toString(mState) + ": processing "
                     + log(evt));
             if (isRequestEvent(Request.INVITE, evt)) {
                 RequestEvent event = (RequestEvent) evt;
@@ -342,13 +341,16 @@
                 newSession.mDialog = newSession.mServerTransaction.getDialog();
                 newSession.mInviteReceived = event;
                 newSession.mPeerProfile = createPeerProfile(event.getRequest());
-                newSession.mState = SipSessionState.INCOMING_CALL;
+                newSession.mState = SipSession.State.INCOMING_CALL;
                 newSession.mPeerSessionDescription =
                         extractContent(event.getRequest());
                 addSipSession(newSession);
                 mProxy.onRinging(newSession, newSession.mPeerProfile,
                         newSession.mPeerSessionDescription);
                 return true;
+            } else if (isRequestEvent(Request.OPTIONS, evt)) {
+                mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
+                return true;
             } else {
                 return false;
             }
@@ -358,7 +360,7 @@
     class SipSessionImpl extends ISipSession.Stub {
         SipProfile mPeerProfile;
         SipSessionListenerProxy mProxy = new SipSessionListenerProxy();
-        int mState = SipSessionState.READY_TO_CALL;
+        int mState = SipSession.State.READY_TO_CALL;
         RequestEvent mInviteReceived;
         Dialog mDialog;
         ServerTransaction mServerTransaction;
@@ -378,7 +380,7 @@
                         sleep(timeout);
                         if (mRunning) timeout();
                     }
-                }).start();
+                }, "SipSessionTimerThread").start();
             }
 
             synchronized void cancel() {
@@ -413,7 +415,7 @@
             mInCall = false;
             removeSipSession(this);
             mPeerProfile = null;
-            mState = SipSessionState.READY_TO_CALL;
+            mState = SipSession.State.READY_TO_CALL;
             mInviteReceived = null;
             mDialog = null;
             mServerTransaction = null;
@@ -470,7 +472,7 @@
                             onError(e);
                         }
                     }
-            }).start();
+            }, "SipSessionAsyncCmdThread").start();
         }
 
         public void makeCall(SipProfile peerProfile, String sessionDescription,
@@ -520,10 +522,10 @@
         }
 
         public void sendKeepAlive() {
-            mState = SipSessionState.PINGING;
+            mState = SipSession.State.PINGING;
             try {
                 processCommand(new OptionsCommand());
-                while (SipSessionState.PINGING == mState) {
+                while (SipSession.State.PINGING == mState) {
                     Thread.sleep(1000);
                 }
             } catch (SipException e) {
@@ -550,7 +552,7 @@
             try {
                 String s = super.toString();
                 return s.substring(s.indexOf("@")) + ":"
-                        + SipSessionState.toString(mState);
+                        + SipSession.State.toString(mState);
             } catch (Throwable e) {
                 return super.toString();
             }
@@ -558,7 +560,7 @@
 
         public boolean process(EventObject evt) throws SipException {
             if (isLoggable(this, evt)) Log.d(TAG, " ~~~~~   " + this + ": "
-                    + SipSessionState.toString(mState) + ": processing "
+                    + SipSession.State.toString(mState) + ": processing "
                     + log(evt));
             synchronized (SipSessionGroup.this) {
                 if (isClosed()) return false;
@@ -574,30 +576,30 @@
                 boolean processed;
 
                 switch (mState) {
-                case SipSessionState.REGISTERING:
-                case SipSessionState.DEREGISTERING:
+                case SipSession.State.REGISTERING:
+                case SipSession.State.DEREGISTERING:
                     processed = registeringToReady(evt);
                     break;
-                case SipSessionState.PINGING:
+                case SipSession.State.PINGING:
                     processed = keepAliveProcess(evt);
                     break;
-                case SipSessionState.READY_TO_CALL:
+                case SipSession.State.READY_TO_CALL:
                     processed = readyForCall(evt);
                     break;
-                case SipSessionState.INCOMING_CALL:
+                case SipSession.State.INCOMING_CALL:
                     processed = incomingCall(evt);
                     break;
-                case SipSessionState.INCOMING_CALL_ANSWERING:
+                case SipSession.State.INCOMING_CALL_ANSWERING:
                     processed = incomingCallToInCall(evt);
                     break;
-                case SipSessionState.OUTGOING_CALL:
-                case SipSessionState.OUTGOING_CALL_RING_BACK:
+                case SipSession.State.OUTGOING_CALL:
+                case SipSession.State.OUTGOING_CALL_RING_BACK:
                     processed = outgoingCall(evt);
                     break;
-                case SipSessionState.OUTGOING_CALL_CANCELING:
+                case SipSession.State.OUTGOING_CALL_CANCELING:
                     processed = outgoingCallToReady(evt);
                     break;
-                case SipSessionState.IN_CALL:
+                case SipSession.State.IN_CALL:
                     processed = inCall(evt);
                     break;
                 default:
@@ -625,6 +627,9 @@
                             (TransactionTerminatedEvent) evt);
                 }
                 return true;
+            } else if (isRequestEvent(Request.OPTIONS, evt)) {
+                mSipHelper.sendResponse((RequestEvent) evt, Response.OK);
+                return true;
             } else if (evt instanceof DialogTerminatedEvent) {
                 processDialogTerminated((DialogTerminatedEvent) evt);
                 return true;
@@ -644,8 +649,8 @@
         private void processTransactionTerminated(
                 TransactionTerminatedEvent event) {
             switch (mState) {
-                case SipSessionState.IN_CALL:
-                case SipSessionState.READY_TO_CALL:
+                case SipSession.State.IN_CALL:
+                case SipSession.State.READY_TO_CALL:
                     Log.d(TAG, "Transaction terminated; do nothing");
                     break;
                 default:
@@ -664,27 +669,27 @@
                     ? event.getServerTransaction()
                     : event.getClientTransaction();
 
-            if ((current != target) && (mState != SipSessionState.PINGING)) {
+            if ((current != target) && (mState != SipSession.State.PINGING)) {
                 Log.d(TAG, "not the current transaction; current=" + current
                         + ", timed out=" + target);
                 return;
             }
             switch (mState) {
-                case SipSessionState.REGISTERING:
-                case SipSessionState.DEREGISTERING:
+                case SipSession.State.REGISTERING:
+                case SipSession.State.DEREGISTERING:
                     reset();
                     mProxy.onRegistrationTimeout(this);
                     break;
-                case SipSessionState.INCOMING_CALL:
-                case SipSessionState.INCOMING_CALL_ANSWERING:
-                case SipSessionState.OUTGOING_CALL:
-                case SipSessionState.OUTGOING_CALL_CANCELING:
+                case SipSession.State.INCOMING_CALL:
+                case SipSession.State.INCOMING_CALL_ANSWERING:
+                case SipSession.State.OUTGOING_CALL:
+                case SipSession.State.OUTGOING_CALL_CANCELING:
                     onError(SipErrorCode.TIME_OUT, event.toString());
                     break;
-                case SipSessionState.PINGING:
+                case SipSession.State.PINGING:
                     reset();
                     mReRegisterFlag = true;
-                    mState = SipSessionState.READY_TO_CALL;
+                    mState = SipSession.State.READY_TO_CALL;
                     break;
 
                 default:
@@ -758,7 +763,7 @@
                 switch (statusCode) {
                 case Response.OK:
                     int state = mState;
-                    onRegistrationDone((state == SipSessionState.REGISTERING)
+                    onRegistrationDone((state == SipSession.State.REGISTERING)
                             ? getExpiryTime(((ResponseEvent) evt).getResponse())
                             : -1);
                     mLastNonce = null;
@@ -845,7 +850,7 @@
                         generateTag());
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSessionState.OUTGOING_CALL;
+                mState = SipSession.State.OUTGOING_CALL;
                 mProxy.onCalling(this);
                 startSessionTimer(cmd.getTimeout());
                 return true;
@@ -855,7 +860,7 @@
                         generateTag(), duration);
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSessionState.REGISTERING;
+                mState = SipSession.State.REGISTERING;
                 mProxy.onRegistering(this);
                 return true;
             } else if (DEREGISTER == evt) {
@@ -863,7 +868,7 @@
                         generateTag(), 0);
                 mDialog = mClientTransaction.getDialog();
                 addSipSession(this);
-                mState = SipSessionState.DEREGISTERING;
+                mState = SipSession.State.DEREGISTERING;
                 mProxy.onRegistering(this);
                 return true;
             }
@@ -878,7 +883,7 @@
                         mLocalProfile,
                         ((MakeCallCommand) evt).getSessionDescription(),
                         mServerTransaction);
-                mState = SipSessionState.INCOMING_CALL_ANSWERING;
+                mState = SipSession.State.INCOMING_CALL_ANSWERING;
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             } else if (END_CALL == evt) {
@@ -919,8 +924,8 @@
                 int statusCode = response.getStatusCode();
                 switch (statusCode) {
                 case Response.RINGING:
-                    if (mState == SipSessionState.OUTGOING_CALL) {
-                        mState = SipSessionState.OUTGOING_CALL_RING_BACK;
+                    if (mState == SipSession.State.OUTGOING_CALL) {
+                        mState = SipSession.State.OUTGOING_CALL_RING_BACK;
                         mProxy.onRingingBack(this);
                         cancelSessionTimer();
                     }
@@ -963,7 +968,7 @@
                 // response comes back yet. We are cheating for not checking
                 // response.
                 mSipHelper.sendCancel(mClientTransaction);
-                mState = SipSessionState.OUTGOING_CALL_CANCELING;
+                mState = SipSession.State.OUTGOING_CALL_CANCELING;
                 startSessionTimer(CANCEL_CALL_TIMER);
                 return true;
             }
@@ -1019,7 +1024,7 @@
             } else if (isRequestEvent(Request.INVITE, evt)) {
                 // got Re-INVITE
                 RequestEvent event = mInviteReceived = (RequestEvent) evt;
-                mState = SipSessionState.INCOMING_CALL;
+                mState = SipSession.State.INCOMING_CALL;
                 mPeerSessionDescription = extractContent(event.getRequest());
                 mServerTransaction = null;
                 mProxy.onRinging(this, mPeerProfile, mPeerSessionDescription);
@@ -1032,7 +1037,7 @@
                 // to change call
                 mClientTransaction = mSipHelper.sendReinvite(mDialog,
                         ((MakeCallCommand) evt).getSessionDescription());
-                mState = SipSessionState.OUTGOING_CALL;
+                mState = SipSession.State.OUTGOING_CALL;
                 startSessionTimer(((MakeCallCommand) evt).getTimeout());
                 return true;
             }
@@ -1060,14 +1065,14 @@
         }
 
         private void establishCall() {
-            mState = SipSessionState.IN_CALL;
+            mState = SipSession.State.IN_CALL;
             mInCall = true;
             cancelSessionTimer();
             mProxy.onCallEstablished(this, mPeerSessionDescription);
         }
 
         private void fallbackToPreviousInCall(int errorCode, String message) {
-            mState = SipSessionState.IN_CALL;
+            mState = SipSession.State.IN_CALL;
             mProxy.onCallChangeFailed(this, errorCode, message);
         }
 
@@ -1089,8 +1094,8 @@
         private void onError(int errorCode, String message) {
             cancelSessionTimer();
             switch (mState) {
-                case SipSessionState.REGISTERING:
-                case SipSessionState.DEREGISTERING:
+                case SipSession.State.REGISTERING:
+                case SipSession.State.DEREGISTERING:
                     onRegistrationFailed(errorCode, message);
                     break;
                 default:
@@ -1264,7 +1269,7 @@
     private static boolean isLoggable(SipSessionImpl s) {
         if (s != null) {
             switch (s.mState) {
-                case SipSessionState.PINGING:
+                case SipSession.State.PINGING:
                     return DEBUG_PING;
             }
         }
diff --git a/services/java/com/android/server/sip/SipSessionListenerProxy.java b/services/java/com/android/server/sip/SipSessionListenerProxy.java
index a4cd102..f8be0a8 100644
--- a/services/java/com/android/server/sip/SipSessionListenerProxy.java
+++ b/services/java/com/android/server/sip/SipSessionListenerProxy.java
@@ -40,7 +40,7 @@
         // One thread for each calling back.
         // Note: Guarantee ordering if the issue becomes important. Currently,
         // the chance of handling two callback events at a time is none.
-        new Thread(runnable).start();
+        new Thread(runnable, "SipSessionCallbackThread").start();
     }
 
     public void onCalling(final ISipSession session) {
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.cpp b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
index 129be4e..ff887e4 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -21,6 +21,7 @@
 #include <sys/types.h>
 
 #include <utils/Errors.h>
+#include <utils/String8.h>
 
 #include <hardware/hardware.h>
 
@@ -100,5 +101,25 @@
     return mList ? mList->hwLayers : 0;
 }
 
+void HWComposer::dump(String8& result, char* buffer, size_t SIZE) const {
+    if (mHwc && mList) {
+        result.append("Hardware Composer state:\n");
+
+        snprintf(buffer, SIZE, "  numHwLayers=%u, flags=%08x\n",
+                mList->numHwLayers, mList->flags);
+        result.append(buffer);
+
+        for (size_t i=0 ; i<mList->numHwLayers ; i++) {
+            const hwc_layer_t& l(mList->hwLayers[i]);
+            snprintf(buffer, SIZE, "  %8s | %08x | %08x | %02x | %04x | [%5d,%5d,%5d,%5d] |  [%5d,%5d,%5d,%5d]\n",
+                    l.compositionType ? "OVERLAY" : "FB",
+                    l.hints, l.flags, l.transform, l.blending,
+                    l.sourceCrop.left, l.sourceCrop.top, l.sourceCrop.right, l.sourceCrop.bottom,
+                    l.displayFrame.left, l.displayFrame.top, l.displayFrame.right, l.displayFrame.bottom);
+            result.append(buffer);
+        }
+    }
+}
+
 // ---------------------------------------------------------------------------
 }; // namespace android
diff --git a/services/surfaceflinger/DisplayHardware/HWComposer.h b/services/surfaceflinger/DisplayHardware/HWComposer.h
index 22ff10c..5a9e9eb 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -27,6 +27,8 @@
 namespace android {
 // ---------------------------------------------------------------------------
 
+class String8;
+
 class HWComposer
 {
 public:
@@ -54,6 +56,9 @@
     size_t getNumLayers() const;
     hwc_layer_t* getLayers() const;
 
+    // for debugging
+    void dump(String8& out, char* scratch, size_t SIZE) const;
+
 private:
     hw_module_t const*      mModule;
     hwc_composer_device_t*  mHwc;
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 06c86dd..b353bff 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -1533,6 +1533,7 @@
                 hwc.initCheck()==NO_ERROR ? "present" : "not present",
                 mDebugDisableHWC ? "disabled" : "enabled");
         result.append(buffer);
+        hwc.dump(result, buffer, SIZE);
 
         const GraphicBufferAllocator& alloc(GraphicBufferAllocator::get());
         alloc.dump(result);
diff --git a/telephony/java/com/android/internal/telephony/sip/SipPhone.java b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
index 9fcf12d..4791fbd 100755
--- a/telephony/java/com/android/internal/telephony/sip/SipPhone.java
+++ b/telephony/java/com/android/internal/telephony/sip/SipPhone.java
@@ -27,7 +27,7 @@
 import android.net.sip.SipException;
 import android.net.sip.SipManager;
 import android.net.sip.SipProfile;
-import android.net.sip.SipSessionState;
+import android.net.sip.SipSession;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.Looper;
@@ -91,7 +91,7 @@
         foregroundCall = new SipCall();
         backgroundCall = new SipCall();
         mProfile = profile;
-        mSipManager = SipManager.getInstance(context);
+        mSipManager = SipManager.newInstance(context);
 
         // FIXME: what's this for SIP?
         //Change the system property
@@ -710,8 +710,8 @@
 
         void dial() throws SipException {
             setState(Call.State.DIALING);
-            mSipAudioCall = mSipManager.makeAudioCall(mContext, mProfile,
-                    mPeer, null, SESSION_TIMEOUT);
+            mSipAudioCall = mSipManager.makeAudioCall(mProfile, mPeer, null,
+                    SESSION_TIMEOUT);
             mSipAudioCall.setRingbackToneEnabled(false);
             mSipAudioCall.setListener(mAdapter);
         }
@@ -807,20 +807,20 @@
         if (sipAudioCall.isOnHold()) return Call.State.HOLDING;
         int sessionState = sipAudioCall.getState();
         switch (sessionState) {
-            case SipSessionState.READY_TO_CALL:            return Call.State.IDLE;
-            case SipSessionState.INCOMING_CALL:
-            case SipSessionState.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
-            case SipSessionState.OUTGOING_CALL:            return Call.State.DIALING;
-            case SipSessionState.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
-            case SipSessionState.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
-            case SipSessionState.IN_CALL:                  return Call.State.ACTIVE;
+            case SipSession.State.READY_TO_CALL:            return Call.State.IDLE;
+            case SipSession.State.INCOMING_CALL:
+            case SipSession.State.INCOMING_CALL_ANSWERING:  return Call.State.INCOMING;
+            case SipSession.State.OUTGOING_CALL:            return Call.State.DIALING;
+            case SipSession.State.OUTGOING_CALL_RING_BACK:  return Call.State.ALERTING;
+            case SipSession.State.OUTGOING_CALL_CANCELING:  return Call.State.DISCONNECTING;
+            case SipSession.State.IN_CALL:                  return Call.State.ACTIVE;
             default:
                 Log.w(LOG_TAG, "illegal connection state: " + sessionState);
                 return Call.State.DISCONNECTED;
         }
     }
 
-    private abstract class SipAudioCallAdapter extends SipAudioCall.Adapter {
+    private abstract class SipAudioCallAdapter extends SipAudioCall.Listener {
         protected abstract void onCallEnded(Connection.DisconnectCause cause);
         protected abstract void onError(Connection.DisconnectCause cause);
 
diff --git a/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java b/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java
index 48c4520..5fb09a7 100644
--- a/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java
+++ b/tests/BatteryWaster/src/com/android/batterywaster/BatteryWaster.java
@@ -25,6 +25,7 @@
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.view.View;
+import android.view.WindowManager;
 import android.widget.CheckBox;
 import android.widget.TextView;
 
@@ -38,7 +39,6 @@
     TextView mLog;
     DateFormat mDateFormat;
     IntentFilter mFilter;
-    PowerManager.WakeLock mWakeLock;
     PowerManager.WakeLock mPartialWakeLock;
     SpinThread mThread;
 
@@ -65,24 +65,26 @@
         mFilter.addAction(Intent.ACTION_POWER_CONNECTED);
 
         PowerManager pm = (PowerManager)getSystemService(POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "BatteryWaster");
-        mWakeLock.setReferenceCounted(false);
         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BatteryWaster");
         mPartialWakeLock.setReferenceCounted(false);
     }
 
     @Override
-    public void onPause() {
-        super.onPause();
-        stopRunning();
+    public void onResume() {
+        super.onResume();
+        if (((CheckBox)findViewById(R.id.checkbox)).isChecked()) {
+            startRunning();
+        }
+        if (((CheckBox)findViewById(R.id.checkbox_wake)).isChecked()) {
+            mWaking = true;
+            updateWakeLock();
+        }
     }
 
     @Override
     public void onDestroy() {
         super.onDestroy();
-        if (mWakeLock.isHeld()) {
-            mWakeLock.release();
-        }
+        stopRunning();
         if (mPartialWakeLock.isHeld()) {
             mPartialWakeLock.release();
         }
@@ -140,13 +142,9 @@
 
     void updateWakeLock() {
         if (mWasting) {
-            if (!mWakeLock.isHeld()) {
-                mWakeLock.acquire();
-            }
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         } else {
-            if (mWakeLock.isHeld()) {
-                mWakeLock.release();
-            }
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
         }
         if (mWaking) {
             if (!mPartialWakeLock.isHeld()) {
diff --git a/voip/java/android/net/sip/SdpSessionDescription.java b/voip/java/android/net/sip/SdpSessionDescription.java
deleted file mode 100644
index f6ae837..0000000
--- a/voip/java/android/net/sip/SdpSessionDescription.java
+++ /dev/null
@@ -1,428 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import gov.nist.javax.sdp.SessionDescriptionImpl;
-import gov.nist.javax.sdp.fields.AttributeField;
-import gov.nist.javax.sdp.fields.ConnectionField;
-import gov.nist.javax.sdp.fields.MediaField;
-import gov.nist.javax.sdp.fields.OriginField;
-import gov.nist.javax.sdp.fields.ProtoVersionField;
-import gov.nist.javax.sdp.fields.SessionNameField;
-import gov.nist.javax.sdp.fields.TimeField;
-import gov.nist.javax.sdp.parser.SDPAnnounceParser;
-
-import android.util.Log;
-
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Vector;
-import javax.sdp.Connection;
-import javax.sdp.MediaDescription;
-import javax.sdp.SdpException;
-
-/**
- * A session description that follows SDP (Session Description Protocol).
- * Refer to <a href="http://tools.ietf.org/html/rfc4566">RFC 4566</a>.
- * @hide
- */
-public class SdpSessionDescription extends SessionDescription {
-    private static final String TAG = "SDP";
-    private static final String AUDIO = "audio";
-    private static final String RTPMAP = "rtpmap";
-    private static final String PTIME = "ptime";
-    private static final String SENDONLY = "sendonly";
-    private static final String RECVONLY = "recvonly";
-    private static final String INACTIVE = "inactive";
-
-    private SessionDescriptionImpl mSessionDescription;
-
-    /**
-     * The audio codec information parsed from "rtpmap".
-     */
-    public static class AudioCodec {
-        public final int payloadType;
-        public final String name;
-        public final int sampleRate;
-        public final int sampleCount;
-
-        public AudioCodec(int payloadType, String name, int sampleRate,
-                int sampleCount) {
-            this.payloadType = payloadType;
-            this.name = name;
-            this.sampleRate = sampleRate;
-            this.sampleCount = sampleCount;
-        }
-    }
-
-    /**
-     * The builder class used to create an {@link SdpSessionDescription} object.
-     */
-    public static class Builder {
-        private SdpSessionDescription mSdp = new SdpSessionDescription();
-        private SessionDescriptionImpl mSessionDescription;
-
-        public Builder(String sessionName) throws SdpException {
-            mSessionDescription = new SessionDescriptionImpl();
-            mSdp.mSessionDescription = mSessionDescription;
-            try {
-                ProtoVersionField proto = new ProtoVersionField();
-                proto.setVersion(0);
-                mSessionDescription.addField(proto);
-
-                TimeField time = new TimeField();
-                time.setZero();
-                mSessionDescription.addField(time);
-
-                SessionNameField session = new SessionNameField();
-                session.setValue(sessionName);
-                mSessionDescription.addField(session);
-            } catch (Exception e) {
-                throwSdpException(e);
-            }
-        }
-
-        public Builder setConnectionInfo(String networkType, String addressType,
-                String addr) throws SdpException {
-            try {
-                ConnectionField connection = new ConnectionField();
-                connection.setNetworkType(networkType);
-                connection.setAddressType(addressType);
-                connection.setAddress(addr);
-                mSessionDescription.addField(connection);
-            } catch (Exception e) {
-                throwSdpException(e);
-            }
-            return this;
-        }
-
-        public Builder setOrigin(SipProfile user, long sessionId,
-                long sessionVersion, String networkType, String addressType,
-                String address) throws SdpException {
-            try {
-                OriginField origin = new OriginField();
-                origin.setUsername(user.getUserName());
-                origin.setSessionId(sessionId);
-                origin.setSessionVersion(sessionVersion);
-                origin.setAddressType(addressType);
-                origin.setNetworkType(networkType);
-                origin.setAddress(address);
-                mSessionDescription.addField(origin);
-            } catch (Exception e) {
-                throwSdpException(e);
-            }
-            return this;
-        }
-
-        public Builder addMedia(String media, int port, int numPorts,
-                String transport, Integer... types) throws SdpException {
-            MediaField field = new MediaField();
-            Vector<Integer> typeVector = new Vector<Integer>();
-            Collections.addAll(typeVector, types);
-            try {
-                field.setMediaType(media);
-                field.setMediaPort(port);
-                field.setPortCount(numPorts);
-                field.setProtocol(transport);
-                field.setMediaFormats(typeVector);
-                mSessionDescription.addField(field);
-            } catch (Exception e) {
-                throwSdpException(e);
-            }
-           return this;
-        }
-
-        public Builder addMediaAttribute(String type, String name, String value)
-                throws SdpException {
-            try {
-                MediaDescription md = mSdp.getMediaDescription(type);
-                if (md == null) {
-                    throw new SdpException("Should add media first!");
-                }
-                AttributeField attribute = new AttributeField();
-                attribute.setName(name);
-                attribute.setValueAllowNull(value);
-                mSessionDescription.addField(attribute);
-            } catch (Exception e) {
-                throwSdpException(e);
-            }
-            return this;
-        }
-
-        public Builder addSessionAttribute(String name, String value)
-                throws SdpException {
-            try {
-                AttributeField attribute = new AttributeField();
-                attribute.setName(name);
-                attribute.setValueAllowNull(value);
-                mSessionDescription.addField(attribute);
-            } catch (Exception e) {
-                throwSdpException(e);
-            }
-            return this;
-        }
-
-        private void throwSdpException(Exception e) throws SdpException {
-            if (e instanceof SdpException) {
-                throw (SdpException) e;
-            } else {
-                throw new SdpException(e.toString(), e);
-            }
-        }
-
-        public String build() {
-            return mSdp.toString();
-        }
-    }
-
-    private SdpSessionDescription() {
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param sdpString an SDP session description to parse
-     */
-    public SdpSessionDescription(String sdpString) throws SdpException {
-        try {
-            mSessionDescription = new SDPAnnounceParser(sdpString).parse();
-        } catch (ParseException e) {
-            throw new SdpException(e.toString(), e);
-        }
-        verify();
-    }
-
-    /**
-     * Constructor.
-     *
-     * @param content a raw SDP session description to parse
-     */
-    public SdpSessionDescription(byte[] content) throws SdpException {
-        this(new String(content));
-    }
-
-    private void verify() throws SdpException {
-        // make sure the syntax is correct over the fields we're interested in
-        Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
-                mSessionDescription.getMediaDescriptions(false);
-        for (MediaDescription md : descriptions) {
-            md.getMedia().getMediaPort();
-            Connection connection = md.getConnection();
-            if (connection != null) connection.getAddress();
-            md.getMedia().getFormats();
-        }
-        Connection connection = mSessionDescription.getConnection();
-        if (connection != null) connection.getAddress();
-    }
-
-    /**
-     * Gets the connection address of the media.
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return the media connection address of the peer
-     */
-    public String getPeerMediaAddress(String type) {
-        try {
-            MediaDescription md = getMediaDescription(type);
-            Connection connection = md.getConnection();
-            if (connection == null) {
-                connection = mSessionDescription.getConnection();
-            }
-            return ((connection == null) ? null : connection.getAddress());
-        } catch (SdpException e) {
-            // should not occur
-            return null;
-        }
-    }
-
-    /**
-     * Gets the connection port number of the media.
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return the media connection port number of the peer
-     */
-    public int getPeerMediaPort(String type) {
-        try {
-            MediaDescription md = getMediaDescription(type);
-            return md.getMedia().getMediaPort();
-        } catch (SdpException e) {
-            // should not occur
-            return -1;
-        }
-    }
-
-    private boolean containsAttribute(String type, String name) {
-        if (name == null) return false;
-        MediaDescription md = getMediaDescription(type);
-        Vector<AttributeField> v = (Vector<AttributeField>)
-                md.getAttributeFields();
-        for (AttributeField field : v) {
-            if (name.equals(field.getAttribute().getName())) return true;
-        }
-        return false;
-    }
-
-    /**
-     * Checks if the media is "sendonly".
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return true if the media is "sendonly"
-     */
-    public boolean isSendOnly(String type) {
-        boolean answer = containsAttribute(type, SENDONLY);
-        Log.d(TAG, "   sendonly? " + answer);
-        return answer;
-    }
-
-    /**
-     * Checks if the media is "recvonly".
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return true if the media is "recvonly"
-     */
-    public boolean isReceiveOnly(String type) {
-        boolean answer = containsAttribute(type, RECVONLY);
-        Log.d(TAG, "   recvonly? " + answer);
-        return answer;
-    }
-
-    /**
-     * Checks if the media is in sending; i.e., not "recvonly" and not
-     * "inactive".
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return true if the media is sending
-     */
-    public boolean isSending(String type) {
-        boolean answer = !containsAttribute(type, RECVONLY)
-                && !containsAttribute(type, INACTIVE);
-
-        Log.d(TAG, "   sending? " + answer);
-        return answer;
-    }
-
-    /**
-     * Checks if the media is in receiving; i.e., not "sendonly" and not
-     * "inactive".
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return true if the media is receiving
-     */
-    public boolean isReceiving(String type) {
-        boolean answer = !containsAttribute(type, SENDONLY)
-                && !containsAttribute(type, INACTIVE);
-        Log.d(TAG, "   receiving? " + answer);
-        return answer;
-    }
-
-    private AudioCodec parseAudioCodec(String rtpmap, int ptime) {
-        String[] ss = rtpmap.split(" ");
-        int payloadType = Integer.parseInt(ss[0]);
-
-        ss = ss[1].split("/");
-        String name = ss[0];
-        int sampleRate = Integer.parseInt(ss[1]);
-        int channelCount = 1;
-        if (ss.length > 2) channelCount = Integer.parseInt(ss[2]);
-        int sampleCount = sampleRate / (1000 / ptime) * channelCount;
-        return new AudioCodec(payloadType, name, sampleRate, sampleCount);
-    }
-
-    /**
-     * Gets the list of audio codecs in this session description.
-     *
-     * @return the list of audio codecs in this session description
-     */
-    public List<AudioCodec> getAudioCodecs() {
-        MediaDescription md = getMediaDescription(AUDIO);
-        if (md == null) return new ArrayList<AudioCodec>();
-
-        // FIXME: what happens if ptime is missing
-        int ptime = 20;
-        try {
-            String value = md.getAttribute(PTIME);
-            if (value != null) ptime = Integer.parseInt(value);
-        } catch (Throwable t) {
-            Log.w(TAG, "getCodecs(): ignored: " + t);
-        }
-
-        List<AudioCodec> codecs = new ArrayList<AudioCodec>();
-        Vector<AttributeField> v = (Vector<AttributeField>)
-                md.getAttributeFields();
-        for (AttributeField field : v) {
-            try {
-                if (RTPMAP.equals(field.getName())) {
-                    AudioCodec codec = parseAudioCodec(field.getValue(), ptime);
-                    if (codec != null) codecs.add(codec);
-                }
-            } catch (Throwable t) {
-                Log.w(TAG, "getCodecs(): ignored: " + t);
-            }
-        }
-        return codecs;
-    }
-
-    /**
-     * Gets the media description of the specified type.
-     *
-     * @param type the media type; e.g., "AUDIO"
-     * @return the media description of the specified type
-     */
-    public MediaDescription getMediaDescription(String type) {
-        MediaDescription[] all = getMediaDescriptions();
-        if ((all == null) || (all.length == 0)) return null;
-        for (MediaDescription md : all) {
-            String t = md.getMedia().getMedia();
-            if (t.equalsIgnoreCase(type)) return md;
-        }
-        return null;
-    }
-
-    /**
-     * Gets all the media descriptions in this session description.
-     *
-     * @return all the media descriptions in this session description
-     */
-    public MediaDescription[] getMediaDescriptions() {
-        try {
-            Vector<MediaDescription> descriptions = (Vector<MediaDescription>)
-                    mSessionDescription.getMediaDescriptions(false);
-            MediaDescription[] all = new MediaDescription[descriptions.size()];
-            return descriptions.toArray(all);
-        } catch (SdpException e) {
-            Log.e(TAG, "getMediaDescriptions", e);
-        }
-        return null;
-    }
-
-    @Override
-    public String getType() {
-        return "sdp";
-    }
-
-    @Override
-    public byte[] getContent() {
-          return mSessionDescription.toString().getBytes();
-    }
-
-    @Override
-    public String toString() {
-        return mSessionDescription.toString();
-    }
-}
diff --git a/voip/java/android/net/sip/SessionDescription.aidl b/voip/java/android/net/sip/SessionDescription.aidl
deleted file mode 100644
index a120d16..0000000
--- a/voip/java/android/net/sip/SessionDescription.aidl
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- * Copyright (C) 2010, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-parcelable SessionDescription;
diff --git a/voip/java/android/net/sip/SessionDescription.java b/voip/java/android/net/sip/SessionDescription.java
deleted file mode 100644
index d476f0b..0000000
--- a/voip/java/android/net/sip/SessionDescription.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * Abstract class of a session description.
- * @hide
- */
-public abstract class SessionDescription implements Parcelable {
-    /** @hide */
-    public static final Parcelable.Creator<SessionDescription> CREATOR =
-            new Parcelable.Creator<SessionDescription>() {
-                public SessionDescription createFromParcel(Parcel in) {
-                    return new SessionDescriptionImpl(in);
-                }
-
-                public SessionDescription[] newArray(int size) {
-                    return new SessionDescriptionImpl[size];
-                }
-            };
-
-    /**
-     * Gets the type of the session description; e.g., "SDP".
-     *
-     * @return the session description type
-     */
-    public abstract String getType();
-
-    /**
-     * Gets the raw content of the session description.
-     *
-     * @return the content of the session description
-     */
-    public abstract byte[] getContent();
-
-    /** @hide */
-    public void writeToParcel(Parcel out, int flags) {
-        out.writeString(getType());
-        out.writeByteArray(getContent());
-    }
-
-    /** @hide */
-    public int describeContents() {
-        return 0;
-    }
-
-    private static class SessionDescriptionImpl extends SessionDescription {
-        private String mType;
-        private byte[] mContent;
-
-        SessionDescriptionImpl(Parcel in) {
-            mType = in.readString();
-            mContent = in.createByteArray();
-        }
-
-        @Override
-        public String getType() {
-            return mType;
-        }
-
-        @Override
-        public byte[] getContent() {
-            return mContent;
-        }
-    }
-}
diff --git a/voip/java/android/net/sip/SipAudioCall.java b/voip/java/android/net/sip/SipAudioCall.java
index 0069fe0..2135fcb 100644
--- a/voip/java/android/net/sip/SipAudioCall.java
+++ b/voip/java/android/net/sip/SipAudioCall.java
@@ -16,120 +16,184 @@
 
 package android.net.sip;
 
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.media.ToneGenerator;
+import android.net.Uri;
+import android.net.rtp.AudioCodec;
 import android.net.rtp.AudioGroup;
 import android.net.rtp.AudioStream;
+import android.net.rtp.RtpStream;
+import android.net.sip.SimpleSessionDescription.Media;
+import android.net.wifi.WifiManager;
 import android.os.Message;
+import android.os.RemoteException;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /**
- * Interface for making audio calls over SIP.
- * @hide
+ * Class that handles an audio call over SIP.
  */
-public interface SipAudioCall {
+/** @hide */
+public class SipAudioCall extends SipSessionAdapter {
+    private static final String TAG = SipAudioCall.class.getSimpleName();
+    private static final boolean RELEASE_SOCKET = true;
+    private static final boolean DONT_RELEASE_SOCKET = false;
+    private static final int SESSION_TIMEOUT = 5; // in seconds
+
     /** Listener class for all event callbacks. */
-    public interface Listener {
+    public static class Listener {
         /**
          * Called when the call object is ready to make another call.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that is ready to make another call
          */
-        void onReadyToCall(SipAudioCall call);
+        public void onReadyToCall(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
          * Called when a request is sent out to initiate a new call.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          */
-        void onCalling(SipAudioCall call);
+        public void onCalling(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
          * Called when a new call comes in.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          * @param caller the SIP profile of the caller
          */
-        void onRinging(SipAudioCall call, SipProfile caller);
+        public void onRinging(SipAudioCall call, SipProfile caller) {
+            onChanged(call);
+        }
 
         /**
-         * Called when a RINGING response is received for the INVITE request sent
+         * Called when a RINGING response is received for the INVITE request
+         * sent. The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          */
-        void onRingingBack(SipAudioCall call);
+        public void onRingingBack(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
          * Called when the session is established.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          */
-        void onCallEstablished(SipAudioCall call);
+        public void onCallEstablished(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
          * Called when the session is terminated.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          */
-        void onCallEnded(SipAudioCall call);
+        public void onCallEnded(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
          * Called when the peer is busy during session initialization.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          */
-        void onCallBusy(SipAudioCall call);
+        public void onCallBusy(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
          * Called when the call is on hold.
+         * The default implementation calls {@link #onChange}.
          *
          * @param call the call object that carries out the audio call
          */
-        void onCallHeld(SipAudioCall call);
+        public void onCallHeld(SipAudioCall call) {
+            onChanged(call);
+        }
 
         /**
-         * Called when an error occurs.
+         * Called when an error occurs. The default implementation is no op.
          *
          * @param call the call object that carries out the audio call
          * @param errorCode error code of this error
          * @param errorMessage error message
          * @see SipErrorCode
          */
-        void onError(SipAudioCall call, int errorCode, String errorMessage);
-    }
-
-    /**
-     * The adapter class for {@link Listener}. The default implementation of
-     * all callback methods is no-op.
-     */
-    public class Adapter implements Listener {
-        protected void onChanged(SipAudioCall call) {
-        }
-        public void onReadyToCall(SipAudioCall call) {
-            onChanged(call);
-        }
-        public void onCalling(SipAudioCall call) {
-            onChanged(call);
-        }
-        public void onRinging(SipAudioCall call, SipProfile caller) {
-            onChanged(call);
-        }
-        public void onRingingBack(SipAudioCall call) {
-            onChanged(call);
-        }
-        public void onCallEstablished(SipAudioCall call) {
-            onChanged(call);
-        }
-        public void onCallEnded(SipAudioCall call) {
-            onChanged(call);
-        }
-        public void onCallBusy(SipAudioCall call) {
-            onChanged(call);
-        }
-        public void onCallHeld(SipAudioCall call) {
-            onChanged(call);
-        }
         public void onError(SipAudioCall call, int errorCode,
                 String errorMessage) {
-            onChanged(call);
+            // no-op
         }
+
+        /**
+         * Called when an event occurs and the corresponding callback is not
+         * overridden. The default implementation is no op. Error events are
+         * not re-directed to this callback and are handled in {@link #onError}.
+         */
+        public void onChanged(SipAudioCall call) {
+            // no-op
+        }
+    }
+
+    private Context mContext;
+    private SipProfile mLocalProfile;
+    private SipAudioCall.Listener mListener;
+    private SipSession mSipSession;
+
+    private long mSessionId = System.currentTimeMillis();
+    private String mPeerSd;
+
+    private AudioStream mAudioStream;
+    private AudioGroup mAudioGroup;
+
+    private boolean mInCall = false;
+    private boolean mMuted = false;
+    private boolean mHold = false;
+
+    private boolean mRingbackToneEnabled = true;
+    private boolean mRingtoneEnabled = true;
+    private Ringtone mRingtone;
+    private ToneGenerator mRingbackTone;
+
+    private SipProfile mPendingCallRequest;
+    private WifiManager mWm;
+    private WifiManager.WifiLock mWifiHighPerfLock;
+
+    private int mErrorCode = SipErrorCode.NO_ERROR;
+    private String mErrorMessage;
+
+    /**
+     * Creates a call object with the local SIP profile.
+     * @param context the context for accessing system services such as
+     *        ringtone, audio, WIFI etc
+     */
+    public SipAudioCall(Context context, SipProfile localProfile) {
+        mContext = context;
+        mLocalProfile = localProfile;
+        mWm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
     }
 
     /**
@@ -139,7 +203,9 @@
      * @param listener to listen to the audio call events of this object
      * @see #setListener(Listener, boolean)
      */
-    void setListener(Listener listener);
+    public void setListener(SipAudioCall.Listener listener) {
+        setListener(listener, false);
+    }
 
     /**
      * Sets the listener to listen to the audio call events. A
@@ -150,12 +216,312 @@
      * @param callbackImmediately set to true if the caller wants to be called
      *      back immediately on the current state
      */
-    void setListener(Listener listener, boolean callbackImmediately);
+    public void setListener(SipAudioCall.Listener listener,
+            boolean callbackImmediately) {
+        mListener = listener;
+        try {
+            if ((listener == null) || !callbackImmediately) {
+                // do nothing
+            } else if (mErrorCode != SipErrorCode.NO_ERROR) {
+                listener.onError(this, mErrorCode, mErrorMessage);
+            } else if (mInCall) {
+                if (mHold) {
+                    listener.onCallHeld(this);
+                } else {
+                    listener.onCallEstablished(this);
+                }
+            } else {
+                int state = getState();
+                switch (state) {
+                    case SipSession.State.READY_TO_CALL:
+                        listener.onReadyToCall(this);
+                        break;
+                    case SipSession.State.INCOMING_CALL:
+                        listener.onRinging(this, getPeerProfile());
+                        break;
+                    case SipSession.State.OUTGOING_CALL:
+                        listener.onCalling(this);
+                        break;
+                    case SipSession.State.OUTGOING_CALL_RING_BACK:
+                        listener.onRingingBack(this);
+                        break;
+                }
+            }
+        } catch (Throwable t) {
+            Log.e(TAG, "setListener()", t);
+        }
+    }
+
+    /**
+     * Checks if the call is established.
+     *
+     * @return true if the call is established
+     */
+    public synchronized boolean isInCall() {
+        return mInCall;
+    }
+
+    /**
+     * Checks if the call is on hold.
+     *
+     * @return true if the call is on hold
+     */
+    public synchronized boolean isOnHold() {
+        return mHold;
+    }
 
     /**
      * Closes this object. This object is not usable after being closed.
      */
-    void close();
+    public void close() {
+        close(true);
+    }
+
+    private synchronized void close(boolean closeRtp) {
+        if (closeRtp) stopCall(RELEASE_SOCKET);
+        stopRingbackTone();
+        stopRinging();
+
+        mInCall = false;
+        mHold = false;
+        mSessionId = System.currentTimeMillis();
+        mErrorCode = SipErrorCode.NO_ERROR;
+        mErrorMessage = null;
+
+        if (mSipSession != null) {
+            mSipSession.setListener(null);
+            mSipSession = null;
+        }
+    }
+
+    /**
+     * Gets the local SIP profile.
+     *
+     * @return the local SIP profile
+     */
+    public synchronized SipProfile getLocalProfile() {
+        return mLocalProfile;
+    }
+
+    /**
+     * Gets the peer's SIP profile.
+     *
+     * @return the peer's SIP profile
+     */
+    public synchronized SipProfile getPeerProfile() {
+        return (mSipSession == null) ? null : mSipSession.getPeerProfile();
+    }
+
+    /**
+     * Gets the state of the {@link SipSession} that carries this call.
+     * The value returned must be one of the states in {@link SipSession.State}.
+     *
+     * @return the session state
+     */
+    public synchronized int getState() {
+        if (mSipSession == null) return SipSession.State.READY_TO_CALL;
+        return mSipSession.getState();
+    }
+
+
+    /**
+     * Gets the {@link SipSession} that carries this call.
+     *
+     * @return the session object that carries this call
+     * @hide
+     */
+    public synchronized SipSession getSipSession() {
+        return mSipSession;
+    }
+
+    private SipSession.Listener createListener() {
+        return new SipSession.Listener() {
+            @Override
+            public void onCalling(SipSession session) {
+                Log.d(TAG, "calling... " + session);
+                Listener listener = mListener;
+                if (listener != null) {
+                    try {
+                        listener.onCalling(SipAudioCall.this);
+                    } catch (Throwable t) {
+                        Log.i(TAG, "onCalling(): " + t);
+                    }
+                }
+            }
+
+            @Override
+            public void onRingingBack(SipSession session) {
+                Log.d(TAG, "sip call ringing back: " + session);
+                if (!mInCall) startRingbackTone();
+                Listener listener = mListener;
+                if (listener != null) {
+                    try {
+                        listener.onRingingBack(SipAudioCall.this);
+                    } catch (Throwable t) {
+                        Log.i(TAG, "onRingingBack(): " + t);
+                    }
+                }
+            }
+
+            @Override
+            public synchronized void onRinging(SipSession session,
+                    SipProfile peerProfile, String sessionDescription) {
+                if ((mSipSession == null) || !mInCall
+                        || !session.getCallId().equals(mSipSession.getCallId())) {
+                    // should not happen
+                    session.endCall();
+                    return;
+                }
+
+                // session changing request
+                try {
+                    String answer = createAnswer(sessionDescription).encode();
+                    mSipSession.answerCall(answer, SESSION_TIMEOUT);
+                } catch (Throwable e) {
+                    Log.e(TAG, "onRinging()", e);
+                    session.endCall();
+                }
+            }
+
+            @Override
+            public void onCallEstablished(SipSession session,
+                    String sessionDescription) {
+                stopRingbackTone();
+                stopRinging();
+                mPeerSd = sessionDescription;
+                Log.v(TAG, "onCallEstablished()" + mPeerSd);
+
+                Listener listener = mListener;
+                if (listener != null) {
+                    try {
+                        if (mHold) {
+                            listener.onCallHeld(SipAudioCall.this);
+                        } else {
+                            listener.onCallEstablished(SipAudioCall.this);
+                        }
+                    } catch (Throwable t) {
+                        Log.i(TAG, "onCallEstablished(): " + t);
+                    }
+                }
+            }
+
+            @Override
+            public void onCallEnded(SipSession session) {
+                Log.d(TAG, "sip call ended: " + session);
+                Listener listener = mListener;
+                if (listener != null) {
+                    try {
+                        listener.onCallEnded(SipAudioCall.this);
+                    } catch (Throwable t) {
+                        Log.i(TAG, "onCallEnded(): " + t);
+                    }
+                }
+                close();
+            }
+
+            @Override
+            public void onCallBusy(SipSession session) {
+                Log.d(TAG, "sip call busy: " + session);
+                Listener listener = mListener;
+                if (listener != null) {
+                    try {
+                        listener.onCallBusy(SipAudioCall.this);
+                    } catch (Throwable t) {
+                        Log.i(TAG, "onCallBusy(): " + t);
+                    }
+                }
+                close(false);
+            }
+
+            @Override
+            public void onCallChangeFailed(SipSession session, int errorCode,
+                    String message) {
+                Log.d(TAG, "sip call change failed: " + message);
+                mErrorCode = errorCode;
+                mErrorMessage = message;
+                Listener listener = mListener;
+                if (listener != null) {
+                    try {
+                        listener.onError(SipAudioCall.this, mErrorCode,
+                                message);
+                    } catch (Throwable t) {
+                        Log.i(TAG, "onCallBusy(): " + t);
+                    }
+                }
+            }
+
+            @Override
+            public void onError(SipSession session, int errorCode,
+                    String message) {
+                SipAudioCall.this.onError(errorCode, message);
+            }
+
+            @Override
+            public void onRegistering(SipSession session) {
+                // irrelevant
+            }
+
+            @Override
+            public void onRegistrationTimeout(SipSession session) {
+                // irrelevant
+            }
+
+            @Override
+            public void onRegistrationFailed(SipSession session, int errorCode,
+                    String message) {
+                // irrelevant
+            }
+
+            @Override
+            public void onRegistrationDone(SipSession session, int duration) {
+                // irrelevant
+            }
+        };
+    }
+
+    private void onError(int errorCode, String message) {
+        Log.d(TAG, "sip session error: "
+                + SipErrorCode.toString(errorCode) + ": " + message);
+        mErrorCode = errorCode;
+        mErrorMessage = message;
+        Listener listener = mListener;
+        if (listener != null) {
+            try {
+                listener.onError(this, errorCode, message);
+            } catch (Throwable t) {
+                Log.i(TAG, "onError(): " + t);
+            }
+        }
+        synchronized (this) {
+            if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
+                    || !isInCall()) {
+                close(true);
+            }
+        }
+    }
+
+    /**
+     * Attaches an incoming call to this call object.
+     *
+     * @param session the session that receives the incoming call
+     * @param sessionDescription the session description of the incoming call
+     * @throws SipException if the SIP service fails to attach this object to
+     *        the session
+     */
+    public synchronized void attachCall(SipSession session,
+            String sessionDescription) throws SipException {
+        mSipSession = session;
+        mPeerSd = sessionDescription;
+        Log.v(TAG, "attachCall()" + mPeerSd);
+        try {
+            session.setListener(createListener());
+
+            if (getState() == SipSession.State.INCOMING_CALL) startRinging();
+        } catch (Throwable e) {
+            Log.e(TAG, "attachCall()", e);
+            throwSipException(e);
+        }
+    }
 
     /**
      * Initiates an audio call to the specified profile. The attempt will be
@@ -165,29 +531,40 @@
      *
      * @param callee the SIP profile to make the call to
      * @param sipManager the {@link SipManager} object to help make call with
-     * @param timeout the timeout value in seconds
+     * @param timeout the timeout value in seconds. Default value (defined by
+     *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @see Listener.onError
+     * @throws SipException if the SIP service fails to create a session for the
+     *        call
      */
-    void makeCall(SipProfile callee, SipManager sipManager, int timeout)
-            throws SipException;
+    public synchronized void makeCall(SipProfile peerProfile,
+        SipManager sipManager, int timeout) throws SipException {
+        SipSession s = mSipSession = sipManager.createSipSession(
+                mLocalProfile, createListener());
+        if (s == null) {
+            throw new SipException(
+                    "Failed to create SipSession; network available?");
+        }
+        try {
+            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+            s.makeCall(peerProfile, createOffer().encode(), timeout);
+        } catch (IOException e) {
+            throw new SipException("makeCall()", e);
+        }
+    }
 
     /**
-     * Starts the audio for the established call. This method should be called
-     * after {@link Listener#onCallEstablished} is called.
+     * Ends a call.
+     * @throws SipException if the SIP service fails to end the call
      */
-    void startAudio();
+    public synchronized void endCall() throws SipException {
+        stopRinging();
+        stopCall(RELEASE_SOCKET);
+        mInCall = false;
 
-    /**
-     * Attaches an incoming call to this call object.
-     *
-     * @param session the session that receives the incoming call
-     * @param sessionDescription the session description of the incoming call
-     */
-    void attachCall(ISipSession session, String sessionDescription)
-            throws SipException;
-
-    /** Ends a call. */
-    void endCall() throws SipException;
+        // perform the above local ops first and then network op
+        if (mSipSession != null) mSipSession.endCall();
+    }
 
     /**
      * Puts a call on hold.  When succeeds, {@link Listener#onCallHeld} is
@@ -196,10 +573,19 @@
      * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
-     * @param timeout the timeout value in seconds
+     * @param timeout the timeout value in seconds. Default value (defined by
+     *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @see Listener.onError
+     * @throws SipException if the SIP service fails to hold the call
      */
-    void holdCall(int timeout) throws SipException;
+    public synchronized void holdCall(int timeout) throws SipException {
+        if (mHold) return;
+        mSipSession.changeCall(createHoldOffer().encode(), timeout);
+        mHold = true;
+
+        AudioGroup audioGroup = getAudioGroup();
+        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+    }
 
     /**
      * Answers a call. The attempt will be timed out if the call is not
@@ -207,10 +593,20 @@
      * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
-     * @param timeout the timeout value in seconds
+     * @param timeout the timeout value in seconds. Default value (defined by
+     *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @see Listener.onError
+     * @throws SipException if the SIP service fails to answer the call
      */
-    void answerCall(int timeout) throws SipException;
+    public synchronized void answerCall(int timeout) throws SipException {
+        stopRinging();
+        try {
+            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
+            mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
+        } catch (IOException e) {
+            throw new SipException("answerCall()", e);
+        }
+    }
 
     /**
      * Continues a call that's on hold. When succeeds,
@@ -219,45 +615,191 @@
      * {@code Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
-     * @param timeout the timeout value in seconds
+     * @param timeout the timeout value in seconds. Default value (defined by
+     *        SIP protocol) is used if {@code timeout} is zero or negative.
      * @see Listener.onError
+     * @throws SipException if the SIP service fails to unhold the call
      */
-    void continueCall(int timeout) throws SipException;
+    public synchronized void continueCall(int timeout) throws SipException {
+        if (!mHold) return;
+        mSipSession.changeCall(createContinueOffer().encode(), timeout);
+        mHold = false;
+        AudioGroup audioGroup = getAudioGroup();
+        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
+    }
 
-    /** Puts the device to speaker mode. */
-    void setSpeakerMode(boolean speakerMode);
+    private SimpleSessionDescription createOffer() {
+        SimpleSessionDescription offer =
+                new SimpleSessionDescription(mSessionId, getLocalIp());
+        AudioCodec[] codecs = AudioCodec.getCodecs();
+        Media media = offer.newMedia(
+                "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+        for (AudioCodec codec : AudioCodec.getCodecs()) {
+            media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+        }
+        media.setRtpPayload(127, "telephone-event/8000", "0-15");
+        return offer;
+    }
+
+    private SimpleSessionDescription createAnswer(String offerSd) {
+        SimpleSessionDescription offer =
+                new SimpleSessionDescription(offerSd);
+        SimpleSessionDescription answer =
+                new SimpleSessionDescription(mSessionId, getLocalIp());
+        AudioCodec codec = null;
+        for (Media media : offer.getMedia()) {
+            if ((codec == null) && (media.getPort() > 0)
+                    && "audio".equals(media.getType())
+                    && "RTP/AVP".equals(media.getProtocol())) {
+                // Find the first audio codec we supported.
+                for (int type : media.getRtpPayloadTypes()) {
+                    codec = AudioCodec.getCodec(type, media.getRtpmap(type),
+                            media.getFmtp(type));
+                    if (codec != null) {
+                        break;
+                    }
+                }
+                if (codec != null) {
+                    Media reply = answer.newMedia(
+                            "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+                    reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+
+                    // Check if DTMF is supported in the same media.
+                    for (int type : media.getRtpPayloadTypes()) {
+                        String rtpmap = media.getRtpmap(type);
+                        if ((type != codec.type) && (rtpmap != null)
+                                && rtpmap.startsWith("telephone-event")) {
+                            reply.setRtpPayload(
+                                    type, rtpmap, media.getFmtp(type));
+                        }
+                    }
+
+                    // Handle recvonly and sendonly.
+                    if (media.getAttribute("recvonly") != null) {
+                        answer.setAttribute("sendonly", "");
+                    } else if(media.getAttribute("sendonly") != null) {
+                        answer.setAttribute("recvonly", "");
+                    } else if(offer.getAttribute("recvonly") != null) {
+                        answer.setAttribute("sendonly", "");
+                    } else if(offer.getAttribute("sendonly") != null) {
+                        answer.setAttribute("recvonly", "");
+                    }
+                    continue;
+                }
+            }
+            // Reject the media.
+            Media reply = answer.newMedia(
+                    media.getType(), 0, 1, media.getProtocol());
+            for (String format : media.getFormats()) {
+                reply.setFormat(format, null);
+            }
+        }
+        if (codec == null) {
+            throw new IllegalStateException("Reject SDP: no suitable codecs");
+        }
+        return answer;
+    }
+
+    private SimpleSessionDescription createHoldOffer() {
+        SimpleSessionDescription offer = createContinueOffer();
+        offer.setAttribute("sendonly", "");
+        return offer;
+    }
+
+    private SimpleSessionDescription createContinueOffer() {
+        SimpleSessionDescription offer =
+                new SimpleSessionDescription(mSessionId, getLocalIp());
+        Media media = offer.newMedia(
+                "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
+        AudioCodec codec = mAudioStream.getCodec();
+        media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
+        int dtmfType = mAudioStream.getDtmfType();
+        if (dtmfType != -1) {
+            media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
+        }
+        return offer;
+    }
+
+    private void grabWifiHighPerfLock() {
+        /* not available in master yet
+        if (mWifiHighPerfLock == null) {
+            Log.v(TAG, "acquire wifi high perf lock");
+            mWifiHighPerfLock = ((WifiManager)
+                    mContext.getSystemService(Context.WIFI_SERVICE))
+                    .createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
+            mWifiHighPerfLock.acquire();
+        }
+        */
+    }
+
+    private void releaseWifiHighPerfLock() {
+        if (mWifiHighPerfLock != null) {
+            Log.v(TAG, "release wifi high perf lock");
+            mWifiHighPerfLock.release();
+            mWifiHighPerfLock = null;
+        }
+    }
+
+    private boolean isWifiOn() {
+        return (mWm.getConnectionInfo().getBSSID() == null) ? false : true;
+    }
 
     /** Toggles mute. */
-    void toggleMute();
-
-    /**
-     * Checks if the call is on hold.
-     *
-     * @return true if the call is on hold
-     */
-    boolean isOnHold();
+    public synchronized void toggleMute() {
+        AudioGroup audioGroup = getAudioGroup();
+        if (audioGroup != null) {
+            audioGroup.setMode(
+                    mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
+            mMuted = !mMuted;
+        }
+    }
 
     /**
      * Checks if the call is muted.
      *
      * @return true if the call is muted
      */
-    boolean isMuted();
+    public synchronized boolean isMuted() {
+        return mMuted;
+    }
+
+    /** Puts the device to speaker mode. */
+    public synchronized void setSpeakerMode(boolean speakerMode) {
+        ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
+                .setSpeakerphoneOn(speakerMode);
+    }
 
     /**
-     * Sends a DTMF code.
+     * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal
+     * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
+     * flash to 16. Currently, event flash is not supported.
      *
-     * @param code the DTMF code to send
+     * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
+     *        inputs.
+     * @see http://tools.ietf.org/html/rfc2833
      */
-    void sendDtmf(int code);
+    public void sendDtmf(int code) {
+        sendDtmf(code, null);
+    }
 
     /**
-     * Sends a DTMF code.
+     * Sends a DTMF code. According to RFC2833, event 0--9 maps to decimal
+     * value 0--9, '*' to 10, '#' to 11, event 'A'--'D' to 12--15, and event
+     * flash to 16. Currently, event flash is not supported.
      *
-     * @param code the DTMF code to send
+     * @param code the DTMF code to send. Value 0 to 15 (inclusive) are valid
+     *        inputs.
      * @param result the result message to send when done
      */
-    void sendDtmf(int code, Message result);
+    public synchronized void sendDtmf(int code, Message result) {
+        AudioGroup audioGroup = getAudioGroup();
+        if ((audioGroup != null) && (mSipSession != null)
+                && (SipSession.State.IN_CALL == getState())) {
+            Log.v(TAG, "send DTMF: " + code);
+            audioGroup.sendDtmf(code);
+        }
+        if (result != null) result.sendToTarget();
+    }
 
     /**
      * Gets the {@link AudioStream} object used in this call. The object
@@ -268,8 +810,11 @@
      *
      * @return the {@link AudioStream} object or null if the RTP stream has not
      *      yet been set up
+     * @hide
      */
-    AudioStream getAudioStream();
+    public synchronized AudioStream getAudioStream() {
+        return mAudioStream;
+    }
 
     /**
      * Gets the {@link AudioGroup} object which the {@link AudioStream} object
@@ -283,8 +828,12 @@
      * @return the {@link AudioGroup} object or null if the RTP stream has not
      *      yet been set up
      * @see #getAudioStream
+     * @hide
      */
-    AudioGroup getAudioGroup();
+    public synchronized AudioGroup getAudioGroup() {
+        if (mAudioGroup != null) return mAudioGroup;
+        return ((mAudioStream == null) ? null : mAudioStream.getGroup());
+    }
 
     /**
      * Sets the {@link AudioGroup} object which the {@link AudioStream} object
@@ -292,56 +841,214 @@
      * will be dynamically created when needed.
      *
      * @see #getAudioStream
+     * @hide
      */
-    void setAudioGroup(AudioGroup audioGroup);
+    public synchronized void setAudioGroup(AudioGroup group) {
+        if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
+            mAudioStream.join(group);
+        }
+        mAudioGroup = group;
+    }
 
     /**
-     * Checks if the call is established.
-     *
-     * @return true if the call is established
+     * Starts the audio for the established call. This method should be called
+     * after {@link Listener#onCallEstablished} is called.
      */
-    boolean isInCall();
+    public void startAudio() {
+        try {
+            startAudioInternal();
+        } catch (UnknownHostException e) {
+            onError(SipErrorCode.PEER_NOT_REACHABLE, e.getMessage());
+        } catch (Throwable e) {
+            onError(SipErrorCode.CLIENT_ERROR, e.getMessage());
+        }
+    }
 
-    /**
-     * Gets the local SIP profile.
-     *
-     * @return the local SIP profile
-     */
-    SipProfile getLocalProfile();
+    private synchronized void startAudioInternal() throws UnknownHostException {
+        if (mPeerSd == null) {
+            Log.v(TAG, "startAudioInternal() mPeerSd = null");
+            throw new IllegalStateException("mPeerSd = null");
+        }
 
-    /**
-     * Gets the peer's SIP profile.
-     *
-     * @return the peer's SIP profile
-     */
-    SipProfile getPeerProfile();
+        stopCall(DONT_RELEASE_SOCKET);
+        mInCall = true;
 
-    /**
-     * Gets the state of the {@link ISipSession} that carries this call.
-     * The value returned must be one of the states in {@link SipSessionState}.
-     *
-     * @return the session state
-     */
-    int getState();
+        // Run exact the same logic in createAnswer() to setup mAudioStream.
+        SimpleSessionDescription offer =
+                new SimpleSessionDescription(mPeerSd);
+        AudioStream stream = mAudioStream;
+        AudioCodec codec = null;
+        for (Media media : offer.getMedia()) {
+            if ((codec == null) && (media.getPort() > 0)
+                    && "audio".equals(media.getType())
+                    && "RTP/AVP".equals(media.getProtocol())) {
+                // Find the first audio codec we supported.
+                for (int type : media.getRtpPayloadTypes()) {
+                    codec = AudioCodec.getCodec(
+                            type, media.getRtpmap(type), media.getFmtp(type));
+                    if (codec != null) {
+                        break;
+                    }
+                }
 
-    /**
-     * Gets the {@link ISipSession} that carries this call.
-     *
-     * @return the session object that carries this call
-     */
-    ISipSession getSipSession();
+                if (codec != null) {
+                    // Associate with the remote host.
+                    String address = media.getAddress();
+                    if (address == null) {
+                        address = offer.getAddress();
+                    }
+                    stream.associate(InetAddress.getByName(address),
+                            media.getPort());
+
+                    stream.setDtmfType(-1);
+                    stream.setCodec(codec);
+                    // Check if DTMF is supported in the same media.
+                    for (int type : media.getRtpPayloadTypes()) {
+                        String rtpmap = media.getRtpmap(type);
+                        if ((type != codec.type) && (rtpmap != null)
+                                && rtpmap.startsWith("telephone-event")) {
+                            stream.setDtmfType(type);
+                        }
+                    }
+
+                    // Handle recvonly and sendonly.
+                    if (mHold) {
+                        stream.setMode(RtpStream.MODE_NORMAL);
+                    } else if (media.getAttribute("recvonly") != null) {
+                        stream.setMode(RtpStream.MODE_SEND_ONLY);
+                    } else if(media.getAttribute("sendonly") != null) {
+                        stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+                    } else if(offer.getAttribute("recvonly") != null) {
+                        stream.setMode(RtpStream.MODE_SEND_ONLY);
+                    } else if(offer.getAttribute("sendonly") != null) {
+                        stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
+                    } else {
+                        stream.setMode(RtpStream.MODE_NORMAL);
+                    }
+                    break;
+                }
+            }
+        }
+        if (codec == null) {
+            throw new IllegalStateException("Reject SDP: no suitable codecs");
+        }
+
+        if (isWifiOn()) grabWifiHighPerfLock();
+
+        if (!mHold) {
+            /* The recorder volume will be very low if the device is in
+             * IN_CALL mode. Therefore, we have to set the mode to NORMAL
+             * in order to have the normal microphone level.
+             */
+            ((AudioManager) mContext.getSystemService
+                    (Context.AUDIO_SERVICE))
+                    .setMode(AudioManager.MODE_NORMAL);
+        }
+
+        // AudioGroup logic:
+        AudioGroup audioGroup = getAudioGroup();
+        if (mHold) {
+            if (audioGroup != null) {
+                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
+            }
+            // don't create an AudioGroup here; doing so will fail if
+            // there's another AudioGroup out there that's active
+        } else {
+            if (audioGroup == null) audioGroup = new AudioGroup();
+            stream.join(audioGroup);
+            if (mMuted) {
+                audioGroup.setMode(AudioGroup.MODE_MUTED);
+            } else {
+                audioGroup.setMode(AudioGroup.MODE_NORMAL);
+            }
+        }
+    }
+
+    private void stopCall(boolean releaseSocket) {
+        Log.d(TAG, "stop audiocall");
+        releaseWifiHighPerfLock();
+        if (mAudioStream != null) {
+            mAudioStream.join(null);
+
+            if (releaseSocket) {
+                mAudioStream.release();
+                mAudioStream = null;
+            }
+        }
+    }
+
+    private String getLocalIp() {
+        return mSipSession.getLocalIp();
+    }
+
 
     /**
      * Enables/disables the ring-back tone.
      *
      * @param enabled true to enable; false to disable
      */
-    void setRingbackToneEnabled(boolean enabled);
+    public synchronized void setRingbackToneEnabled(boolean enabled) {
+        mRingbackToneEnabled = enabled;
+    }
 
     /**
      * Enables/disables the ring tone.
      *
      * @param enabled true to enable; false to disable
      */
-    void setRingtoneEnabled(boolean enabled);
+    public synchronized void setRingtoneEnabled(boolean enabled) {
+        mRingtoneEnabled = enabled;
+    }
+
+    private void startRingbackTone() {
+        if (!mRingbackToneEnabled) return;
+        if (mRingbackTone == null) {
+            // The volume relative to other sounds in the stream
+            int toneVolume = 80;
+            mRingbackTone = new ToneGenerator(
+                    AudioManager.STREAM_VOICE_CALL, toneVolume);
+        }
+        mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
+    }
+
+    private void stopRingbackTone() {
+        if (mRingbackTone != null) {
+            mRingbackTone.stopTone();
+            mRingbackTone.release();
+            mRingbackTone = null;
+        }
+    }
+
+    private void startRinging() {
+        if (!mRingtoneEnabled) return;
+        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
+                .vibrate(new long[] {0, 1000, 1000}, 1);
+        AudioManager am = (AudioManager)
+                mContext.getSystemService(Context.AUDIO_SERVICE);
+        if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
+            String ringtoneUri =
+                    Settings.System.DEFAULT_RINGTONE_URI.toString();
+            mRingtone = RingtoneManager.getRingtone(mContext,
+                    Uri.parse(ringtoneUri));
+            mRingtone.play();
+        }
+    }
+
+    private void stopRinging() {
+        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
+                .cancel();
+        if (mRingtone != null) mRingtone.stop();
+    }
+
+    private void throwSipException(Throwable throwable) throws SipException {
+        if (throwable instanceof SipException) {
+            throw (SipException) throwable;
+        } else {
+            throw new SipException("", throwable);
+        }
+    }
+
+    private SipProfile getPeerProfile(SipSession session) {
+        return session.getPeerProfile();
+    }
 }
diff --git a/voip/java/android/net/sip/SipAudioCallImpl.java b/voip/java/android/net/sip/SipAudioCallImpl.java
deleted file mode 100644
index 5eecc05..0000000
--- a/voip/java/android/net/sip/SipAudioCallImpl.java
+++ /dev/null
@@ -1,738 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-import android.content.Context;
-import android.media.AudioManager;
-import android.media.Ringtone;
-import android.media.RingtoneManager;
-import android.media.ToneGenerator;
-import android.net.Uri;
-import android.net.rtp.AudioCodec;
-import android.net.rtp.AudioGroup;
-import android.net.rtp.AudioStream;
-import android.net.rtp.RtpStream;
-import android.net.sip.SimpleSessionDescription.Media;
-import android.net.wifi.WifiManager;
-import android.os.Message;
-import android.os.RemoteException;
-import android.os.Vibrator;
-import android.provider.Settings;
-import android.util.Log;
-
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Class that handles an audio call over SIP.
- */
-/** @hide */
-public class SipAudioCallImpl extends SipSessionAdapter
-        implements SipAudioCall {
-    private static final String TAG = SipAudioCallImpl.class.getSimpleName();
-    private static final boolean RELEASE_SOCKET = true;
-    private static final boolean DONT_RELEASE_SOCKET = false;
-    private static final int SESSION_TIMEOUT = 5; // in seconds
-
-    private Context mContext;
-    private SipProfile mLocalProfile;
-    private SipAudioCall.Listener mListener;
-    private ISipSession mSipSession;
-
-    private long mSessionId = System.currentTimeMillis();
-    private String mPeerSd;
-
-    private AudioStream mAudioStream;
-    private AudioGroup mAudioGroup;
-
-    private boolean mInCall = false;
-    private boolean mMuted = false;
-    private boolean mHold = false;
-
-    private boolean mRingbackToneEnabled = true;
-    private boolean mRingtoneEnabled = true;
-    private Ringtone mRingtone;
-    private ToneGenerator mRingbackTone;
-
-    private SipProfile mPendingCallRequest;
-
-    private int mErrorCode = SipErrorCode.NO_ERROR;
-    private String mErrorMessage;
-
-    public SipAudioCallImpl(Context context, SipProfile localProfile) {
-        mContext = context;
-        mLocalProfile = localProfile;
-    }
-
-    public void setListener(SipAudioCall.Listener listener) {
-        setListener(listener, false);
-    }
-
-    public void setListener(SipAudioCall.Listener listener,
-            boolean callbackImmediately) {
-        mListener = listener;
-        try {
-            if ((listener == null) || !callbackImmediately) {
-                // do nothing
-            } else if (mErrorCode != SipErrorCode.NO_ERROR) {
-                listener.onError(this, mErrorCode, mErrorMessage);
-            } else if (mInCall) {
-                if (mHold) {
-                    listener.onCallHeld(this);
-                } else {
-                    listener.onCallEstablished(this);
-                }
-            } else {
-                int state = getState();
-                switch (state) {
-                    case SipSessionState.READY_TO_CALL:
-                        listener.onReadyToCall(this);
-                        break;
-                    case SipSessionState.INCOMING_CALL:
-                        listener.onRinging(this, getPeerProfile(mSipSession));
-                        break;
-                    case SipSessionState.OUTGOING_CALL:
-                        listener.onCalling(this);
-                        break;
-                    case SipSessionState.OUTGOING_CALL_RING_BACK:
-                        listener.onRingingBack(this);
-                        break;
-                }
-            }
-        } catch (Throwable t) {
-            Log.e(TAG, "setListener()", t);
-        }
-    }
-
-    public synchronized boolean isInCall() {
-        return mInCall;
-    }
-
-    public synchronized boolean isOnHold() {
-        return mHold;
-    }
-
-    public void close() {
-        close(true);
-    }
-
-    private synchronized void close(boolean closeRtp) {
-        if (closeRtp) stopCall(RELEASE_SOCKET);
-        stopRingbackTone();
-        stopRinging();
-
-        mInCall = false;
-        mHold = false;
-        mSessionId = System.currentTimeMillis();
-        mErrorCode = SipErrorCode.NO_ERROR;
-        mErrorMessage = null;
-
-        if (mSipSession != null) {
-            try {
-                mSipSession.setListener(null);
-            } catch (RemoteException e) {
-                // don't care
-            }
-            mSipSession = null;
-        }
-    }
-
-    public synchronized SipProfile getLocalProfile() {
-        return mLocalProfile;
-    }
-
-    public synchronized SipProfile getPeerProfile() {
-        try {
-            return (mSipSession == null) ? null : mSipSession.getPeerProfile();
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-
-    public synchronized int getState() {
-        if (mSipSession == null) return SipSessionState.READY_TO_CALL;
-        try {
-            return mSipSession.getState();
-        } catch (RemoteException e) {
-            return SipSessionState.REMOTE_ERROR;
-        }
-    }
-
-
-    public synchronized ISipSession getSipSession() {
-        return mSipSession;
-    }
-
-    @Override
-    public void onCalling(ISipSession session) {
-        Log.d(TAG, "calling... " + session);
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                listener.onCalling(this);
-            } catch (Throwable t) {
-                Log.e(TAG, "onCalling()", t);
-            }
-        }
-    }
-
-    @Override
-    public void onRingingBack(ISipSession session) {
-        Log.d(TAG, "sip call ringing back: " + session);
-        if (!mInCall) startRingbackTone();
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                listener.onRingingBack(this);
-            } catch (Throwable t) {
-                Log.e(TAG, "onRingingBack()", t);
-            }
-        }
-    }
-
-    @Override
-    public synchronized void onRinging(ISipSession session,
-            SipProfile peerProfile, String sessionDescription) {
-        try {
-            if ((mSipSession == null) || !mInCall
-                    || !session.getCallId().equals(mSipSession.getCallId())) {
-                // should not happen
-                session.endCall();
-                return;
-            }
-
-            // session changing request
-            try {
-                String answer = createAnswer(sessionDescription).encode();
-                mSipSession.answerCall(answer, SESSION_TIMEOUT);
-            } catch (Throwable e) {
-                Log.e(TAG, "onRinging()", e);
-                session.endCall();
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "onRinging()", e);
-        }
-    }
-
-    @Override
-    public void onCallEstablished(ISipSession session,
-            String sessionDescription) {
-        stopRingbackTone();
-        stopRinging();
-        mPeerSd = sessionDescription;
-        Log.v(TAG, "onCallEstablished()" + mPeerSd);
-
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                if (mHold) {
-                    listener.onCallHeld(this);
-                } else {
-                    listener.onCallEstablished(this);
-                }
-            } catch (Throwable t) {
-                Log.e(TAG, "onCallEstablished()", t);
-            }
-        }
-    }
-
-    @Override
-    public void onCallEnded(ISipSession session) {
-        Log.d(TAG, "sip call ended: " + session);
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                listener.onCallEnded(this);
-            } catch (Throwable t) {
-                Log.e(TAG, "onCallEnded()", t);
-            }
-        }
-        close();
-    }
-
-    @Override
-    public void onCallBusy(ISipSession session) {
-        Log.d(TAG, "sip call busy: " + session);
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                listener.onCallBusy(this);
-            } catch (Throwable t) {
-                Log.e(TAG, "onCallBusy()", t);
-            }
-        }
-        close(false);
-    }
-
-    @Override
-    public void onCallChangeFailed(ISipSession session, int errorCode,
-            String message) {
-        Log.d(TAG, "sip call change failed: " + message);
-        mErrorCode = errorCode;
-        mErrorMessage = message;
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                listener.onError(this, mErrorCode, message);
-            } catch (Throwable t) {
-                Log.e(TAG, "onCallBusy()", t);
-            }
-        }
-    }
-
-    @Override
-    public void onError(ISipSession session, int errorCode, String message) {
-        Log.d(TAG, "sip session error: " + SipErrorCode.toString(errorCode)
-                + ": " + message);
-        mErrorCode = errorCode;
-        mErrorMessage = message;
-        Listener listener = mListener;
-        if (listener != null) {
-            try {
-                listener.onError(this, errorCode, message);
-            } catch (Throwable t) {
-                Log.e(TAG, "onError()", t);
-            }
-        }
-        synchronized (this) {
-            if ((errorCode == SipErrorCode.DATA_CONNECTION_LOST)
-                    || !isInCall()) {
-                close(true);
-            }
-        }
-    }
-
-    public synchronized void attachCall(ISipSession session,
-            String sessionDescription) throws SipException {
-        mSipSession = session;
-        mPeerSd = sessionDescription;
-        Log.v(TAG, "attachCall()" + mPeerSd);
-        try {
-            session.setListener(this);
-            if (getState() == SipSessionState.INCOMING_CALL) startRinging();
-        } catch (Throwable e) {
-            Log.e(TAG, "attachCall()", e);
-            throwSipException(e);
-        }
-    }
-
-    public synchronized void makeCall(SipProfile peerProfile,
-            SipManager sipManager, int timeout) throws SipException {
-        try {
-            mSipSession = sipManager.createSipSession(mLocalProfile, this);
-            if (mSipSession == null) {
-                throw new SipException(
-                        "Failed to create SipSession; network available?");
-            }
-            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
-            mSipSession.makeCall(peerProfile, createOffer().encode(), timeout);
-        } catch (Throwable e) {
-            if (e instanceof SipException) {
-                throw (SipException) e;
-            } else {
-                throwSipException(e);
-            }
-        }
-    }
-
-    public synchronized void endCall() throws SipException {
-        try {
-            stopRinging();
-            stopCall(RELEASE_SOCKET);
-            mInCall = false;
-
-            // perform the above local ops first and then network op
-            if (mSipSession != null) mSipSession.endCall();
-        } catch (Throwable e) {
-            throwSipException(e);
-        }
-    }
-
-    public synchronized void answerCall(int timeout) throws SipException {
-        try {
-            stopRinging();
-            mAudioStream = new AudioStream(InetAddress.getByName(getLocalIp()));
-            mSipSession.answerCall(createAnswer(mPeerSd).encode(), timeout);
-        } catch (Throwable e) {
-            Log.e(TAG, "answerCall()", e);
-            throwSipException(e);
-        }
-    }
-
-    public synchronized void holdCall(int timeout) throws SipException {
-        if (mHold) return;
-        try {
-            mSipSession.changeCall(createHoldOffer().encode(), timeout);
-        } catch (Throwable e) {
-            throwSipException(e);
-        }
-        mHold = true;
-        AudioGroup audioGroup = getAudioGroup();
-        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
-    }
-
-    public synchronized void continueCall(int timeout) throws SipException {
-        if (!mHold) return;
-        try {
-            mSipSession.changeCall(createContinueOffer().encode(), timeout);
-        } catch (Throwable e) {
-            throwSipException(e);
-        }
-        mHold = false;
-        AudioGroup audioGroup = getAudioGroup();
-        if (audioGroup != null) audioGroup.setMode(AudioGroup.MODE_NORMAL);
-    }
-
-    private SimpleSessionDescription createOffer() {
-        SimpleSessionDescription offer =
-                new SimpleSessionDescription(mSessionId, getLocalIp());
-        AudioCodec[] codecs = AudioCodec.getCodecs();
-        Media media = offer.newMedia(
-                "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
-        for (AudioCodec codec : AudioCodec.getCodecs()) {
-            media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
-        }
-        media.setRtpPayload(127, "telephone-event/8000", "0-15");
-        return offer;
-    }
-
-    private SimpleSessionDescription createAnswer(String offerSd) {
-        SimpleSessionDescription offer =
-                new SimpleSessionDescription(offerSd);
-        SimpleSessionDescription answer =
-                new SimpleSessionDescription(mSessionId, getLocalIp());
-        AudioCodec codec = null;
-        for (Media media : offer.getMedia()) {
-            if ((codec == null) && (media.getPort() > 0)
-                    && "audio".equals(media.getType())
-                    && "RTP/AVP".equals(media.getProtocol())) {
-                // Find the first audio codec we supported.
-                for (int type : media.getRtpPayloadTypes()) {
-                    codec = AudioCodec.getCodec(type, media.getRtpmap(type),
-                            media.getFmtp(type));
-                    if (codec != null) {
-                        break;
-                    }
-                }
-                if (codec != null) {
-                    Media reply = answer.newMedia(
-                            "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
-                    reply.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
-
-                    // Check if DTMF is supported in the same media.
-                    for (int type : media.getRtpPayloadTypes()) {
-                        String rtpmap = media.getRtpmap(type);
-                        if ((type != codec.type) && (rtpmap != null)
-                                && rtpmap.startsWith("telephone-event")) {
-                            reply.setRtpPayload(
-                                    type, rtpmap, media.getFmtp(type));
-                        }
-                    }
-
-                    // Handle recvonly and sendonly.
-                    if (media.getAttribute("recvonly") != null) {
-                        answer.setAttribute("sendonly", "");
-                    } else if(media.getAttribute("sendonly") != null) {
-                        answer.setAttribute("recvonly", "");
-                    } else if(offer.getAttribute("recvonly") != null) {
-                        answer.setAttribute("sendonly", "");
-                    } else if(offer.getAttribute("sendonly") != null) {
-                        answer.setAttribute("recvonly", "");
-                    }
-                    continue;
-                }
-            }
-            // Reject the media.
-            Media reply = answer.newMedia(
-                    media.getType(), 0, 1, media.getProtocol());
-            for (String format : media.getFormats()) {
-                reply.setFormat(format, null);
-            }
-        }
-        if (codec == null) {
-            throw new IllegalStateException("Reject SDP: no suitable codecs");
-        }
-        return answer;
-    }
-
-    private SimpleSessionDescription createHoldOffer() {
-        SimpleSessionDescription offer = createContinueOffer();
-        offer.setAttribute("sendonly", "");
-        return offer;
-    }
-
-    private SimpleSessionDescription createContinueOffer() {
-        SimpleSessionDescription offer =
-                new SimpleSessionDescription(mSessionId, getLocalIp());
-        Media media = offer.newMedia(
-                "audio", mAudioStream.getLocalPort(), 1, "RTP/AVP");
-        AudioCodec codec = mAudioStream.getCodec();
-        media.setRtpPayload(codec.type, codec.rtpmap, codec.fmtp);
-        int dtmfType = mAudioStream.getDtmfType();
-        if (dtmfType != -1) {
-            media.setRtpPayload(dtmfType, "telephone-event/8000", "0-15");
-        }
-        return offer;
-    }
-
-    public synchronized void toggleMute() {
-        AudioGroup audioGroup = getAudioGroup();
-        if (audioGroup != null) {
-            audioGroup.setMode(
-                    mMuted ? AudioGroup.MODE_NORMAL : AudioGroup.MODE_MUTED);
-            mMuted = !mMuted;
-        }
-    }
-
-    public synchronized boolean isMuted() {
-        return mMuted;
-    }
-
-    public synchronized void setSpeakerMode(boolean speakerMode) {
-        ((AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE))
-                .setSpeakerphoneOn(speakerMode);
-    }
-
-    public void sendDtmf(int code) {
-        sendDtmf(code, null);
-    }
-
-    public synchronized void sendDtmf(int code, Message result) {
-        AudioGroup audioGroup = getAudioGroup();
-        if ((audioGroup != null) && (mSipSession != null)
-                && (SipSessionState.IN_CALL == getState())) {
-            Log.v(TAG, "send DTMF: " + code);
-            audioGroup.sendDtmf(code);
-        }
-        if (result != null) result.sendToTarget();
-    }
-
-    public synchronized AudioStream getAudioStream() {
-        return mAudioStream;
-    }
-
-    public synchronized AudioGroup getAudioGroup() {
-        if (mAudioGroup != null) return mAudioGroup;
-        return ((mAudioStream == null) ? null : mAudioStream.getGroup());
-    }
-
-    public synchronized void setAudioGroup(AudioGroup group) {
-        if ((mAudioStream != null) && (mAudioStream.getGroup() != null)) {
-            mAudioStream.join(group);
-        }
-        mAudioGroup = group;
-    }
-
-    public void startAudio() {
-        try {
-            startAudioInternal();
-        } catch (UnknownHostException e) {
-            onError(mSipSession, SipErrorCode.PEER_NOT_REACHABLE,
-                    e.getMessage());
-        } catch (Throwable e) {
-            onError(mSipSession, SipErrorCode.CLIENT_ERROR,
-                    e.getMessage());
-        }
-    }
-
-    private synchronized void startAudioInternal() throws UnknownHostException {
-        if (mPeerSd == null) {
-            Log.v(TAG, "startAudioInternal() mPeerSd = null");
-            throw new IllegalStateException("mPeerSd = null");
-        }
-
-        stopCall(DONT_RELEASE_SOCKET);
-        mInCall = true;
-
-        // Run exact the same logic in createAnswer() to setup mAudioStream.
-        SimpleSessionDescription offer =
-                new SimpleSessionDescription(mPeerSd);
-        AudioStream stream = mAudioStream;
-        AudioCodec codec = null;
-        for (Media media : offer.getMedia()) {
-            if ((codec == null) && (media.getPort() > 0)
-                    && "audio".equals(media.getType())
-                    && "RTP/AVP".equals(media.getProtocol())) {
-                // Find the first audio codec we supported.
-                for (int type : media.getRtpPayloadTypes()) {
-                    codec = AudioCodec.getCodec(
-                            type, media.getRtpmap(type), media.getFmtp(type));
-                    if (codec != null) {
-                        break;
-                    }
-                }
-
-                if (codec != null) {
-                    // Associate with the remote host.
-                    String address = media.getAddress();
-                    if (address == null) {
-                        address = offer.getAddress();
-                    }
-                    stream.associate(InetAddress.getByName(address),
-                            media.getPort());
-
-                    stream.setDtmfType(-1);
-                    stream.setCodec(codec);
-                    // Check if DTMF is supported in the same media.
-                    for (int type : media.getRtpPayloadTypes()) {
-                        String rtpmap = media.getRtpmap(type);
-                        if ((type != codec.type) && (rtpmap != null)
-                                && rtpmap.startsWith("telephone-event")) {
-                            stream.setDtmfType(type);
-                        }
-                    }
-
-                    // Handle recvonly and sendonly.
-                    if (mHold) {
-                        stream.setMode(RtpStream.MODE_NORMAL);
-                    } else if (media.getAttribute("recvonly") != null) {
-                        stream.setMode(RtpStream.MODE_SEND_ONLY);
-                    } else if(media.getAttribute("sendonly") != null) {
-                        stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
-                    } else if(offer.getAttribute("recvonly") != null) {
-                        stream.setMode(RtpStream.MODE_SEND_ONLY);
-                    } else if(offer.getAttribute("sendonly") != null) {
-                        stream.setMode(RtpStream.MODE_RECEIVE_ONLY);
-                    } else {
-                        stream.setMode(RtpStream.MODE_NORMAL);
-                    }
-                    break;
-                }
-            }
-        }
-        if (codec == null) {
-            throw new IllegalStateException("Reject SDP: no suitable codecs");
-        }
-
-        if (!mHold) {
-            /* The recorder volume will be very low if the device is in
-             * IN_CALL mode. Therefore, we have to set the mode to NORMAL
-             * in order to have the normal microphone level.
-             */
-            ((AudioManager) mContext.getSystemService
-                    (Context.AUDIO_SERVICE))
-                    .setMode(AudioManager.MODE_NORMAL);
-        }
-
-        // AudioGroup logic:
-        AudioGroup audioGroup = getAudioGroup();
-        if (mHold) {
-            if (audioGroup != null) {
-                audioGroup.setMode(AudioGroup.MODE_ON_HOLD);
-            }
-            // don't create an AudioGroup here; doing so will fail if
-            // there's another AudioGroup out there that's active
-        } else {
-            if (audioGroup == null) audioGroup = new AudioGroup();
-            mAudioStream.join(audioGroup);
-            if (mMuted) {
-                audioGroup.setMode(AudioGroup.MODE_MUTED);
-            } else {
-                audioGroup.setMode(AudioGroup.MODE_NORMAL);
-            }
-        }
-    }
-
-    private void stopCall(boolean releaseSocket) {
-        Log.d(TAG, "stop audiocall");
-        if (mAudioStream != null) {
-            mAudioStream.join(null);
-
-            if (releaseSocket) {
-                mAudioStream.release();
-                mAudioStream = null;
-            }
-        }
-    }
-
-    private String getLocalIp() {
-        try {
-            return mSipSession.getLocalIp();
-        } catch (RemoteException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    public synchronized void setRingbackToneEnabled(boolean enabled) {
-        mRingbackToneEnabled = enabled;
-    }
-
-    public synchronized void setRingtoneEnabled(boolean enabled) {
-        mRingtoneEnabled = enabled;
-    }
-
-    private void startRingbackTone() {
-        if (!mRingbackToneEnabled) return;
-        if (mRingbackTone == null) {
-            // The volume relative to other sounds in the stream
-            int toneVolume = 80;
-            mRingbackTone = new ToneGenerator(
-                    AudioManager.STREAM_VOICE_CALL, toneVolume);
-        }
-        mRingbackTone.startTone(ToneGenerator.TONE_CDMA_LOW_PBX_L);
-    }
-
-    private void stopRingbackTone() {
-        if (mRingbackTone != null) {
-            mRingbackTone.stopTone();
-            mRingbackTone.release();
-            mRingbackTone = null;
-        }
-    }
-
-    private void startRinging() {
-        if (!mRingtoneEnabled) return;
-        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
-                .vibrate(new long[] {0, 1000, 1000}, 1);
-        AudioManager am = (AudioManager)
-                mContext.getSystemService(Context.AUDIO_SERVICE);
-        if (am.getStreamVolume(AudioManager.STREAM_RING) > 0) {
-            String ringtoneUri =
-                    Settings.System.DEFAULT_RINGTONE_URI.toString();
-            mRingtone = RingtoneManager.getRingtone(mContext,
-                    Uri.parse(ringtoneUri));
-            mRingtone.play();
-        }
-    }
-
-    private void stopRinging() {
-        ((Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE))
-                .cancel();
-        if (mRingtone != null) mRingtone.stop();
-    }
-
-    private void throwSipException(Throwable throwable) throws SipException {
-        if (throwable instanceof SipException) {
-            throw (SipException) throwable;
-        } else {
-            throw new SipException("", throwable);
-        }
-    }
-
-    private SipProfile getPeerProfile(ISipSession session) {
-        try {
-            return session.getPeerProfile();
-        } catch (RemoteException e) {
-            return null;
-        }
-    }
-}
diff --git a/voip/java/android/net/sip/SipManager.java b/voip/java/android/net/sip/SipManager.java
index 31768d7..5976a04 100644
--- a/voip/java/android/net/sip/SipManager.java
+++ b/voip/java/android/net/sip/SipManager.java
@@ -30,8 +30,9 @@
  * The class provides API for various SIP related tasks. Specifically, the API
  * allows an application to:
  * <ul>
- * <li>register a {@link SipProfile} to have the background SIP service listen
- *      to incoming calls and broadcast them with registered command string. See
+ * <li>open a {@link SipProfile} to get ready for making outbound calls or have
+ *      the background SIP service listen to incoming calls and broadcast them
+ *      with registered command string. See
  *      {@link #open(SipProfile, String, SipRegistrationListener)},
  *      {@link #open(SipProfile)}, {@link #close}, {@link #isOpened} and
  *      {@link #isRegistered}. It also facilitates handling of the incoming call
@@ -40,39 +41,59 @@
  *      {@link #getOfferSessionDescription} and {@link #takeAudioCall}.</li>
  * <li>make/take SIP-based audio calls. See
  *      {@link #makeAudioCall} and {@link #takeAudioCall}.</li>
- * <li>register/unregister with a SIP service provider. See
+ * <li>register/unregister with a SIP service provider manually. See
  *      {@link #register} and {@link #unregister}.</li>
- * <li>process SIP events directly with a {@link ISipSession} created by
+ * <li>process SIP events directly with a {@link SipSession} created by
  *      {@link #createSipSession}.</li>
  * </ul>
  * @hide
  */
 public class SipManager {
-    /** @hide */
-    public static final String SIP_INCOMING_CALL_ACTION =
+    /**
+     * Action string for the incoming call intent for the Phone app.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_SIP_INCOMING_CALL =
             "com.android.phone.SIP_INCOMING_CALL";
-    /** @hide */
-    public static final String SIP_ADD_PHONE_ACTION =
+    /**
+     * Action string for the add-phone intent.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_SIP_ADD_PHONE =
             "com.android.phone.SIP_ADD_PHONE";
-    /** @hide */
-    public static final String SIP_REMOVE_PHONE_ACTION =
+    /**
+     * Action string for the remove-phone intent.
+     * Internal use only.
+     * @hide
+     */
+    public static final String ACTION_SIP_REMOVE_PHONE =
             "com.android.phone.SIP_REMOVE_PHONE";
-    /** @hide */
-    public static final String LOCAL_URI_KEY = "LOCAL SIPURI";
+    /**
+     * Part of the ACTION_SIP_ADD_PHONE and ACTION_SIP_REMOVE_PHONE intents.
+     * Internal use only.
+     * @hide
+     */
+    public static final String EXTRA_LOCAL_URI = "android:localSipUri";
 
-    private static final String CALL_ID_KEY = "CallID";
-    private static final String OFFER_SD_KEY = "OfferSD";
+    /** Part of the incoming call intent. */
+    public static final String EXTRA_CALL_ID = "android:sipCallID";
+
+    /** Part of the incoming call intent. */
+    public static final String EXTRA_OFFER_SD = "android:sipOfferSD";
 
     private ISipService mSipService;
+    private Context mContext;
 
     /**
-     * Gets a manager instance. Returns null if SIP API is not supported.
+     * Creates a manager instance. Returns null if SIP API is not supported.
      *
-     * @param context application context for checking if SIP API is supported
+     * @param context application context for creating the manager object
      * @return the manager instance or null if SIP API is not supported
      */
-    public static SipManager getInstance(Context context) {
-        return (isApiSupported(context) ? new SipManager() : null);
+    public static SipManager newInstance(Context context) {
+        return (isApiSupported(context) ? new SipManager(context) : null);
     }
 
     /**
@@ -80,7 +101,7 @@
      */
     public static boolean isApiSupported(Context context) {
         return true;
-        /* 
+        /* TODO: uncomment this before ship
         return context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SIP);
          */
@@ -91,7 +112,7 @@
      */
     public static boolean isVoipSupported(Context context) {
         return true;
-        /* 
+        /* TODO: uncomment this before ship
         return context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SIP_VOIP) && isApiSupported(context);
          */
@@ -105,23 +126,21 @@
                 com.android.internal.R.bool.config_sip_wifi_only);
     }
 
-    private SipManager() {
+    private SipManager(Context context) {
+        mContext = context;
         createSipService();
     }
 
     private void createSipService() {
-        if (mSipService != null) return;
         IBinder b = ServiceManager.getService(Context.SIP_SERVICE);
         mSipService = ISipService.Stub.asInterface(b);
     }
 
     /**
-     * Opens the profile for making calls and/or receiving calls. Subsequent
-     * SIP calls can be made through the default phone UI. The caller may also
-     * make subsequent calls through {@link #makeAudioCall}.
-     * If the receiving-call option is enabled in the profile, the SIP service
-     * will register the profile to the corresponding server periodically in
-     * order to receive calls from the server.
+     * Opens the profile for making calls. The caller may make subsequent calls
+     * through {@link #makeAudioCall}. If one also wants to receive calls on the
+     * profile, use {@link #open(SipProfile, String, SipRegistrationListener)}
+     * instead.
      *
      * @param localProfile the SIP profile to make calls from
      * @throws SipException if the profile contains incorrect settings or
@@ -136,12 +155,11 @@
     }
 
     /**
-     * Opens the profile for making calls and/or receiving calls. Subsequent
-     * SIP calls can be made through the default phone UI. The caller may also
-     * make subsequent calls through {@link #makeAudioCall}.
-     * If the receiving-call option is enabled in the profile, the SIP service
-     * will register the profile to the corresponding server periodically in
-     * order to receive calls from the server.
+     * Opens the profile for making calls and/or receiving calls. The caller may
+     * make subsequent calls through {@link #makeAudioCall}. If the
+     * auto-registration option is enabled in the profile, the SIP service
+     * will register the profile to the corresponding SIP provider periodically
+     * in order to receive calls from the provider.
      *
      * @param localProfile the SIP profile to receive incoming calls for
      * @param incomingCallBroadcastAction the action to be broadcast when an
@@ -195,7 +213,8 @@
     }
 
     /**
-     * Checks if the specified profile is enabled to receive calls.
+     * Checks if the specified profile is opened in the SIP service for
+     * making and/or receiving calls.
      *
      * @param localProfileUri the URI of the profile in question
      * @return true if the profile is enabled to receive calls
@@ -210,11 +229,16 @@
     }
 
     /**
-     * Checks if the specified profile is registered to the server for
-     * receiving calls.
+     * Checks if the SIP service has successfully registered the profile to the
+     * SIP provider (specified in the profile) for receiving calls. Returning
+     * true from this method also implies the profile is opened
+     * ({@link #isOpened}).
      *
      * @param localProfileUri the URI of the profile in question
-     * @return true if the profile is registered to the server
+     * @return true if the profile is registered to the SIP provider; false if
+     *        the profile has not been opened in the SIP service or the SIP
+     *        service has not yet successfully registered the profile to the SIP
+     *        provider
      * @throws SipException if calling the SIP service results in an error
      */
     public boolean isRegistered(String localProfileUri) throws SipException {
@@ -231,7 +255,6 @@
      * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
-     * @param context context to create a {@link SipAudioCall} object
      * @param localProfile the SIP profile to make the call from
      * @param peerProfile the SIP profile to make the call to
      * @param listener to listen to the call events from {@link SipAudioCall};
@@ -241,10 +264,10 @@
      * @throws SipException if calling the SIP service results in an error
      * @see SipAudioCall.Listener.onError
      */
-    public SipAudioCall makeAudioCall(Context context, SipProfile localProfile,
+    public SipAudioCall makeAudioCall(SipProfile localProfile,
             SipProfile peerProfile, SipAudioCall.Listener listener, int timeout)
             throws SipException {
-        SipAudioCall call = new SipAudioCallImpl(context, localProfile);
+        SipAudioCall call = new SipAudioCall(mContext, localProfile);
         call.setListener(listener);
         call.makeCall(peerProfile, this, timeout);
         return call;
@@ -257,7 +280,6 @@
      * {@code SipAudioCall.Listener.onError(SipAudioCall, SipErrorCode.TIME_OUT, String)}
      * will be called.
      *
-     * @param context context to create a {@link SipAudioCall} object
      * @param localProfileUri URI of the SIP profile to make the call from
      * @param peerProfileUri URI of the SIP profile to make the call to
      * @param listener to listen to the call events from {@link SipAudioCall};
@@ -267,11 +289,11 @@
      * @throws SipException if calling the SIP service results in an error
      * @see SipAudioCall.Listener.onError
      */
-    public SipAudioCall makeAudioCall(Context context, String localProfileUri,
+    public SipAudioCall makeAudioCall(String localProfileUri,
             String peerProfileUri, SipAudioCall.Listener listener, int timeout)
             throws SipException {
         try {
-            return makeAudioCall(context,
+            return makeAudioCall(
                     new SipProfile.Builder(localProfileUri).build(),
                     new SipProfile.Builder(peerProfileUri).build(), listener,
                     timeout);
@@ -281,15 +303,14 @@
     }
 
     /**
-     * The method calls {@code takeAudioCall(context, incomingCallIntent,
+     * The method calls {@code takeAudioCall(incomingCallIntent,
      * listener, true}.
      *
-     * @see #takeAudioCall(Context, Intent, SipAudioCall.Listener, boolean)
+     * @see #takeAudioCall(Intent, SipAudioCall.Listener, boolean)
      */
-    public SipAudioCall takeAudioCall(Context context,
-            Intent incomingCallIntent, SipAudioCall.Listener listener)
-            throws SipException {
-        return takeAudioCall(context, incomingCallIntent, listener, true);
+    public SipAudioCall takeAudioCall(Intent incomingCallIntent,
+            SipAudioCall.Listener listener) throws SipException {
+        return takeAudioCall(incomingCallIntent, listener, true);
     }
 
     /**
@@ -298,16 +319,15 @@
      * {@link SipAudioCall.Listener#onRinging}
      * callback.
      *
-     * @param context context to create a {@link SipAudioCall} object
      * @param incomingCallIntent the incoming call broadcast intent
      * @param listener to listen to the call events from {@link SipAudioCall};
      *      can be null
      * @return a {@link SipAudioCall} object
      * @throws SipException if calling the SIP service results in an error
      */
-    public SipAudioCall takeAudioCall(Context context,
-            Intent incomingCallIntent, SipAudioCall.Listener listener,
-            boolean ringtoneEnabled) throws SipException {
+    public SipAudioCall takeAudioCall(Intent incomingCallIntent,
+            SipAudioCall.Listener listener, boolean ringtoneEnabled)
+            throws SipException {
         if (incomingCallIntent == null) return null;
 
         String callId = getCallId(incomingCallIntent);
@@ -324,10 +344,10 @@
         try {
             ISipSession session = mSipService.getPendingSession(callId);
             if (session == null) return null;
-            SipAudioCall call = new SipAudioCallImpl(
-                    context, session.getLocalProfile());
+            SipAudioCall call = new SipAudioCall(
+                    mContext, session.getLocalProfile());
             call.setRingtoneEnabled(ringtoneEnabled);
-            call.attachCall(session, offerSd);
+            call.attachCall(new SipSession(session), offerSd);
             call.setListener(listener);
             return call;
         } catch (Throwable t) {
@@ -355,7 +375,7 @@
      * @return the call ID or null if the intent does not contain it
      */
     public static String getCallId(Intent incomingCallIntent) {
-        return incomingCallIntent.getStringExtra(CALL_ID_KEY);
+        return incomingCallIntent.getStringExtra(EXTRA_CALL_ID);
     }
 
     /**
@@ -367,30 +387,30 @@
      *      have it
      */
     public static String getOfferSessionDescription(Intent incomingCallIntent) {
-        return incomingCallIntent.getStringExtra(OFFER_SD_KEY);
+        return incomingCallIntent.getStringExtra(EXTRA_OFFER_SD);
     }
 
     /**
      * Creates an incoming call broadcast intent.
      *
-     * @param action the action string to broadcast
      * @param callId the call ID of the incoming call
      * @param sessionDescription the session description of the incoming call
      * @return the incoming call intent
      * @hide
      */
-    public static Intent createIncomingCallBroadcast(String action,
-            String callId, String sessionDescription) {
-        Intent intent = new Intent(action);
-        intent.putExtra(CALL_ID_KEY, callId);
-        intent.putExtra(OFFER_SD_KEY, sessionDescription);
+    public static Intent createIncomingCallBroadcast(String callId,
+            String sessionDescription) {
+        Intent intent = new Intent();
+        intent.putExtra(EXTRA_CALL_ID, callId);
+        intent.putExtra(EXTRA_OFFER_SD, sessionDescription);
         return intent;
     }
 
     /**
-     * Registers the profile to the corresponding server for receiving calls.
-     * {@link #open} is still needed to be called at least once in order for
-     * the SIP service to broadcast an intent when an incoming call is received.
+     * Manually registers the profile to the corresponding SIP provider for
+     * receiving calls. {@link #open(SipProfile, String, SipRegistrationListener)}
+     * is still needed to be called at least once in order for the SIP service
+     * to broadcast an intent when an incoming call is received.
      *
      * @param localProfile the SIP profile to register with
      * @param expiryTime registration expiration time (in seconds)
@@ -409,8 +429,10 @@
     }
 
     /**
-     * Unregisters the profile from the corresponding server for not receiving
-     * further calls.
+     * Manually unregisters the profile from the corresponding SIP provider for
+     * stop receiving further calls. This may interference with the auto
+     * registration process in the SIP service if the auto-registration option
+     * in the profile is enabled.
      *
      * @param localProfile the SIP profile to register with
      * @param listener to listen to the registration events
@@ -460,10 +482,11 @@
      * @param localProfile the SIP profile the session is associated with
      * @param listener to listen to SIP session events
      */
-    public ISipSession createSipSession(SipProfile localProfile,
-            ISipSessionListener listener) throws SipException {
+    public SipSession createSipSession(SipProfile localProfile,
+            SipSession.Listener listener) throws SipException {
         try {
-            return mSipService.createSession(localProfile, listener);
+            ISipSession s = mSipService.createSession(localProfile, null);
+            return new SipSession(s, listener);
         } catch (RemoteException e) {
             throw new SipException("createSipSession()", e);
         }
diff --git a/voip/java/android/net/sip/SipProfile.java b/voip/java/android/net/sip/SipProfile.java
index 88bfba9..6d5cb3c 100644
--- a/voip/java/android/net/sip/SipProfile.java
+++ b/voip/java/android/net/sip/SipProfile.java
@@ -48,7 +48,6 @@
     private boolean mAutoRegistration = true;
     private transient int mCallingUid = 0;
 
-    /** @hide */
     public static final Parcelable.Creator<SipProfile> CREATOR =
             new Parcelable.Creator<SipProfile>() {
                 public SipProfile createFromParcel(Parcel in) {
@@ -287,7 +286,7 @@
         mCallingUid = in.readInt();
     }
 
-    /** @hide */
+    @Override
     public void writeToParcel(Parcel out, int flags) {
         out.writeSerializable(mAddress);
         out.writeString(mProxyAddress);
@@ -300,7 +299,7 @@
         out.writeInt(mCallingUid);
     }
 
-    /** @hide */
+    @Override
     public int describeContents() {
         return 0;
     }
diff --git a/voip/java/android/net/sip/SipSession.java b/voip/java/android/net/sip/SipSession.java
new file mode 100644
index 0000000..0cc7206
--- /dev/null
+++ b/voip/java/android/net/sip/SipSession.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.sip;
+
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * A SIP session that is associated with a SIP dialog or a standalone
+ * transaction not within a dialog.
+ * @hide
+ */
+public final class SipSession {
+    private static final String TAG = "SipSession";
+
+    /**
+     * Defines {@link SipSession} states.
+     * @hide
+     */
+    public static class State {
+        /** When session is ready to initiate a call or transaction. */
+        public static final int READY_TO_CALL = 0;
+
+        /** When the registration request is sent out. */
+        public static final int REGISTERING = 1;
+
+        /** When the unregistration request is sent out. */
+        public static final int DEREGISTERING = 2;
+
+        /** When an INVITE request is received. */
+        public static final int INCOMING_CALL = 3;
+
+        /** When an OK response is sent for the INVITE request received. */
+        public static final int INCOMING_CALL_ANSWERING = 4;
+
+        /** When an INVITE request is sent. */
+        public static final int OUTGOING_CALL = 5;
+
+        /** When a RINGING response is received for the INVITE request sent. */
+        public static final int OUTGOING_CALL_RING_BACK = 6;
+
+        /** When a CANCEL request is sent for the INVITE request sent. */
+        public static final int OUTGOING_CALL_CANCELING = 7;
+
+        /** When a call is established. */
+        public static final int IN_CALL = 8;
+
+        /** When an OPTIONS request is sent. */
+        public static final int PINGING = 9;
+
+        /** Not defined. */
+        public static final int NOT_DEFINED = 101;
+
+        /**
+         * Converts the state to string.
+         */
+        public static String toString(int state) {
+            switch (state) {
+                case READY_TO_CALL:
+                    return "READY_TO_CALL";
+                case REGISTERING:
+                    return "REGISTERING";
+                case DEREGISTERING:
+                    return "DEREGISTERING";
+                case INCOMING_CALL:
+                    return "INCOMING_CALL";
+                case INCOMING_CALL_ANSWERING:
+                    return "INCOMING_CALL_ANSWERING";
+                case OUTGOING_CALL:
+                    return "OUTGOING_CALL";
+                case OUTGOING_CALL_RING_BACK:
+                    return "OUTGOING_CALL_RING_BACK";
+                case OUTGOING_CALL_CANCELING:
+                    return "OUTGOING_CALL_CANCELING";
+                case IN_CALL:
+                    return "IN_CALL";
+                case PINGING:
+                    return "PINGING";
+                default:
+                    return "NOT_DEFINED";
+            }
+        }
+
+        private State() {
+        }
+    }
+
+    /**
+     * Listener class that listens to {@link SipSession} events.
+     * @hide
+     */
+    public static class Listener {
+        /**
+         * Called when an INVITE request is sent to initiate a new call.
+         *
+         * @param session the session object that carries out the transaction
+         */
+        public void onCalling(SipSession session) {
+        }
+
+        /**
+         * Called when an INVITE request is received.
+         *
+         * @param session the session object that carries out the transaction
+         * @param caller the SIP profile of the caller
+         * @param sessionDescription the caller's session description
+         */
+        public void onRinging(SipSession session, SipProfile caller,
+                String sessionDescription) {
+        }
+
+        /**
+         * Called when a RINGING response is received for the INVITE request sent
+         *
+         * @param session the session object that carries out the transaction
+         */
+        public void onRingingBack(SipSession session) {
+        }
+
+        /**
+         * Called when the session is established.
+         *
+         * @param session the session object that is associated with the dialog
+         * @param sessionDescription the peer's session description
+         */
+        public void onCallEstablished(SipSession session,
+                String sessionDescription) {
+        }
+
+        /**
+         * Called when the session is terminated.
+         *
+         * @param session the session object that is associated with the dialog
+         */
+        public void onCallEnded(SipSession session) {
+        }
+
+        /**
+         * Called when the peer is busy during session initialization.
+         *
+         * @param session the session object that carries out the transaction
+         */
+        public void onCallBusy(SipSession session) {
+        }
+
+        /**
+         * Called when an error occurs during session initialization and
+         * termination.
+         *
+         * @param session the session object that carries out the transaction
+         * @param errorCode error code defined in {@link SipErrorCode}
+         * @param errorMessage error message
+         */
+        public void onError(SipSession session, int errorCode,
+                String errorMessage) {
+        }
+
+        /**
+         * Called when an error occurs during session modification negotiation.
+         *
+         * @param session the session object that carries out the transaction
+         * @param errorCode error code defined in {@link SipErrorCode}
+         * @param errorMessage error message
+         */
+        public void onCallChangeFailed(SipSession session, int errorCode,
+                String errorMessage) {
+        }
+
+        /**
+         * Called when a registration request is sent.
+         *
+         * @param session the session object that carries out the transaction
+         */
+        public void onRegistering(SipSession session) {
+        }
+
+        /**
+         * Called when registration is successfully done.
+         *
+         * @param session the session object that carries out the transaction
+         * @param duration duration in second before the registration expires
+         */
+        public void onRegistrationDone(SipSession session, int duration) {
+        }
+
+        /**
+         * Called when the registration fails.
+         *
+         * @param session the session object that carries out the transaction
+         * @param errorCode error code defined in {@link SipErrorCode}
+         * @param errorMessage error message
+         */
+        public void onRegistrationFailed(SipSession session, int errorCode,
+                String errorMessage) {
+        }
+
+        /**
+         * Called when the registration gets timed out.
+         *
+         * @param session the session object that carries out the transaction
+         */
+        public void onRegistrationTimeout(SipSession session) {
+        }
+    }
+
+    private final ISipSession mSession;
+    private Listener mListener;
+
+    SipSession(ISipSession realSession) {
+        mSession = realSession;
+        if (realSession != null) {
+            try {
+                realSession.setListener(createListener());
+            } catch (RemoteException e) {
+                Log.e(TAG, "SipSession.setListener(): " + e);
+            }
+        }
+    }
+
+    SipSession(ISipSession realSession, Listener listener) {
+        this(realSession);
+        setListener(listener);
+    }
+
+    /**
+     * Gets the IP address of the local host on which this SIP session runs.
+     *
+     * @return the IP address of the local host
+     */
+    public String getLocalIp() {
+        try {
+            return mSession.getLocalIp();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getLocalIp(): " + e);
+            return "127.0.0.1";
+        }
+    }
+
+    /**
+     * Gets the SIP profile that this session is associated with.
+     *
+     * @return the SIP profile that this session is associated with
+     */
+    public SipProfile getLocalProfile() {
+        try {
+            return mSession.getLocalProfile();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getLocalProfile(): " + e);
+            return null;
+        }
+    }
+
+    /**
+     * Gets the SIP profile that this session is connected to. Only available
+     * when the session is associated with a SIP dialog.
+     *
+     * @return the SIP profile that this session is connected to
+     */
+    public SipProfile getPeerProfile() {
+        try {
+            return mSession.getPeerProfile();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getPeerProfile(): " + e);
+            return null;
+        }
+    }
+
+    /**
+     * Gets the session state. The value returned must be one of the states in
+     * {@link SipSessionState}.
+     *
+     * @return the session state
+     */
+    public int getState() {
+        try {
+            return mSession.getState();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getState(): " + e);
+            return State.NOT_DEFINED;
+        }
+    }
+
+    /**
+     * Checks if the session is in a call.
+     *
+     * @return true if the session is in a call
+     */
+    public boolean isInCall() {
+        try {
+            return mSession.isInCall();
+        } catch (RemoteException e) {
+            Log.e(TAG, "isInCall(): " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Gets the call ID of the session.
+     *
+     * @return the call ID
+     */
+    public String getCallId() {
+        try {
+            return mSession.getCallId();
+        } catch (RemoteException e) {
+            Log.e(TAG, "getCallId(): " + e);
+            return null;
+        }
+    }
+
+
+    /**
+     * Sets the listener to listen to the session events. A {@code SipSession}
+     * can only hold one listener at a time. Subsequent calls to this method
+     * override the previous listener.
+     *
+     * @param listener to listen to the session events of this object
+     */
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
+
+
+    /**
+     * Performs registration to the server specified by the associated local
+     * profile. The session listener is called back upon success or failure of
+     * registration. The method is only valid to call when the session state is
+     * in {@link SipSessionState#READY_TO_CALL}.
+     *
+     * @param duration duration in second before the registration expires
+     * @see Listener
+     */
+    public void register(int duration) {
+        try {
+            mSession.register(duration);
+        } catch (RemoteException e) {
+            Log.e(TAG, "register(): " + e);
+        }
+    }
+
+    /**
+     * Performs unregistration to the server specified by the associated local
+     * profile. Unregistration is technically the same as registration with zero
+     * expiration duration. The session listener is called back upon success or
+     * failure of unregistration. The method is only valid to call when the
+     * session state is in {@link SipSessionState#READY_TO_CALL}.
+     *
+     * @see Listener
+     */
+    public void unregister() {
+        try {
+            mSession.unregister();
+        } catch (RemoteException e) {
+            Log.e(TAG, "unregister(): " + e);
+        }
+    }
+
+    /**
+     * Initiates a call to the specified profile. The session listener is called
+     * back upon defined session events. The method is only valid to call when
+     * the session state is in {@link SipSessionState#READY_TO_CALL}.
+     *
+     * @param callee the SIP profile to make the call to
+     * @param sessionDescription the session description of this call
+     * @param timeout the session will be timed out if the call is not
+     *        established within {@code timeout} seconds. Default value (defined
+     *        by SIP protocol) is used if {@code timeout} is zero or negative.
+     * @see Listener
+     */
+    public void makeCall(SipProfile callee, String sessionDescription,
+            int timeout) {
+        try {
+            mSession.makeCall(callee, sessionDescription, timeout);
+        } catch (RemoteException e) {
+            Log.e(TAG, "makeCall(): " + e);
+        }
+    }
+
+    /**
+     * Answers an incoming call with the specified session description. The
+     * method is only valid to call when the session state is in
+     * {@link SipSessionState#INCOMING_CALL}.
+     *
+     * @param sessionDescription the session description to answer this call
+     * @param timeout the session will be timed out if the call is not
+     *        established within {@code timeout} seconds. Default value (defined
+     *        by SIP protocol) is used if {@code timeout} is zero or negative.
+     */
+    public void answerCall(String sessionDescription, int timeout) {
+        try {
+            mSession.answerCall(sessionDescription, timeout);
+        } catch (RemoteException e) {
+            Log.e(TAG, "answerCall(): " + e);
+        }
+    }
+
+    /**
+     * Ends an established call, terminates an outgoing call or rejects an
+     * incoming call. The method is only valid to call when the session state is
+     * in {@link SipSessionState#IN_CALL},
+     * {@link SipSessionState#INCOMING_CALL},
+     * {@link SipSessionState#OUTGOING_CALL} or
+     * {@link SipSessionState#OUTGOING_CALL_RING_BACK}.
+     */
+    public void endCall() {
+        try {
+            mSession.endCall();
+        } catch (RemoteException e) {
+            Log.e(TAG, "endCall(): " + e);
+        }
+    }
+
+    /**
+     * Changes the session description during a call. The method is only valid
+     * to call when the session state is in {@link SipSessionState#IN_CALL}.
+     *
+     * @param sessionDescription the new session description
+     * @param timeout the session will be timed out if the call is not
+     *        established within {@code timeout} seconds. Default value (defined
+     *        by SIP protocol) is used if {@code timeout} is zero or negative.
+     */
+    public void changeCall(String sessionDescription, int timeout) {
+        try {
+            mSession.changeCall(sessionDescription, timeout);
+        } catch (RemoteException e) {
+            Log.e(TAG, "changeCall(): " + e);
+        }
+    }
+
+    ISipSession getRealSession() {
+        return mSession;
+    }
+
+    private ISipSessionListener createListener() {
+        return new ISipSessionListener.Stub() {
+            public void onCalling(ISipSession session) {
+                if (mListener != null) {
+                    mListener.onCalling(SipSession.this);
+                }
+            }
+
+            public void onRinging(ISipSession session, SipProfile caller,
+                    String sessionDescription) {
+                if (mListener != null) {
+                    mListener.onRinging(SipSession.this, caller,
+                            sessionDescription);
+                }
+            }
+
+            public void onRingingBack(ISipSession session) {
+                if (mListener != null) {
+                    mListener.onRingingBack(SipSession.this);
+                }
+            }
+
+            public void onCallEstablished(ISipSession session,
+                    String sessionDescription) {
+                if (mListener != null) {
+                    mListener.onCallEstablished(SipSession.this,
+                            sessionDescription);
+                }
+            }
+
+            public void onCallEnded(ISipSession session) {
+                if (mListener != null) {
+                    mListener.onCallEnded(SipSession.this);
+                }
+            }
+
+            public void onCallBusy(ISipSession session) {
+                if (mListener != null) {
+                    mListener.onCallBusy(SipSession.this);
+                }
+            }
+
+            public void onCallChangeFailed(ISipSession session, int errorCode,
+                    String message) {
+                if (mListener != null) {
+                    mListener.onCallChangeFailed(SipSession.this, errorCode,
+                            message);
+                }
+            }
+
+            public void onError(ISipSession session, int errorCode, String message) {
+                if (mListener != null) {
+                    mListener.onError(SipSession.this, errorCode, message);
+                }
+            }
+
+            public void onRegistering(ISipSession session) {
+                if (mListener != null) {
+                    mListener.onRegistering(SipSession.this);
+                }
+            }
+
+            public void onRegistrationDone(ISipSession session, int duration) {
+                if (mListener != null) {
+                    mListener.onRegistrationDone(SipSession.this, duration);
+                }
+            }
+
+            public void onRegistrationFailed(ISipSession session, int errorCode,
+                    String message) {
+                if (mListener != null) {
+                    mListener.onRegistrationFailed(SipSession.this, errorCode,
+                            message);
+                }
+            }
+
+            public void onRegistrationTimeout(ISipSession session) {
+                if (mListener != null) {
+                    mListener.onRegistrationTimeout(SipSession.this);
+                }
+            }
+        };
+    }
+}
diff --git a/voip/java/android/net/sip/SipSessionState.java b/voip/java/android/net/sip/SipSessionState.java
deleted file mode 100644
index 31e9d3f..0000000
--- a/voip/java/android/net/sip/SipSessionState.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.net.sip;
-
-/**
- * Defines {@link ISipSession} states.
- * @hide
- */
-public class SipSessionState {
-    /** When session is ready to initiate a call or transaction. */
-    public static final int READY_TO_CALL = 0;
-
-    /** When the registration request is sent out. */
-    public static final int REGISTERING = 1;
-
-    /** When the unregistration request is sent out. */
-    public static final int DEREGISTERING = 2;
-
-    /** When an INVITE request is received. */
-    public static final int INCOMING_CALL = 3;
-
-    /** When an OK response is sent for the INVITE request received. */
-    public static final int INCOMING_CALL_ANSWERING = 4;
-
-    /** When an INVITE request is sent. */
-    public static final int OUTGOING_CALL = 5;
-
-    /** When a RINGING response is received for the INVITE request sent. */
-    public static final int OUTGOING_CALL_RING_BACK = 6;
-
-    /** When a CANCEL request is sent for the INVITE request sent. */
-    public static final int OUTGOING_CALL_CANCELING = 7;
-
-    /** When a call is established. */
-    public static final int IN_CALL = 8;
-
-    /** Some error occurs when making a remote call to {@link ISipSession}. */
-    public static final int REMOTE_ERROR = 9;
-
-    /** When an OPTIONS request is sent. */
-    public static final int PINGING = 10;
-
-    /** Not defined. */
-    public static final int NOT_DEFINED = 101;
-
-    /**
-     * Converts the state to string.
-     */
-    public static String toString(int state) {
-        switch (state) {
-            case READY_TO_CALL:
-                return "READY_TO_CALL";
-            case REGISTERING:
-                return "REGISTERING";
-            case DEREGISTERING:
-                return "DEREGISTERING";
-            case INCOMING_CALL:
-                return "INCOMING_CALL";
-            case INCOMING_CALL_ANSWERING:
-                return "INCOMING_CALL_ANSWERING";
-            case OUTGOING_CALL:
-                return "OUTGOING_CALL";
-            case OUTGOING_CALL_RING_BACK:
-                return "OUTGOING_CALL_RING_BACK";
-            case OUTGOING_CALL_CANCELING:
-                return "OUTGOING_CALL_CANCELING";
-            case IN_CALL:
-                return "IN_CALL";
-            case REMOTE_ERROR:
-                return "REMOTE_ERROR";
-            case PINGING:
-                return "PINGING";
-            default:
-                return "NOT_DEFINED";
-        }
-    }
-
-    private SipSessionState() {
-    }
-}