Merge "Enhanced VelocityTracker for > 5 pointers and fixed bugs." into gingerbread
diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java
index b7a750b..00063af 100644
--- a/core/java/android/app/Service.java
+++ b/core/java/android/app/Service.java
@@ -403,7 +403,7 @@
      *
      * <p class="caution">Note that the system calls this on your
      * service's main thread.  A service's main thread is the same
-     * thread where UI operations place for Activities running in the
+     * thread where UI operations take place for Activities running in the
      * same process.  You should always avoid stalling the main
      * thread's event loop.  When doing long-running operations,
      * network calls, or heavy disk I/O, you should kick off a new
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 73c4011..0b35d8b 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -587,7 +587,7 @@
      * location from the apk location at the given file path.
      * @param packageFilePath file location of the apk
      * @param flags Special parse flags
-     * @return PackageLite object with package information.
+     * @return PackageLite object with package information or null on failure.
      */
     public static PackageLite parsePackageLite(String packageFilePath, int flags) {
         XmlResourceParser parser = null;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7bb89f5..ea26207 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3373,6 +3373,14 @@
         public static final String THROTTLE_MAX_NTP_CACHE_AGE_SEC =
                 "throttle_max_ntp_cache_age_sec";
 
+        /**
+         * The maximum size, in bytes, of a download that the download manager will transfer over
+         * a non-wifi connection.
+         * @hide
+         */
+        public static final String DOWNLOAD_MAX_BYTES_OVER_MOBILE =
+                "download_manager_max_bytes_over_mobile";
+
 
         /**
          * @hide
diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java
index d2563a8..5dd45f9 100644
--- a/core/java/android/view/ViewDebug.java
+++ b/core/java/android/view/ViewDebug.java
@@ -916,72 +916,13 @@
             out = new BufferedWriter(new OutputStreamWriter(clientStream), 32 * 1024);
 
             if (view != null) {
-                final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
-                    public Void[] pre() {
-                        forceLayout(view);
-                        return null;
-                    }
-
-                    private void forceLayout(View view) {
-                        view.forceLayout();
-                        if (view instanceof ViewGroup) {
-                            ViewGroup group = (ViewGroup) view;
-                            final int count = group.getChildCount();
-                            for (int i = 0; i < count; i++) {
-                                forceLayout(group.getChildAt(i));
-                            }
-                        }
-                    }
-
-                    public void run(Void... data) {
-                        view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
-                    }
-
-                    public void post(Void... data) {
-                    }
-                });
-
-                final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
-                    public Void[] pre() {
-                        return null;
-                    }
-
-                    public void run(Void... data) {
-                        view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
-                    }
-
-                    public void post(Void... data) {
-                    }
-                });
-
-                final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
-                    public Object[] pre() {
-                        final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
-                        final Bitmap bitmap = Bitmap.createBitmap(metrics.widthPixels,
-                                metrics.heightPixels, Bitmap.Config.RGB_565);
-                        final Canvas canvas = new Canvas(bitmap);
-                        return new Object[] { bitmap, canvas };
-                    }
-
-                    public void run(Object... data) {
-                        view.draw((Canvas) data[1]);
-                    }
-
-                    public void post(Object... data) {
-                        ((Bitmap) data[0]).recycle();
-                    }
-                });
-
-                out.write(String.valueOf(durationMeasure));
-                out.write(' ');
-                out.write(String.valueOf(durationLayout));
-                out.write(' ');
-                out.write(String.valueOf(durationDraw));
-                out.newLine();
+                profileViewAndChildren(view, out);
             } else {
                 out.write("-1 -1 -1");
                 out.newLine();
             }
+            out.write("DONE.");
+            out.newLine();
         } catch (Exception e) {
             android.util.Log.w("View", "Problem profiling the view:", e);
         } finally {
@@ -991,6 +932,82 @@
         }
     }
 
+    private static void profileViewAndChildren(final View view, BufferedWriter out)
+            throws IOException {
+        final long durationMeasure = profileViewOperation(view, new ViewOperation<Void>() {
+            public Void[] pre() {
+                forceLayout(view);
+                return null;
+            }
+
+            private void forceLayout(View view) {
+                view.forceLayout();
+                if (view instanceof ViewGroup) {
+                    ViewGroup group = (ViewGroup) view;
+                    final int count = group.getChildCount();
+                    for (int i = 0; i < count; i++) {
+                        forceLayout(group.getChildAt(i));
+                    }
+                }
+            }
+
+            public void run(Void... data) {
+                view.measure(view.mOldWidthMeasureSpec, view.mOldHeightMeasureSpec);
+            }
+
+            public void post(Void... data) {
+            }
+        });
+
+        final long durationLayout = profileViewOperation(view, new ViewOperation<Void>() {
+            public Void[] pre() {
+                return null;
+            }
+
+            public void run(Void... data) {
+                view.layout(view.mLeft, view.mTop, view.mRight, view.mBottom);
+            }
+
+            public void post(Void... data) {
+            }
+        });
+
+        final long durationDraw = profileViewOperation(view, new ViewOperation<Object>() {
+            public Object[] pre() {
+                final DisplayMetrics metrics = view.getResources().getDisplayMetrics();
+                final Bitmap bitmap =
+                        Bitmap.createBitmap(metrics.widthPixels, metrics.heightPixels,
+                                Bitmap.Config.RGB_565);
+                final Canvas canvas = new Canvas(bitmap);
+                return new Object[] {
+                        bitmap, canvas
+                };
+            }
+
+            public void run(Object... data) {
+                view.draw((Canvas) data[1]);
+            }
+
+            public void post(Object... data) {
+                ((Bitmap) data[0]).recycle();
+            }
+        });
+
+        out.write(String.valueOf(durationMeasure));
+        out.write(' ');
+        out.write(String.valueOf(durationLayout));
+        out.write(' ');
+        out.write(String.valueOf(durationDraw));
+        out.newLine();
+        if (view instanceof ViewGroup) {
+            ViewGroup group = (ViewGroup) view;
+            final int count = group.getChildCount();
+            for (int i = 0; i < count; i++) {
+                profileViewAndChildren(group.getChildAt(i), out);
+            }
+        }
+    }
+
     interface ViewOperation<T> {
         T[] pre();
         void run(T... data);
diff --git a/docs/html/index.jd b/docs/html/index.jd
index 01940e8..f37a122 100644
--- a/docs/html/index.jd
+++ b/docs/html/index.jd
@@ -163,12 +163,11 @@
       'img':"devphone-large.png",
       'title':"Android Dev Phones",
       'desc': "<p>Run and debug your Android applications directly on one of these "
- + "device. Modify and rebuild the Android operating system, and flash it onto "
- + "the phone. The Android Dev Phones are carrier independent, and available for "
- + "purchase by any developer registered with <a "
- + "href='http://market.android.com/publish'>Android Market</a>.</p><p><a "
- + "href='/guide/developing/device.html#dev-phone-1'>Learn more about the "
- + "Android Dev Phones &raquo;</a></p>"
+ + "devices. Modify and rebuild the Android operating system, and flash it onto "
+ + "the phone. The Android Dev Phones are carrier-independent, and available for "
+ + "purchase by developers through their Android Market publisher accounts.</p><p> "
+ + "<a href='http://market.android.com/publish'>Visit Android Market "
+ + "to learn more &raquo;</a></p>"
     },
 
     'mapskey': {
diff --git a/docs/html/resources/dashboard/platform-versions.jd b/docs/html/resources/dashboard/platform-versions.jd
index d4b6db5..ec47796 100644
--- a/docs/html/resources/dashboard/platform-versions.jd
+++ b/docs/html/resources/dashboard/platform-versions.jd
@@ -52,8 +52,9 @@
 <div class="dashboard-panel">
 
 <img alt="" height="250" width="460"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:0.3,18.9,22.1,55.5,3.3&chl=Other*|
-Android%201.5|Android%201.6|Android%202.1|Android%202.2&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:15.3,20.3,0.2,59.7,4.5&chl=
+Android%201.5|Android%201.6|Other*|Android%202.1|Android%202.2&chco=c4df9b,
+6fad0c" />
 
 <table>
 <tr>
@@ -61,14 +62,14 @@
   <th>API Level</th>
   <th>Distribution</th>
 </tr>
-<tr><td>Android 1.5</td><td>3</td><td>18.9%</td></tr>
-<tr><td>Android 1.6</td><td>4</td><td>22.1%</td></tr>
-<tr><td>Android 2.1</td><td>7</td><td>55.5%</td></tr>
-<tr><td>Android 2.2</td><td>8</td><td>3.3%</td></tr>
+<tr><td>Android 1.5</td><td>3</td><td>15.3%</td></tr>
+<tr><td>Android 1.6</td><td>4</td><td>20.3%</td></tr>
+<tr><td>Android 2.1</td><td>7</td><td>59.7%</td></tr>
+<tr><td>Android 2.2</td><td>8</td><td>4.5%</td></tr>
 </table>
 
-<p><em>Data collected during two weeks ending on July 15, 2010</em></p>
-<p style="font-size:.9em">* <em>Other: 0.3% of devices running obsolete versions</em></p>
+<p><em>Data collected during two weeks ending on August 2, 2010</em></p>
+<p style="font-size:.9em">* <em>Other: 0.2% of devices running obsolete versions</em></p>
 
 </div><!-- end dashboard-panel -->
 
@@ -94,21 +95,20 @@
 
 <div class="dashboard-panel">
 
-<img alt="" height="265" width="700" style="padding:5px;background:#fff"
-src="http://chart.apis.google.com/chart?&cht=lc&chs=700x265&chxt=x,y,r&chxr=0,0,10%7C1,0,100%7C2,0,
-100&chxl=0%3A%7C2010/02/01%7C02/15%7C03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%
-7C2010/07/01%7C1%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C2%3A%7C0%25%7C25%25%7C50%25%7C75%25%
-7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10&chxtc=0,5&chd=t:99.0,99.2,99.4,99.5,99.6,99.6,99.6,99.7,100.6
-,101.1,99.9%7C63.4,62.5,61.6,60.6,61.5,61.7,62.3,63.5,73.0,76.4,78.6%7C22.6,23.2,24.3,25.4,29.4,30.2
-,32.7,35.3,46.2,51.3,55.1%7C0.0,0.0,0.0,0.0,4.0,28.3,32.0,34.9,45.9,51.0,54.9%7C0.0,0.0,0.0,0.0,0.0,
-0.0,0.0,0.0,0.8,1.2,1.8&chm=tAndroid%201.5,7caa36,0,0,15,,t::-5%7Cb,c3df9b,0,1,0%7CtAndroid%201.6,
-638d23,1,0,15,,t::-5%7Cb,b0db6e,1,2,0%7CtAndroid%202.0.1,496c13,2,0,15,,t::-5%7Cb,9ddb3d,2,3,0%
-7CtAndroid%202.1,2f4708,3,5,15,,t::-5%7Cb,89cf19,3,4,0%7CB,6fad0c,4,5,0&chg=9,25&chdl=Android%201.5%
-20(API%20Level%203)%7CAndroid%201.6%20(API%20Level%204)%7CAndroid%202.0.1%20(API%20Level%206)%
-7CAndroid%202.1%20(API%20Level%207)%7CAndroid%202.2%20(API%20Level %208)&chco=add274,
-9ad145,84c323,6ba213,507d08" />
+<img alt="" height="250" width="660" style="padding:5px;background:#fff"
+src="http://chart.apis.google.com/chart?&cht=lc&chs=660x250&chxt=x,y,r&chxr=0,0,12|1,0,100|2,0,100&
+chxl=0%3A%7C2010/02/01%7C02/15%7C03/01%7C03/15%7C04/01%7C04/15%7C05/01%7C05/15%7C06/01%7C06/15%7C07/
+01%7C07/15%7C2010/08/01%7C1%3A%7C0%25%7C25%25%7C50%25%7C75%25%7C100%25%7C2%3A%7C0%25%7C25%25%7C50%25
+%7C75%25%7C100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.0,99.2,99.4,99.5,99.6,99.6,
+99.6,99.7,100.6,101.1,99.9,100.0,100.0|63.4,62.5,61.6,60.6,61.5,61.7,62.3,63.5,73.0,76.4,78.6,81.1,
+84.5|22.6,23.2,24.3,25.4,29.4,30.2,32.7,35.3,46.2,51.3,55.1,59.0,64.1|0.0,0.0,0.0,0.0,4.0,28.3,32.0,
+34.9,45.9,51.0,54.9,58.8,64.0|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.8,1.2,1.8,3.3,4.3&chm=tAndroid%201.5
+,7caa36,0,0,15,,t::-5|b,c3df9b,0,1,0|tAndroid%201.6,638d23,1,0,15,,t::-5|b,b0db6e,1,2,0|tAndroid%202
+.0.1,496c13,2,0,15,,t::-5|b,9ddb3d,2,3,0|tAndroid%202.1,2f4708,3,5,15,,t::-5|b,89cf19,3,4,0|B,6fad0c
+,4,5,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.0.1|Android%202.1|Android%202.2&chco=
+add274,9ad145,84c323,6ba213,507d08" />
 
-<p><em>Last historical dataset collected during two weeks ending on July 1, 2010</em></p>
+<p><em>Last historical dataset collected during two weeks ending on August 2, 2010</em></p>
 
 
 </div><!-- end dashboard-panel -->
diff --git a/docs/html/resources/dashboard/screens.jd b/docs/html/resources/dashboard/screens.jd
index b20b17d..90f3f1a 100644
--- a/docs/html/resources/dashboard/screens.jd
+++ b/docs/html/resources/dashboard/screens.jd
@@ -49,8 +49,8 @@
 <div class="dashboard-panel">
 
 <img alt="" width="460" height="250"
-src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:1.8,51.5,46.6&chl=Small%20/%20ldpi|
-Normal%20/%20mdpi|Normal%20/%20hdpi&chco=c4df9b,6fad0c" />
+src="http://chart.apis.google.com/chart?&cht=p&chs=460x250&chd=t:2.3,0.4,45.9,51.2&chl=Small%20/%
+20ldpi|Normal%20/%20ldpi|Normal%20/%20mdpi|Normal%20/%20hdpi&chco=c4df9b,6fad0c" />
 
 <table>
 <tr>
@@ -60,22 +60,22 @@
 <th scope="col">High Density</th>
 </tr>
 <tr><th scope="row">Small</th> 
-<td class='cent hi'>1.8%</td> 
+<td class='cent hi'>2.3%</td> 
 <td></td> 
 <td></td> 
 </tr> 
 <tr><th scope="row">Normal</th> 
-<td></td> 
-<td class='cent hi'>51.5%</td> 
-<td class='cent hi'>46.6%</td> 
+<td class='cent '>0.4%</td> 
+<td class='cent hi'>45.9%</td> 
+<td class='cent hi'>51.2%</td> 
 </tr> 
 <tr><th scope="row">Large</th> 
 <td></td> 
 <td></td> 
 <td></td> 
-</tr>
+</tr> 
 </table>
 
-<p><em>Data collected during two weeks ending on July 15, 2010</em></p>
+<p><em>Data collected during two weeks ending on August 2, 2010</em></p>
 </div>
 
diff --git a/include/media/stagefright/AudioSource.h b/include/media/stagefright/AudioSource.h
index 628200d..f1d45d2 100644
--- a/include/media/stagefright/AudioSource.h
+++ b/include/media/stagefright/AudioSource.h
@@ -57,11 +57,10 @@
 
     bool mCollectStats;
     bool mTrackMaxAmplitude;
-    int64_t mTotalReadTimeUs;
-    int64_t mTotalReadBytes;
-    int64_t mTotalReads;
     int64_t mStartTimeUs;
     int16_t mMaxAmplitude;
+    int64_t mPrevSampleTimeUs;
+    int64_t mNumLostFrames;
 
     MediaBufferGroup *mGroup;
 
diff --git a/include/media/stagefright/MPEG4Writer.h b/include/media/stagefright/MPEG4Writer.h
index d0c2dca..2e1e8d8 100644
--- a/include/media/stagefright/MPEG4Writer.h
+++ b/include/media/stagefright/MPEG4Writer.h
@@ -75,6 +75,7 @@
     uint32_t mInterleaveDurationUs;
     int32_t mTimeScale;
     int64_t mStartTimestampUs;
+
     Mutex mLock;
 
     List<Track *> mTracks;
@@ -87,6 +88,46 @@
     size_t numTracks();
     int64_t estimateMoovBoxSize(int32_t bitRate);
 
+    struct Chunk {
+        Track               *mTrack;        // Owner
+        int64_t             mTimeStampUs;   // Timestamp of the 1st sample
+        List<MediaBuffer *> mSamples;       // Sample data
+
+        // Convenient constructor
+        Chunk(Track *track, int64_t timeUs, List<MediaBuffer *> samples)
+            : mTrack(track), mTimeStampUs(timeUs), mSamples(samples) {
+        }
+
+    };
+    struct ChunkInfo {
+        Track               *mTrack;        // Owner
+        List<Chunk>         mChunks;        // Remaining chunks to be written
+    };
+
+    bool            mIsFirstChunk;
+    volatile bool   mDone;                  // Writer thread is done?
+    pthread_t       mThread;                // Thread id for the writer
+    List<ChunkInfo> mChunkInfos;            // Chunk infos
+    Condition       mChunkReadyCondition;   // Signal that chunks are available
+
+    // Writer thread handling
+    status_t startWriterThread();
+    void stopWriterThread();
+    static void *ThreadWrapper(void *me);
+    void threadFunc();
+
+    // Buffer a single chunk to be written out later.
+    void bufferChunk(const Chunk& chunk);
+
+    // Write all buffered chunks from all tracks
+    void writeChunks();
+
+    // Write a chunk if there is one
+    status_t writeOneChunk();
+
+    // Write the first chunk from the given ChunkInfo.
+    void writeFirstChunk(ChunkInfo* info);
+
     void lock();
     void unlock();
 
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index 50c0edc..99978e8 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -26,8 +26,7 @@
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/MetaData.h>
 #include <cutils/properties.h>
-#include <sys/time.h>
-#include <time.h>
+#include <stdlib.h>
 
 namespace android {
 
@@ -35,9 +34,8 @@
         int inputSource, uint32_t sampleRate, uint32_t channels)
     : mStarted(false),
       mCollectStats(false),
-      mTotalReadTimeUs(0),
-      mTotalReadBytes(0),
-      mTotalReads(0),
+      mPrevSampleTimeUs(0),
+      mNumLostFrames(0),
       mGroup(NULL) {
 
     LOGV("sampleRate: %d, channels: %d", sampleRate, channels);
@@ -110,10 +108,7 @@
     mStarted = false;
 
     if (mCollectStats) {
-        LOGI("%lld reads: %.2f bps in %lld us",
-                mTotalReads,
-                (mTotalReadBytes * 8000000.0) / mTotalReadTimeUs,
-                mTotalReadTimeUs);
+        LOGI("Total lost audio frames: %lld", mNumLostFrames);
     }
 
     return OK;
@@ -129,67 +124,113 @@
     return meta;
 }
 
+/*
+ * Returns -1 if frame skipping request is too long.
+ * Returns  0 if there is no need to skip frames.
+ * Returns  1 if we need to skip frames.
+ */
+static int skipFrame(int64_t timestampUs,
+        const MediaSource::ReadOptions *options) {
+
+    int64_t skipFrameUs;
+    if (!options || !options->getSkipFrame(&skipFrameUs)) {
+        return 0;
+    }
+
+    if (skipFrameUs <= timestampUs) {
+        return 0;
+    }
+
+    // Safe guard against the abuse of the kSkipFrame_Option.
+    if (skipFrameUs - timestampUs >= 1E6) {
+        LOGE("Frame skipping requested is way too long: %lld us",
+            skipFrameUs - timestampUs);
+
+        return -1;
+    }
+
+    LOGV("skipFrame: %lld us > timestamp: %lld us",
+        skipFrameUs, timestampUs);
+
+    return 1;
+
+}
+
 status_t AudioSource::read(
         MediaBuffer **out, const ReadOptions *options) {
     *out = NULL;
-    ++mTotalReads;
 
     MediaBuffer *buffer;
     CHECK_EQ(mGroup->acquire_buffer(&buffer), OK);
 
+    int err = 0;
     while (mStarted) {
+
         uint32_t numFramesRecorded;
         mRecord->getPosition(&numFramesRecorded);
-        int64_t latency = mRecord->latency() * 1000;
 
-        int64_t readTime = systemTime() / 1000;
 
-        if (numFramesRecorded == 0) {
+        if (numFramesRecorded == 0 && mPrevSampleTimeUs == 0) {
             // Initial delay
             if (mStartTimeUs > 0) {
-                mStartTimeUs = readTime - mStartTimeUs;
+                mStartTimeUs = systemTime() / 1000 - mStartTimeUs;
             } else {
-                mStartTimeUs += latency;
+                // Assume latency is constant.
+                mStartTimeUs += mRecord->latency() * 1000;
             }
-        }
-
-        ssize_t n = 0;
-        if (mCollectStats) {
-            n = mRecord->read(buffer->data(), buffer->size());
-            int64_t endTime = systemTime() / 1000;
-            mTotalReadTimeUs += (endTime - readTime);
-            if (n >= 0) {
-                mTotalReadBytes += n;
-            }
-        } else {
-            n = mRecord->read(buffer->data(), buffer->size());
-        }
-
-        if (n < 0) {
-            buffer->release();
-            buffer = NULL;
-
-            return (status_t)n;
+            mPrevSampleTimeUs = mStartTimeUs;
         }
 
         uint32_t sampleRate = mRecord->getSampleRate();
-        int64_t timestampUs = (1000000LL * numFramesRecorded) / sampleRate +
-                                 mStartTimeUs;
-        int64_t skipFrameUs;
-        if (!options || !options->getSkipFrame(&skipFrameUs)) {
-            skipFrameUs = timestampUs;  // Don't skip frame
-        }
 
-        if (skipFrameUs > timestampUs) {
-            // Safe guard against the abuse of the kSkipFrame_Option.
-            if (skipFrameUs - timestampUs >= 1E6) {
-                LOGE("Frame skipping requested is way too long: %lld us",
-                    skipFrameUs - timestampUs);
+        // Insert null frames when lost frames are detected.
+        int64_t timestampUs = mPrevSampleTimeUs;
+        uint32_t numLostBytes = mRecord->getInputFramesLost() << 1;
+#if 0
+        // Simulate lost frames
+        numLostBytes = ((rand() * 1.0 / RAND_MAX)) * kMaxBufferSize;
+        numLostBytes &= 0xFFFFFFFE; // Alignment request
+
+        // Reduce the chance to lose
+        if (rand() * 1.0 / RAND_MAX >= 0.05) {
+            numLostBytes = 0;
+        }
+#endif
+        if (numLostBytes > 0) {
+            // Not expect too many lost frames!
+            CHECK(numLostBytes <= kMaxBufferSize);
+
+            timestampUs += (1000000LL * numLostBytes >> 1) / sampleRate;
+            CHECK(timestampUs > mPrevSampleTimeUs);
+            if (mCollectStats) {
+                mNumLostFrames += (numLostBytes >> 1);
+            }
+            if ((err = skipFrame(timestampUs, options)) == -1) {
                 buffer->release();
                 return UNKNOWN_ERROR;
+            } else if (err != 0) {
+                continue;
             }
-            LOGV("skipFrame: %lld us > timestamp: %lld us, samples %d",
-                skipFrameUs, timestampUs, numFramesRecorded);
+            memset(buffer->data(), 0, numLostBytes);
+            buffer->set_range(0, numLostBytes);
+            buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs);
+            mPrevSampleTimeUs = timestampUs;
+            *out = buffer;
+            return OK;
+        }
+
+        ssize_t n = mRecord->read(buffer->data(), buffer->size());
+        if (n < 0) {
+            buffer->release();
+            return (status_t)n;
+        }
+
+        int64_t recordDurationUs = (1000000LL * n >> 1) / sampleRate;
+        timestampUs += recordDurationUs;
+        if ((err = skipFrame(timestampUs, options)) == -1) {
+            buffer->release();
+            return UNKNOWN_ERROR;
+        } else if (err != 0) {
             continue;
         }
 
@@ -197,7 +238,13 @@
             trackMaxAmplitude((int16_t *) buffer->data(), n >> 1);
         }
 
-        buffer->meta_data()->setInt64(kKeyTime, timestampUs);
+        buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs);
+        CHECK(timestampUs > mPrevSampleTimeUs);
+        if (mNumLostFrames == 0) {
+            CHECK_EQ(mPrevSampleTimeUs,
+                mStartTimeUs + (1000000LL * numFramesRecorded) / sampleRate);
+        }
+        mPrevSampleTimeUs = timestampUs;
         LOGV("initial delay: %lld, sample rate: %d, timestamp: %lld",
                 mStartTimeUs, sampleRate, timestampUs);
 
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index 9f712c3..4928951 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -52,6 +52,11 @@
     int64_t getDurationUs() const;
     int64_t getEstimatedTrackSizeBytes() const;
     void writeTrackHeader(int32_t trackID, bool use32BitOffset = true);
+    void bufferChunk(int64_t timestampUs);
+    bool isAvc() const { return mIsAvc; }
+    bool isAudio() const { return mIsAudio; }
+    bool isMPEG4() const { return mIsMPEG4; }
+    void addChunkOffset(off_t offset) { mChunkOffsets.push_back(offset); }
 
 private:
     MPEG4Writer *mOwner;
@@ -60,8 +65,12 @@
     volatile bool mDone;
     volatile bool mPaused;
     volatile bool mResumed;
+    bool mIsAvc;
+    bool mIsAudio;
+    bool mIsMPEG4;
     int64_t mMaxTimeStampUs;
     int64_t mEstimatedTrackSizeBytes;
+    int64_t mMaxWriteTimeUs;
     int32_t mTimeScale;
 
     pthread_t mThread;
@@ -117,7 +126,6 @@
 
     status_t makeAVCCodecSpecificData(
             const uint8_t *data, size_t size);
-    void writeOneChunk(bool isAvc);
 
     // Track authoring progress status
     void trackProgressStatus(int64_t timeUs, status_t err = OK);
@@ -320,10 +328,17 @@
     } else {
         write("\x00\x00\x00\x01mdat????????", 16);
     }
-    status_t err = startTracks(param);
+
+    status_t err = startWriterThread();
     if (err != OK) {
         return err;
     }
+
+    err = startTracks(param);
+    if (err != OK) {
+        return err;
+    }
+
     mStarted = true;
     return OK;
 }
@@ -339,6 +354,20 @@
     }
 }
 
+void MPEG4Writer::stopWriterThread() {
+    LOGV("stopWriterThread");
+
+    {
+        Mutex::Autolock autolock(mLock);
+
+        mDone = true;
+        mChunkReadyCondition.signal();
+    }
+
+    void *dummy;
+    pthread_join(mThread, &dummy);
+}
+
 void MPEG4Writer::stop() {
     if (mFile == NULL) {
         return;
@@ -355,6 +384,7 @@
         }
     }
 
+    stopWriterThread();
 
     // Fix up the size of the 'mdat' chunk.
     if (mUse32BitOffset) {
@@ -693,6 +723,14 @@
     if (!mMeta->findInt32(kKeyTimeScale, &mTimeScale)) {
         mTimeScale = 1000;
     }
+
+    const char *mime;
+    mMeta->findCString(kKeyMIMEType, &mime);
+    mIsAvc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
+    mIsAudio = !strncasecmp(mime, "audio/", 6);
+    mIsMPEG4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) ||
+               !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC);
+
     CHECK(mTimeScale > 0);
 }
 
@@ -751,6 +789,148 @@
     }
 }
 
+// static
+void *MPEG4Writer::ThreadWrapper(void *me) {
+    LOGV("ThreadWrapper: %p", me);
+    MPEG4Writer *writer = static_cast<MPEG4Writer *>(me);
+    writer->threadFunc();
+    return NULL;
+}
+
+void MPEG4Writer::bufferChunk(const Chunk& chunk) {
+    LOGV("bufferChunk: %p", chunk.mTrack);
+    Mutex::Autolock autolock(mLock);
+    CHECK_EQ(mDone, false);
+
+    for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
+         it != mChunkInfos.end(); ++it) {
+
+        if (chunk.mTrack == it->mTrack) {  // Found owner
+            it->mChunks.push_back(chunk);
+            mChunkReadyCondition.signal();
+            return;
+        }
+    }
+
+    CHECK("Received a chunk for a unknown track" == 0);
+}
+
+void MPEG4Writer::writeFirstChunk(ChunkInfo* info) {
+    LOGV("writeFirstChunk: %p", info->mTrack);
+
+    List<Chunk>::iterator chunkIt = info->mChunks.begin();
+    for (List<MediaBuffer *>::iterator it = chunkIt->mSamples.begin();
+         it != chunkIt->mSamples.end(); ++it) {
+
+        off_t offset = info->mTrack->isAvc()
+                            ? addLengthPrefixedSample_l(*it)
+                            : addSample_l(*it);
+        if (it == chunkIt->mSamples.begin()) {
+            info->mTrack->addChunkOffset(offset);
+        }
+    }
+
+    // Done with the current chunk.
+    // Release all the samples in this chunk.
+    while (!chunkIt->mSamples.empty()) {
+        List<MediaBuffer *>::iterator it = chunkIt->mSamples.begin();
+        (*it)->release();
+        (*it) = NULL;
+        chunkIt->mSamples.erase(it);
+    }
+    chunkIt->mSamples.clear();
+    info->mChunks.erase(chunkIt);
+}
+
+void MPEG4Writer::writeChunks() {
+    LOGV("writeChunks");
+    size_t outstandingChunks = 0;
+    while (!mChunkInfos.empty()) {
+        List<ChunkInfo>::iterator it = mChunkInfos.begin();
+        while (!it->mChunks.empty()) {
+            CHECK_EQ(OK, writeOneChunk());
+            ++outstandingChunks;
+        }
+        it->mTrack = NULL;
+        mChunkInfos.erase(it);
+    }
+    mChunkInfos.clear();
+    LOGD("%d chunks are written in the last batch", outstandingChunks);
+}
+
+status_t MPEG4Writer::writeOneChunk() {
+    LOGV("writeOneChunk");
+
+    // Find the smallest timestamp, and write that chunk out
+    // XXX: What if some track is just too slow?
+    int64_t minTimestampUs = 0x7FFFFFFFFFFFFFFFLL;
+    Track *track = NULL;
+    for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
+         it != mChunkInfos.end(); ++it) {
+        if (!it->mChunks.empty()) {
+            List<Chunk>::iterator chunkIt = it->mChunks.begin();
+            if (chunkIt->mTimeStampUs < minTimestampUs) {
+                minTimestampUs = chunkIt->mTimeStampUs;
+                track = it->mTrack;
+            }
+        }
+    }
+
+    if (track == NULL) {
+        LOGV("Nothing to be written after all");
+        return OK;
+    }
+
+    if (mIsFirstChunk) {
+        mIsFirstChunk = false;
+    }
+    for (List<ChunkInfo>::iterator it = mChunkInfos.begin();
+         it != mChunkInfos.end(); ++it) {
+        if (it->mTrack == track) {
+            writeFirstChunk(&(*it));
+        }
+    }
+    return OK;
+}
+
+void MPEG4Writer::threadFunc() {
+    LOGV("threadFunc");
+
+    while (!mDone) {
+        {
+            Mutex::Autolock autolock(mLock);
+            mChunkReadyCondition.wait(mLock);
+            CHECK_EQ(writeOneChunk(), OK);
+        }
+    }
+
+    {
+        // Write ALL samples
+        Mutex::Autolock autolock(mLock);
+        writeChunks();
+    }
+}
+
+status_t MPEG4Writer::startWriterThread() {
+    LOGV("startWriterThread");
+
+    mDone = false;
+    mIsFirstChunk = true;
+    for (List<Track *>::iterator it = mTracks.begin();
+         it != mTracks.end(); ++it) {
+        ChunkInfo info;
+        info.mTrack = *it;
+        mChunkInfos.push_back(info);
+    }
+
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+    pthread_create(&mThread, &attr, ThreadWrapper, this);
+    pthread_attr_destroy(&attr);
+    return OK;
+}
+
 status_t MPEG4Writer::Track::start(MetaData *params) {
     if (!mDone && mPaused) {
         mPaused = false;
@@ -926,13 +1106,6 @@
 }
 
 void MPEG4Writer::Track::threadEntry() {
-    sp<MetaData> meta = mSource->getFormat();
-    const char *mime;
-    meta->findCString(kKeyMIMEType, &mime);
-    bool is_mpeg4 = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) ||
-                    !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC);
-    bool is_avc = !strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC);
-    bool is_audio = !strncasecmp(mime, "audio/", 6);
     int32_t count = 0;
     const int64_t interleaveDurationUs = mOwner->interleaveDuration();
     int64_t chunkTimestampUs = 0;
@@ -943,10 +1116,12 @@
     int32_t sampleCount = 1;      // Sample count in the current stts table entry
     uint32_t previousSampleSize = 0;  // Size of the previous sample
     int64_t previousPausedDurationUs = 0;
+    int64_t timestampUs;
     sp<MetaData> meta_data;
     bool collectStats = collectStatisticalData();
 
     mNumSamples = 0;
+    mMaxWriteTimeUs = 0;
     status_t err = OK;
     MediaBuffer *buffer;
     while (!mDone && (err = mSource->read(&buffer)) == OK) {
@@ -973,13 +1148,13 @@
                 && isCodecConfig) {
             CHECK(!mGotAllCodecSpecificData);
 
-            if (is_avc) {
+            if (mIsAvc) {
                 status_t err = makeAVCCodecSpecificData(
                         (const uint8_t *)buffer->data()
                             + buffer->range_offset(),
                         buffer->range_length());
                 CHECK_EQ(OK, err);
-            } else if (is_mpeg4) {
+            } else if (mIsMPEG4) {
                 mCodecSpecificDataSize = buffer->range_length();
                 mCodecSpecificData = malloc(mCodecSpecificDataSize);
                 memcpy(mCodecSpecificData,
@@ -994,7 +1169,7 @@
             mGotAllCodecSpecificData = true;
             continue;
         } else if (!mGotAllCodecSpecificData &&
-                count == 1 && is_mpeg4 && mCodecSpecificData == NULL) {
+                count == 1 && mIsMPEG4 && mCodecSpecificData == NULL) {
             // The TI mpeg4 encoder does not properly set the
             // codec-specific-data flag.
 
@@ -1034,7 +1209,7 @@
             }
 
             mGotAllCodecSpecificData = true;
-        } else if (!mGotAllCodecSpecificData && is_avc && count < 3) {
+        } else if (!mGotAllCodecSpecificData && mIsAvc && count < 3) {
             // The TI video encoder does not flag codec specific data
             // as such and also splits up SPS and PPS across two buffers.
 
@@ -1090,10 +1265,10 @@
         buffer->release();
         buffer = NULL;
 
-        if (is_avc) StripStartcode(copy);
+        if (mIsAvc) StripStartcode(copy);
 
         size_t sampleSize;
-        sampleSize = is_avc
+        sampleSize = mIsAvc
 #if USE_NALLEN_FOUR
                 ? copy->range_length() + 4
 #else
@@ -1116,7 +1291,6 @@
         int32_t isSync = false;
         meta_data->findInt32(kKeyIsSyncFrame, &isSync);
 
-        int64_t timestampUs;
         CHECK(meta_data->findInt64(kKeyTime, &timestampUs));
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -1168,7 +1342,7 @@
             trackProgressStatus(timestampUs);
         }
         if (mOwner->numTracks() == 1) {
-            off_t offset = is_avc? mOwner->addLengthPrefixedSample_l(copy)
+            off_t offset = mIsAvc? mOwner->addLengthPrefixedSample_l(copy)
                                  : mOwner->addSample_l(copy);
             if (mChunkOffsets.empty()) {
                 mChunkOffsets.push_back(offset);
@@ -1182,7 +1356,7 @@
         if (interleaveDurationUs == 0) {
             StscTableEntry stscEntry(++nChunks, 1, 1);
             mStscTableEntries.push_back(stscEntry);
-            writeOneChunk(is_avc);
+            bufferChunk(timestampUs);
         } else {
             if (chunkTimestampUs == 0) {
                 chunkTimestampUs = timestampUs;
@@ -1199,7 +1373,7 @@
                                 mChunkSamples.size(), 1);
                         mStscTableEntries.push_back(stscEntry);
                     }
-                    writeOneChunk(is_avc);
+                    bufferChunk(timestampUs);
                     chunkTimestampUs = timestampUs;
                 }
             }
@@ -1220,7 +1394,7 @@
         ++nChunks;
         StscTableEntry stscEntry(nChunks, mChunkSamples.size(), 1);
         mStscTableEntries.push_back(stscEntry);
-        writeOneChunk(is_avc);
+        bufferChunk(timestampUs);
     }
 
     // We don't really know how long the last frame lasts, since
@@ -1234,10 +1408,10 @@
     SttsTableEntry sttsEntry(sampleCount, lastDurationUs);
     mSttsTableEntries.push_back(sttsEntry);
     mReachedEOS = true;
-    LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames - %s",
-            count, nZeroLengthFrames, mNumSamples, is_audio? "audio": "video");
+    LOGI("Received total/0-length (%d/%d) buffers and encoded %d frames. Max write time: %lld us - %s",
+            count, nZeroLengthFrames, mNumSamples, mMaxWriteTimeUs, mIsAudio? "audio": "video");
 
-    logStatisticalData(is_audio);
+    logStatisticalData(mIsAudio);
 }
 
 void MPEG4Writer::Track::trackProgressStatus(int64_t timeUs, status_t err) {
@@ -1380,24 +1554,17 @@
     }
 }
 
-void MPEG4Writer::Track::writeOneChunk(bool isAvc) {
-    mOwner->lock();
-    for (List<MediaBuffer *>::iterator it = mChunkSamples.begin();
-         it != mChunkSamples.end(); ++it) {
-        off_t offset = isAvc? mOwner->addLengthPrefixedSample_l(*it)
-                            : mOwner->addSample_l(*it);
-        if (it == mChunkSamples.begin()) {
-            mChunkOffsets.push_back(offset);
-        }
-    }
-    mOwner->unlock();
-    while (!mChunkSamples.empty()) {
-        List<MediaBuffer *>::iterator it = mChunkSamples.begin();
-        (*it)->release();
-        (*it) = NULL;
-        mChunkSamples.erase(it);
-    }
+void MPEG4Writer::Track::bufferChunk(int64_t timestampUs) {
+    LOGV("bufferChunk");
+
+    int64_t startTimeUs = systemTime() / 1000;
+    Chunk chunk(this, timestampUs, mChunkSamples);
+    mOwner->bufferChunk(chunk);
     mChunkSamples.clear();
+    int64_t endTimeUs = systemTime() / 1000;
+    if (mMaxWriteTimeUs < endTimeUs - startTimeUs) {
+        mMaxWriteTimeUs = endTimeUs - startTimeUs;
+    }
 }
 
 int64_t MPEG4Writer::Track::getDurationUs() const {
@@ -1414,9 +1581,8 @@
     bool success = mMeta->findCString(kKeyMIMEType, &mime);
     CHECK(success);
 
-    bool is_audio = !strncasecmp(mime, "audio/", 6);
     LOGV("%s track time scale: %d",
-        is_audio? "Audio": "Video", mTimeScale);
+        mIsAudio? "Audio": "Video", mTimeScale);
 
 
     time_t now = time(NULL);
@@ -1440,7 +1606,7 @@
         mOwner->writeInt32(0);             // reserved
         mOwner->writeInt16(0);             // layer
         mOwner->writeInt16(0);             // alternate group
-        mOwner->writeInt16(is_audio ? 0x100 : 0);  // volume
+        mOwner->writeInt16(mIsAudio ? 0x100 : 0);  // volume
         mOwner->writeInt16(0);             // reserved
 
         mOwner->writeInt32(0x10000);       // matrix
@@ -1453,7 +1619,7 @@
         mOwner->writeInt32(0);
         mOwner->writeInt32(0x40000000);
 
-        if (is_audio) {
+        if (mIsAudio) {
             mOwner->writeInt32(0);
             mOwner->writeInt32(0);
         } else {
@@ -1511,16 +1677,16 @@
         mOwner->beginBox("hdlr");
           mOwner->writeInt32(0);             // version=0, flags=0
           mOwner->writeInt32(0);             // component type: should be mhlr
-          mOwner->writeFourcc(is_audio ? "soun" : "vide");  // component subtype
+          mOwner->writeFourcc(mIsAudio ? "soun" : "vide");  // component subtype
           mOwner->writeInt32(0);             // reserved
           mOwner->writeInt32(0);             // reserved
           mOwner->writeInt32(0);             // reserved
           // Removing "r" for the name string just makes the string 4 byte aligned
-          mOwner->writeCString(is_audio ? "SoundHandle": "VideoHandle");  // name
+          mOwner->writeCString(mIsAudio ? "SoundHandle": "VideoHandle");  // name
         mOwner->endBox();
 
         mOwner->beginBox("minf");
-          if (is_audio) {
+          if (mIsAudio) {
               mOwner->beginBox("smhd");
               mOwner->writeInt32(0);           // version=0, flags=0
               mOwner->writeInt16(0);           // balance
@@ -1553,7 +1719,7 @@
           mOwner->beginBox("stsd");
             mOwner->writeInt32(0);               // version=0, flags=0
             mOwner->writeInt32(1);               // entry count
-            if (is_audio) {
+            if (mIsAudio) {
                 const char *fourcc = NULL;
                 if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_AMR_NB, mime)) {
                     fourcc = "samr";
@@ -1735,7 +1901,7 @@
             }
           mOwner->endBox();  // stts
 
-          if (!is_audio) {
+          if (!mIsAudio) {
             mOwner->beginBox("stss");
               mOwner->writeInt32(0);  // version=0, flags=0
               mOwner->writeInt32(mStssTableEntries.size());  // number of sync frames
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
index a0ef905..0e3029b 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/performance/MediaPlayerPerformance.java
@@ -518,7 +518,7 @@
     // Test case 6: Capture the memory usage after every 20 video and audio
     // recorded
     @LargeTest
-    public void testRecordVidedAudioMemoryUsage() throws Exception {
+    public void testRecordVideoAudioMemoryUsage() throws Exception {
         boolean memoryResult = false;
         mStartPid = getMediaserverPid();
 
diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk
index 6b7020f..ae924cd 100644
--- a/opengl/libs/Android.mk
+++ b/opengl/libs/Android.mk
@@ -6,10 +6,11 @@
 
 include $(CLEAR_VARS)
 
-LOCAL_SRC_FILES:= 	\
-	EGL/egl.cpp 	\
-	EGL/hooks.cpp 	\
-	EGL/Loader.cpp 	\
+LOCAL_SRC_FILES:= 	       \
+	EGL/egl.cpp 	       \
+	EGL/getProcAddress.cpp.arm \
+	EGL/hooks.cpp 	       \
+	EGL/Loader.cpp 	       \
 #
 
 LOCAL_SHARED_LIBRARIES += libcutils libutils
diff --git a/opengl/libs/EGL/egl.cpp b/opengl/libs/EGL/egl.cpp
index 665446a..315a2a36 100644
--- a/opengl/libs/EGL/egl.cpp
+++ b/opengl/libs/EGL/egl.cpp
@@ -37,6 +37,8 @@
 #include <cutils/memory.h>
 
 #include <utils/SortedVector.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
 
 #include "hooks.h"
 #include "egl_impl.h"
@@ -410,7 +412,11 @@
             (__eglMustCastToProperFunctionPointerType)&eglGetRenderBufferANDROID }, 
 };
 
-static extention_map_t gGLExtentionMap[MAX_NUMBER_OF_GL_EXTENSIONS];
+extern const __eglMustCastToProperFunctionPointerType gExtensionForwarders[MAX_NUMBER_OF_GL_EXTENSIONS];
+
+// accesses protected by gInitDriverMutex
+static DefaultKeyedVector<String8, __eglMustCastToProperFunctionPointerType> gGLExtentionMap;
+static int gGLExtentionSlot = 0;
 
 static void(*findProcAddress(const char* name,
         const extention_map_t* map, size_t n))() 
@@ -1369,55 +1375,54 @@
     addr = findProcAddress(procname, gExtentionMap, NELEM(gExtentionMap));
     if (addr) return addr;
 
-    return NULL; // TODO: finish implementation below
+    // this protects accesses to gGLExtentionMap and gGLExtentionSlot
+    pthread_mutex_lock(&gInitDriverMutex);
 
-    addr = findProcAddress(procname, gGLExtentionMap, NELEM(gGLExtentionMap));
-    if (addr) return addr;
-    
-    addr = 0;
-    int slot = -1;
-    for (int i=0 ; i<IMPL_NUM_IMPLEMENTATIONS ; i++) {
-        egl_connection_t* const cnx = &gEGLImpl[i];
-        if (cnx->dso) {
-            if (cnx->egl.eglGetProcAddress) {
-                addr = cnx->egl.eglGetProcAddress(procname);
-                if (addr) {
-                    if (slot == -1) {
-                        slot = 0; // XXX: find free slot
-                        if (slot == -1) {
-                            addr = 0;
-                            break;
-                        }
-                    }
-                    //cnx->hooks->ext.extensions[slot] = addr;
+        /*
+         * Since eglGetProcAddress() is not associated to anything, it needs
+         * to return a function pointer that "works" regardless of what
+         * the current context is.
+         *
+         * For this reason, we return a "forwarder", a small stub that takes
+         * care of calling the function associated with the context
+         * currently bound.
+         *
+         * We first look for extensions we've already resolved, if we're seeing
+         * this extension for the first time, we go through all our
+         * implementations and call eglGetProcAddress() and record the
+         * result in the appropriate implementation hooks and return the
+         * address of the forwarder corresponding to that hook set.
+         *
+         */
+
+        const String8 name(procname);
+        addr = gGLExtentionMap.valueFor(name);
+        const int slot = gGLExtentionSlot;
+
+        LOGE_IF(slot >= MAX_NUMBER_OF_GL_EXTENSIONS,
+                "no more slots for eglGetProcAddress(\"%s\")",
+                procname);
+
+        if (!addr && (slot < MAX_NUMBER_OF_GL_EXTENSIONS)) {
+            bool found = false;
+            for (int i=0 ; i<IMPL_NUM_IMPLEMENTATIONS ; i++) {
+                egl_connection_t* const cnx = &gEGLImpl[i];
+                if (cnx->dso && cnx->egl.eglGetProcAddress) {
+                    found = true;
+                    cnx->hooks[i]->ext.extensions[slot] =
+                            cnx->egl.eglGetProcAddress(procname);
                 }
             }
+            if (found) {
+                addr = gExtensionForwarders[slot];
+                gGLExtentionMap.add(name, addr);
+                gGLExtentionSlot++;
+            }
         }
-    }
-    
-    if (slot >= 0) {
-        addr = 0; // XXX: address of stub 'slot'
-        gGLExtentionMap[slot].name = strdup(procname);
-        gGLExtentionMap[slot].address = addr;
-    }
-    
-    return addr;
 
-    
-    /*
-     *  TODO: For OpenGL ES extensions, we must generate a stub
-     *  that looks like
-     *      mov     r12, #0xFFFF0FFF
-     *      ldr     r12, [r12, #-15]
-     *      ldr     r12, [r12, #TLS_SLOT_OPENGL_API*4]
-     *      mov     r12, [r12, #api_offset]
-     *      ldrne   pc, r12
-     *      mov     pc, #unsupported_extension
-     * 
-     *  and write the address of the extension in *all*
-     *  gl_hooks_t::gl_ext_t at offset "api_offset" from gl_hooks_t
-     * 
-     */
+    pthread_mutex_unlock(&gInitDriverMutex);
+
+    return addr;
 }
 
 EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw)
diff --git a/opengl/libs/EGL/getProcAddress.cpp b/opengl/libs/EGL/getProcAddress.cpp
new file mode 100644
index 0000000..23837ef
--- /dev/null
+++ b/opengl/libs/EGL/getProcAddress.cpp
@@ -0,0 +1,176 @@
+/*
+ ** Copyright 2009, The Android Open Source Project
+ **
+ ** Licensed under the Apache License, Version 2.0 (the "License");
+ ** you may not use this file except in compliance with the License.
+ ** You may obtain a copy of the License at
+ **
+ **     http://www.apache.org/licenses/LICENSE-2.0
+ **
+ ** Unless required by applicable law or agreed to in writing, software
+ ** distributed under the License is distributed on an "AS IS" BASIS,
+ ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ** See the License for the specific language governing permissions and
+ ** limitations under the License.
+ */
+
+#include <ctype.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include <cutils/log.h>
+
+#include "hooks.h"
+
+// ----------------------------------------------------------------------------
+namespace android {
+// ----------------------------------------------------------------------------
+
+#undef API_ENTRY
+#undef CALL_GL_API
+#undef GL_EXTENSION
+#undef GL_EXTENSION_NAME
+
+#if defined(__arm__)
+
+    #ifdef HAVE_ARM_TLS_REGISTER
+        #define GET_TLS(reg) \
+            "mrc p15, 0, " #reg ", c13, c0, 3 \n"
+    #else
+        #define GET_TLS(reg) \
+            "mov   " #reg ", #0xFFFF0FFF      \n"  \
+            "ldr   " #reg ", [" #reg ", #-15] \n"
+    #endif
+
+    #define API_ENTRY(_api) __attribute__((naked)) _api
+
+    #define CALL_GL_EXTENSION_API(_api)                         \
+         asm volatile(                                          \
+            GET_TLS(r12)                                        \
+            "ldr   r12, [r12, %[tls]] \n"                       \
+            "cmp   r12, #0            \n"                       \
+            "ldrne r12, [r12, %[api]] \n"                       \
+            "cmpne r12, #0            \n"                       \
+            "bxne  r12                \n"                       \
+            "bx    lr                 \n"                       \
+            :                                                   \
+            : [tls] "J"(TLS_SLOT_OPENGL_API*4),                 \
+              [api] "J"(__builtin_offsetof(gl_hooks_t,          \
+                                      ext.extensions[_api]))    \
+            :                                                   \
+            );
+
+    #define GL_EXTENSION_NAME(_n)  __glExtFwd##_n
+
+    #define GL_EXTENSION(_n)                         \
+        void API_ENTRY(GL_EXTENSION_NAME(_n))() {    \
+            CALL_GL_EXTENSION_API(_n);               \
+        }
+
+
+#else
+
+    #define GL_EXTENSION_NAME(_n) NULL
+
+    #define GL_EXTENSION(_n)
+
+    #warning "eglGetProcAddress() partially supported on this architecture"
+
+#endif
+
+GL_EXTENSION(0)
+GL_EXTENSION(1)
+GL_EXTENSION(2)
+GL_EXTENSION(3)
+GL_EXTENSION(4)
+GL_EXTENSION(5)
+GL_EXTENSION(6)
+GL_EXTENSION(7)
+GL_EXTENSION(8)
+GL_EXTENSION(9)
+GL_EXTENSION(10)
+GL_EXTENSION(11)
+GL_EXTENSION(12)
+GL_EXTENSION(13)
+GL_EXTENSION(14)
+GL_EXTENSION(15)
+
+GL_EXTENSION(16)
+GL_EXTENSION(17)
+GL_EXTENSION(18)
+GL_EXTENSION(19)
+GL_EXTENSION(20)
+GL_EXTENSION(21)
+GL_EXTENSION(22)
+GL_EXTENSION(23)
+GL_EXTENSION(24)
+GL_EXTENSION(25)
+GL_EXTENSION(26)
+GL_EXTENSION(27)
+GL_EXTENSION(28)
+GL_EXTENSION(29)
+GL_EXTENSION(30)
+GL_EXTENSION(31)
+
+GL_EXTENSION(32)
+GL_EXTENSION(33)
+GL_EXTENSION(34)
+GL_EXTENSION(35)
+GL_EXTENSION(36)
+GL_EXTENSION(37)
+GL_EXTENSION(38)
+GL_EXTENSION(39)
+GL_EXTENSION(40)
+GL_EXTENSION(41)
+GL_EXTENSION(42)
+GL_EXTENSION(43)
+GL_EXTENSION(44)
+GL_EXTENSION(45)
+GL_EXTENSION(46)
+GL_EXTENSION(47)
+
+GL_EXTENSION(48)
+GL_EXTENSION(49)
+GL_EXTENSION(50)
+GL_EXTENSION(51)
+GL_EXTENSION(52)
+GL_EXTENSION(53)
+GL_EXTENSION(54)
+GL_EXTENSION(55)
+GL_EXTENSION(56)
+GL_EXTENSION(57)
+GL_EXTENSION(58)
+GL_EXTENSION(59)
+GL_EXTENSION(60)
+GL_EXTENSION(61)
+GL_EXTENSION(62)
+GL_EXTENSION(63)
+
+extern const __eglMustCastToProperFunctionPointerType gExtensionForwarders[MAX_NUMBER_OF_GL_EXTENSIONS] = {
+     GL_EXTENSION_NAME(0),  GL_EXTENSION_NAME(1),  GL_EXTENSION_NAME(2),  GL_EXTENSION_NAME(3),
+     GL_EXTENSION_NAME(4),  GL_EXTENSION_NAME(5),  GL_EXTENSION_NAME(6),  GL_EXTENSION_NAME(7),
+     GL_EXTENSION_NAME(8),  GL_EXTENSION_NAME(9),  GL_EXTENSION_NAME(10), GL_EXTENSION_NAME(11),
+     GL_EXTENSION_NAME(12), GL_EXTENSION_NAME(13), GL_EXTENSION_NAME(14), GL_EXTENSION_NAME(15),
+     GL_EXTENSION_NAME(16), GL_EXTENSION_NAME(17), GL_EXTENSION_NAME(18), GL_EXTENSION_NAME(19),
+     GL_EXTENSION_NAME(20), GL_EXTENSION_NAME(21), GL_EXTENSION_NAME(22), GL_EXTENSION_NAME(23),
+     GL_EXTENSION_NAME(24), GL_EXTENSION_NAME(25), GL_EXTENSION_NAME(26), GL_EXTENSION_NAME(27),
+     GL_EXTENSION_NAME(28), GL_EXTENSION_NAME(29), GL_EXTENSION_NAME(30), GL_EXTENSION_NAME(31),
+     GL_EXTENSION_NAME(32), GL_EXTENSION_NAME(33), GL_EXTENSION_NAME(34), GL_EXTENSION_NAME(35),
+     GL_EXTENSION_NAME(36), GL_EXTENSION_NAME(37), GL_EXTENSION_NAME(38), GL_EXTENSION_NAME(39),
+     GL_EXTENSION_NAME(40), GL_EXTENSION_NAME(41), GL_EXTENSION_NAME(42), GL_EXTENSION_NAME(43),
+     GL_EXTENSION_NAME(44), GL_EXTENSION_NAME(45), GL_EXTENSION_NAME(46), GL_EXTENSION_NAME(47),
+     GL_EXTENSION_NAME(48), GL_EXTENSION_NAME(49), GL_EXTENSION_NAME(50), GL_EXTENSION_NAME(51),
+     GL_EXTENSION_NAME(52), GL_EXTENSION_NAME(53), GL_EXTENSION_NAME(54), GL_EXTENSION_NAME(55),
+     GL_EXTENSION_NAME(56), GL_EXTENSION_NAME(57), GL_EXTENSION_NAME(58), GL_EXTENSION_NAME(59),
+     GL_EXTENSION_NAME(60), GL_EXTENSION_NAME(61), GL_EXTENSION_NAME(62), GL_EXTENSION_NAME(63)
+ };
+
+#undef GL_EXTENSION_NAME
+#undef GL_EXTENSION
+#undef API_ENTRY
+#undef CALL_GL_API
+
+// ----------------------------------------------------------------------------
+}; // namespace android
+// ----------------------------------------------------------------------------
+
diff --git a/opengl/libs/hooks.h b/opengl/libs/hooks.h
index f47f093..1ab58cc 100644
--- a/opengl/libs/hooks.h
+++ b/opengl/libs/hooks.h
@@ -37,7 +37,7 @@
 #endif
 #undef NELEM
 #define NELEM(x)                    (sizeof(x)/sizeof(*(x)))
-#define MAX_NUMBER_OF_GL_EXTENSIONS 32
+#define MAX_NUMBER_OF_GL_EXTENSIONS 64
 
 
 #if defined(HAVE_ANDROID_OS) && !USE_SLOW_BINDING && __OPTIMIZE__
@@ -86,7 +86,7 @@
         #include "entries.in"
     } gl;
     struct gl_ext_t {
-        void (*extensions[MAX_NUMBER_OF_GL_EXTENSIONS])(void);
+        __eglMustCastToProperFunctionPointerType extensions[MAX_NUMBER_OF_GL_EXTENSIONS];
     } ext;
 };
 #undef GL_ENTRY
diff --git a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
index 6eaf0cc..f1c6532 100644
--- a/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
+++ b/packages/DefaultContainerService/src/com/android/defcontainer/DefaultContainerService.java
@@ -125,8 +125,6 @@
             metrics.setToDefaults();
             PackageParser.PackageLite pkg = packageParser.parsePackageLite(
                     archiveFilePath, 0);
-            ret.packageName = pkg.packageName;
-            ret.installLocation = pkg.installLocation;
             // Nuke the parser reference right away and force a gc
             packageParser = null;
             Runtime.getRuntime().gc();
@@ -136,6 +134,7 @@
                 return ret;
             }
             ret.packageName = pkg.packageName;
+            ret.installLocation = pkg.installLocation;
             ret.recommendedInstallLocation = recommendAppInstallLocation(pkg.installLocation, archiveFilePath, flags);
             return ret;
         }
diff --git a/services/java/com/android/server/WindowManagerService.java b/services/java/com/android/server/WindowManagerService.java
index 5615232..cd687da 100644
--- a/services/java/com/android/server/WindowManagerService.java
+++ b/services/java/com/android/server/WindowManagerService.java
@@ -5514,7 +5514,7 @@
                 throw new SecurityException(
                         "Injecting to another application requires INJECT_EVENTS permission");
             case InputManager.INPUT_EVENT_INJECTION_SUCCEEDED:
-                Slog.v(TAG, "Input event injection succeeded.");
+                //Slog.v(TAG, "Input event injection succeeded.");
                 return true;
             case InputManager.INPUT_EVENT_INJECTION_TIMED_OUT:
                 Slog.w(TAG, "Input event injection timed out.");
@@ -7822,8 +7822,8 @@
                             } catch (RemoteException e) {
                                 // Ignore if process has died.
                             }
+                            notifyFocusChanged();
                         }
-                        notifyFocusChanged();
                     }
                 } break;
 
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index a60d2be..03194ff 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -135,9 +135,9 @@
         }
 
         // TODO: We don't check for SecurityException here (requires
-        // READ_PHONE_STATE permission).
+        // CALL_PRIVILEGED permission).
         if (scheme.equals("voicemail")) {
-            return TelephonyManager.getDefault().getVoiceMailNumber();
+            return TelephonyManager.getDefault().getCompleteVoiceMailNumber();
         }
 
         if (context == null) {
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index ab63017..aa916e0 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -665,6 +665,25 @@
     }
 
     /**
+     * Returns the complete voice mail number. Return null if it is unavailable.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#CALL_PRIVILEGED CALL_PRIVILEGED}
+     *
+     * @hide
+     */
+    public String getCompleteVoiceMailNumber() {
+        try {
+            return getSubscriberInfo().getCompleteVoiceMailNumber();
+        } catch (RemoteException ex) {
+            return null;
+        } catch (NullPointerException ex) {
+            // This could happen before phone restarts due to crashing
+            return null;
+        }
+    }
+
+    /**
      * Returns the voice mail count. Return 0 if unavailable.
      * <p>
      * Requires Permission:
diff --git a/telephony/java/com/android/internal/telephony/CallManager.java b/telephony/java/com/android/internal/telephony/CallManager.java
new file mode 100644
index 0000000..9822694
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/CallManager.java
@@ -0,0 +1,983 @@
+/*
+ * 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.telephony;
+
+
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RegistrantList;
+import android.telephony.PhoneStateListener;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @hide
+ *
+ * CallManager class provides an abstract layer for PhoneApp to access
+ * and control calls. It implements Phone interface.
+ *
+ * CallManager provides call and connection control as well as
+ * channel capability.
+ *
+ * There are three categories of APIs CallManager provided
+ *
+ *  1. Call control and operation, such as dial() and hangup()
+ *  2. Channel capabilities, such as CanConference()
+ *  3. Register notification
+ *
+ *
+ */
+public final class CallManager {
+
+    private static final int EVENT_DISCONNECT = 100;
+    private static final int EVENT_CALL_STATE_CHANGED = 101;
+
+
+    // Singleton instance
+    private static final CallManager INSTANCE = new CallManager();
+
+    // list of registered phones
+    private final ArrayList<Phone> mPhones;
+
+    // list of supported ringing calls
+    private final ArrayList<Call> mRingingCalls;
+
+    // list of supported background calls
+    private final ArrayList<Call> mBackgroundCalls;
+
+    // list of supported foreground calls
+    private final ArrayList<Call> mForegroundCalls;
+
+    // empty connection list
+    private final ArrayList<Connection> emptyConnections = new ArrayList<Connection>();
+
+    // default phone as the first phone registered
+    private Phone mDefaultPhone;
+
+    // state registrants
+    protected final RegistrantList mPreciseCallStateRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mNewRingingConnectionRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mIncomingRingRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mDisconnectRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mServiceStateRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mMmiCompleteRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mMmiRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mUnknownConnectionRegistrants
+    = new RegistrantList();
+
+    protected final RegistrantList mSuppServiceFailedRegistrants
+    = new RegistrantList();
+
+    private CallManager() {
+        mPhones = new ArrayList<Phone>();
+        mRingingCalls = new ArrayList<Call>();
+        mBackgroundCalls = new ArrayList<Call>();
+        mForegroundCalls = new ArrayList<Call>();
+        mDefaultPhone = null;
+    }
+
+    /**
+     * get singleton instance of CallManager
+     * @return CallManager
+     */
+    public static CallManager getInstance() {
+        return INSTANCE;
+    }
+
+    /**
+     * Register phone to CallManager
+     * @param phone
+     * @return
+     */
+    public boolean registerPhone(Phone phone) {
+        if (phone != null && !mPhones.contains(phone)) {
+            if (mPhones.isEmpty()) {
+                mDefaultPhone = phone;
+            }
+            mPhones.add(phone);
+            mRingingCalls.add(phone.getRingingCall());
+            mBackgroundCalls.add(phone.getBackgroundCall());
+            mForegroundCalls.add(phone.getForegroundCall());
+            registerForPhoneStates(phone);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * unregister phone from CallManager
+     * @param phone
+     */
+    public void unregisterPhone(Phone phone) {
+        if (phone != null && !mPhones.contains(phone)) {
+            mPhones.remove(phone);
+            mRingingCalls.remove(phone.getRingingCall());
+            mBackgroundCalls.remove(phone.getBackgroundCall());
+            mForegroundCalls.remove(phone.getForegroundCall());
+            unregisterForPhoneStates(phone);
+            if (phone == mDefaultPhone) {
+                if (mPhones.isEmpty()) {
+                    mDefaultPhone = null;
+                } else {
+                    mDefaultPhone = mPhones.get(0);
+                }
+            }
+        }
+    }
+
+    private void registerForPhoneStates(Phone phone) {
+        phone.registerForPreciseCallStateChanged(mHandler, EVENT_CALL_STATE_CHANGED, null);
+        phone.registerForDisconnect(mHandler, EVENT_DISCONNECT, null);
+    }
+
+    private void unregisterForPhoneStates(Phone phone) {
+        phone.unregisterForPreciseCallStateChanged(mHandler);
+        phone.unregisterForDisconnect(mHandler);
+    }
+
+    /**
+     * Answers a ringing or waiting call.
+     *
+     * Active call, if any, go on hold.
+     * If active call can't be held, i.e., a background call of the same channel exists,
+     * the active call will be hang up.
+     *
+     * Answering occurs asynchronously, and final notification occurs via
+     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+     * java.lang.Object) registerForPreciseCallStateChanged()}.
+     *
+     * @exception CallStateException when call is not ringing or waiting
+     */
+    public void acceptCall(Call ringingCall) throws CallStateException {
+        Phone ringingPhone = ringingCall.getPhone();
+
+        if ( hasActiveFgCall() ) {
+            Phone activePhone = getActiveFgCall().getPhone();
+            boolean hasBgCall = activePhone.getBackgroundCall().isIdle();
+            boolean sameChannel = (activePhone == ringingPhone);
+
+            if (sameChannel && hasBgCall) {
+                getActiveFgCall().hangup();
+            } else if (!sameChannel && !hasBgCall) {
+                activePhone.switchHoldingAndActive();
+            } else if (!sameChannel && hasBgCall) {
+                getActiveFgCall().hangup();
+            }
+        }
+
+        ringingPhone.acceptCall();
+    }
+
+    /**
+     * Reject (ignore) a ringing call. In GSM, this means UDUB
+     * (User Determined User Busy). Reject occurs asynchronously,
+     * and final notification occurs via
+     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+     * java.lang.Object) registerForPreciseCallStateChanged()}.
+     *
+     * @exception CallStateException when no call is ringing or waiting
+     */
+    public void rejectCall(Call ringingCall) throws CallStateException {
+        Phone ringingPhone = ringingCall.getPhone();
+
+        ringingPhone.rejectCall();
+    }
+
+    /**
+     * Places any active calls on hold, and makes any held calls
+     *  active. Switch occurs asynchronously and may fail.
+     * Final notification occurs via
+     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+     * java.lang.Object) registerForPreciseCallStateChanged()}.
+     *
+     * @exception CallStateException if active call is ringing, waiting, or
+     * dialing/alerting, or heldCall can�t be active.
+     * In these cases, this operation may not be performed.
+     */
+    public void switchHoldingAndActive(Call heldCall) throws CallStateException {
+        Phone activePhone = null;
+        Phone heldPhone = null;
+
+        if (hasActiveFgCall()) {
+            activePhone = getActiveFgCall().getPhone();
+        }
+
+        if (heldCall != null) {
+            heldPhone = heldCall.getPhone();
+        }
+
+        if (activePhone != heldPhone) {
+            activePhone.switchHoldingAndActive();
+        }
+
+        heldPhone.switchHoldingAndActive();
+    }
+
+    /**
+     * Whether or not the phone can conference in the current phone
+     * state--that is, one call holding and one call active.
+     * @return true if the phone can conference; false otherwise.
+     */
+    public boolean canConference(Call heldCall) {
+        Phone activePhone = null;
+        Phone heldPhone = null;
+
+        if (hasActiveFgCall()) {
+            activePhone = getActiveFgCall().getPhone();
+        }
+
+        if (heldCall != null) {
+            heldPhone = heldCall.getPhone();
+        }
+
+        return (heldPhone == activePhone);
+    }
+
+    /**
+     * Conferences holding and active. Conference occurs asynchronously
+     * and may fail. Final notification occurs via
+     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+     * java.lang.Object) registerForPreciseCallStateChanged()}.
+     *
+     * @exception CallStateException if canConference() would return false.
+     * In these cases, this operation may not be performed.
+     */
+    public void conference(Call heldCall) throws CallStateException {
+        if (canConference(heldCall))
+            throw(new CallStateException("Can't conference foreground and selected background call"));
+
+        heldCall.getPhone().conference();
+    }
+
+    /**
+     * Initiate a new voice connection. This happens asynchronously, so you
+     * cannot assume the audio path is connected (or a call index has been
+     * assigned) until PhoneStateChanged notification has occurred.
+     *
+     * @exception CallStateException if a new outgoing call is not currently
+     * possible because no more call slots exist or a call exists that is
+     * dialing, alerting, ringing, or waiting.  Other errors are
+     * handled asynchronously.
+     */
+    public Connection dial(Phone phone, String dialString) throws CallStateException {
+        return phone.dial(dialString);
+    }
+
+    /**
+     * Initiate a new voice connection. This happens asynchronously, so you
+     * cannot assume the audio path is connected (or a call index has been
+     * assigned) until PhoneStateChanged notification has occurred.
+     *
+     * @exception CallStateException if a new outgoing call is not currently
+     * possible because no more call slots exist or a call exists that is
+     * dialing, alerting, ringing, or waiting.  Other errors are
+     * handled asynchronously.
+     */
+    public Connection dial(Phone phone, String dialString, UUSInfo uusInfo) throws CallStateException {
+        return phone.dial(dialString, uusInfo);
+    }
+
+    /**
+     * clear disconnect connection for each phone
+     */
+    public void clearDisconnected() {
+        for(Phone phone : mPhones) {
+            phone.clearDisconnected();
+        }
+    }
+
+    /**
+     * Whether or not the phone can do explicit call transfer in the current
+     * phone state--that is, one call holding and one call active.
+     * @return true if the phone can do explicit call transfer; false otherwise.
+     */
+    public boolean canTransfer(Call heldCall) {
+        Phone activePhone = null;
+        Phone heldPhone = null;
+
+        if (hasActiveFgCall()) {
+            activePhone = getActiveFgCall().getPhone();
+        }
+
+        if (heldCall != null) {
+            heldPhone = heldCall.getPhone();
+        }
+
+        return (heldPhone == activePhone && activePhone.canTransfer());
+    }
+
+    /**
+     * Connects the held call and active call
+     * Disconnects the subscriber from both calls
+     *
+     * Explicit Call Transfer occurs asynchronously
+     * and may fail. Final notification occurs via
+     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+     * java.lang.Object) registerForPreciseCallStateChanged()}.
+     *
+     * @exception CallStateException if canTransfer() would return false.
+     * In these cases, this operation may not be performed.
+     */
+    public void explicitCallTransfer(Call heldCall) throws CallStateException {
+        if (canTransfer(heldCall)) {
+            heldCall.getPhone().explicitCallTransfer();
+        }
+    }
+
+    /**
+     * Returns a list of MMI codes that are pending for a phone. (They have initiated
+     * but have not yet completed).
+     * Presently there is only ever one.
+     *
+     * Use <code>registerForMmiInitiate</code>
+     * and <code>registerForMmiComplete</code> for change notification.
+     * @return null if phone doesn't have or support mmi code
+     */
+    public List<? extends MmiCode> getPendingMmiCodes(Phone phone) {
+        return null;
+    }
+
+    /**
+     * Sends user response to a USSD REQUEST message.  An MmiCode instance
+     * representing this response is sent to handlers registered with
+     * registerForMmiInitiate.
+     *
+     * @param ussdMessge    Message to send in the response.
+     * @return false if phone doesn't support ussd service
+     */
+    public boolean sendUssdResponse(Phone phone, String ussdMessge) {
+        return false;
+    }
+
+    /**
+     * Mutes or unmutes the microphone for the active call. The microphone
+     * is automatically unmuted if a call is answered, dialed, or resumed
+     * from a holding state.
+     *
+     * @param muted true to mute the microphone,
+     * false to activate the microphone.
+     */
+
+    public void setMute(boolean muted) {
+        if (hasActiveFgCall()) {
+            getActiveFgCall().getPhone().setMute(muted);
+        }
+    }
+
+    /**
+     * Gets current mute status. Use
+     * {@link #registerForPreciseCallStateChanged(android.os.Handler, int,
+     * java.lang.Object) registerForPreciseCallStateChanged()}
+     * as a change notifcation, although presently phone state changed is not
+     * fired when setMute() is called.
+     *
+     * @return true is muting, false is unmuting
+     */
+    public boolean getMute() {
+        if (hasActiveFgCall()) {
+            return getActiveFgCall().getPhone().getMute();
+        }
+        return false;
+    }
+
+    /**
+     * Play a DTMF tone on the active call.
+     *
+     * @param c should be one of 0-9, '*' or '#'. Other values will be
+     * silently ignored.
+     * @return false if no active call or the active call doesn't support
+     *         dtmf tone
+     */
+    public boolean sendDtmf(char c) {
+        if (hasActiveFgCall()) {
+            getActiveFgCall().getPhone().sendDtmf(c);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Start to paly a DTMF tone on the active call.
+     * or there is a playing DTMF tone.
+     * @param c should be one of 0-9, '*' or '#'. Other values will be
+     * silently ignored.
+     *
+     * @return false if no active call or the active call doesn't support
+     *         dtmf tone
+     */
+    public boolean startDtmf(char c) {
+        if (hasActiveFgCall()) {
+            getActiveFgCall().getPhone().sendDtmf(c);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Stop the playing DTMF tone. Ignored if there is no playing DTMF
+     * tone or no active call.
+     */
+    public void stopDtmf(Phone phone) {
+        phone.stopDtmf();
+    }
+
+    /**
+     * send burst DTMF tone, it can send the string as single character or multiple character
+     * ignore if there is no active call or not valid digits string.
+     * Valid digit means only includes characters ISO-LATIN characters 0-9, *, #
+     * The difference between sendDtmf and sendBurstDtmf is sendDtmf only sends one character,
+     * this api can send single character and multiple character, also, this api has response
+     * back to caller.
+     *
+     * @param dtmfString is string representing the dialing digit(s) in the active call
+     * @param on the DTMF ON length in milliseconds, or 0 for default
+     * @param off the DTMF OFF length in milliseconds, or 0 for default
+     * @param onComplete is the callback message when the action is processed by BP
+     *
+     */
+    public boolean sendBurstDtmf(Phone phone, String dtmfString, int on, int off, Message onComplete) {
+        if (hasActiveFgCall()) {
+            getActiveFgCall().getPhone().sendBurstDtmf(dtmfString, on, off, onComplete);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Notifies when a voice connection has disconnected, either due to local
+     * or remote hangup or error.
+     *
+     *  Messages received from this will have the following members:<p>
+     *  <ul><li>Message.obj will be an AsyncResult</li>
+     *  <li>AsyncResult.userObj = obj</li>
+     *  <li>AsyncResult.result = a Connection object that is
+     *  no longer connected.</li></ul>
+     */
+    public void registerForDisconnect(Handler h, int what, Object obj) {
+        mDisconnectRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregisters for voice disconnection notification.
+     * Extraneous calls are tolerated silently
+     */
+    public void unregisterForDisconnect(Handler h){
+        mDisconnectRegistrants.remove(h);
+    }
+
+    /**
+     * Register for getting notifications for change in the Call State {@link Call.State}
+     * This is called PreciseCallState because the call state is more precise than the
+     * {@link Phone.State} which can be obtained using the {@link PhoneStateListener}
+     *
+     * Resulting events will have an AsyncResult in <code>Message.obj</code>.
+     * AsyncResult.userData will be set to the obj argument here.
+     * The <em>h</em> parameter is held only by a weak reference.
+     */
+    public void registerForPreciseCallStateChanged(Handler h, int what, Object obj){
+        mPreciseCallStateRegistrants.addUnique(h, what, obj);
+    }
+
+    /**
+     * Unregisters for voice call state change notifications.
+     * Extraneous calls are tolerated silently.
+     */
+    public void unregisterForPreciseCallStateChanged(Handler h){
+        mPreciseCallStateRegistrants.remove(h);
+    }
+
+    /**
+     * Notifies when a previously untracked non-ringing/waiting connection has appeared.
+     * This is likely due to some other entity (eg, SIM card application) initiating a call.
+     */
+    public void registerForUnknownConnection(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for unknown connection notifications.
+     */
+    public void unregisterForUnknownConnection(Handler h){}
+
+
+    /**
+     * Notifies when a new ringing or waiting connection has appeared.<p>
+     *
+     *  Messages received from this:
+     *  Message.obj will be an AsyncResult
+     *  AsyncResult.userObj = obj
+     *  AsyncResult.result = a Connection. <p>
+     *  Please check Connection.isRinging() to make sure the Connection
+     *  has not dropped since this message was posted.
+     *  If Connection.isRinging() is true, then
+     *   Connection.getCall() == Phone.getRingingCall()
+     */
+    public void registerForNewRingingConnection(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for new ringing connection notification.
+     * Extraneous calls are tolerated silently
+     */
+
+    public void unregisterForNewRingingConnection(Handler h){}
+
+    /**
+     * Notifies when an incoming call rings.<p>
+     *
+     *  Messages received from this:
+     *  Message.obj will be an AsyncResult
+     *  AsyncResult.userObj = obj
+     *  AsyncResult.result = a Connection. <p>
+     */
+    public void registerForIncomingRing(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for ring notification.
+     * Extraneous calls are tolerated silently
+     */
+
+    public void unregisterForIncomingRing(Handler h){}
+
+    /**
+     * Notifies when out-band ringback tone is needed.<p>
+     *
+     *  Messages received from this:
+     *  Message.obj will be an AsyncResult
+     *  AsyncResult.userObj = obj
+     *  AsyncResult.result = boolean, true to start play ringback tone
+     *                       and false to stop. <p>
+     */
+    public void registerForRingbackTone(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for ringback tone notification.
+     */
+
+    public void unregisterForRingbackTone(Handler h){}
+
+    /**
+     * Registers the handler to reset the uplink mute state to get
+     * uplink audio.
+     */
+    public void registerForResendIncallMute(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for resend incall mute notifications.
+     */
+    public void unregisterForResendIncallMute(Handler h){}
+
+
+
+    /**
+     * Register for notifications of initiation of a new MMI code request.
+     * MMI codes for GSM are discussed in 3GPP TS 22.030.<p>
+     *
+     * Example: If Phone.dial is called with "*#31#", then the app will
+     * be notified here.<p>
+     *
+     * The returned <code>Message.obj</code> will contain an AsyncResult.
+     *
+     * <code>obj.result</code> will be an "MmiCode" object.
+     */
+    public void registerForMmiInitiate(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for new MMI initiate notification.
+     * Extraneous calls are tolerated silently
+     */
+    public void unregisterForMmiInitiate(Handler h){}
+
+    /**
+     * Register for notifications that an MMI request has completed
+     * its network activity and is in its final state. This may mean a state
+     * of COMPLETE, FAILED, or CANCELLED.
+     *
+     * <code>Message.obj</code> will contain an AsyncResult.
+     * <code>obj.result</code> will be an "MmiCode" object
+     */
+    public void registerForMmiComplete(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for MMI complete notification.
+     * Extraneous calls are tolerated silently
+     */
+    public void unregisterForMmiComplete(Handler h){}
+
+    /**
+     * Registration point for Ecm timer reset
+     * @param h handler to notify
+     * @param what user-defined message code
+     * @param obj placed in Message.obj
+     */
+    public void registerForEcmTimerReset(Handler h, int what, Object obj){}
+
+    /**
+     * Unregister for notification for Ecm timer reset
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForEcmTimerReset(Handler h){}
+
+
+
+    /**
+     * Register for ServiceState changed.
+     * Message.obj will contain an AsyncResult.
+     * AsyncResult.result will be a ServiceState instance
+     */
+    public void registerForServiceStateChanged(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for ServiceStateChange notification.
+     * Extraneous calls are tolerated silently
+     */
+    public void unregisterForServiceStateChanged(Handler h){}
+
+    /**
+     * Register for Supplementary Service notifications from the network.
+     * Message.obj will contain an AsyncResult.
+     * AsyncResult.result will be a SuppServiceNotification instance.
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSuppServiceNotification(Handler h, int what, Object obj){}
+
+    /**
+     * Unregisters for Supplementary Service notifications.
+     * Extraneous calls are tolerated silently
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSuppServiceNotification(Handler h){}
+
+    /**
+     * Register for notifications when a supplementary service attempt fails.
+     * Message.obj will contain an AsyncResult.
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForSuppServiceFailed(Handler h, int what, Object obj){}
+
+    /**
+     * Unregister for notifications when a supplementary service attempt fails.
+     * Extraneous calls are tolerated silently
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSuppServiceFailed(Handler h){}
+
+    /**
+     * Register for notifications when a sInCall VoicePrivacy is enabled
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForInCallVoicePrivacyOn(Handler h, int what, Object obj){}
+
+    /**
+     * Unegister for notifications when a sInCall VoicePrivacy is enabled
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForInCallVoicePrivacyOn(Handler h){}
+
+    /**
+     * Register for notifications when a sInCall VoicePrivacy is disabled
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForInCallVoicePrivacyOff(Handler h, int what, Object obj){}
+
+    /**
+     * Unegister for notifications when a sInCall VoicePrivacy is disabled
+     *
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForInCallVoicePrivacyOff(Handler h){}
+
+    /**
+     * Register for notifications when CDMA OTA Provision status change
+     *
+     * @param h Handler that receives the notification message.
+     * @param what User-defined message code.
+     * @param obj User object.
+     */
+    public void registerForCdmaOtaStatusChange(Handler h, int what, Object obj){}
+
+    /**
+     * Unegister for notifications when CDMA OTA Provision status change
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForCdmaOtaStatusChange(Handler h){}
+
+    /**
+     * Registration point for subscription info ready
+     * @param h handler to notify
+     * @param what what code of message when delivered
+     * @param obj placed in Message.obj
+     */
+    public void registerForSubscriptionInfoReady(Handler h, int what, Object obj){}
+
+    /**
+     * Unregister for notifications for subscription info
+     * @param h Handler to be removed from the registrant list.
+     */
+    public void unregisterForSubscriptionInfoReady(Handler h){}
+
+    /* APIs to access foregroudCalls, backgroudCalls, and ringingCalls
+     * 1. APIs to access list of calls
+     * 2. APIs to check if any active call, which has connection other than
+     * disconnected ones, pleaser refer to Call.isIdle()
+     * 3. APIs to return first active call
+     * 4. APIs to return the connections of first active call
+     * 5. APIs to return other property of first active call
+     */
+
+    /**
+     * @return list of ringing calls
+     */
+    public ArrayList<Call> getRingingCalls() {
+        return mBackgroundCalls;
+    }
+
+    /**
+     * @return list of background calls
+     */
+    public ArrayList<Call> getBackgroundCalls() {
+        return mBackgroundCalls;
+    }
+
+    /**
+     * Return true if there is at least one active foreground call
+     */
+    public boolean hasActiveFgCall() {
+        return (getFirstActiveCall(mForegroundCalls) != null);
+    }
+
+    /**
+     * Return true if there is at least one active background call
+     */
+    public boolean hasActiveBgCall() {
+        // TODO since hasActiveBgCall may get called often
+        // better to cache it to improve performance
+        return (getFirstActiveCall(mBackgroundCalls) != null);
+    }
+
+    /**
+     * Return true if there is at least one active ringing call
+     *
+     */
+    public boolean hasActiveRingingCall() {
+        return (getFirstActiveCall(mRingingCalls) != null);
+    }
+
+    /**
+     * return the active foreground call from foreground calls
+     *
+     * Active call means the call is NOT in Call.State.IDLE
+     *
+     * 1. If there is active foreground call, return it
+     * 2. If there is no active foreground call, return the
+     *    foreground call associated with default phone, which state is IDLE.
+     * 3. If there is no phone registered at all, return null.
+     *
+     */
+    public Call getActiveFgCall() {
+        for (Call call : mForegroundCalls) {
+            if (call.getState() != Call.State.IDLE) {
+                return call;
+            }
+        }
+        return (mDefaultPhone == null) ?
+                null : mDefaultPhone.getForegroundCall();
+    }
+
+    /**
+     * return one active background call from background calls
+     *
+     * Active call means the call is NOT idle defined by Call.isIdle()
+     *
+     * 1. If there is only one active background call, return it
+     * 2. If there is more than one active background call, return the first one
+     * 3. If there is no active background call, return the background call
+     *    associated with default phone, which state is IDLE.
+     * 4. If there is no background call at all, return null.
+     *
+     * Complete background calls list can be get by getBackgroundCalls()
+     */
+    public Call getFirstActiveBgCall() {
+        for (Call call : mBackgroundCalls) {
+            if (!call.isIdle()) {
+                return call;
+            }
+        }
+        return (mDefaultPhone == null) ?
+                null : mDefaultPhone.getBackgroundCall();
+    }
+
+    /**
+     * return one active ringing call from ringing calls
+     *
+     * Active call means the call is NOT idle defined by Call.isIdle()
+     *
+     * 1. If there is only one active ringing call, return it
+     * 2. If there is more than one active ringing call, return the first one
+     * 3. If there is no active ringing call, return the ringing call
+     *    associated with default phone, which state is IDLE.
+     * 4. If there is no ringing call at all, return null.
+     *
+     * Complete ringing calls list can be get by getRingingCalls()
+     */
+    public Call getFirstActiveRingingCall() {
+        for (Call call : mRingingCalls) {
+            if (!call.isIdle()) {
+                return call;
+            }
+        }
+        return (mDefaultPhone == null) ?
+                null : mDefaultPhone.getRingingCall();
+    }
+
+    /**
+     * @return the state of active foreground call
+     * return IDLE if there is no active foreground call
+     */
+    public Call.State getActiveFgCallState() {
+        Call fgCall = getActiveFgCall();
+
+        if (fgCall != null) {
+            return fgCall.getState();
+        }
+
+        return Call.State.IDLE;
+    }
+
+    /**
+     * @return the connections of active foreground call
+     * return null if there is no active foreground call
+     */
+    public List<Connection> getFgCallConnections() {
+        Call fgCall = getActiveFgCall();
+        if ( fgCall != null) {
+            return fgCall.getConnections();
+        }
+        return emptyConnections;
+    }
+
+    /**
+     * @return the connections of active background call
+     * return empty list if there is no active background call
+     */
+    public List<Connection> getBgCallConnections() {
+        Call bgCall = getActiveFgCall();
+        if ( bgCall != null) {
+            return bgCall.getConnections();
+        }
+        return emptyConnections;
+    }
+
+    /**
+     * @return the latest connection of active foreground call
+     * return null if there is no active foreground call
+     */
+    public Connection getFgCallLatestConnection() {
+        Call fgCall = getActiveFgCall();
+        if ( fgCall != null) {
+            return fgCall.getLatestConnection();
+        }
+        return null;
+    }
+
+    /**
+     * @return true if there is at least one Foreground call in disconnected state
+     */
+    public boolean hasDisconnectedFgCall() {
+        return (getFirstCallOfState(mForegroundCalls, Call.State.DISCONNECTED) != null);
+    }
+
+    /**
+     * @return true if there is at least one background call in disconnected state
+     */
+    public boolean hasDisconnectedBgCall() {
+        return (getFirstCallOfState(mBackgroundCalls, Call.State.DISCONNECTED) != null);
+    }
+
+    /**
+     * @return the first active call from a call list
+     */
+    private  Call getFirstActiveCall(ArrayList<Call> calls) {
+        for (Call call : calls) {
+            if (!call.isIdle()) {
+                return call;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the first call in a the Call.state from a call list
+     */
+    private Call getFirstCallOfState(ArrayList<Call> calls, Call.State state) {
+        for (Call call : calls) {
+            if (call.getState() == state) {
+                return call;
+            }
+        }
+        return null;
+    }
+
+
+
+
+    private Handler mHandler = new Handler() {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_DISCONNECT:
+                    mDisconnectRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+                    break;
+                case EVENT_CALL_STATE_CHANGED:
+                    mPreciseCallStateRegistrants.notifyRegistrants((AsyncResult) msg.obj);
+                    break;
+            }
+        }
+    };
+}
diff --git a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
index e74b9e4..5cba2e1 100644
--- a/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
+++ b/telephony/java/com/android/internal/telephony/IPhoneSubInfo.aidl
@@ -59,6 +59,11 @@
     String getVoiceMailNumber();
 
     /**
+     * Retrieves the complete voice mail number.
+     */
+    String getCompleteVoiceMailNumber();
+
+    /**
      * Retrieves the alpha identifier associated with the voice mail number.
      */
     String getVoiceMailAlphaTag();
diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
index 4f71bb1..86c86bb 100644
--- a/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
+++ b/telephony/java/com/android/internal/telephony/PhoneSubInfo.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.os.Binder;
+import android.telephony.PhoneNumberUtils;
 import android.util.Log;
 
 public class PhoneSubInfo extends IPhoneSubInfo.Stub {
@@ -29,6 +30,9 @@
     private Context mContext;
     private static final String READ_PHONE_STATE =
         android.Manifest.permission.READ_PHONE_STATE;
+    private static final String CALL_PRIVILEGED =
+        // TODO Add core/res/AndriodManifest.xml#READ_PRIVILEGED_PHONE_STATE
+        android.Manifest.permission.CALL_PRIVILEGED;
 
     public PhoneSubInfo(Phone phone) {
         mPhone = phone;
@@ -101,7 +105,22 @@
      */
     public String getVoiceMailNumber() {
         mContext.enforceCallingOrSelfPermission(READ_PHONE_STATE, "Requires READ_PHONE_STATE");
-        return (String) mPhone.getVoiceMailNumber();
+        String number = PhoneNumberUtils.extractNetworkPortion(mPhone.getVoiceMailNumber());
+        Log.d(LOG_TAG, "VM: PhoneSubInfo.getVoiceMailNUmber: "); // + number);
+        return number;
+    }
+
+    /**
+     * Retrieves the compelete voice mail number.
+     *
+     * @hide
+     */
+    public String getCompleteVoiceMailNumber() {
+        mContext.enforceCallingOrSelfPermission(CALL_PRIVILEGED,
+                "Requires CALL_PRIVILEGED");
+        String number = mPhone.getVoiceMailNumber();
+        Log.d(LOG_TAG, "VM: PhoneSubInfo.getCompleteVoiceMailNUmber: "); // + number);
+        return number;
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java b/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java
index 202ded2..7009893 100644
--- a/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java
+++ b/telephony/java/com/android/internal/telephony/PhoneSubInfoProxy.java
@@ -82,6 +82,13 @@
     }
 
     /**
+     * Retrieves the complete voice mail number.
+     */
+    public String getCompleteVoiceMailNumber() {
+        return mPhoneSubInfo.getCompleteVoiceMailNumber();
+    }
+
+    /**
      * Retrieves the alpha identifier associated with the voice mail number.
      */
     public String getVoiceMailAlphaTag() {