Merge "audioflinger: more info in dumpsys"
diff --git a/api/current.txt b/api/current.txt
index 6056bf7..0e3fee6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2688,13 +2688,20 @@
     method public final deprecated boolean showDialog(int, android.os.Bundle);
     method public android.view.ActionMode startActionMode(android.view.ActionMode.Callback);
     method public void startActivityForResult(android.content.Intent, int);
+    method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
     method public void startActivityFromChild(android.app.Activity, android.content.Intent, int);
+    method public void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle);
     method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int);
+    method public void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle);
     method public boolean startActivityIfNeeded(android.content.Intent, int);
+    method public boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle);
     method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
+    method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
+    method public void startIntentSenderFromChild(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public deprecated void startManagingCursor(android.database.Cursor);
     method public boolean startNextMatchingActivity(android.content.Intent);
+    method public boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle);
     method public void startSearch(java.lang.String, boolean, android.os.Bundle, boolean);
     method public deprecated void stopManagingCursor(android.database.Cursor);
     method public void takeKeyEvents(boolean);
@@ -3320,7 +3327,9 @@
     method public void setTargetFragment(android.app.Fragment, int);
     method public void setUserVisibleHint(boolean);
     method public void startActivity(android.content.Intent);
+    method public void startActivity(android.content.Intent, android.os.Bundle);
     method public void startActivityForResult(android.content.Intent, int);
+    method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
     method public void unregisterForContextMenu(android.view.View);
   }
 
@@ -5149,9 +5158,12 @@
     method public abstract deprecated void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
     method public abstract deprecated void setWallpaper(java.io.InputStream) throws java.io.IOException;
     method public abstract void startActivities(android.content.Intent[]);
+    method public abstract void startActivities(android.content.Intent[], android.os.Bundle);
     method public abstract void startActivity(android.content.Intent);
+    method public abstract void startActivity(android.content.Intent, android.os.Bundle);
     method public abstract boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public abstract void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
+    method public abstract void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public abstract android.content.ComponentName startService(android.content.Intent);
     method public abstract boolean stopService(android.content.Intent);
     method public abstract void unbindService(android.content.ServiceConnection);
@@ -5274,9 +5286,12 @@
     method public void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
     method public void setWallpaper(java.io.InputStream) throws java.io.IOException;
     method public void startActivities(android.content.Intent[]);
+    method public void startActivities(android.content.Intent[], android.os.Bundle);
     method public void startActivity(android.content.Intent);
+    method public void startActivity(android.content.Intent, android.os.Bundle);
     method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
+    method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public android.content.ComponentName startService(android.content.Intent);
     method public boolean stopService(android.content.Intent);
     method public void unbindService(android.content.ServiceConnection);
@@ -19926,9 +19941,12 @@
     method public void setWallpaper(android.graphics.Bitmap) throws java.io.IOException;
     method public void setWallpaper(java.io.InputStream) throws java.io.IOException;
     method public void startActivities(android.content.Intent[]);
+    method public void startActivities(android.content.Intent[], android.os.Bundle);
     method public void startActivity(android.content.Intent);
+    method public void startActivity(android.content.Intent, android.os.Bundle);
     method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
+    method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public android.content.ComponentName startService(android.content.Intent);
     method public boolean stopService(android.content.Intent);
     method public void unbindService(android.content.ServiceConnection);
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index da38df1..c15c49f 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -57,16 +57,13 @@
     private int mNextArg;
     private String mCurArgData;
 
-    private boolean mDebugOption = false;
+    private int mStartFlags = 0;
     private boolean mWaitOption = false;
     private boolean mStopOption = false;
 
-    private boolean mOpenglTraceOption = false;
-
     private int mRepeat = 0;
 
     private String mProfileFile;
-    private boolean mProfileAutoStop;
 
     // These are magic strings understood by the Eclipse plugin.
     private static final String FATAL_ERROR_CODE = "Error type 1";
@@ -150,10 +147,9 @@
         Intent baseIntent = intent;
         boolean hasIntentInfo = false;
 
-        mDebugOption = false;
+        mStartFlags = 0;
         mWaitOption = false;
         mStopOption = false;
-        mOpenglTraceOption = false;
         mRepeat = 0;
         mProfileFile = null;
         Uri data = null;
@@ -297,21 +293,21 @@
                 intent.setDataAndType(data, type);
                 intent = new Intent();
             } else if (opt.equals("-D")) {
-                mDebugOption = true;
+                mStartFlags |= ActivityManager.START_FLAG_DEBUG;
             } else if (opt.equals("-W")) {
                 mWaitOption = true;
             } else if (opt.equals("-P")) {
                 mProfileFile = nextArgRequired();
-                mProfileAutoStop = true;
+                mStartFlags |= ActivityManager.START_FLAG_AUTO_STOP_PROFILER;
             } else if (opt.equals("--start-profiler")) {
                 mProfileFile = nextArgRequired();
-                mProfileAutoStop = false;
+                mStartFlags &= ~ActivityManager.START_FLAG_AUTO_STOP_PROFILER;
             } else if (opt.equals("-R")) {
                 mRepeat = Integer.parseInt(nextArgRequired());
             } else if (opt.equals("-S")) {
                 mStopOption = true;
             } else if (opt.equals("--opengl-trace")) {
-                mOpenglTraceOption = true;
+                mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 showUsage();
@@ -450,64 +446,60 @@
             int res;
             if (mWaitOption) {
                 result = mAm.startActivityAndWait(null, intent, mimeType,
-                            null, 0, null, null, 0, false,
-                            mDebugOption, mOpenglTraceOption,
-                            mProfileFile, fd, mProfileAutoStop);
+                            null, null, 0, mStartFlags, mProfileFile, fd, null);
                 res = result.result;
             } else {
                 res = mAm.startActivity(null, intent, mimeType,
-                        null, 0, null, null, 0, false,
-                        mDebugOption, mOpenglTraceOption,
-                        mProfileFile, fd, mProfileAutoStop);
+                        null, null, 0, mStartFlags, mProfileFile, fd, null);
             }
             PrintStream out = mWaitOption ? System.out : System.err;
             boolean launched = false;
             switch (res) {
-                case IActivityManager.START_SUCCESS:
+                case ActivityManager.START_SUCCESS:
                     launched = true;
                     break;
-                case IActivityManager.START_SWITCHES_CANCELED:
+                case ActivityManager.START_SWITCHES_CANCELED:
                     launched = true;
                     out.println(
                             "Warning: Activity not started because the "
                             + " current activity is being kept for the user.");
                     break;
-                case IActivityManager.START_DELIVERED_TO_TOP:
+                case ActivityManager.START_DELIVERED_TO_TOP:
                     launched = true;
                     out.println(
                             "Warning: Activity not started, intent has "
                             + "been delivered to currently running "
                             + "top-most instance.");
                     break;
-                case IActivityManager.START_RETURN_INTENT_TO_CALLER:
+                case ActivityManager.START_RETURN_INTENT_TO_CALLER:
                     launched = true;
                     out.println(
                             "Warning: Activity not started because intent "
                             + "should be handled by the caller");
                     break;
-                case IActivityManager.START_TASK_TO_FRONT:
+                case ActivityManager.START_TASK_TO_FRONT:
                     launched = true;
                     out.println(
                             "Warning: Activity not started, its current "
                             + "task has been brought to the front");
                     break;
-                case IActivityManager.START_INTENT_NOT_RESOLVED:
+                case ActivityManager.START_INTENT_NOT_RESOLVED:
                     out.println(
                             "Error: Activity not started, unable to "
                             + "resolve " + intent.toString());
                     break;
-                case IActivityManager.START_CLASS_NOT_FOUND:
+                case ActivityManager.START_CLASS_NOT_FOUND:
                     out.println(NO_CLASS_ERROR_CODE);
                     out.println("Error: Activity class " +
                             intent.getComponent().toShortString()
                             + " does not exist.");
                     break;
-                case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+                case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                     out.println(
                             "Error: Activity not started, you requested to "
                             + "both forward and receive its result");
                     break;
-                case IActivityManager.START_PERMISSION_DENIED:
+                case ActivityManager.START_PERMISSION_DENIED:
                     out.println(
                             "Error: Activity not started, you do not "
                             + "have permission to access it.");
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 0f2aa46..dc12acd 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3166,42 +3166,60 @@
     }
 
     /**
+     * Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity 
+     */
+    public void startActivityForResult(Intent intent, int requestCode) {
+        startActivityForResult(intent, requestCode, null);
+    }
+
+    /**
      * Launch an activity for which you would like a result when it finished.
      * When this activity exits, your
      * onActivityResult() method will be called with the given requestCode. 
      * Using a negative requestCode is the same as calling 
      * {@link #startActivity} (the activity is not launched as a sub-activity).
-     * 
+     *
      * <p>Note that this method should only be used with Intent protocols
      * that are defined to return a result.  In other protocols (such as
      * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
      * not get the result when you expect.  For example, if the activity you
      * are launching uses the singleTask launch mode, it will not run in your
      * task and thus you will immediately receive a cancel result.
-     * 
+     *
      * <p>As a special case, if you call startActivityForResult() with a requestCode 
      * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
      * activity, then your window will not be displayed until a result is 
      * returned back from the started activity.  This is to avoid visible 
      * flickering when redirecting to another activity. 
-     * 
+     *
      * <p>This method throws {@link android.content.ActivityNotFoundException}
      * if there was no Activity found to run the given Intent.
-     * 
+     *
      * @param intent The intent to start.
      * @param requestCode If >= 0, this code will be returned in
      *                    onActivityResult() when the activity exits.
-     * 
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
+     *
      * @throws android.content.ActivityNotFoundException
-     * 
+     *
      * @see #startActivity 
      */
-    public void startActivityForResult(Intent intent, int requestCode) {
+    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
         if (mParent == null) {
             Instrumentation.ActivityResult ar =
                 mInstrumentation.execStartActivity(
                     this, mMainThread.getApplicationThread(), mToken, this,
-                    intent, requestCode);
+                    intent, requestCode, options);
             if (ar != null) {
                 mMainThread.sendActivityResult(
                     mToken, mEmbeddedID, requestCode, ar.getResultCode(),
@@ -3218,11 +3236,39 @@
                 mStartedActivity = true;
             }
         } else {
-            mParent.startActivityFromChild(this, intent, requestCode);
+            if (options != null) {
+                mParent.startActivityFromChild(this, intent, requestCode, options);
+            } else {
+                // Note we want to go through this method for compatibility with
+                // existing applications that may have overridden it.
+                mParent.startActivityFromChild(this, intent, requestCode);
+            }
         }
     }
 
     /**
+     * Same as calling {@link #startIntentSenderForResult(IntentSender, int,
+     * Intent, int, int, int, Bundle)} with no options.
+     *
+     * @param intent The IntentSender to launch.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     */
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+            throws IntentSender.SendIntentException {
+        startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+                flagsValues, extraFlags, null);
+    }
+
+    /**
      * Like {@link #startActivityForResult(Intent, int)}, but allowing you
      * to use a IntentSender to describe the activity to be started.  If
      * the IntentSender is for an activity, that activity will be started
@@ -3241,21 +3287,29 @@
      * @param flagsValues Desired values for any bits set in
      * <var>flagsMask</var>
      * @param extraFlags Always set to 0.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      */
     public void startIntentSenderForResult(IntentSender intent, int requestCode,
-            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
-            throws IntentSender.SendIntentException {
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
         if (mParent == null) {
             startIntentSenderForResultInner(intent, requestCode, fillInIntent,
-                    flagsMask, flagsValues, this);
+                    flagsMask, flagsValues, this, options);
+        } else if (options != null) {
+            mParent.startIntentSenderFromChild(this, intent, requestCode,
+                    fillInIntent, flagsMask, flagsValues, extraFlags, options);
         } else {
+            // Note we want to go through this call for compatibility with
+            // existing applications that may have overridden the method.
             mParent.startIntentSenderFromChild(this, intent, requestCode,
                     fillInIntent, flagsMask, flagsValues, extraFlags);
         }
     }
 
     private void startIntentSenderForResultInner(IntentSender intent, int requestCode,
-            Intent fillInIntent, int flagsMask, int flagsValues, Activity activity)
+            Intent fillInIntent, int flagsMask, int flagsValues, Activity activity,
+            Bundle options)
             throws IntentSender.SendIntentException {
         try {
             String resolvedType = null;
@@ -3266,8 +3320,8 @@
             int result = ActivityManagerNative.getDefault()
                 .startActivityIntentSender(mMainThread.getApplicationThread(), intent,
                         fillInIntent, resolvedType, mToken, activity.mEmbeddedID,
-                        requestCode, flagsMask, flagsValues);
-            if (result == IActivityManager.START_CANCELED) {
+                        requestCode, flagsMask, flagsValues, options);
+            if (result == ActivityManager.START_CANCELED) {
                 throw new IntentSender.SendIntentException();
             }
             Instrumentation.checkStartActivityResult(result, null);
@@ -3286,6 +3340,22 @@
     }
 
     /**
+     * Same as {@link #startActivity(Intent, Bundle)} with no options
+     * specified.
+     *
+     * @param intent The intent to start.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see {@link #startActivity(Intent, Bundle)}
+     * @see #startActivityForResult
+     */
+    @Override
+    public void startActivity(Intent intent) {
+        startActivity(intent, null);
+    }
+
+    /**
      * Launch a new activity.  You will not receive any information about when
      * the activity exits.  This implementation overrides the base version,
      * providing information about
@@ -3298,14 +3368,39 @@
      * if there was no Activity found to run the given Intent.
      * 
      * @param intent The intent to start. 
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      * 
      * @throws android.content.ActivityNotFoundException
-     * 
+     *
+     * @see {@link #startActivity(Intent)}
      * @see #startActivityForResult 
      */
     @Override
-    public void startActivity(Intent intent) {
-        startActivityForResult(intent, -1);
+    public void startActivity(Intent intent, Bundle options) {
+        if (options != null) {
+            startActivityForResult(intent, -1, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // applications that may have overridden the method.
+            startActivityForResult(intent, -1);
+        }
+    }
+
+    /**
+     * Same as {@link #startActivities(Intent[], Bundle)} with no options
+     * specified.
+     *
+     * @param intents The intents to start.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see {@link #startActivities(Intent[], Bundle)}
+     * @see #startActivityForResult
+     */
+    @Override
+    public void startActivities(Intent[] intents) {
+        startActivities(intents, null);
     }
 
     /**
@@ -3321,22 +3416,23 @@
      * if there was no Activity found to run the given Intent.
      *
      * @param intents The intents to start.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      *
      * @throws android.content.ActivityNotFoundException
      *
+     * @see {@link #startActivities(Intent[])}
      * @see #startActivityForResult
      */
     @Override
-    public void startActivities(Intent[] intents) {
+    public void startActivities(Intent[] intents, Bundle options) {
         mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(),
-                mToken, this, intents);
+                mToken, this, intents, options);
     }
 
     /**
-     * Like {@link #startActivity(Intent)}, but taking a IntentSender
-     * to start; see
-     * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
-     * for more information.
+     * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+     * with no options.
      * 
      * @param intent The IntentSender to launch.
      * @param fillInIntent If non-null, this will be provided as the
@@ -3350,8 +3446,58 @@
     public void startIntentSender(IntentSender intent,
             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
             throws IntentSender.SendIntentException {
-        startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
-                flagsValues, extraFlags);
+        startIntentSender(intent, fillInIntent, flagsMask, flagsValues,
+                extraFlags, null);
+    }
+
+    /**
+     * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
+     * to start; see
+     * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)}
+     * for more information.
+     *
+     * @param intent The IntentSender to launch.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
+     */
+    public void startIntentSender(IntentSender intent,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        if (options != null) {
+            startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+                    flagsValues, extraFlags, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // applications that may have overridden the method.
+            startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+                    flagsValues, extraFlags);
+        }
+    }
+
+    /**
+     * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *         onActivityResult() when the activity exits, as described in
+     *         {@link #startActivityForResult}.
+     *
+     * @return If a new activity was launched then true is returned; otherwise
+     *         false is returned and you must handle the Intent yourself.
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public boolean startActivityIfNeeded(Intent intent, int requestCode) {
+        return startActivityIfNeeded(intent, requestCode, null);
     }
 
     /**
@@ -3374,6 +3520,8 @@
      * @param requestCode If >= 0, this code will be returned in
      *         onActivityResult() when the activity exits, as described in
      *         {@link #startActivityForResult}.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      * 
      * @return If a new activity was launched then true is returned; otherwise
      *         false is returned and you must handle the Intent yourself.
@@ -3381,17 +3529,17 @@
      * @see #startActivity
      * @see #startActivityForResult
      */
-    public boolean startActivityIfNeeded(Intent intent, int requestCode) {
+    public boolean startActivityIfNeeded(Intent intent, int requestCode, Bundle options) {
         if (mParent == null) {
-            int result = IActivityManager.START_RETURN_INTENT_TO_CALLER;
+            int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
             try {
                 intent.setAllowFds(false);
                 result = ActivityManagerNative.getDefault()
                     .startActivity(mMainThread.getApplicationThread(),
                             intent, intent.resolveTypeIfNeeded(getContentResolver()),
-                            null, 0,
-                            mToken, mEmbeddedID, requestCode, true /* onlyIfNeeded */,
-                            false, false, null, null, false);
+                            mToken, mEmbeddedID, requestCode,
+                            ActivityManager.START_FLAG_ONLY_IF_NEEDED, null, null,
+                            options);
             } catch (RemoteException e) {
                 // Empty
             }
@@ -3408,7 +3556,7 @@
                 // activity is finished, no matter what happens to it.
                 mStartedActivity = true;
             }
-            return result != IActivityManager.START_RETURN_INTENT_TO_CALLER;
+            return result != ActivityManager.START_RETURN_INTENT_TO_CALLER;
         }
 
         throw new UnsupportedOperationException(
@@ -3416,6 +3564,24 @@
     }
 
     /**
+     * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with
+     * no options.
+     *
+     * @param intent The intent to dispatch to the next activity.  For
+     * correct behavior, this must be the same as the Intent that started
+     * your own activity; the only changes you can make are to the extras
+     * inside of it.
+     *
+     * @return Returns a boolean indicating whether there was another Activity
+     * to start: true if there was a next activity to start, false if there
+     * wasn't.  In general, if true is returned you will then want to call
+     * finish() on yourself.
+     */
+    public boolean startNextMatchingActivity(Intent intent) {
+        return startNextMatchingActivity(intent, null);
+    }
+
+    /**
      * Special version of starting an activity, for use when you are replacing
      * other activity components.  You can use this to hand the Intent off
      * to the next Activity that can handle it.  You typically call this in
@@ -3425,18 +3591,20 @@
      * correct behavior, this must be the same as the Intent that started
      * your own activity; the only changes you can make are to the extras
      * inside of it.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      * 
      * @return Returns a boolean indicating whether there was another Activity
      * to start: true if there was a next activity to start, false if there
      * wasn't.  In general, if true is returned you will then want to call
      * finish() on yourself.
      */
-    public boolean startNextMatchingActivity(Intent intent) {
+    public boolean startNextMatchingActivity(Intent intent, Bundle options) {
         if (mParent == null) {
             try {
                 intent.setAllowFds(false);
                 return ActivityManagerNative.getDefault()
-                    .startNextMatchingActivity(mToken, intent);
+                    .startNextMatchingActivity(mToken, intent, options);
             } catch (RemoteException e) {
                 // Empty
             }
@@ -3446,7 +3614,25 @@
         throw new UnsupportedOperationException(
             "startNextMatchingActivity can only be called from a top-level activity");
     }
-    
+
+    /**
+     * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param child The activity making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public void startActivityFromChild(Activity child, Intent intent,
+            int requestCode) {
+        startActivityFromChild(child, intent, requestCode);
+    }
+
     /**
      * This is called when a child activity of this one calls its 
      * {@link #startActivity} or {@link #startActivityForResult} method.
@@ -3456,7 +3642,9 @@
      * 
      * @param child The activity making the call.
      * @param intent The intent to start.
-     * @param requestCode Reply request code.  < 0 if reply is not requested. 
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      * 
      * @throws android.content.ActivityNotFoundException
      * 
@@ -3464,11 +3652,11 @@
      * @see #startActivityForResult 
      */
     public void startActivityFromChild(Activity child, Intent intent, 
-            int requestCode) {
+            int requestCode, Bundle options) {
         Instrumentation.ActivityResult ar =
             mInstrumentation.execStartActivity(
                 this, mMainThread.getApplicationThread(), mToken, child,
-                intent, requestCode);
+                intent, requestCode, options);
         if (ar != null) {
             mMainThread.sendActivityResult(
                 mToken, child.mEmbeddedID, requestCode,
@@ -3477,6 +3665,24 @@
     }
 
     /**
+     * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param fragment The fragment making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see Fragment#startActivity
+     * @see Fragment#startActivityForResult
+     */
+    public void startActivityFromFragment(Fragment fragment, Intent intent, 
+            int requestCode) {
+        startActivityFromFragment(fragment, intent, requestCode, null);
+    }
+
+    /**
      * This is called when a Fragment in this activity calls its 
      * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
      * method.
@@ -3487,6 +3693,8 @@
      * @param fragment The fragment making the call.
      * @param intent The intent to start.
      * @param requestCode Reply request code.  < 0 if reply is not requested. 
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      * 
      * @throws android.content.ActivityNotFoundException
      * 
@@ -3494,11 +3702,11 @@
      * @see Fragment#startActivityForResult 
      */
     public void startActivityFromFragment(Fragment fragment, Intent intent, 
-            int requestCode) {
+            int requestCode, Bundle options) {
         Instrumentation.ActivityResult ar =
             mInstrumentation.execStartActivity(
                 this, mMainThread.getApplicationThread(), mToken, fragment,
-                intent, requestCode);
+                intent, requestCode, options);
         if (ar != null) {
             mMainThread.sendActivityResult(
                 mToken, fragment.mWho, requestCode,
@@ -3507,6 +3715,18 @@
     }
 
     /**
+     * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender,
+     * int, Intent, int, int, int, Bundle)} with no options.
+     */
+    public void startIntentSenderFromChild(Activity child, IntentSender intent,
+            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+            int extraFlags)
+            throws IntentSender.SendIntentException {
+        startIntentSenderFromChild(child, intent, requestCode, fillInIntent,
+                flagsMask, flagsValues, extraFlags, null);
+    }
+
+    /**
      * Like {@link #startActivityFromChild(Activity, Intent, int)}, but
      * taking a IntentSender; see
      * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
@@ -3514,10 +3734,10 @@
      */
     public void startIntentSenderFromChild(Activity child, IntentSender intent,
             int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
-            int extraFlags)
+            int extraFlags, Bundle options)
             throws IntentSender.SendIntentException {
         startIntentSenderForResultInner(intent, requestCode, fillInIntent,
-                flagsMask, flagsValues, child);
+                flagsMask, flagsValues, child, options);
     }
 
     /**
@@ -3843,7 +4063,7 @@
             data.setAllowFds(false);
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
-                        IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
+                        ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
                         mParent == null ? mToken : mParent.mToken,
                         mEmbeddedID, requestCode, new Intent[] { data }, null, flags);
             return target != null ? new PendingIntent(target) : null;
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 59c803e..d056b17 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -59,6 +59,154 @@
     private final Context mContext;
     private final Handler mHandler;
 
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * start had to be canceled.
+     * @hide
+     */
+    public static final int START_CANCELED = -6;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * thing being started is not an activity.
+     * @hide
+     */
+    public static final int START_NOT_ACTIVITY = -5;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * caller does not have permission to start the activity.
+     * @hide
+     */
+    public static final int START_PERMISSION_DENIED = -4;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * caller has requested both to forward a result and to receive
+     * a result.
+     * @hide
+     */
+    public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * requested class is not found.
+     * @hide
+     */
+    public static final int START_CLASS_NOT_FOUND = -2;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * given Intent could not be resolved to an activity.
+     * @hide
+     */
+    public static final int START_INTENT_NOT_RESOLVED = -1;
+
+    /**
+     * Result for IActivityManaqer.startActivity: the activity was started
+     * successfully as normal.
+     * @hide
+     */
+    public static final int START_SUCCESS = 0;
+
+    /**
+     * Result for IActivityManaqer.startActivity: the caller asked that the Intent not
+     * be executed if it is the recipient, and that is indeed the case.
+     * @hide
+     */
+    public static final int START_RETURN_INTENT_TO_CALLER = 1;
+
+    /**
+     * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+     * a task was simply brought to the foreground.
+     * @hide
+     */
+    public static final int START_TASK_TO_FRONT = 2;
+
+    /**
+     * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+     * the given Intent was given to the existing top activity.
+     * @hide
+     */
+    public static final int START_DELIVERED_TO_TOP = 3;
+
+    /**
+     * Result for IActivityManaqer.startActivity: request was canceled because
+     * app switches are temporarily canceled to ensure the user's last request
+     * (such as pressing home) is performed.
+     * @hide
+     */
+    public static final int START_SWITCHES_CANCELED = 4;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: do special start mode where
+     * a new activity is launched only if it is needed.
+     * @hide
+     */
+    public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: launch the app for
+     * debugging.
+     * @hide
+     */
+    public static final int START_FLAG_DEBUG = 1<<1;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: launch the app for
+     * OpenGL tracing.
+     * @hide
+     */
+    public static final int START_FLAG_OPENGL_TRACES = 1<<2;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: if the app is being
+     * launched for profiling, automatically stop the profiler once done.
+     * @hide
+     */
+    public static final int START_FLAG_AUTO_STOP_PROFILER = 1<<3;
+
+    /**
+     * Result for IActivityManaqer.broadcastIntent: success!
+     * @hide
+     */
+    public static final int BROADCAST_SUCCESS = 0;
+
+    /**
+     * Result for IActivityManaqer.broadcastIntent: attempt to broadcast
+     * a sticky intent without appropriate permission.
+     * @hide
+     */
+    public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a sendBroadcast operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_BROADCAST = 1;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a startActivity operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_ACTIVITY = 2;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for an activity result operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a startService operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_SERVICE = 4;
+
     /*package*/ ActivityManager(Context context, Handler handler) {
         mContext = context;
         mHandler = handler;
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 7daaf7d..732d211 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -117,22 +117,18 @@
             IApplicationThread app = ApplicationThreadNative.asInterface(b);
             Intent intent = Intent.CREATOR.createFromParcel(data);
             String resolvedType = data.readString();
-            Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
-            int grantedMode = data.readInt();
             IBinder resultTo = data.readStrongBinder();
             String resultWho = data.readString();
             int requestCode = data.readInt();
-            boolean onlyIfNeeded = data.readInt() != 0;
-            boolean debug = data.readInt() != 0;
-            boolean openglTrace = data.readInt() != 0;
+            int startFlags = data.readInt();
             String profileFile = data.readString();
             ParcelFileDescriptor profileFd = data.readInt() != 0
                     ? data.readFileDescriptor() : null;
-            boolean autoStopProfiler = data.readInt() != 0;
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
             int result = startActivity(app, intent, resolvedType,
-                    grantedUriPermissions, grantedMode, resultTo, resultWho,
-                    requestCode, onlyIfNeeded, debug, openglTrace,
-                    profileFile, profileFd, autoStopProfiler);
+                    resultTo, resultWho, requestCode, startFlags,
+                    profileFile, profileFd, options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -145,22 +141,18 @@
             IApplicationThread app = ApplicationThreadNative.asInterface(b);
             Intent intent = Intent.CREATOR.createFromParcel(data);
             String resolvedType = data.readString();
-            Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
-            int grantedMode = data.readInt();
             IBinder resultTo = data.readStrongBinder();
             String resultWho = data.readString();
             int requestCode = data.readInt();
-            boolean onlyIfNeeded = data.readInt() != 0;
-            boolean debug = data.readInt() != 0;
-            boolean openglTrace = data.readInt() != 0;
+            int startFlags = data.readInt();
             String profileFile = data.readString();
             ParcelFileDescriptor profileFd = data.readInt() != 0
                     ? data.readFileDescriptor() : null;
-            boolean autoStopProfiler = data.readInt() != 0;
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
             WaitResult result = startActivityAndWait(app, intent, resolvedType,
-                    grantedUriPermissions, grantedMode, resultTo, resultWho,
-                    requestCode, onlyIfNeeded, debug, openglTrace,
-                    profileFile, profileFd, autoStopProfiler);
+                    resultTo, resultWho, requestCode, startFlags,
+                    profileFile, profileFd, options);
             reply.writeNoException();
             result.writeToParcel(reply, 0);
             return true;
@@ -173,17 +165,15 @@
             IApplicationThread app = ApplicationThreadNative.asInterface(b);
             Intent intent = Intent.CREATOR.createFromParcel(data);
             String resolvedType = data.readString();
-            Uri[] grantedUriPermissions = data.createTypedArray(Uri.CREATOR);
-            int grantedMode = data.readInt();
             IBinder resultTo = data.readStrongBinder();
             String resultWho = data.readString();    
             int requestCode = data.readInt();
-            boolean onlyIfNeeded = data.readInt() != 0;
-            boolean debug = data.readInt() != 0;
+            int startFlags = data.readInt();
             Configuration config = Configuration.CREATOR.createFromParcel(data);
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
             int result = startActivityWithConfig(app, intent, resolvedType,
-                    grantedUriPermissions, grantedMode, resultTo, resultWho,
-                    requestCode, onlyIfNeeded, debug, config);
+                    resultTo, resultWho, requestCode, startFlags, config, options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -205,9 +195,11 @@
             int requestCode = data.readInt();
             int flagsMask = data.readInt();
             int flagsValues = data.readInt();
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
             int result = startActivityIntentSender(app, intent,
                     fillInIntent, resolvedType, resultTo, resultWho,
-                    requestCode, flagsMask, flagsValues);
+                    requestCode, flagsMask, flagsValues, options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -218,7 +210,9 @@
             data.enforceInterface(IActivityManager.descriptor);
             IBinder callingActivity = data.readStrongBinder();
             Intent intent = Intent.CREATOR.createFromParcel(data);
-            boolean result = startNextMatchingActivity(callingActivity, intent);
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
+            boolean result = startNextMatchingActivity(callingActivity, intent, options);
             reply.writeNoException();
             reply.writeInt(result ? 1 : 0);
             return true;
@@ -1231,9 +1225,11 @@
             IBinder resultTo = data.readStrongBinder();
             String resultWho = data.readString();    
             int requestCode = data.readInt();
-            boolean onlyIfNeeded = data.readInt() != 0;
+            int startFlags = data.readInt();
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
             int result = startActivityInPackage(uid, intent, resolvedType,
-                    resultTo, resultWho, requestCode, onlyIfNeeded);
+                    resultTo, resultWho, requestCode, startFlags, options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -1412,7 +1408,10 @@
             Intent[] intents = data.createTypedArray(Intent.CREATOR);
             String[] resolvedTypes = data.createStringArray();
             IBinder resultTo = data.readStrongBinder();
-            int result = startActivitiesInPackage(uid, intents, resolvedTypes, resultTo);
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
+            int result = startActivitiesInPackage(uid, intents, resolvedTypes,
+                    resultTo, options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -1426,7 +1425,10 @@
             Intent[] intents = data.createTypedArray(Intent.CREATOR);
             String[] resolvedTypes = data.createStringArray();
             IBinder resultTo = data.readStrongBinder();
-            int result = startActivities(app, intents, resolvedTypes, resultTo);
+            Bundle options = data.readInt() != 0
+                    ? Bundle.CREATOR.createFromParcel(data) : null;
+            int result = startActivities(app, intents, resolvedTypes, resultTo,
+                    options);
             reply.writeNoException();
             reply.writeInt(result);
             return true;
@@ -1618,25 +1620,19 @@
     }
 
     public int startActivity(IApplicationThread caller, Intent intent,
-            String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
-            IBinder resultTo, String resultWho,
-            int requestCode, boolean onlyIfNeeded,
-            boolean debug, boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd,
-            boolean autoStopProfiler) throws RemoteException {
+            String resolvedType, IBinder resultTo, String resultWho, int requestCode,
+            int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(caller != null ? caller.asBinder() : null);
         intent.writeToParcel(data, 0);
         data.writeString(resolvedType);
-        data.writeTypedArray(grantedUriPermissions, 0);
-        data.writeInt(grantedMode);
         data.writeStrongBinder(resultTo);
         data.writeString(resultWho);
         data.writeInt(requestCode);
-        data.writeInt(onlyIfNeeded ? 1 : 0);
-        data.writeInt(debug ? 1 : 0);
-        data.writeInt(openglTrace ? 1 : 0);
+        data.writeInt(startFlags);
         data.writeString(profileFile);
         if (profileFd != null) {
             data.writeInt(1);
@@ -1644,7 +1640,12 @@
         } else {
             data.writeInt(0);
         }
-        data.writeInt(autoStopProfiler ? 1 : 0);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
@@ -1653,25 +1654,19 @@
         return result;
     }
     public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
-            String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
-            IBinder resultTo, String resultWho,
-            int requestCode, boolean onlyIfNeeded,
-            boolean debug, boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd,
-            boolean autoStopProfiler) throws RemoteException {
+            String resolvedType, IBinder resultTo, String resultWho,
+            int requestCode, int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(caller != null ? caller.asBinder() : null);
         intent.writeToParcel(data, 0);
         data.writeString(resolvedType);
-        data.writeTypedArray(grantedUriPermissions, 0);
-        data.writeInt(grantedMode);
         data.writeStrongBinder(resultTo);
         data.writeString(resultWho);
         data.writeInt(requestCode);
-        data.writeInt(onlyIfNeeded ? 1 : 0);
-        data.writeInt(debug ? 1 : 0);
-        data.writeInt(openglTrace ? 1 : 0);
+        data.writeInt(startFlags);
         data.writeString(profileFile);
         if (profileFd != null) {
             data.writeInt(1);
@@ -1679,7 +1674,12 @@
         } else {
             data.writeInt(0);
         }
-        data.writeInt(autoStopProfiler ? 1 : 0);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0);
         reply.readException();
         WaitResult result = WaitResult.CREATOR.createFromParcel(reply);
@@ -1688,24 +1688,26 @@
         return result;
     }
     public int startActivityWithConfig(IApplicationThread caller, Intent intent,
-            String resolvedType, Uri[] grantedUriPermissions, int grantedMode,
-            IBinder resultTo, String resultWho,
-            int requestCode, boolean onlyIfNeeded,
-            boolean debug, Configuration config) throws RemoteException {
+            String resolvedType, IBinder resultTo, String resultWho,
+            int requestCode, int startFlags, Configuration config,
+            Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(caller != null ? caller.asBinder() : null);
         intent.writeToParcel(data, 0);
         data.writeString(resolvedType);
-        data.writeTypedArray(grantedUriPermissions, 0);
-        data.writeInt(grantedMode);
         data.writeStrongBinder(resultTo);
         data.writeString(resultWho);
         data.writeInt(requestCode);
-        data.writeInt(onlyIfNeeded ? 1 : 0);
-        data.writeInt(debug ? 1 : 0);
+        data.writeInt(startFlags);
         config.writeToParcel(data, 0);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
@@ -1716,7 +1718,7 @@
     public int startActivityIntentSender(IApplicationThread caller,
             IntentSender intent, Intent fillInIntent, String resolvedType,
             IBinder resultTo, String resultWho, int requestCode,
-            int flagsMask, int flagsValues) throws RemoteException {
+            int flagsMask, int flagsValues, Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
@@ -1734,6 +1736,12 @@
         data.writeInt(requestCode);
         data.writeInt(flagsMask);
         data.writeInt(flagsValues);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITY_INTENT_SENDER_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
@@ -1742,12 +1750,18 @@
         return result;
     }
     public boolean startNextMatchingActivity(IBinder callingActivity,
-            Intent intent) throws RemoteException {
+            Intent intent, Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(callingActivity);
         intent.writeToParcel(data, 0);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_NEXT_MATCHING_ACTIVITY_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
@@ -3075,7 +3089,7 @@
     
     public int startActivityInPackage(int uid,
             Intent intent, String resolvedType, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded)
+            String resultWho, int requestCode, int startFlags, Bundle options)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -3086,7 +3100,13 @@
         data.writeStrongBinder(resultTo);
         data.writeString(resultWho);
         data.writeInt(requestCode);
-        data.writeInt(onlyIfNeeded ? 1 : 0);
+        data.writeInt(startFlags);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
@@ -3339,7 +3359,8 @@
     }
     
     public int startActivities(IApplicationThread caller,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException {
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3347,6 +3368,12 @@
         data.writeTypedArray(intents, 0);
         data.writeStringArray(resolvedTypes);
         data.writeStrongBinder(resultTo);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
@@ -3356,7 +3383,8 @@
     }
 
     public int startActivitiesInPackage(int uid,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException {
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
@@ -3364,6 +3392,12 @@
         data.writeTypedArray(intents, 0);
         data.writeStringArray(resolvedTypes);
         data.writeStrongBinder(resultTo);
+        if (options != null) {
+            data.writeInt(1);
+            options.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
         mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0);
         reply.readException();
         int result = reply.readInt();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2610d87..2a3e213 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1802,7 +1802,7 @@
         if (aInfo == null) {
             // Throw an exception.
             Instrumentation.checkStartActivityResult(
-                    IActivityManager.START_CLASS_NOT_FOUND, intent);
+                    ActivityManager.START_CLASS_NOT_FOUND, intent);
         }
         return aInfo;
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index e348b87..7043a73 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -852,6 +852,11 @@
 
     @Override
     public void startActivity(Intent intent) {
+        startActivity(intent, null);
+    }
+
+    @Override
+    public void startActivity(Intent intent, Bundle options) {
         if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
                     "Calling startActivity() from outside of an Activity "
@@ -860,11 +865,16 @@
         }
         mMainThread.getInstrumentation().execStartActivity(
             getOuterContext(), mMainThread.getApplicationThread(), null,
-            (Activity)null, intent, -1);
+            (Activity)null, intent, -1, options);
     }
 
     @Override
     public void startActivities(Intent[] intents) {
+        startActivities(intents, null);
+    }
+
+    @Override
+    public void startActivities(Intent[] intents, Bundle options) {
         if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
             throw new AndroidRuntimeException(
                     "Calling startActivities() from outside of an Activity "
@@ -873,13 +883,20 @@
         }
         mMainThread.getInstrumentation().execStartActivities(
             getOuterContext(), mMainThread.getApplicationThread(), null,
-            (Activity)null, intents);
+            (Activity)null, intents, options);
     }
 
     @Override
     public void startIntentSender(IntentSender intent,
             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
             throws IntentSender.SendIntentException {
+        startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, null);
+    }
+
+    @Override
+    public void startIntentSender(IntentSender intent, Intent fillInIntent,
+            int flagsMask, int flagsValues, int extraFlags, Bundle options)
+            throws IntentSender.SendIntentException {
         try {
             String resolvedType = null;
             if (fillInIntent != null) {
@@ -889,8 +906,8 @@
             int result = ActivityManagerNative.getDefault()
                 .startActivityIntentSender(mMainThread.getApplicationThread(), intent,
                         fillInIntent, resolvedType, null, null,
-                        0, flagsMask, flagsValues);
-            if (result == IActivityManager.START_CANCELED) {
+                        0, flagsMask, flagsValues, options);
+            if (result == ActivityManager.START_CANCELED) {
                 throw new IntentSender.SendIntentException();
             }
             Instrumentation.checkStartActivityResult(result, null);
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index 492fcc7..dadc4e5 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -961,27 +961,55 @@
         mLoaderManager = mActivity.getLoaderManager(mIndex, mLoadersStarted, true);
         return mLoaderManager;
     }
-    
+
     /**
      * Call {@link Activity#startActivity(Intent)} on the fragment's
      * containing Activity.
      */
     public void startActivity(Intent intent) {
+        startActivity(intent, null);
+    }
+    
+    /**
+     * Call {@link Activity#startActivity(Intent, Bundle)} on the fragment's
+     * containing Activity.
+     */
+    public void startActivity(Intent intent, Bundle options) {
         if (mActivity == null) {
             throw new IllegalStateException("Fragment " + this + " not attached to Activity");
         }
-        mActivity.startActivityFromFragment(this, intent, -1);
+        if (options != null) {
+            mActivity.startActivityFromFragment(this, intent, -1, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // applications that may have overridden the method.
+            mActivity.startActivityFromFragment(this, intent, -1);
+        }
     }
-    
+
     /**
      * Call {@link Activity#startActivityForResult(Intent, int)} on the fragment's
      * containing Activity.
      */
     public void startActivityForResult(Intent intent, int requestCode) {
+        startActivityForResult(intent, requestCode, null);
+    }
+
+    /**
+     * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} on the fragment's
+     * containing Activity.
+     */
+    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
         if (mActivity == null) {
             throw new IllegalStateException("Fragment " + this + " not attached to Activity");
         }
-        mActivity.startActivityFromFragment(this, intent, requestCode);
+        if (options != null) {
+            mActivity.startActivityFromFragment(this, intent, requestCode, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // applications that may have overridden the method.
+            mActivity.startActivityFromFragment(this, intent, requestCode, options);
+        }
     }
     
     /**
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index acebf58..9306bd2 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -50,57 +50,24 @@
  * {@hide}
  */
 public interface IActivityManager extends IInterface {
-    /**
-     * Returned by startActivity() if the start request was canceled because
-     * app switches are temporarily canceled to ensure the user's last request
-     * (such as pressing home) is performed.
-     */
-    public static final int START_SWITCHES_CANCELED = 4;
-    /**
-     * Returned by startActivity() if an activity wasn't really started, but
-     * the given Intent was given to the existing top activity.
-     */
-    public static final int START_DELIVERED_TO_TOP = 3;
-    /**
-     * Returned by startActivity() if an activity wasn't really started, but
-     * a task was simply brought to the foreground.
-     */
-    public static final int START_TASK_TO_FRONT = 2;
-    /**
-     * Returned by startActivity() if the caller asked that the Intent not
-     * be executed if it is the recipient, and that is indeed the case.
-     */
-    public static final int START_RETURN_INTENT_TO_CALLER = 1;
-    /**
-     * Activity was started successfully as normal.
-     */
-    public static final int START_SUCCESS = 0;
-    public static final int START_INTENT_NOT_RESOLVED = -1;
-    public static final int START_CLASS_NOT_FOUND = -2;
-    public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3;
-    public static final int START_PERMISSION_DENIED = -4;
-    public static final int START_NOT_ACTIVITY = -5;
-    public static final int START_CANCELED = -6;
     public int startActivity(IApplicationThread caller,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
-            boolean onlyIfNeeded, boolean debug, boolean openglTrace, String profileFile,
-            ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException;
+            Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+            int requestCode, int flags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
     public WaitResult startActivityAndWait(IApplicationThread caller,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
-            boolean onlyIfNeeded, boolean debug, boolean openglTrace, String profileFile,
-            ParcelFileDescriptor profileFd, boolean autoStopProfiler) throws RemoteException;
+            Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+            int requestCode, int flags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
     public int startActivityWithConfig(IApplicationThread caller,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo, String resultWho, int requestCode,
-            boolean onlyIfNeeded, boolean debug, Configuration newConfig) throws RemoteException;
+            Intent intent, String resolvedType, IBinder resultTo, String resultWho,
+            int requestCode, int startFlags, Configuration newConfig,
+            Bundle options) throws RemoteException;
     public int startActivityIntentSender(IApplicationThread caller,
             IntentSender intent, Intent fillInIntent, String resolvedType,
             IBinder resultTo, String resultWho, int requestCode,
-            int flagsMask, int flagsValues) throws RemoteException;
+            int flagsMask, int flagsValues, Bundle options) throws RemoteException;
     public boolean startNextMatchingActivity(IBinder callingActivity,
-            Intent intent) throws RemoteException;
+            Intent intent, Bundle options) throws RemoteException;
     public boolean finishActivity(IBinder token, int code, Intent data)
             throws RemoteException;
     public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException;
@@ -109,8 +76,6 @@
             IIntentReceiver receiver, IntentFilter filter,
             String requiredPermission) throws RemoteException;
     public void unregisterReceiver(IIntentReceiver receiver) throws RemoteException;
-    public static final int BROADCAST_SUCCESS = 0;
-    public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
     public int broadcastIntent(IApplicationThread caller, Intent intent,
             String resolvedType, IIntentReceiver resultTo, int resultCode,
             String resultData, Bundle map, String requiredPermission,
@@ -201,10 +166,6 @@
     public ComponentName getActivityClassForToken(IBinder token) throws RemoteException;
     public String getPackageForToken(IBinder token) throws RemoteException;
 
-    public static final int INTENT_SENDER_BROADCAST = 1;
-    public static final int INTENT_SENDER_ACTIVITY = 2;
-    public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
-    public static final int INTENT_SENDER_SERVICE = 4;
     public IIntentSender getIntentSender(int type,
             String packageName, IBinder token, String resultWho,
             int requestCode, Intent[] intents, String[] resolvedTypes,
@@ -302,7 +263,7 @@
     
     public int startActivityInPackage(int uid,
             Intent intent, String resolvedType, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded)
+            String resultWho, int requestCode, int startFlags, Bundle options)
             throws RemoteException;
 
     public void killApplicationWithUid(String pkg, int uid) throws RemoteException;
@@ -342,9 +303,11 @@
         ParcelFileDescriptor fd) throws RemoteException;
 
     public int startActivities(IApplicationThread caller,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options) throws RemoteException;
     public int startActivitiesInPackage(int uid,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException;
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options) throws RemoteException;
 
     public int getFrontActivityScreenCompatMode() throws RemoteException;
     public void setFrontActivityScreenCompatMode(int mode) throws RemoteException;
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index a34e1d3..16299de 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -1346,6 +1346,7 @@
      * @param intent The actual Intent to start.
      * @param requestCode Identifier for this request's result; less than zero 
      *                    if the caller is not expecting a result.
+     * @param options Addition options.
      * 
      * @return To force the return of a particular result, return an 
      *         ActivityResult object containing the desired data; otherwise
@@ -1361,7 +1362,7 @@
      */
     public ActivityResult execStartActivity(
             Context who, IBinder contextThread, IBinder token, Activity target,
-            Intent intent, int requestCode) {
+            Intent intent, int requestCode, Bundle options) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (mActivityMonitors != null) {
             synchronized (mSync) {
@@ -1383,8 +1384,8 @@
             int result = ActivityManagerNative.getDefault()
                 .startActivity(whoThread, intent,
                         intent.resolveTypeIfNeeded(who.getContentResolver()),
-                        null, 0, token, target != null ? target.mEmbeddedID : null,
-                        requestCode, false, false, false, null, null, false);
+                        token, target != null ? target.mEmbeddedID : null,
+                        requestCode, 0, null, null, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
         }
@@ -1400,7 +1401,7 @@
      * {@hide}
      */
     public void execStartActivities(Context who, IBinder contextThread,
-            IBinder token, Activity target, Intent[] intents) {
+            IBinder token, Activity target, Intent[] intents, Bundle options) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (mActivityMonitors != null) {
             synchronized (mSync) {
@@ -1424,7 +1425,7 @@
                 resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver());
             }
             int result = ActivityManagerNative.getDefault()
-                .startActivities(whoThread, intents, resolvedTypes, token);
+                .startActivities(whoThread, intents, resolvedTypes, token, options);
             checkStartActivityResult(result, intents[0]);
         } catch (RemoteException e) {
         }
@@ -1459,7 +1460,7 @@
      */
     public ActivityResult execStartActivity(
         Context who, IBinder contextThread, IBinder token, Fragment target,
-        Intent intent, int requestCode) {
+        Intent intent, int requestCode, Bundle options) {
         IApplicationThread whoThread = (IApplicationThread) contextThread;
         if (mActivityMonitors != null) {
             synchronized (mSync) {
@@ -1481,9 +1482,8 @@
             int result = ActivityManagerNative.getDefault()
                 .startActivity(whoThread, intent,
                         intent.resolveTypeIfNeeded(who.getContentResolver()),
-                        null, 0, token, target != null ? target.mWho : null,
-                        requestCode, false, false /* debug */, false /* openglTrace */,
-                        null, null, false);
+                        token, target != null ? target.mWho : null,
+                        requestCode, 0, null, null, options);
             checkStartActivityResult(result, intent);
         } catch (RemoteException e) {
         }
@@ -1502,13 +1502,13 @@
     }
 
     /*package*/ static void checkStartActivityResult(int res, Object intent) {
-        if (res >= IActivityManager.START_SUCCESS) {
+        if (res >= ActivityManager.START_SUCCESS) {
             return;
         }
         
         switch (res) {
-            case IActivityManager.START_INTENT_NOT_RESOLVED:
-            case IActivityManager.START_CLASS_NOT_FOUND:
+            case ActivityManager.START_INTENT_NOT_RESOLVED:
+            case ActivityManager.START_CLASS_NOT_FOUND:
                 if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                     throw new ActivityNotFoundException(
                             "Unable to find explicit activity class "
@@ -1516,13 +1516,13 @@
                             + "; have you declared this activity in your AndroidManifest.xml?");
                 throw new ActivityNotFoundException(
                         "No Activity found to handle " + intent);
-            case IActivityManager.START_PERMISSION_DENIED:
+            case ActivityManager.START_PERMISSION_DENIED:
                 throw new SecurityException("Not allowed to start activity "
                         + intent);
-            case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+            case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                 throw new AndroidRuntimeException(
                         "FORWARD_RESULT_FLAG used while also requesting a result");
-            case IActivityManager.START_NOT_ACTIVITY:
+            case ActivityManager.START_NOT_ACTIVITY:
                 throw new IllegalArgumentException(
                         "PendingIntent is not an activity");
             default:
diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java
index c95066c..57192c3 100644
--- a/core/java/android/app/PendingIntent.java
+++ b/core/java/android/app/PendingIntent.java
@@ -195,7 +195,7 @@
             intent.setAllowFds(false);
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
-                    IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+                    ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
                     null, null, requestCode, new Intent[] { intent },
                     resolvedType != null ? new String[] { resolvedType } : null, flags);
             return target != null ? new PendingIntent(target) : null;
@@ -256,7 +256,7 @@
         try {
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
-                    IActivityManager.INTENT_SENDER_ACTIVITY, packageName,
+                    ActivityManager.INTENT_SENDER_ACTIVITY, packageName,
                     null, null, requestCode, intents, resolvedTypes, flags);
             return target != null ? new PendingIntent(target) : null;
         } catch (RemoteException e) {
@@ -292,7 +292,7 @@
             intent.setAllowFds(false);
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
-                    IActivityManager.INTENT_SENDER_BROADCAST, packageName,
+                    ActivityManager.INTENT_SENDER_BROADCAST, packageName,
                     null, null, requestCode, new Intent[] { intent },
                     resolvedType != null ? new String[] { resolvedType } : null, flags);
             return target != null ? new PendingIntent(target) : null;
@@ -330,7 +330,7 @@
             intent.setAllowFds(false);
             IIntentSender target =
                 ActivityManagerNative.getDefault().getIntentSender(
-                    IActivityManager.INTENT_SENDER_SERVICE, packageName,
+                    ActivityManager.INTENT_SENDER_SERVICE, packageName,
                     null, null, requestCode, new Intent[] { intent },
                     resolvedType != null ? new String[] { resolvedType } : null, flags);
             return target != null ? new PendingIntent(target) : null;
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 0e9e256..19a5bc0 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -818,6 +818,19 @@
     public abstract void clearWallpaper() throws IOException;
 
     /**
+     * Same as {@link #startActivity(Intent, Bundle)} with no options
+     * specified.
+     *
+     * @param intent The description of the activity to start.
+     *
+     * @throws ActivityNotFoundException
+     *
+     * @see {@link #startActivity(Intent, Bundle)}
+     * @see PackageManager#resolveActivity
+     */
+    public abstract void startActivity(Intent intent);
+
+    /**
      * Launch a new activity.  You will not receive any information about when
      * the activity exits.
      *
@@ -832,12 +845,28 @@
      * if there was no Activity found to run the given Intent.
      *
      * @param intent The description of the activity to start.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      *
      * @throws ActivityNotFoundException
      *
+     * @see {@link #startActivity(Intent)}
      * @see PackageManager#resolveActivity
      */
-    public abstract void startActivity(Intent intent);
+    public abstract void startActivity(Intent intent, Bundle options);
+
+    /**
+     * Same as {@link #startActivities(Intent[], Bundle)} with no options
+     * specified.
+     *
+     * @param intents An array of Intents to be started.
+     *
+     * @throws ActivityNotFoundException
+     *
+     * @see {@link #startActivities(Intent[], Bundle)}
+     * @see PackageManager#resolveActivity
+     */
+    public abstract void startActivities(Intent[] intents);
 
     /**
      * Launch multiple new activities.  This is generally the same as calling
@@ -854,15 +883,38 @@
      * list may be on it, some not), so you probably want to avoid such situations.
      *
      * @param intents An array of Intents to be started.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
      *
      * @throws ActivityNotFoundException
      *
+     * @see {@link #startActivities(Intent[])}
      * @see PackageManager#resolveActivity
      */
-    public abstract void startActivities(Intent[] intents);
+    public abstract void startActivities(Intent[] intents, Bundle options);
 
     /**
-     * Like {@link #startActivity(Intent)}, but taking a IntentSender
+     * Same as {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+     * with no options specified.
+     *
+     * @param intent The IntentSender to launch.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     *
+     * @see #startActivity(Intent)
+     * @see #startIntentSender(IntentSender, Intent, int, int, int, Bundle)
+     */
+    public abstract void startIntentSender(IntentSender intent,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+            throws IntentSender.SendIntentException;
+
+    /**
+     * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
      * to start.  If the IntentSender is for an activity, that activity will be started
      * as if you had called the regular {@link #startActivity(Intent)}
      * here; otherwise, its associated action will be executed (such as
@@ -877,10 +929,15 @@
      * @param flagsValues Desired values for any bits set in
      * <var>flagsMask</var>
      * @param extraFlags Always set to 0.
+     * @param options Additional options for how the Activity should be started.
+     * May be null if there are no options.
+     *
+     * @see #startActivity(Intent, Bundle)
+     * @see #startIntentSender(IntentSender, Intent, int, int, int)
      */
     public abstract void startIntentSender(IntentSender intent,
-            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
-            throws IntentSender.SendIntentException;
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException;
 
     /**
      * Broadcast the given intent to all interested BroadcastReceivers.  This
diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java
index 5ba9dcc..6b950e0 100644
--- a/core/java/android/content/ContextWrapper.java
+++ b/core/java/android/content/ContextWrapper.java
@@ -277,17 +277,35 @@
     }
 
     @Override
+    public void startActivity(Intent intent, Bundle options) {
+        mBase.startActivity(intent, options);
+    }
+
+    @Override
     public void startActivities(Intent[] intents) {
         mBase.startActivities(intents);
     }
 
     @Override
+    public void startActivities(Intent[] intents, Bundle options) {
+        mBase.startActivities(intents, options);
+    }
+
+    @Override
     public void startIntentSender(IntentSender intent,
             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
             throws IntentSender.SendIntentException {
         mBase.startIntentSender(intent, fillInIntent, flagsMask,
                 flagsValues, extraFlags);
     }
+
+    @Override
+    public void startIntentSender(IntentSender intent,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        mBase.startIntentSender(intent, fillInIntent, flagsMask,
+                flagsValues, extraFlags, options);
+    }
     
     @Override
     public void sendBroadcast(Intent intent) {
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 0e6d07d..9612151 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -596,8 +596,8 @@
              * values:</p>
              *
              * <ul>
-             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
-             *   that make up the message.</li>
+             *   <li><em>message</em> - An SmsCbMessage object containing the broadcast message
+             *   data. This is not an emergency alert, so ETWS and CMAS data will be null.</li>
              * </ul>
              *
              * <p>The extra values can be extracted using
@@ -616,8 +616,8 @@
              * values:</p>
              *
              * <ul>
-             *   <li><em>pdus</em> - An Object[] of byte[]s containing the PDUs
-             *   that make up the message.</li>
+             *   <li><em>message</em> - An SmsCbMessage object containing the broadcast message
+             *   data, including ETWS or CMAS warning notification info if present.</li>
              * </ul>
              *
              * <p>The extra values can be extracted using
@@ -631,6 +631,26 @@
                     "android.provider.Telephony.SMS_EMERGENCY_CB_RECEIVED";
 
             /**
+             * Broadcast Action: A new CDMA SMS has been received containing Service Category
+             * Program Data (updates the list of enabled broadcast channels). The intent will
+             * have the following extra values:</p>
+             *
+             * <ul>
+             *   <li><em>operations</em> - An array of CdmaSmsCbProgramData objects containing
+             *   the service category operations (add/delete/clear) to perform.</li>
+             * </ul>
+             *
+             * <p>The extra values can be extracted using
+             * {@link #getMessagesFromIntent(Intent)}.</p>
+             *
+             * <p>If a BroadcastReceiver encounters an error while processing
+             * this intent it should set the result code appropriately.</p>
+             */
+            @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+            public static final String SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION =
+                    "android.provider.Telephony.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED";
+
+            /**
              * Broadcast Action: The SIM storage for SMS messages is full.  If
              * space is not freed, messages targeted for the SIM (class 2) may
              * not be saved.
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index fac549d..730ad08 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -112,6 +112,18 @@
         }
     };
 
+    MediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
+        new MediaPlayer.OnVideoSizeChangedListener() {
+            @Override
+            public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+                mVideoWidth = mp.getVideoWidth();
+                mVideoHeight = mp.getVideoHeight();
+                if (mVideoWidth != 0 && mVideoHeight != 0) {
+                    mVideoSurfaceView.getHolder().setFixedSize(mVideoWidth, mVideoHeight);
+                }
+            }
+    };
+
     private SurfaceView getSurfaceView() {
         return mVideoSurfaceView;
     }
@@ -150,6 +162,7 @@
         mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
         setMediaController(mc);
         mPlayer.setScreenOnWhilePlaying(true);
+        mPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
         prepareDataAndDisplayMode(mProxy);
     }
 
diff --git a/core/java/com/android/internal/util/BitwiseOutputStream.java b/core/java/com/android/internal/util/BitwiseOutputStream.java
index 70c0be8..ddecbed 100644
--- a/core/java/com/android/internal/util/BitwiseOutputStream.java
+++ b/core/java/com/android/internal/util/BitwiseOutputStream.java
@@ -77,6 +77,7 @@
         byte[] newBuf = new byte[(mPos + bits) >>> 2];
         System.arraycopy(mBuf, 0, newBuf, 0, mEnd >>> 3);
         mBuf = newBuf;
+        mEnd = newBuf.length << 3;
     }
 
     /**
diff --git a/core/jni/android_media_AudioRecord.cpp b/core/jni/android_media_AudioRecord.cpp
index 7984b9c..480c3a6 100644
--- a/core/jni/android_media_AudioRecord.cpp
+++ b/core/jni/android_media_AudioRecord.cpp
@@ -23,13 +23,13 @@
 #include <fcntl.h>
 #include <math.h>
 
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
 
-#include "utils/Log.h"
-#include "media/AudioRecord.h"
-#include "media/mediarecorder.h"
+#include <utils/Log.h>
+#include <media/AudioRecord.h>
+#include <media/mediarecorder.h>
 
 #include <cutils/bitops.h>
 
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index bff5994..f522a9a 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -16,16 +16,16 @@
 */
 
 #define LOG_TAG "AudioSystem"
-#include "utils/Log.h"
+#include <utils/Log.h>
 
 #include <stdio.h>
 #include <unistd.h>
 #include <fcntl.h>
 #include <math.h>
 
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
 
 #include <media/AudioSystem.h>
 
diff --git a/core/jni/android_media_AudioTrack.cpp b/core/jni/android_media_AudioTrack.cpp
index d4ed06c..9fbc477 100644
--- a/core/jni/android_media_AudioTrack.cpp
+++ b/core/jni/android_media_AudioTrack.cpp
@@ -22,13 +22,13 @@
 #include <fcntl.h>
 #include <math.h>
 
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
 
-#include "utils/Log.h"
-#include "media/AudioSystem.h"
-#include "media/AudioTrack.h"
+#include <utils/Log.h>
+#include <media/AudioSystem.h>
+#include <media/AudioTrack.h>
 
 #include <binder/MemoryHeapBase.h>
 #include <binder/MemoryBase.h>
diff --git a/core/jni/android_media_JetPlayer.cpp b/core/jni/android_media_JetPlayer.cpp
index 6fedc6b..a785f52 100644
--- a/core/jni/android_media_JetPlayer.cpp
+++ b/core/jni/android_media_JetPlayer.cpp
@@ -22,12 +22,12 @@
 #include <unistd.h>
 #include <fcntl.h>
 
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
 
-#include "utils/Log.h"
-#include "media/JetPlayer.h"
+#include <utils/Log.h>
+#include <media/JetPlayer.h>
 
 
 using namespace android;
diff --git a/core/jni/android_media_ToneGenerator.cpp b/core/jni/android_media_ToneGenerator.cpp
index 544b4c0..31151a6 100644
--- a/core/jni/android_media_ToneGenerator.cpp
+++ b/core/jni/android_media_ToneGenerator.cpp
@@ -21,13 +21,13 @@
 #include <unistd.h>
 #include <fcntl.h>
 
-#include "jni.h"
-#include "JNIHelp.h"
-#include "android_runtime/AndroidRuntime.h"
+#include <jni.h>
+#include <JNIHelp.h>
+#include <android_runtime/AndroidRuntime.h>
 
-#include "utils/Log.h"
-#include "media/AudioSystem.h"
-#include "media/ToneGenerator.h"
+#include <utils/Log.h>
+#include <media/AudioSystem.h>
+#include <media/ToneGenerator.h>
 
 // ----------------------------------------------------------------------------
 
diff --git a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java
index a304b68..306f58f 100644
--- a/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java
+++ b/core/tests/coretests/src/com/android/internal/util/BitwiseStreamsTest.java
@@ -133,4 +133,25 @@
         long end = android.os.SystemClock.elapsedRealtime();
         Log.d(LOG_TAG, "repeated encode-decode took " + (end - start) + " ms");
     }
+
+    @SmallTest
+    public void testExpandArray() throws Exception {
+        Random random = new Random();
+        int iterations = 10000;
+        int[] sizeArr = new int[iterations];
+        int[] valueArr = new int[iterations];
+        BitwiseOutputStream outStream = new BitwiseOutputStream(8);
+        for (int i = 0; i < iterations; i++) {
+            int x = random.nextInt();
+            int size = (x & 0x07) + 1;
+            int value = x & (-1 >>> (32 - size));
+            sizeArr[i] = size;
+            valueArr[i] = value;
+            outStream.write(size, value);
+        }
+        BitwiseInputStream inStream = new BitwiseInputStream(outStream.toByteArray());
+        for (int i = 0; i < iterations; i++) {
+            assertEquals(valueArr[i], inStream.read(sizeArr[i]));
+        }
+    }
 }
diff --git a/include/media/JetPlayer.h b/include/media/JetPlayer.h
index 9f6ff4c..38a3e44 100644
--- a/include/media/JetPlayer.h
+++ b/include/media/JetPlayer.h
@@ -22,7 +22,7 @@
 
 #include <libsonivox/jet.h>
 #include <libsonivox/eas_types.h>
-#include "AudioTrack.h"
+#include <media/AudioTrack.h>
 
 
 namespace android {
diff --git a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
index 3714283..ca93ce5 100644
--- a/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
+++ b/media/libeffects/lvm/wrapper/Bundle/EffectBundle.cpp
@@ -24,7 +24,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <new>
-#include <EffectBundle.h>
+#include "EffectBundle.h"
 
 
 // effect_handle_t interface implementation for bass boost
diff --git a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
index 358357e..9599dcc 100755
--- a/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
+++ b/media/libeffects/lvm/wrapper/Reverb/EffectReverb.cpp
@@ -24,8 +24,9 @@
 #include <stdlib.h>
 #include <string.h>
 #include <new>
-#include <EffectReverb.h>
-#include <LVREV.h>
+#include "EffectReverb.h"
+// from Reverb/lib
+#include "LVREV.h"
 
 // effect_handle_t interface implementation for reverb
 extern "C" const struct effect_interface_s gReverbInterface;
diff --git a/media/mediaserver/Android.mk b/media/mediaserver/Android.mk
index 0559812..4e9b4cf 100644
--- a/media/mediaserver/Android.mk
+++ b/media/mediaserver/Android.mk
@@ -11,12 +11,12 @@
 	libutils \
 	libbinder
 
-base := $(LOCAL_PATH)/../..
-
+# FIXME The duplicate audioflinger is temporary
 LOCAL_C_INCLUDES := \
-    $(base)/services/audioflinger \
-    $(base)/services/camera/libcameraservice \
-    $(base)/media/libmediaplayerservice
+    frameworks/native/services/audioflinger \
+    frameworks/base/services/audioflinger \
+    frameworks/base/services/camera/libcameraservice \
+    frameworks/base/media/libmediaplayerservice
 
 LOCAL_MODULE:= mediaserver
 
diff --git a/media/mediaserver/main_mediaserver.cpp b/media/mediaserver/main_mediaserver.cpp
index 1520c01..6b1abb1 100644
--- a/media/mediaserver/main_mediaserver.cpp
+++ b/media/mediaserver/main_mediaserver.cpp
@@ -16,16 +16,18 @@
 */
 
 #define LOG_TAG "mediaserver"
+//#define LOG_NDEBUG 0
 
 #include <binder/IPCThreadState.h>
 #include <binder/ProcessState.h>
 #include <binder/IServiceManager.h>
 #include <utils/Log.h>
 
-#include <AudioFlinger.h>
-#include <CameraService.h>
-#include <MediaPlayerService.h>
-#include <AudioPolicyService.h>
+// from LOCAL_C_INCLUDES
+#include "AudioFlinger.h"
+#include "CameraService.h"
+#include "MediaPlayerService.h"
+#include "AudioPolicyService.h"
 
 using namespace android;
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index e0a0d2d..a1f7316 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -17,8 +17,8 @@
 package com.android.internal.policy.impl;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
-import android.app.IActivityManager;
 import android.app.IUiModeManager;
 import android.app.ProgressDialog;
 import android.app.UiModeManager;
@@ -3832,10 +3832,10 @@
                         int result = ActivityManagerNative.getDefault()
                                 .startActivity(null, dock,
                                         dock.resolveTypeIfNeeded(mContext.getContentResolver()),
-                                        null, 0, null, null, 0, true /* onlyIfNeeded*/,
-                                        false /* debug */, false /* openglTrace */,
-                                        null, null, false);
-                        if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) {
+                                        null, null, 0,
+                                        ActivityManager.START_FLAG_ONLY_IF_NEEDED,
+                                        null, null, null);
+                        if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
                             return false;
                         }
                     }
@@ -3843,10 +3843,10 @@
                 int result = ActivityManagerNative.getDefault()
                         .startActivity(null, mHomeIntent,
                                 mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                                null, 0, null, null, 0, true /* onlyIfNeeded*/,
-                                false /* debug */, false /* openglTrace */,
-                                null, null, false);
-                if (result == IActivityManager.START_RETURN_INTENT_TO_CALLER) {
+                                null, null, 0,
+                                ActivityManager.START_FLAG_ONLY_IF_NEEDED,
+                                null, null, null);
+                if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
                     return false;
                 }
             } catch (RemoteException ex) {
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index eeecc4c..6802bab 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -37,8 +37,12 @@
 #include <cutils/properties.h>
 #include <cutils/compiler.h>
 
+#undef ADD_BATTERY_DATA
+
+#ifdef ADD_BATTERY_DATA
 #include <media/IMediaPlayerService.h>
 #include <media/IMediaDeathNotifier.h>
+#endif
 
 #include <private/media/AudioTrackShared.h>
 #include <private/media/AudioEffectShared.h>
@@ -105,6 +109,7 @@
 
 // ----------------------------------------------------------------------------
 
+#ifdef ADD_BATTERY_DATA
 // To collect the amplifier usage
 static void addBatteryData(uint32_t params) {
     sp<IMediaPlayerService> service = IMediaDeathNotifier::getMediaPlayerService();
@@ -115,6 +120,7 @@
 
     service->addBatteryData(params);
 }
+#endif
 
 static int load_audio_interface(const char *if_name, const hw_module_t **mod,
                                 audio_hw_device_t **dev)
@@ -2616,6 +2622,7 @@
             }
         }
         if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) {
+#ifdef ADD_BATTERY_DATA
             // when changing the audio output device, call addBatteryData to notify
             // the change
             if ((int)mDevice != value) {
@@ -2636,6 +2643,7 @@
                     addBatteryData(params);
                 }
             }
+#endif
 
             // forward device change to effects that have requested to be
             // aware of attached audio device.
@@ -3458,8 +3466,10 @@
                 if (mState == ACTIVE || mState == RESUMING) {
                     AudioSystem::stopOutput(thread->id(), mStreamType, mSessionId);
 
+#ifdef ADD_BATTERY_DATA
                     // to track the speaker usage
                     addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop);
+#endif
                 }
                 AudioSystem::releaseOutput(thread->id());
             }
@@ -3576,10 +3586,12 @@
             status = AudioSystem::startOutput(thread->id(), mStreamType, mSessionId);
             thread->mLock.lock();
 
+#ifdef ADD_BATTERY_DATA
             // to track the speaker usage
             if (status == NO_ERROR) {
                 addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStart);
             }
+#endif
         }
         if (status == NO_ERROR) {
             PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
@@ -3614,8 +3626,10 @@
             AudioSystem::stopOutput(thread->id(), mStreamType, mSessionId);
             thread->mLock.lock();
 
+#ifdef ADD_BATTERY_DATA
             // to track the speaker usage
             addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop);
+#endif
         }
     }
 }
@@ -3634,8 +3648,10 @@
                 AudioSystem::stopOutput(thread->id(), mStreamType, mSessionId);
                 thread->mLock.lock();
 
+#ifdef ADD_BATTERY_DATA
                 // to track the speaker usage
                 addBatteryData(IMediaPlayerService::kBatteryDataAudioFlingerStop);
+#endif
             }
         }
     }
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index c5c2901..84daead 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -189,8 +189,8 @@
                     }
                     try {
                         ActivityManagerNative.getDefault().startActivityWithConfig(
-                                null, homeIntent, null, null, 0, null, null, 0, false, false,
-                                newConfig);
+                                null, homeIntent, null, null, null, 0, 0,
+                                newConfig, null);
                         mHoldingConfiguration = false;
                     } catch (RemoteException e) {
                         Slog.w(TAG, e.getCause());
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 94b9e91..6f89f6e 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -286,9 +286,7 @@
     static class PendingActivityLaunch {
         ActivityRecord r;
         ActivityRecord sourceRecord;
-        Uri[] grantedUriPermissions;
-        int grantedMode;
-        boolean onlyIfNeeded;
+        int startFlags;
     }
     
     final ArrayList<PendingActivityLaunch> mPendingActivityLaunches
@@ -2095,8 +2093,8 @@
                     aInfo.applicationInfo.uid);
             if (app == null || app.instrumentationClass == null) {
                 intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
-                mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo,
-                        null, null, 0, 0, 0, false, false, null);
+                mMainStack.startActivityLocked(null, intent, null, aInfo,
+                        null, null, 0, 0, 0, 0, null, false, null);
             }
         }
 
@@ -2150,8 +2148,8 @@
                     intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                     intent.setComponent(new ComponentName(
                             ri.activityInfo.packageName, ri.activityInfo.name));
-                    mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo,
-                            null, null, 0, 0, 0, false, false, null);
+                    mMainStack.startActivityLocked(null, intent, null, ri.activityInfo,
+                            null, null, 0, 0, 0, 0, null, false, null);
                 }
             }
         }
@@ -2257,18 +2255,15 @@
         for (int i=0; i<N; i++) {
             PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
             mMainStack.startActivityUncheckedLocked(pal.r, pal.sourceRecord,
-                    pal.grantedUriPermissions, pal.grantedMode, pal.onlyIfNeeded,
-                    doResume && i == (N-1));
+                    pal.startFlags, doResume && i == (N-1));
         }
         mPendingActivityLaunches.clear();
     }
 
     public final int startActivity(IApplicationThread caller,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug,
-            boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd,
-            boolean autoStopProfiler) {
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags,
+            String profileFile, ParcelFileDescriptor profileFd, Bundle options) {
         enforceNotIsolatedCaller("startActivity");
         int userId = 0;
         if (intent.getCategories() != null && intent.getCategories().contains(Intent.CATEGORY_HOME)) {
@@ -2285,43 +2280,38 @@
             }
         }
         return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
-                grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded,
-                debug, openglTrace, profileFile, profileFd, autoStopProfiler, null, null, userId);
+                resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+                null, null, options, userId);
     }
 
     public final WaitResult startActivityAndWait(IApplicationThread caller,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug,
-            boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd,
-            boolean autoStopProfiler) {
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, Bundle options) {
         enforceNotIsolatedCaller("startActivityAndWait");
         WaitResult res = new WaitResult();
         int userId = Binder.getOrigCallingUser();
         mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
-                grantedUriPermissions, grantedMode, resultTo, resultWho,
-                requestCode, onlyIfNeeded, debug, openglTrace, profileFile, profileFd, autoStopProfiler,
-                res, null, userId);
+                resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
+                res, null, options, userId);
         return res;
     }
 
     public final int startActivityWithConfig(IApplicationThread caller,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded,
-            boolean debug, Configuration config) {
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, Configuration config,
+            Bundle options) {
         enforceNotIsolatedCaller("startActivityWithConfig");
         int ret = mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
-                grantedUriPermissions, grantedMode, resultTo, resultWho,
-                requestCode, onlyIfNeeded,
-                debug, false, null, null, false, null, config, Binder.getOrigCallingUser());
+                resultTo, resultWho, requestCode, startFlags,
+                null, null, null, config, options, Binder.getOrigCallingUser());
         return ret;
     }
 
     public int startActivityIntentSender(IApplicationThread caller,
             IntentSender intent, Intent fillInIntent, String resolvedType,
             IBinder resultTo, String resultWho, int requestCode,
-            int flagsMask, int flagsValues) {
+            int flagsMask, int flagsValues, Bundle options) {
         enforceNotIsolatedCaller("startActivityIntentSender");
         // Refuse possible leaked file descriptors
         if (fillInIntent != null && fillInIntent.hasFileDescriptors()) {
@@ -2344,13 +2334,13 @@
                 mAppSwitchesAllowedTime = 0;
             }
         }
-        int ret = pir.sendInner(0, fillInIntent, resolvedType, null,
-                null, resultTo, resultWho, requestCode, flagsMask, flagsValues);
+        int ret = pir.sendInner(0, fillInIntent, resolvedType, null, null,
+                resultTo, resultWho, requestCode, flagsMask, flagsValues, options);
         return ret;
     }
     
     public boolean startNextMatchingActivity(IBinder callingActivity,
-            Intent intent) {
+            Intent intent, Bundle options) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors() == true) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -2430,13 +2420,13 @@
             // XXX we are not dealing with propagating grantedUriPermissions...
             // those are not yet exposed to user code, so there is no need.
             int res = mMainStack.startActivityLocked(r.app.thread, intent,
-                    r.resolvedType, null, 0, aInfo,
-                    resultTo != null ? resultTo.appToken : null, resultWho,
-                    requestCode, -1, r.launchedFromUid, false, false, null);
+                    r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null,
+                    resultWho, requestCode, -1, r.launchedFromUid, 0,
+                    options, false, null);
             Binder.restoreCallingIdentity(origId);
 
             r.finishing = wasFinishing;
-            if (res != START_SUCCESS) {
+            if (res != ActivityManager.START_SUCCESS) {
                 return false;
             }
             return true;
@@ -2445,7 +2435,7 @@
 
     public final int startActivityInPackage(int uid,
             Intent intent, String resolvedType, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded) {
+            String resultWho, int requestCode, int startFlags, Bundle options) {
         
         // This is so super not safe, that only the system (or okay root)
         // can do it.
@@ -2457,21 +2447,22 @@
         }
 
         int ret = mMainStack.startActivityMayWait(null, uid, intent, resolvedType,
-                null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, false,
-                null, null, false, null, null, userId);
+                resultTo, resultWho, requestCode, startFlags,
+                null, null, null, null, options, userId);
         return ret;
     }
 
     public final int startActivities(IApplicationThread caller,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo) {
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options) {
         enforceNotIsolatedCaller("startActivities");
         int ret = mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo,
-                Binder.getOrigCallingUser());
+                options, Binder.getOrigCallingUser());
         return ret;
     }
 
     public final int startActivitiesInPackage(int uid,
-            Intent[] intents, String[] resolvedTypes, IBinder resultTo) {
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options) {
 
         // This is so super not safe, that only the system (or okay root)
         // can do it.
@@ -2481,7 +2472,7 @@
                     "startActivityInPackage only available to the system");
         }
         int ret = mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo,
-                UserId.getUserId(uid));
+                options, UserId.getUserId(uid));
         return ret;
     }
 
@@ -4193,7 +4184,7 @@
                     if (intent.hasFileDescriptors()) {
                         throw new IllegalArgumentException("File descriptors passed in Intent");
                     }
-                    if (type == INTENT_SENDER_BROADCAST &&
+                    if (type == ActivityManager.INTENT_SENDER_BROADCAST &&
                             (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) {
                         throw new IllegalArgumentException(
                                 "Can't use FLAG_RECEIVER_BOOT_UPGRADE here");
@@ -4242,7 +4233,7 @@
         if (DEBUG_MU)
             Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
         ActivityRecord activity = null;
-        if (type == INTENT_SENDER_ACTIVITY_RESULT) {
+        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
             activity = mMainStack.isInStackLocked(token);
             if (activity == null) {
                 return null;
@@ -4289,7 +4280,7 @@
         }
         rec = new PendingIntentRecord(this, key, callingUid);
         mIntentSenderRecords.put(key, rec.ref);
-        if (type == INTENT_SENDER_ACTIVITY_RESULT) {
+        if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) {
             if (activity.pendingResults == null) {
                 activity.pendingResults
                         = new HashSet<WeakReference<PendingIntentRecord>>();
@@ -12454,7 +12445,7 @@
                 }
             } catch (RemoteException e) {
                 Slog.w(TAG, "Remote exception", e);
-                return BROADCAST_SUCCESS;
+                return ActivityManager.BROADCAST_SUCCESS;
             }
         }
         
@@ -12472,7 +12463,7 @@
             if (requiredPermission != null) {
                 Slog.w(TAG, "Can't broadcast sticky intent " + intent
                         + " and enforce permission " + requiredPermission);
-                return BROADCAST_STICKY_CANT_HAVE_PERMISSION;
+                return ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION;
             }
             if (intent.getComponent() != null) {
                 throw new SecurityException(
@@ -12646,7 +12637,7 @@
             }
         }
 
-        return BROADCAST_SUCCESS;
+        return ActivityManager.BROADCAST_SUCCESS;
     }
 
     final Intent verifyBroadcastLocked(Intent intent) {
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 64d52ed..edebbac 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -25,15 +25,6 @@
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.IThumbnailRetriever;
-import static android.app.IActivityManager.START_CLASS_NOT_FOUND;
-import static android.app.IActivityManager.START_DELIVERED_TO_TOP;
-import static android.app.IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
-import static android.app.IActivityManager.START_INTENT_NOT_RESOLVED;
-import static android.app.IActivityManager.START_PERMISSION_DENIED;
-import static android.app.IActivityManager.START_RETURN_INTENT_TO_CALLER;
-import static android.app.IActivityManager.START_SUCCESS;
-import static android.app.IActivityManager.START_SWITCHES_CANCELED;
-import static android.app.IActivityManager.START_TASK_TO_FRONT;
 import android.app.IApplicationThread;
 import android.app.PendingIntent;
 import android.app.ResultInfo;
@@ -2270,14 +2261,12 @@
     }
 
     final int startActivityLocked(IApplicationThread caller,
-            Intent intent, String resolvedType,
-            Uri[] grantedUriPermissions,
-            int grantedMode, ActivityInfo aInfo, IBinder resultTo,
+            Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo,
             String resultWho, int requestCode,
-            int callingPid, int callingUid, boolean onlyIfNeeded,
+            int callingPid, int callingUid, int startFlags, Bundle options,
             boolean componentSpecified, ActivityRecord[] outActivity) {
 
-        int err = START_SUCCESS;
+        int err = ActivityManager.START_SUCCESS;
 
         ProcessRecord callerApp = null;
         if (caller != null) {
@@ -2289,11 +2278,11 @@
                 Slog.w(TAG, "Unable to find app for caller " + caller
                       + " (pid=" + callingPid + ") when starting: "
                       + intent.toString());
-                err = START_PERMISSION_DENIED;
+                err = ActivityManager.START_PERMISSION_DENIED;
             }
         }
 
-        if (err == START_SUCCESS) {
+        if (err == ActivityManager.START_SUCCESS) {
             Slog.i(TAG, "START {" + intent.toShortString(true, true, true, false)
                     + "} from pid " + (callerApp != null ? callerApp.pid : callingPid));
         }
@@ -2319,7 +2308,7 @@
             // Transfer the result target from the source activity to the new
             // one being started, including any failures.
             if (requestCode >= 0) {
-                return START_FORWARD_AND_REQUEST_CONFLICT;
+                return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
             }
             resultRecord = sourceRecord.resultTo;
             resultWho = sourceRecord.resultWho;
@@ -2331,19 +2320,19 @@
             }
         }
 
-        if (err == START_SUCCESS && intent.getComponent() == null) {
+        if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) {
             // We couldn't find a class that can handle the given Intent.
             // That's the end of that!
-            err = START_INTENT_NOT_RESOLVED;
+            err = ActivityManager.START_INTENT_NOT_RESOLVED;
         }
 
-        if (err == START_SUCCESS && aInfo == null) {
+        if (err == ActivityManager.START_SUCCESS && aInfo == null) {
             // We couldn't find the specific class specified in the Intent.
             // Also the end of the line.
-            err = START_CLASS_NOT_FOUND;
+            err = ActivityManager.START_CLASS_NOT_FOUND;
         }
 
-        if (err != START_SUCCESS) {
+        if (err != ActivityManager.START_SUCCESS) {
             if (resultRecord != null) {
                 sendActivityResultLocked(-1,
                     resultRecord, resultWho, requestCode,
@@ -2400,7 +2389,7 @@
                     // We pretend to the caller that it was really started, but
                     // they will just get a cancel result.
                     mDismissKeyguardOnNextActivity = false;
-                    return START_SUCCESS;
+                    return ActivityManager.START_SUCCESS;
                 }
             }
         }
@@ -2419,12 +2408,10 @@
                     PendingActivityLaunch pal = new PendingActivityLaunch();
                     pal.r = r;
                     pal.sourceRecord = sourceRecord;
-                    pal.grantedUriPermissions = grantedUriPermissions;
-                    pal.grantedMode = grantedMode;
-                    pal.onlyIfNeeded = onlyIfNeeded;
+                    pal.startFlags = startFlags;
                     mService.mPendingActivityLaunches.add(pal);
                     mDismissKeyguardOnNextActivity = false;
-                    return START_SWITCHES_CANCELED;
+                    return ActivityManager.START_SWITCHES_CANCELED;
                 }
             }
         
@@ -2443,7 +2430,7 @@
         }
         
         err = startActivityUncheckedLocked(r, sourceRecord,
-                grantedUriPermissions, grantedMode, onlyIfNeeded, true);
+                startFlags, true);
         if (mDismissKeyguardOnNextActivity && mPausingActivity == null) {
             // Someone asked to have the keyguard dismissed on the next
             // activity start, but we are not actually doing an activity
@@ -2466,8 +2453,7 @@
     }
 
     final int startActivityUncheckedLocked(ActivityRecord r,
-            ActivityRecord sourceRecord, Uri[] grantedUriPermissions,
-            int grantedMode, boolean onlyIfNeeded, boolean doResume) {
+            ActivityRecord sourceRecord, int startFlags, boolean doResume) {
         final Intent intent = r.intent;
         final int callingUid = r.launchedFromUid;
         final int userId = r.userId;
@@ -2494,14 +2480,14 @@
         // being launched is the same as the one making the call...  or, as
         // a special case, if we do not know the caller then we count the
         // current top activity as the caller.
-        if (onlyIfNeeded) {
+        if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
             ActivityRecord checkedCaller = sourceRecord;
             if (checkedCaller == null) {
                 checkedCaller = topRunningNonDelayedActivityLocked(notTop);
             }
             if (!checkedCaller.realActivity.equals(r.realActivity)) {
                 // Caller is not the same as launcher, so always needed.
-                onlyIfNeeded = false;
+                startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED;
             }
         }
 
@@ -2586,7 +2572,7 @@
                     if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
                         taskTop = resetTaskIfNeededLocked(taskTop, r);
                     }
-                    if (onlyIfNeeded) {
+                    if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED)  != 0) {
                         // We don't need to start a new activity, and
                         // the client said not to do anything if that
                         // is the case, so this is it!  And for paranoia, make
@@ -2594,7 +2580,7 @@
                         if (doResume) {
                             resumeTopActivityLocked(null);
                         }
-                        return START_RETURN_INTENT_TO_CALLER;
+                        return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                     }
                     if ((launchFlags &
                             (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK))
@@ -2681,7 +2667,7 @@
                         if (doResume) {
                             resumeTopActivityLocked(null);
                         }
-                        return START_TASK_TO_FRONT;
+                        return ActivityManager.START_TASK_TO_FRONT;
                     }
                 }
             }
@@ -2710,14 +2696,14 @@
                             if (doResume) {
                                 resumeTopActivityLocked(null);
                             }
-                            if (onlyIfNeeded) {
+                            if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
                                 // We don't need to start a new activity, and
                                 // the client said not to do anything if that
                                 // is the case, so this is it!
-                                return START_RETURN_INTENT_TO_CALLER;
+                                return ActivityManager.START_RETURN_INTENT_TO_CALLER;
                             }
                             top.deliverNewIntentLocked(callingUid, r.intent);
-                            return START_DELIVERED_TO_TOP;
+                            return ActivityManager.START_DELIVERED_TO_TOP;
                         }
                     }
                 }
@@ -2729,7 +2715,7 @@
                         r.resultTo, r.resultWho, r.requestCode,
                     Activity.RESULT_CANCELED, null);
             }
-            return START_CLASS_NOT_FOUND;
+            return ActivityManager.START_CLASS_NOT_FOUND;
         }
 
         boolean newTask = false;
@@ -2770,7 +2756,7 @@
                     if (doResume) {
                         resumeTopActivityLocked(null);
                     }
-                    return START_DELIVERED_TO_TOP;
+                    return ActivityManager.START_DELIVERED_TO_TOP;
                 }
             } else if (!addingToTask &&
                     (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
@@ -2785,7 +2771,7 @@
                     if (doResume) {
                         resumeTopActivityLocked(null);
                     }
-                    return START_DELIVERED_TO_TOP;
+                    return ActivityManager.START_DELIVERED_TO_TOP;
                 }
             }
             // An existing activity is starting this new activity, so we want
@@ -2809,13 +2795,6 @@
                     + " in new guessed " + r.task);
         }
 
-        if (grantedUriPermissions != null && callingUid > 0) {
-            for (int i=0; i<grantedUriPermissions.length; i++) {
-                mService.grantUriPermissionLocked(callingUid, r.packageName,
-                        grantedUriPermissions[i], grantedMode, r.getUriPermissionsLocked());
-            }
-        }
-
         mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
                 intent, r.getUriPermissionsLocked());
 
@@ -2824,12 +2803,11 @@
         }
         logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task);
         startActivityLocked(r, newTask, doResume, keepCurTransition);
-        return START_SUCCESS;
+        return ActivityManager.START_SUCCESS;
     }
 
-    ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug,
-            boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd,
-            boolean autoStopProfiler) {
+    ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags,
+             String profileFile, ParcelFileDescriptor profileFd) {
         // Collect information about the target of the Intent.
         ActivityInfo aInfo;
         try {
@@ -2852,13 +2830,13 @@
                     aInfo.applicationInfo.packageName, aInfo.name));
 
             // Don't debug things in the system process
-            if (debug) {
+            if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) {
                 if (!aInfo.processName.equals("system")) {
                     mService.setDebugApp(aInfo.processName, true, false);
                 }
             }
 
-            if (openglTrace) {
+            if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) {
                 if (!aInfo.processName.equals("system")) {
                     mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName);
                 }
@@ -2867,7 +2845,8 @@
             if (profileFile != null) {
                 if (!aInfo.processName.equals("system")) {
                     mService.setProfileApp(aInfo.applicationInfo, aInfo.processName,
-                            profileFile, profileFd, autoStopProfiler);
+                            profileFile, profileFd,
+                            (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0);
                 }
             }
         }
@@ -2875,12 +2854,10 @@
     }
 
     final int startActivityMayWait(IApplicationThread caller, int callingUid,
-            Intent intent, String resolvedType, Uri[] grantedUriPermissions,
-            int grantedMode, IBinder resultTo,
-            String resultWho, int requestCode, boolean onlyIfNeeded,
-            boolean debug, boolean openglTrace, String profileFile, ParcelFileDescriptor profileFd,
-            boolean autoStopProfiler,
-            WaitResult outResult, Configuration config, int userId) {
+            Intent intent, String resolvedType, IBinder resultTo,
+            String resultWho, int requestCode, int startFlags, String profileFile,
+            ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config,
+            Bundle options, int userId) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -2891,8 +2868,8 @@
         intent = new Intent(intent);
 
         // Collect information about the target of the Intent.
-        ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug, openglTrace,
-                profileFile, profileFd, autoStopProfiler);
+        ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags,
+                profileFile, profileFd);
         aInfo = mService.getActivityInfoForUser(aInfo, userId);
 
         synchronized (mService) {
@@ -2932,12 +2909,12 @@
                                 Slog.w(TAG, "Unable to find app for caller " + caller
                                       + " (pid=" + realCallingPid + ") when starting: "
                                       + intent.toString());
-                                return START_PERMISSION_DENIED;
+                                return ActivityManager.START_PERMISSION_DENIED;
                             }
                         }
                         
                         IIntentSender target = mService.getIntentSenderLocked(
-                                IActivityManager.INTENT_SENDER_ACTIVITY, "android",
+                                ActivityManager.INTENT_SENDER_ACTIVITY, "android",
                                 realCallingUid, null, null, 0, new Intent[] { intent },
                                 new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT
                                 | PendingIntent.FLAG_ONE_SHOT);
@@ -2983,9 +2960,8 @@
             }
             
             int res = startActivityLocked(caller, intent, resolvedType,
-                    grantedUriPermissions, grantedMode, aInfo,
-                    resultTo, resultWho, requestCode, callingPid, callingUid,
-                    onlyIfNeeded, componentSpecified, null);
+                    aInfo, resultTo, resultWho, requestCode, callingPid, callingUid,
+                    startFlags, options, componentSpecified, null);
             
             if (mConfigWillChange && mMainStack) {
                 // If the caller also wants to switch to a new configuration,
@@ -3004,7 +2980,7 @@
             
             if (outResult != null) {
                 outResult.result = res;
-                if (res == IActivityManager.START_SUCCESS) {
+                if (res == ActivityManager.START_SUCCESS) {
                     mWaitingActivityLaunched.add(outResult);
                     do {
                         try {
@@ -3012,7 +2988,7 @@
                         } catch (InterruptedException e) {
                         }
                     } while (!outResult.timeout && outResult.who == null);
-                } else if (res == IActivityManager.START_TASK_TO_FRONT) {
+                } else if (res == ActivityManager.START_TASK_TO_FRONT) {
                     ActivityRecord r = this.topRunningActivityLocked(null);
                     if (r.nowVisible) {
                         outResult.timeout = false;
@@ -3037,8 +3013,8 @@
     }
     
     final int startActivities(IApplicationThread caller, int callingUid,
-            Intent[] intents,
-            String[] resolvedTypes, IBinder resultTo, int userId) {
+            Intent[] intents, String[] resolvedTypes, IBinder resultTo,
+            Bundle options, int userId) {
         if (intents == null) {
             throw new NullPointerException("intents is null");
         }
@@ -3081,8 +3057,8 @@
                     intent = new Intent(intent);
 
                     // Collect information about the target of the Intent.
-                    ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false, false,
-                            null, null, false);
+                    ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i],
+                            0, null, null);
                     // TODO: New, check if this is correct
                     aInfo = mService.getActivityInfoForUser(aInfo, userId);
 
@@ -3093,8 +3069,8 @@
                     }
 
                     int res = startActivityLocked(caller, intent, resolvedTypes[i],
-                            null, 0, aInfo, resultTo, null, -1, callingPid, callingUid,
-                            false, componentSpecified, outActivity);
+                            aInfo, resultTo, null, -1, callingPid, callingUid,
+                            0, options, componentSpecified, outActivity);
                     if (res < 0) {
                         return res;
                     }
@@ -3106,7 +3082,7 @@
             Binder.restoreCallingIdentity(origId);
         }
 
-        return IActivityManager.START_SUCCESS;
+        return ActivityManager.START_SUCCESS;
     }
 
     void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r,
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index 0043874..9676084 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -16,12 +16,13 @@
 
 package com.android.server.am;
 
-import android.app.IActivityManager;
+import android.app.ActivityManager;
 import android.content.IIntentSender;
 import android.content.IIntentReceiver;
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.UserId;
@@ -158,13 +159,13 @@
         
         String typeName() {
             switch (type) {
-                case IActivityManager.INTENT_SENDER_ACTIVITY:
+                case ActivityManager.INTENT_SENDER_ACTIVITY:
                     return "startActivity";
-                case IActivityManager.INTENT_SENDER_BROADCAST:
+                case ActivityManager.INTENT_SENDER_BROADCAST:
                     return "broadcastIntent";
-                case IActivityManager.INTENT_SENDER_SERVICE:
+                case ActivityManager.INTENT_SENDER_SERVICE:
                     return "startService";
-                case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
+                case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
                     return "activityResult";
             }
             return Integer.toString(type);
@@ -181,13 +182,13 @@
     public int send(int code, Intent intent, String resolvedType,
             IIntentReceiver finishedReceiver, String requiredPermission) {
         return sendInner(code, intent, resolvedType, finishedReceiver,
-                requiredPermission, null, null, 0, 0, 0);
+                requiredPermission, null, null, 0, 0, 0, null);
     }
     
     int sendInner(int code, Intent intent, String resolvedType,
             IIntentReceiver finishedReceiver, String requiredPermission,
             IBinder resultTo, String resultWho, int requestCode,
-            int flagsMask, int flagsValues) {
+            int flagsMask, int flagsValues, Bundle options) {
         synchronized(owner) {
             if (!canceled) {
                 sent = true;
@@ -213,7 +214,7 @@
                 
                 boolean sendFinish = finishedReceiver != null;
                 switch (key.type) {
-                    case IActivityManager.INTENT_SENDER_ACTIVITY:
+                    case ActivityManager.INTENT_SENDER_ACTIVITY:
                         try {
                             if (key.allIntents != null && key.allIntents.length > 1) {
                                 Intent[] allIntents = new Intent[key.allIntents.length];
@@ -227,22 +228,22 @@
                                 allIntents[allIntents.length-1] = finalIntent;
                                 allResolvedTypes[allResolvedTypes.length-1] = resolvedType;
                                 owner.startActivitiesInPackage(uid, allIntents,
-                                        allResolvedTypes, resultTo);
+                                        allResolvedTypes, resultTo, options);
                             } else {
                                 owner.startActivityInPackage(uid,
                                         finalIntent, resolvedType,
-                                        resultTo, resultWho, requestCode, false);
+                                        resultTo, resultWho, requestCode, 0, options);
                             }
                         } catch (RuntimeException e) {
                             Slog.w(ActivityManagerService.TAG,
                                     "Unable to send startActivity intent", e);
                         }
                         break;
-                    case IActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
+                    case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT:
                         key.activity.stack.sendActivityResultLocked(-1, key.activity,
                                 key.who, key.requestCode, code, finalIntent);
                         break;
-                    case IActivityManager.INTENT_SENDER_BROADCAST:
+                    case ActivityManager.INTENT_SENDER_BROADCAST:
                         try {
                             // If a completion callback has been requested, require
                             // that the broadcast be delivered synchronously
@@ -257,7 +258,7 @@
                                     "Unable to send startActivity intent", e);
                         }
                         break;
-                    case IActivityManager.INTENT_SENDER_SERVICE:
+                    case ActivityManager.INTENT_SENDER_SERVICE:
                         try {
                             owner.startServiceInPackage(uid,
                                     finalIntent, resolvedType);
@@ -281,7 +282,7 @@
                 return 0;
             }
         }
-        return IActivityManager.START_CANCELED;
+        return ActivityManager.START_CANCELED;
     }
     
     protected void finalize() throws Throwable {
diff --git a/telephony/java/android/telephony/SmsCbCmasInfo.java b/telephony/java/android/telephony/SmsCbCmasInfo.java
new file mode 100644
index 0000000..7a89d94
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbCmasInfo.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Contains CMAS warning notification Type 1 elements for a {@link SmsCbMessage}.
+ * Supported values for each element are defined in TIA-1149-0-1 (CMAS over CDMA) and
+ * 3GPP TS 23.041 (for GSM/UMTS).
+ *
+ * {@hide}
+ */
+public class SmsCbCmasInfo implements Parcelable {
+
+    // CMAS message class (in GSM/UMTS message identifier or CDMA service category).
+
+    /** Presidential-level alert (Korean Public Alert System Class 0 message). */
+    public static final int CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT = 0x00;
+
+    /** Extreme threat to life and property (Korean Public Alert System Class 1 message). */
+    public static final int CMAS_CLASS_EXTREME_THREAT = 0x01;
+
+    /** Severe threat to life and property (Korean Public Alert System Class 1 message). */
+    public static final int CMAS_CLASS_SEVERE_THREAT = 0x02;
+
+    /** Child abduction emergency (AMBER Alert). */
+    public static final int CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY = 0x03;
+
+    /** CMAS test message. */
+    public static final int CMAS_CLASS_REQUIRED_MONTHLY_TEST = 0x04;
+
+    /** CMAS exercise. */
+    public static final int CMAS_CLASS_CMAS_EXERCISE = 0x05;
+
+    /** CMAS category for operator defined use. */
+    public static final int CMAS_CLASS_OPERATOR_DEFINED_USE = 0x06;
+
+    /** CMAS category for warning types that are reserved for future extension. */
+    public static final int CMAS_CLASS_UNKNOWN = -1;
+
+    // CMAS alert category (in CDMA type 1 elements record).
+
+    /** CMAS alert category: Geophysical including landslide. */
+    public static final int CMAS_CATEGORY_GEO = 0x00;
+
+    /** CMAS alert category: Meteorological including flood. */
+    public static final int CMAS_CATEGORY_MET = 0x01;
+
+    /** CMAS alert category: General emergency and public safety. */
+    public static final int CMAS_CATEGORY_SAFETY = 0x02;
+
+    /** CMAS alert category: Law enforcement, military, homeland/local/private security. */
+    public static final int CMAS_CATEGORY_SECURITY = 0x03;
+
+    /** CMAS alert category: Rescue and recovery. */
+    public static final int CMAS_CATEGORY_RESCUE = 0x04;
+
+    /** CMAS alert category: Fire suppression and rescue. */
+    public static final int CMAS_CATEGORY_FIRE = 0x05;
+
+    /** CMAS alert category: Medical and public health. */
+    public static final int CMAS_CATEGORY_HEALTH = 0x06;
+
+    /** CMAS alert category: Pollution and other environmental. */
+    public static final int CMAS_CATEGORY_ENV = 0x07;
+
+    /** CMAS alert category: Public and private transportation. */
+    public static final int CMAS_CATEGORY_TRANSPORT = 0x08;
+
+    /** CMAS alert category: Utility, telecom, other non-transport infrastructure. */
+    public static final int CMAS_CATEGORY_INFRA = 0x09;
+
+    /** CMAS alert category: Chem, bio, radiological, nuclear, high explosive threat or attack. */
+    public static final int CMAS_CATEGORY_CBRNE = 0x0a;
+
+    /** CMAS alert category: Other events. */
+    public static final int CMAS_CATEGORY_OTHER = 0x0b;
+
+    /**
+     * CMAS alert category is unknown. The category is only available for CDMA broadcasts
+     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+     */
+    public static final int CMAS_CATEGORY_UNKNOWN = -1;
+
+    // CMAS response type (in CDMA type 1 elements record).
+
+    /** CMAS response type: Take shelter in place. */
+    public static final int CMAS_RESPONSE_TYPE_SHELTER = 0x00;
+
+    /** CMAS response type: Evacuate (Relocate). */
+    public static final int CMAS_RESPONSE_TYPE_EVACUATE = 0x01;
+
+    /** CMAS response type: Make preparations. */
+    public static final int CMAS_RESPONSE_TYPE_PREPARE = 0x02;
+
+    /** CMAS response type: Execute a pre-planned activity. */
+    public static final int CMAS_RESPONSE_TYPE_EXECUTE = 0x03;
+
+    /** CMAS response type: Attend to information sources. */
+    public static final int CMAS_RESPONSE_TYPE_MONITOR = 0x04;
+
+    /** CMAS response type: Avoid hazard. */
+    public static final int CMAS_RESPONSE_TYPE_AVOID = 0x05;
+
+    /** CMAS response type: Evaluate the information in this message (not for public warnings). */
+    public static final int CMAS_RESPONSE_TYPE_ASSESS = 0x06;
+
+    /** CMAS response type: No action recommended. */
+    public static final int CMAS_RESPONSE_TYPE_NONE = 0x07;
+
+    /**
+     * CMAS response type is unknown. The response type is only available for CDMA broadcasts
+     * containing a type 1 elements record, so GSM and UMTS broadcasts always return unknown.
+     */
+    public static final int CMAS_RESPONSE_TYPE_UNKNOWN = -1;
+
+    // 4-bit CMAS severity (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS severity type: Extraordinary threat to life or property. */
+    public static final int CMAS_SEVERITY_EXTREME = 0x0;
+
+    /** CMAS severity type: Significant threat to life or property. */
+    public static final int CMAS_SEVERITY_SEVERE = 0x1;
+
+    /**
+     * CMAS alert severity is unknown. The severity is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_SEVERITY_UNKNOWN = -1;
+
+    // CMAS urgency (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS urgency type: Responsive action should be taken immediately. */
+    public static final int CMAS_URGENCY_IMMEDIATE = 0x0;
+
+    /** CMAS urgency type: Responsive action should be taken within the next hour. */
+    public static final int CMAS_URGENCY_EXPECTED = 0x1;
+
+    /**
+     * CMAS alert urgency is unknown. The urgency is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_URGENCY_UNKNOWN = -1;
+
+    // CMAS certainty (in GSM/UMTS message identifier or CDMA type 1 elements record).
+
+    /** CMAS certainty type: Determined to have occurred or to be ongoing. */
+    public static final int CMAS_CERTAINTY_OBSERVED = 0x0;
+
+    /** CMAS certainty type: Likely (probability > ~50%). */
+    public static final int CMAS_CERTAINTY_LIKELY = 0x1;
+
+    /**
+     * CMAS alert certainty is unknown. The certainty is available for CDMA warning alerts
+     * containing a type 1 elements record and for all GSM and UMTS alerts except for the
+     * Presidential-level alert class (Korean Public Alert System Class 0).
+     */
+    public static final int CMAS_CERTAINTY_UNKNOWN = -1;
+
+    /** CMAS message class. */
+    private final int mMessageClass;
+
+    /** CMAS category. */
+    private final int mCategory;
+
+    /** CMAS response type. */
+    private final int mResponseType;
+
+    /** CMAS severity. */
+    private final int mSeverity;
+
+    /** CMAS urgency. */
+    private final int mUrgency;
+
+    /** CMAS certainty. */
+    private final int mCertainty;
+
+    /** Create a new SmsCbCmasInfo object with the specified values. */
+    public SmsCbCmasInfo(int messageClass, int category, int responseType, int severity,
+            int urgency, int certainty) {
+        mMessageClass = messageClass;
+        mCategory = category;
+        mResponseType = responseType;
+        mSeverity = severity;
+        mUrgency = urgency;
+        mCertainty = certainty;
+    }
+
+    /** Create a new SmsCbCmasInfo object from a Parcel. */
+    SmsCbCmasInfo(Parcel in) {
+        mMessageClass = in.readInt();
+        mCategory = in.readInt();
+        mResponseType = in.readInt();
+        mSeverity = in.readInt();
+        mUrgency = in.readInt();
+        mCertainty = in.readInt();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageClass);
+        dest.writeInt(mCategory);
+        dest.writeInt(mResponseType);
+        dest.writeInt(mSeverity);
+        dest.writeInt(mUrgency);
+        dest.writeInt(mCertainty);
+    }
+
+    /**
+     * Returns the CMAS message class, e.g. {@link #CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT}.
+     * @return one of the {@code CMAS_CLASS} values
+     */
+    public int getMessageClass() {
+        return mMessageClass;
+    }
+
+    /**
+     * Returns the CMAS category, e.g. {@link #CMAS_CATEGORY_GEO}.
+     * @return one of the {@code CMAS_CATEGORY} values
+     */
+    public int getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Returns the CMAS response type, e.g. {@link #CMAS_RESPONSE_TYPE_SHELTER}.
+     * @return one of the {@code CMAS_RESPONSE_TYPE} values
+     */
+    public int getResponseType() {
+        return mResponseType;
+    }
+
+    /**
+     * Returns the CMAS severity, e.g. {@link #CMAS_SEVERITY_EXTREME}.
+     * @return one of the {@code CMAS_SEVERITY} values
+     */
+    public int getSeverity() {
+        return mSeverity;
+    }
+
+    /**
+     * Returns the CMAS urgency, e.g. {@link #CMAS_URGENCY_IMMEDIATE}.
+     * @return one of the {@code CMAS_URGENCY} values
+     */
+    public int getUrgency() {
+        return mUrgency;
+    }
+
+    /**
+     * Returns the CMAS certainty, e.g. {@link #CMAS_CERTAINTY_OBSERVED}.
+     * @return one of the {@code CMAS_CERTAINTY} values
+     */
+    public int getCertainty() {
+        return mCertainty;
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbCmasInfo{messageClass=" + mMessageClass + ", category=" + mCategory
+                + ", responseType=" + mResponseType + ", severity=" + mSeverity
+                + ", urgency=" + mUrgency + ", certainty=" + mCertainty + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    public static final Parcelable.Creator<SmsCbCmasInfo>
+            CREATOR = new Parcelable.Creator<SmsCbCmasInfo>() {
+        public SmsCbCmasInfo createFromParcel(Parcel in) {
+            return new SmsCbCmasInfo(in);
+        }
+
+        public SmsCbCmasInfo[] newArray(int size) {
+            return new SmsCbCmasInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/SmsCbEtwsInfo.java b/telephony/java/android/telephony/SmsCbEtwsInfo.java
new file mode 100644
index 0000000..0890d52
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbEtwsInfo.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.format.Time;
+
+import com.android.internal.telephony.IccUtils;
+
+import java.util.Arrays;
+
+/**
+ * Contains information elements for a GSM or UMTS ETWS warning notification.
+ * Supported values for each element are defined in 3GPP TS 23.041.
+ *
+ * {@hide}
+ */
+public class SmsCbEtwsInfo implements Parcelable {
+
+    /** ETWS warning type for earthquake. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE = 0x00;
+
+    /** ETWS warning type for tsunami. */
+    public static final int ETWS_WARNING_TYPE_TSUNAMI = 0x01;
+
+    /** ETWS warning type for earthquake and tsunami. */
+    public static final int ETWS_WARNING_TYPE_EARTHQUAKE_AND_TSUNAMI = 0x02;
+
+    /** ETWS warning type for test messages. */
+    public static final int ETWS_WARNING_TYPE_TEST_MESSAGE = 0x03;
+
+    /** ETWS warning type for other emergency types. */
+    public static final int ETWS_WARNING_TYPE_OTHER_EMERGENCY = 0x04;
+
+    /** Unknown ETWS warning type. */
+    public static final int ETWS_WARNING_TYPE_UNKNOWN = -1;
+
+    /** One of the ETWS warning type constants defined in this class. */
+    private final int mWarningType;
+
+    /** Whether or not to activate the emergency user alert tone and vibration. */
+    private final boolean mEmergencyUserAlert;
+
+    /** Whether or not to activate a popup alert. */
+    private final boolean mActivatePopup;
+
+    /**
+     * 50-byte security information (ETWS primary notification for GSM only). As of Release 10,
+     * 3GPP TS 23.041 states that the UE shall ignore the ETWS primary notification timestamp
+     * and digital signature if received. Therefore it is treated as a raw byte array and
+     * parceled with the broadcast intent if present, but the timestamp is only computed if an
+     * application asks for the individual components.
+     */
+    private final byte[] mWarningSecurityInformation;
+
+    /** Create a new SmsCbEtwsInfo object with the specified values. */
+    public SmsCbEtwsInfo(int warningType, boolean emergencyUserAlert, boolean activatePopup,
+            byte[] warningSecurityInformation) {
+        mWarningType = warningType;
+        mEmergencyUserAlert = emergencyUserAlert;
+        mActivatePopup = activatePopup;
+        mWarningSecurityInformation = warningSecurityInformation;
+    }
+
+    /** Create a new SmsCbEtwsInfo object from a Parcel. */
+    SmsCbEtwsInfo(Parcel in) {
+        mWarningType = in.readInt();
+        mEmergencyUserAlert = (in.readInt() != 0);
+        mActivatePopup = (in.readInt() != 0);
+        mWarningSecurityInformation = in.createByteArray();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mWarningType);
+        dest.writeInt(mEmergencyUserAlert ? 1 : 0);
+        dest.writeInt(mActivatePopup ? 1 : 0);
+        dest.writeByteArray(mWarningSecurityInformation);
+    }
+
+    /**
+     * Returns the ETWS warning type.
+     * @return a warning type such as {@link #ETWS_WARNING_TYPE_EARTHQUAKE}
+     */
+    public int getWarningType() {
+        return mWarningType;
+    }
+
+    /**
+     * Returns the ETWS emergency user alert flag.
+     * @return true to notify terminal to activate emergency user alert; false otherwise
+     */
+    public boolean isEmergencyUserAlert() {
+        return mEmergencyUserAlert;
+    }
+
+    /**
+     * Returns the ETWS activate popup flag.
+     * @return true to notify terminal to activate display popup; false otherwise
+     */
+    public boolean isPopupAlert() {
+        return mActivatePopup;
+    }
+
+    /**
+     * Returns the Warning-Security-Information timestamp (GSM primary notifications only).
+     * As of Release 10, 3GPP TS 23.041 states that the UE shall ignore this value if received.
+     * @return a UTC timestamp in System.currentTimeMillis() format, or 0 if not present
+     */
+    public long getPrimaryNotificationTimestamp() {
+        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 7) {
+            return 0;
+        }
+
+        int year = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[0]);
+        int month = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[1]);
+        int day = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[2]);
+        int hour = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[3]);
+        int minute = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[4]);
+        int second = IccUtils.gsmBcdByteToInt(mWarningSecurityInformation[5]);
+
+        // For the timezone, the most significant bit of the
+        // least significant nibble is the sign byte
+        // (meaning the max range of this field is 79 quarter-hours,
+        // which is more than enough)
+
+        byte tzByte = mWarningSecurityInformation[6];
+
+        // Mask out sign bit.
+        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
+
+        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
+
+        Time time = new Time(Time.TIMEZONE_UTC);
+
+        // We only need to support years above 2000.
+        time.year = year + 2000;
+        time.month = month - 1;
+        time.monthDay = day;
+        time.hour = hour;
+        time.minute = minute;
+        time.second = second;
+
+        // Timezone offset is in quarter hours.
+        return time.toMillis(true) - (long) (timezoneOffset * 15 * 60 * 1000);
+    }
+
+    /**
+     * Returns the digital signature (GSM primary notifications only). As of Release 10,
+     * 3GPP TS 23.041 states that the UE shall ignore this value if received.
+     * @return a byte array containing a copy of the primary notification digital signature
+     */
+    public byte[] getPrimaryNotificationSignature() {
+        if (mWarningSecurityInformation == null || mWarningSecurityInformation.length < 50) {
+            return null;
+        }
+        return Arrays.copyOfRange(mWarningSecurityInformation, 7, 50);
+    }
+
+    @Override
+    public String toString() {
+        return "SmsCbEtwsInfo{warningType=" + mWarningType + ", emergencyUserAlert="
+                + mEmergencyUserAlert + ", activatePopup=" + mActivatePopup + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    public static final Creator<SmsCbEtwsInfo> CREATOR = new Creator<SmsCbEtwsInfo>() {
+        public SmsCbEtwsInfo createFromParcel(Parcel in) {
+            return new SmsCbEtwsInfo(in);
+        }
+
+        public SmsCbEtwsInfo[] newArray(int size) {
+            return new SmsCbEtwsInfo[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/SmsCbLocation.java b/telephony/java/android/telephony/SmsCbLocation.java
new file mode 100644
index 0000000..7b5bd0d
--- /dev/null
+++ b/telephony/java/android/telephony/SmsCbLocation.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.telephony.gsm.GsmCellLocation;
+
+/**
+ * Represents the location and geographical scope of a cell broadcast message.
+ * For GSM/UMTS, the Location Area and Cell ID are set when the broadcast
+ * geographical scope is cell wide or Location Area wide. For CDMA, the
+ * broadcast geographical scope is always PLMN wide.
+ *
+ * @hide
+ */
+public class SmsCbLocation implements Parcelable {
+
+    /** The PLMN. Note that this field may be an empty string, but isn't allowed to be null. */
+    private final String mPlmn;
+
+    private final int mLac;
+    private final int mCid;
+
+    /**
+     * Construct an empty location object. This is used for some test cases, and for
+     * cell broadcasts saved in older versions of the database without location info.
+     */
+    public SmsCbLocation() {
+        mPlmn = "";
+        mLac = -1;
+        mCid = -1;
+    }
+
+    /**
+     * Construct a location object for the PLMN. This class is immutable, so
+     * the same object can be reused for multiple broadcasts.
+     */
+    public SmsCbLocation(String plmn) {
+        mPlmn = plmn;
+        mLac = -1;
+        mCid = -1;
+    }
+
+    /**
+     * Construct a location object for the PLMN, LAC, and Cell ID. This class is immutable, so
+     * the same object can be reused for multiple broadcasts.
+     */
+    public SmsCbLocation(String plmn, int lac, int cid) {
+        mPlmn = plmn;
+        mLac = lac;
+        mCid = cid;
+    }
+
+    /**
+     * Initialize the object from a Parcel.
+     */
+    public SmsCbLocation(Parcel in) {
+        mPlmn = in.readString();
+        mLac = in.readInt();
+        mCid = in.readInt();
+    }
+
+    /**
+     * Returns the MCC/MNC of the network as a String.
+     * @return the PLMN identifier (MCC+MNC) as a String
+     */
+    public String getPlmn() {
+        return mPlmn;
+    }
+
+    /**
+     * Returns the GSM location area code, or UMTS service area code.
+     * @return location area code, -1 if unknown, 0xffff max legal value
+     */
+    public int getLac() {
+        return mLac;
+    }
+
+    /**
+     * Returns the GSM or UMTS cell ID.
+     * @return gsm cell id, -1 if unknown, 0xffff max legal value
+     */
+    public int getCid() {
+        return mCid;
+    }
+
+    @Override
+    public int hashCode() {
+        int hash = mPlmn.hashCode();
+        hash = hash * 31 + mLac;
+        hash = hash * 31 + mCid;
+        return hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o == null || !(o instanceof SmsCbLocation)) {
+            return false;
+        }
+        SmsCbLocation other = (SmsCbLocation) o;
+        return mPlmn.equals(other.mPlmn) && mLac == other.mLac && mCid == other.mCid;
+    }
+
+    @Override
+    public String toString() {
+        return '[' + mPlmn + ',' + mLac + ',' + mCid + ']';
+    }
+
+    /**
+     * Test whether this location is within the location area of the specified object.
+     *
+     * @param area the location area to compare with this location
+     * @return true if this location is contained within the specified location area
+     */
+    public boolean isInLocationArea(SmsCbLocation area) {
+        if (mCid != -1 && mCid != area.mCid) {
+            return false;
+        }
+        if (mLac != -1 && mLac != area.mLac) {
+            return false;
+        }
+        return mPlmn.equals(area.mPlmn);
+    }
+
+    /**
+     * Test whether this location is within the location area of the CellLocation.
+     *
+     * @param plmn the PLMN to use for comparison
+     * @param lac the Location Area (GSM) or Service Area (UMTS) to compare with
+     * @param cid the Cell ID to compare with
+     * @return true if this location is contained within the specified PLMN, LAC, and Cell ID
+     */
+    public boolean isInLocationArea(String plmn, int lac, int cid) {
+        if (!mPlmn.equals(plmn)) {
+            return false;
+        }
+
+        if (mLac != -1 && mLac != lac) {
+            return false;
+        }
+
+        if (mCid != -1 && mCid != cid) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(mPlmn);
+        dest.writeInt(mLac);
+        dest.writeInt(mCid);
+    }
+
+    public static final Parcelable.Creator<SmsCbLocation> CREATOR
+            = new Parcelable.Creator<SmsCbLocation>() {
+        @Override
+        public SmsCbLocation createFromParcel(Parcel in) {
+            return new SmsCbLocation(in);
+        }
+
+        @Override
+        public SmsCbLocation[] newArray(int size) {
+            return new SmsCbLocation[size];
+        }
+    };
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 383e0f9..046bf8c 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -16,118 +16,262 @@
 
 package android.telephony;
 
-import android.text.format.Time;
-import android.util.Log;
-
-import com.android.internal.telephony.GsmAlphabet;
-import com.android.internal.telephony.IccUtils;
-import com.android.internal.telephony.gsm.SmsCbHeader;
-
-import java.io.UnsupportedEncodingException;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 /**
- * Describes an SMS-CB message.
+ * Parcelable object containing a received cell broadcast message. There are four different types
+ * of Cell Broadcast messages:
  *
- * {@hide}
+ * <ul>
+ * <li>opt-in informational broadcasts, e.g. news, weather, stock quotes, sports scores</li>
+ * <li>cell information messages, broadcast on channel 50, indicating the current cell name for
+ *  roaming purposes (required to display on the idle screen in Brazil)</li>
+ * <li>emergency broadcasts for the Japanese Earthquake and Tsunami Warning System (ETWS)</li>
+ * <li>emergency broadcasts for the American Commercial Mobile Alert Service (CMAS)</li>
+ * </ul>
+ *
+ * <p>There are also four different CB message formats: GSM, ETWS Primary Notification (GSM only),
+ * UMTS, and CDMA. Some fields are only applicable for some message formats. Other fields were
+ * unified under a common name, avoiding some names, such as "Message Identifier", that refer to
+ * two completely different concepts in 3GPP and CDMA.
+ *
+ * <p>The GSM/UMTS Message Identifier field is available via {@link #getServiceCategory}, the name
+ * of the equivalent field in CDMA. In both cases the service category is a 16-bit value, but 3GPP
+ * and 3GPP2 have completely different meanings for the respective values. For ETWS and CMAS, the
+ * application should
+ *
+ * <p>The CDMA Message Identifier field is available via {@link #getSerialNumber}, which is used
+ * to detect the receipt of a duplicate message to be discarded. In CDMA, the message ID is
+ * unique to the current PLMN. In GSM/UMTS, there is a 16-bit serial number containing a 2-bit
+ * Geographical Scope field which indicates whether the 10-bit message code and 4-bit update number
+ * are considered unique to the PLMN, to the current cell, or to the current Location Area (or
+ * Service Area in UMTS). The relevant values are concatenated into a single String which will be
+ * unique if the messages are not duplicates.
+ *
+ * <p>The SMS dispatcher does not detect duplicate messages. However, it does concatenate the
+ * pages of a GSM multi-page cell broadcast into a single SmsCbMessage object.
+ *
+ * <p>Interested applications with {@code RECEIVE_SMS_PERMISSION} can register to receive
+ * {@code SMS_CB_RECEIVED_ACTION} broadcast intents for incoming non-emergency broadcasts.
+ * Only system applications such as the CellBroadcastReceiver may receive notifications for
+ * emergency broadcasts (ETWS and CMAS). This is intended to prevent any potential for delays or
+ * interference with the immediate display of the alert message and playing of the alert sound and
+ * vibration pattern, which could be caused by poorly written or malicious non-system code.
+ *
+ * @hide
  */
-public class SmsCbMessage {
+public class SmsCbMessage implements Parcelable {
 
-    /**
-     * Cell wide immediate geographical scope
-     */
+    protected static final String LOG_TAG = "SMSCB";
+
+    /** Cell wide geographical scope with immediate display (GSM/UMTS only). */
     public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE = 0;
 
-    /**
-     * PLMN wide geographical scope
-     */
+    /** PLMN wide geographical scope (GSM/UMTS and all CDMA broadcasts). */
     public static final int GEOGRAPHICAL_SCOPE_PLMN_WIDE = 1;
 
-    /**
-     * Location / service area wide geographical scope
-     */
+    /** Location / service area wide geographical scope (GSM/UMTS only). */
     public static final int GEOGRAPHICAL_SCOPE_LA_WIDE = 2;
 
-    /**
-     * Cell wide geographical scope
-     */
+    /** Cell wide geographical scope (GSM/UMTS only). */
     public static final int GEOGRAPHICAL_SCOPE_CELL_WIDE = 3;
 
+    /** GSM or UMTS format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP = 1;
+
+    /** CDMA format cell broadcast. */
+    public static final int MESSAGE_FORMAT_3GPP2 = 2;
+
+    /** Normal message priority. */
+    public static final int MESSAGE_PRIORITY_NORMAL = 0;
+
+    /** Interactive message priority. */
+    public static final int MESSAGE_PRIORITY_INTERACTIVE = 1;
+
+    /** Urgent message priority. */
+    public static final int MESSAGE_PRIORITY_URGENT = 2;
+
+    /** Emergency message priority. */
+    public static final int MESSAGE_PRIORITY_EMERGENCY = 3;
+
+    /** Format of this message (for interpretation of service category values). */
+    private final int mMessageFormat;
+
+    /** Geographical scope of broadcast. */
+    private final int mGeographicalScope;
+
     /**
-     * Create an instance of this class from a received PDU
+     * Serial number of broadcast (message identifier for CDMA, geographical scope + message code +
+     * update number for GSM/UMTS). The serial number plus the location code uniquely identify
+     * a cell broadcast for duplicate detection.
+     */
+    private final int mSerialNumber;
+
+    /**
+     * Location identifier for this message. It consists of the current operator MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included for comparison. If the GS is
+     * 00 or 11, the Cell ID is also included. LAC and Cell ID are -1 if not specified.
+     */
+    private final SmsCbLocation mLocation;
+
+    /**
+     * 16-bit CDMA service category or GSM/UMTS message identifier. For ETWS and CMAS warnings,
+     * the information provided by the category is also available via {@link #getEtwsWarningInfo()}
+     * or {@link #getCmasWarningInfo()}.
+     */
+    private final int mServiceCategory;
+
+    /** Message language, as a two-character string, e.g. "en". */
+    private final String mLanguage;
+
+    /** Message body, as a String. */
+    private final String mBody;
+
+    /** Message priority (including emergency priority). */
+    private final int mPriority;
+
+    /** ETWS warning notification information (ETWS warnings only). */
+    private final SmsCbEtwsInfo mEtwsWarningInfo;
+
+    /** CMAS warning notification information (CMAS warnings only). */
+    private final SmsCbCmasInfo mCmasWarningInfo;
+
+    /**
+     * Create a new SmsCbMessage with the specified data.
+     */
+    public SmsCbMessage(int messageFormat, int geographicalScope, int serialNumber,
+            SmsCbLocation location, int serviceCategory, String language, String body,
+            int priority, SmsCbEtwsInfo etwsWarningInfo, SmsCbCmasInfo cmasWarningInfo) {
+        mMessageFormat = messageFormat;
+        mGeographicalScope = geographicalScope;
+        mSerialNumber = serialNumber;
+        mLocation = location;
+        mServiceCategory = serviceCategory;
+        mLanguage = language;
+        mBody = body;
+        mPriority = priority;
+        mEtwsWarningInfo = etwsWarningInfo;
+        mCmasWarningInfo = cmasWarningInfo;
+    }
+
+    /** Create a new SmsCbMessage object from a Parcel. */
+    public SmsCbMessage(Parcel in) {
+        mMessageFormat = in.readInt();
+        mGeographicalScope = in.readInt();
+        mSerialNumber = in.readInt();
+        mLocation = new SmsCbLocation(in);
+        mServiceCategory = in.readInt();
+        mLanguage = in.readString();
+        mBody = in.readString();
+        mPriority = in.readInt();
+        int type = in.readInt();
+        switch (type) {
+            case 'E':
+                // unparcel ETWS warning information
+                mEtwsWarningInfo = new SmsCbEtwsInfo(in);
+                mCmasWarningInfo = null;
+                break;
+
+            case 'C':
+                // unparcel CMAS warning information
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = new SmsCbCmasInfo(in);
+                break;
+
+            default:
+                mEtwsWarningInfo = null;
+                mCmasWarningInfo = null;
+        }
+    }
+
+    /**
+     * Flatten this object into a Parcel.
      *
-     * @param pdu PDU bytes
-     * @return An instance of this class, or null if invalid pdu
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
      */
-    public static SmsCbMessage createFromPdu(byte[] pdu) {
-        try {
-            return new SmsCbMessage(pdu);
-        } catch (IllegalArgumentException e) {
-            Log.w(LOG_TAG, "Failed parsing SMS-CB pdu", e);
-            return null;
-        }
-    }
-
-    private static final String LOG_TAG = "SMSCB";
-
-    /**
-     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
-     */
-    private static final String[] LANGUAGE_CODES_GROUP_0 = {
-            "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
-            "pl", null
-    };
-
-    /**
-     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
-     */
-    private static final String[] LANGUAGE_CODES_GROUP_2 = {
-            "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
-            null, null
-    };
-
-    private static final char CARRIAGE_RETURN = 0x0d;
-
-    private static final int PDU_BODY_PAGE_LENGTH = 82;
-
-    private SmsCbHeader mHeader;
-
-    private String mLanguage;
-
-    private String mBody;
-
-    /** Timestamp of ETWS primary notification with security. */
-    private long mPrimaryNotificationTimestamp;
-
-    /** 43 byte digital signature of ETWS primary notification with security. */
-    private byte[] mPrimaryNotificationDigitalSignature;
-
-    private SmsCbMessage(byte[] pdu) throws IllegalArgumentException {
-        mHeader = new SmsCbHeader(pdu);
-        if (mHeader.format == SmsCbHeader.FORMAT_ETWS_PRIMARY) {
-            mBody = "ETWS";
-            // ETWS primary notification with security is 56 octets in length
-            if (pdu.length >= SmsCbHeader.PDU_LENGTH_ETWS) {
-                mPrimaryNotificationTimestamp = getTimestampMillis(pdu);
-                mPrimaryNotificationDigitalSignature = new byte[43];
-                // digital signature starts after 6 byte header and 7 byte timestamp
-                System.arraycopy(pdu, 13, mPrimaryNotificationDigitalSignature, 0, 43);
-            }
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMessageFormat);
+        dest.writeInt(mGeographicalScope);
+        dest.writeInt(mSerialNumber);
+        mLocation.writeToParcel(dest, flags);
+        dest.writeInt(mServiceCategory);
+        dest.writeString(mLanguage);
+        dest.writeString(mBody);
+        dest.writeInt(mPriority);
+        if (mEtwsWarningInfo != null) {
+            // parcel ETWS warning information
+            dest.writeInt('E');
+            mEtwsWarningInfo.writeToParcel(dest, flags);
+        } else if (mCmasWarningInfo != null) {
+            // parcel CMAS warning information
+            dest.writeInt('C');
+            mCmasWarningInfo.writeToParcel(dest, flags);
         } else {
-            parseBody(pdu);
+            // no ETWS or CMAS warning information
+            dest.writeInt('0');
         }
     }
 
+    public static final Parcelable.Creator<SmsCbMessage> CREATOR
+            = new Parcelable.Creator<SmsCbMessage>() {
+        @Override
+        public SmsCbMessage createFromParcel(Parcel in) {
+            return new SmsCbMessage(in);
+        }
+
+        @Override
+        public SmsCbMessage[] newArray(int size) {
+            return new SmsCbMessage[size];
+        }
+    };
+
     /**
-     * Return the geographical scope of this message, one of
-     * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE},
-     * {@link #GEOGRAPHICAL_SCOPE_PLMN_WIDE},
-     * {@link #GEOGRAPHICAL_SCOPE_LA_WIDE},
-     * {@link #GEOGRAPHICAL_SCOPE_CELL_WIDE}
+     * Return the geographical scope of this message (GSM/UMTS only).
      *
      * @return Geographical scope
      */
     public int getGeographicalScope() {
-        return mHeader.geographicalScope;
+        return mGeographicalScope;
+    }
+
+    /**
+     * Return the broadcast serial number of broadcast (message identifier for CDMA, or
+     * geographical scope + message code + update number for GSM/UMTS). The serial number plus
+     * the location code uniquely identify a cell broadcast for duplicate detection.
+     *
+     * @return the 16-bit CDMA message identifier or GSM/UMTS serial number
+     */
+    public int getSerialNumber() {
+        return mSerialNumber;
+    }
+
+    /**
+     * Return the location identifier for this message, consisting of the MCC/MNC as a
+     * 5 or 6-digit decimal string. In addition, for GSM/UMTS, if the Geographical Scope of the
+     * message is not binary 01, the Location Area is included. If the GS is 00 or 11, the
+     * cell ID is also included. The {@link SmsCbLocation} object includes a method to test
+     * if the location is included within another location area or within a PLMN and CellLocation.
+     *
+     * @return the geographical location code for duplicate message detection
+     */
+    public SmsCbLocation getLocation() {
+        return mLocation;
+    }
+
+    /**
+     * Return the 16-bit CDMA service category or GSM/UMTS message identifier. The interpretation
+     * of the category is radio technology specific. For ETWS and CMAS warnings, the information
+     * provided by the category is available via {@link #getEtwsWarningInfo()} or
+     * {@link #getCmasWarningInfo()} in a radio technology independent format.
+     *
+     * @return the radio technology specific service category
+     */
+    public int getServiceCategory() {
+        return mServiceCategory;
     }
 
     /**
@@ -149,311 +293,90 @@
     }
 
     /**
-     * Get the message identifier of this message (0-65535)
-     *
-     * @return Message identifier
-     */
-    public int getMessageIdentifier() {
-        return mHeader.messageIdentifier;
-    }
-
-    /**
-     * Get the message code of this message (0-1023)
-     *
-     * @return Message code
-     */
-    public int getMessageCode() {
-        return mHeader.messageCode;
-    }
-
-    /**
-     * Get the update number of this message (0-15)
-     *
-     * @return Update number
-     */
-    public int getUpdateNumber() {
-        return mHeader.updateNumber;
-    }
-
-    /**
-     * Get the format of this message.
-     * @return {@link SmsCbHeader#FORMAT_GSM}, {@link SmsCbHeader#FORMAT_UMTS}, or
-     *         {@link SmsCbHeader#FORMAT_ETWS_PRIMARY}
+     * Get the message format ({@link #MESSAGE_FORMAT_3GPP} or {@link #MESSAGE_FORMAT_3GPP2}).
+     * @return an integer representing 3GPP or 3GPP2 message format
      */
     public int getMessageFormat() {
-        return mHeader.format;
+        return mMessageFormat;
     }
 
     /**
-     * For ETWS primary notifications, return the emergency user alert flag.
-     * @return true to notify terminal to activate emergency user alert; false otherwise
+     * Get the message priority. Normal broadcasts return {@link #MESSAGE_PRIORITY_NORMAL}
+     * and emergency broadcasts return {@link #MESSAGE_PRIORITY_EMERGENCY}. CDMA also may return
+     * {@link #MESSAGE_PRIORITY_INTERACTIVE} or {@link #MESSAGE_PRIORITY_URGENT}.
+     * @return an integer representing the message priority
      */
-    public boolean getEtwsEmergencyUserAlert() {
-        return mHeader.etwsEmergencyUserAlert;
+    public int getMessagePriority() {
+        return mPriority;
     }
 
     /**
-     * For ETWS primary notifications, return the popup flag.
-     * @return true to notify terminal to activate display popup; false otherwise
-     */
-    public boolean getEtwsPopup() {
-        return mHeader.etwsPopup;
-    }
-
-    /**
-     * For ETWS primary notifications, return the warning type.
-     * @return a value such as {@link SmsCbConstants#ETWS_WARNING_TYPE_EARTHQUAKE}
-     */
-    public int getEtwsWarningType() {
-        return mHeader.etwsWarningType;
-    }
-
-    /**
-     * For ETWS primary notifications, return the Warning-Security-Information timestamp.
-     * @return a timestamp in System.currentTimeMillis() format.
-     */
-    public long getEtwsSecurityTimestamp() {
-        return mPrimaryNotificationTimestamp;
-    }
-
-    /**
-     * For ETWS primary notifications, return the 43 byte digital signature.
-     * @return a byte array containing a copy of the digital signature
-     */
-    public byte[] getEtwsSecuritySignature() {
-        return mPrimaryNotificationDigitalSignature.clone();
-    }
-
-    /**
-     * Parse and unpack the body text according to the encoding in the DCS.
-     * After completing successfully this method will have assigned the body
-     * text into mBody, and optionally the language code into mLanguage
+     * If this is an ETWS warning notification then this method will return an object containing
+     * the ETWS warning type, the emergency user alert flag, and the popup flag. If this is an
+     * ETWS primary notification (GSM only), there will also be a 7-byte timestamp and 43-byte
+     * digital signature. As of Release 10, 3GPP TS 23.041 states that the UE shall ignore the
+     * ETWS primary notification timestamp and digital signature if received.
      *
-     * @param pdu The pdu
+     * @return an SmsCbEtwsInfo object, or null if this is not an ETWS warning notification
      */
-    private void parseBody(byte[] pdu) {
-        int encoding;
-        boolean hasLanguageIndicator = false;
-
-        // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
-        // section 5.
-        switch ((mHeader.dataCodingScheme & 0xf0) >> 4) {
-            case 0x00:
-                encoding = SmsMessage.ENCODING_7BIT;
-                mLanguage = LANGUAGE_CODES_GROUP_0[mHeader.dataCodingScheme & 0x0f];
-                break;
-
-            case 0x01:
-                hasLanguageIndicator = true;
-                if ((mHeader.dataCodingScheme & 0x0f) == 0x01) {
-                    encoding = SmsMessage.ENCODING_16BIT;
-                } else {
-                    encoding = SmsMessage.ENCODING_7BIT;
-                }
-                break;
-
-            case 0x02:
-                encoding = SmsMessage.ENCODING_7BIT;
-                mLanguage = LANGUAGE_CODES_GROUP_2[mHeader.dataCodingScheme & 0x0f];
-                break;
-
-            case 0x03:
-                encoding = SmsMessage.ENCODING_7BIT;
-                break;
-
-            case 0x04:
-            case 0x05:
-                switch ((mHeader.dataCodingScheme & 0x0c) >> 2) {
-                    case 0x01:
-                        encoding = SmsMessage.ENCODING_8BIT;
-                        break;
-
-                    case 0x02:
-                        encoding = SmsMessage.ENCODING_16BIT;
-                        break;
-
-                    case 0x00:
-                    default:
-                        encoding = SmsMessage.ENCODING_7BIT;
-                        break;
-                }
-                break;
-
-            case 0x06:
-            case 0x07:
-                // Compression not supported
-            case 0x09:
-                // UDH structure not supported
-            case 0x0e:
-                // Defined by the WAP forum not supported
-                encoding = SmsMessage.ENCODING_UNKNOWN;
-                break;
-
-            case 0x0f:
-                if (((mHeader.dataCodingScheme & 0x04) >> 2) == 0x01) {
-                    encoding = SmsMessage.ENCODING_8BIT;
-                } else {
-                    encoding = SmsMessage.ENCODING_7BIT;
-                }
-                break;
-
-            default:
-                // Reserved values are to be treated as 7-bit
-                encoding = SmsMessage.ENCODING_7BIT;
-                break;
-        }
-
-        if (mHeader.format == SmsCbHeader.FORMAT_UMTS) {
-            // Payload may contain multiple pages
-            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
-
-            if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
-                    * nrPages) {
-                throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
-                        + nrPages + " pages");
-            }
-
-            StringBuilder sb = new StringBuilder();
-
-            for (int i = 0; i < nrPages; i++) {
-                // Each page is 82 bytes followed by a length octet indicating
-                // the number of useful octets within those 82
-                int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
-                int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
-
-                if (length > PDU_BODY_PAGE_LENGTH) {
-                    throw new IllegalArgumentException("Page length " + length
-                            + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
-                }
-
-                sb.append(unpackBody(pdu, encoding, offset, length, hasLanguageIndicator));
-            }
-            mBody = sb.toString();
-        } else {
-            // Payload is one single page
-            int offset = SmsCbHeader.PDU_HEADER_LENGTH;
-            int length = pdu.length - offset;
-
-            mBody = unpackBody(pdu, encoding, offset, length, hasLanguageIndicator);
-        }
+    public SmsCbEtwsInfo getEtwsWarningInfo() {
+        return mEtwsWarningInfo;
     }
 
     /**
-     * Unpack body text from the pdu using the given encoding, position and
-     * length within the pdu
+     * If this is a CMAS warning notification then this method will return an object containing
+     * the CMAS message class, category, response type, severity, urgency and certainty.
+     * The message class is always present. Severity, urgency and certainty are present for CDMA
+     * warning notifications containing a type 1 elements record and for GSM and UMTS warnings
+     * except for the Presidential-level alert category. Category and response type are only
+     * available for CDMA notifications containing a type 1 elements record.
      *
-     * @param pdu The pdu
-     * @param encoding The encoding, as derived from the DCS
-     * @param offset Position of the first byte to unpack
-     * @param length Number of bytes to unpack
-     * @param hasLanguageIndicator true if the body text is preceded by a
-     *            language indicator. If so, this method will as a side-effect
-     *            assign the extracted language code into mLanguage
-     * @return Body text
+     * @return an SmsCbCmasInfo object, or null if this is not a CMAS warning notification
      */
-    private String unpackBody(byte[] pdu, int encoding, int offset, int length,
-            boolean hasLanguageIndicator) {
-        String body = null;
-
-        switch (encoding) {
-            case SmsMessage.ENCODING_7BIT:
-                body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
-
-                if (hasLanguageIndicator && body != null && body.length() > 2) {
-                    // Language is two GSM characters followed by a CR.
-                    // The actual body text is offset by 3 characters.
-                    mLanguage = body.substring(0, 2);
-                    body = body.substring(3);
-                }
-                break;
-
-            case SmsMessage.ENCODING_16BIT:
-                if (hasLanguageIndicator && pdu.length >= offset + 2) {
-                    // Language is two GSM characters.
-                    // The actual body text is offset by 2 bytes.
-                    mLanguage = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
-                    offset += 2;
-                    length -= 2;
-                }
-
-                try {
-                    body = new String(pdu, offset, (length & 0xfffe), "utf-16");
-                } catch (UnsupportedEncodingException e) {
-                    // Eeeek
-                }
-                break;
-
-            default:
-                break;
-        }
-
-        if (body != null) {
-            // Remove trailing carriage return
-            for (int i = body.length() - 1; i >= 0; i--) {
-                if (body.charAt(i) != CARRIAGE_RETURN) {
-                    body = body.substring(0, i + 1);
-                    break;
-                }
-            }
-        } else {
-            body = "";
-        }
-
-        return body;
+    public SmsCbCmasInfo getCmasWarningInfo() {
+        return mCmasWarningInfo;
     }
 
     /**
-     * Parses an ETWS primary notification timestamp and returns a currentTimeMillis()-style
-     * timestamp. Copied from com.android.internal.telephony.gsm.SmsMessage.
-     * @param pdu the ETWS primary notification PDU to decode
-     * @return the UTC timestamp from the Warning-Security-Information parameter
+     * Return whether this message is an emergency (PWS) message type.
+     * @return true if the message is a public warning notification; false otherwise
      */
-    private long getTimestampMillis(byte[] pdu) {
-        // Timestamp starts after CB header, in pdu[6]
-        int year = IccUtils.gsmBcdByteToInt(pdu[6]);
-        int month = IccUtils.gsmBcdByteToInt(pdu[7]);
-        int day = IccUtils.gsmBcdByteToInt(pdu[8]);
-        int hour = IccUtils.gsmBcdByteToInt(pdu[9]);
-        int minute = IccUtils.gsmBcdByteToInt(pdu[10]);
-        int second = IccUtils.gsmBcdByteToInt(pdu[11]);
-
-        // For the timezone, the most significant bit of the
-        // least significant nibble is the sign byte
-        // (meaning the max range of this field is 79 quarter-hours,
-        // which is more than enough)
-
-        byte tzByte = pdu[12];
-
-        // Mask out sign bit.
-        int timezoneOffset = IccUtils.gsmBcdByteToInt((byte) (tzByte & (~0x08)));
-
-        timezoneOffset = ((tzByte & 0x08) == 0) ? timezoneOffset : -timezoneOffset;
-
-        Time time = new Time(Time.TIMEZONE_UTC);
-
-        // It's 2006.  Should I really support years < 2000?
-        time.year = year >= 90 ? year + 1900 : year + 2000;
-        time.month = month - 1;
-        time.monthDay = day;
-        time.hour = hour;
-        time.minute = minute;
-        time.second = second;
-
-        // Timezone offset is in quarter hours.
-        return time.toMillis(true) - (timezoneOffset * 15 * 60 * 1000);
+    public boolean isEmergencyMessage() {
+        return mPriority == MESSAGE_PRIORITY_EMERGENCY;
     }
 
     /**
-     * Append text to the message body. This is used to concatenate multi-page GSM broadcasts.
-     * @param body the text to append to this message
+     * Return whether this message is an ETWS warning alert.
+     * @return true if the message is an ETWS warning notification; false otherwise
      */
-    public void appendToBody(String body) {
-        mBody = mBody + body;
+    public boolean isEtwsMessage() {
+        return mEtwsWarningInfo != null;
+    }
+
+    /**
+     * Return whether this message is a CMAS warning alert.
+     * @return true if the message is a CMAS warning notification; false otherwise
+     */
+    public boolean isCmasMessage() {
+        return mCmasWarningInfo != null;
     }
 
     @Override
     public String toString() {
-        return "SmsCbMessage{" + mHeader.toString() + ", language=" + mLanguage +
-                ", body=\"" + mBody + "\"}";
+        return "SmsCbMessage{geographicalScope=" + mGeographicalScope + ", serialNumber="
+                + mSerialNumber + ", location=" + mLocation + ", serviceCategory="
+                + mServiceCategory + ", language=" + mLanguage + ", body=" + mBody
+                + ", priority=" + mPriority
+                + (mEtwsWarningInfo != null ? (", " + mEtwsWarningInfo.toString()) : "")
+                + (mCmasWarningInfo != null ? (", " + mCmasWarningInfo.toString()) : "") + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
     }
 }
diff --git a/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
new file mode 100644
index 0000000..f94efd8
--- /dev/null
+++ b/telephony/java/android/telephony/cdma/CdmaSmsCbProgramData.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.cdma;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * CDMA Service Category Program Data from SCPT teleservice SMS.
+ * The CellBroadcastReceiver app receives an Intent with action
+ * {@link android.provider.Telephony.Sms.Intents#SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION}
+ * containing an array of these objects to update its list of cell broadcast service categories
+ * to display.
+ *
+ * {@hide}
+ */
+public class CdmaSmsCbProgramData implements Parcelable {
+
+    /** Delete the specified service category from the list of enabled categories. */
+    public static final int OPERATION_DELETE_CATEGORY   = 0;
+
+    /** Add the specified service category to the list of enabled categories. */
+    public static final int OPERATION_ADD_CATEGORY      = 1;
+
+    /** Clear all service categories from the list of enabled categories. */
+    public static final int OPERATION_CLEAR_CATEGORIES  = 2;
+
+    /** Alert option: no alert. */
+    public static final int ALERT_OPTION_NO_ALERT               = 0;
+
+    /** Alert option: default alert. */
+    public static final int ALERT_OPTION_DEFAULT_ALERT          = 1;
+
+    /** Alert option: vibrate alert once. */
+    public static final int ALERT_OPTION_VIBRATE_ONCE           = 2;
+
+    /** Alert option: vibrate alert - repeat. */
+    public static final int ALERT_OPTION_VIBRATE_REPEAT         = 3;
+
+    /** Alert option: visual alert once. */
+    public static final int ALERT_OPTION_VISUAL_ONCE            = 4;
+
+    /** Alert option: visual alert - repeat. */
+    public static final int ALERT_OPTION_VISUAL_REPEAT          = 5;
+
+    /** Alert option: low-priority alert once. */
+    public static final int ALERT_OPTION_LOW_PRIORITY_ONCE      = 6;
+
+    /** Alert option: low-priority alert - repeat. */
+    public static final int ALERT_OPTION_LOW_PRIORITY_REPEAT    = 7;
+
+    /** Alert option: medium-priority alert once. */
+    public static final int ALERT_OPTION_MED_PRIORITY_ONCE      = 8;
+
+    /** Alert option: medium-priority alert - repeat. */
+    public static final int ALERT_OPTION_MED_PRIORITY_REPEAT    = 9;
+
+    /** Alert option: high-priority alert once. */
+    public static final int ALERT_OPTION_HIGH_PRIORITY_ONCE     = 10;
+
+    /** Alert option: high-priority alert - repeat. */
+    public static final int ALERT_OPTION_HIGH_PRIORITY_REPEAT   = 11;
+
+    /** Service category operation (add/delete/clear). */
+    private final int mOperation;
+
+    /** Service category to modify. */
+    private final int mCategory;
+
+    /** Language used for service category name (ISO 639 two character code). */
+    private final String mLanguage;
+
+    /** Maximum number of messages to store for this service category. */
+    private final int mMaxMessages;
+
+    /** Service category alert option. */
+    private final int mAlertOption;
+
+    /** Name of service category. */
+    private final String mCategoryName;
+
+    /** Create a new CdmaSmsCbProgramData object with the specified values. */
+    public CdmaSmsCbProgramData(int operation, int category, String language, int maxMessages,
+            int alertOption, String categoryName) {
+        mOperation = operation;
+        mCategory = category;
+        mLanguage = language;
+        mMaxMessages = maxMessages;
+        mAlertOption = alertOption;
+        mCategoryName = categoryName;
+    }
+
+    /** Create a new CdmaSmsCbProgramData object from a Parcel. */
+    CdmaSmsCbProgramData(Parcel in) {
+        mOperation = in.readInt();
+        mCategory = in.readInt();
+        mLanguage = in.readString();
+        mMaxMessages = in.readInt();
+        mAlertOption = in.readInt();
+        mCategoryName = in.readString();
+    }
+
+    /**
+     * Flatten this object into a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written (ignored).
+     */
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mOperation);
+        dest.writeInt(mCategory);
+        dest.writeString(mLanguage);
+        dest.writeInt(mMaxMessages);
+        dest.writeInt(mAlertOption);
+        dest.writeString(mCategoryName);
+    }
+
+    /**
+     * Returns the service category operation, e.g. {@link #OPERATION_ADD_CATEGORY}.
+     * @return one of the {@code OPERATION_*} values
+     */
+    public int getOperation() {
+        return mOperation;
+    }
+
+    /**
+     * Returns the CDMA service category to modify.
+     * @return a 16-bit CDMA service category value
+     */
+    public int getCategory() {
+        return mCategory;
+    }
+
+    /**
+     * Returns the ISO-639-1 language code for the service category name, or null if not present.
+     * @return a two-digit ISO-639-1 language code, e.g. "en" for English
+     */
+    public String getLanguageCode() {
+        return mLanguage;
+    }
+
+    /**
+     * Returns the maximum number of messages to store for this service category.
+     * @return the maximum number of messages to store for this service category
+     */
+    public int getMaxMessages() {
+        return mMaxMessages;
+    }
+
+    /**
+     * Returns the service category alert option, e.g. {@link #ALERT_OPTION_DEFAULT_ALERT}.
+     * @return one of the {@code ALERT_OPTION_*} values
+     */
+    public int getAlertOption() {
+        return mAlertOption;
+    }
+
+    /**
+     * Returns the service category name, in the language specified by {@link #getLanguageCode()}.
+     * @return an optional service category name
+     */
+    public String getCategoryName() {
+        return mCategoryName;
+    }
+
+    @Override
+    public String toString() {
+        return "CdmaSmsCbProgramData{operation=" + mOperation + ", category=" + mCategory
+                + ", language=" + mLanguage + ", max messages=" + mMaxMessages
+                + ", alert option=" + mAlertOption + ", category name=" + mCategoryName + '}';
+    }
+
+    /**
+     * Describe the kinds of special objects contained in the marshalled representation.
+     * @return a bitmask indicating this Parcelable contains no special objects
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Creator for unparcelling objects. */
+    public static final Parcelable.Creator<CdmaSmsCbProgramData>
+            CREATOR = new Parcelable.Creator<CdmaSmsCbProgramData>() {
+        @Override
+        public CdmaSmsCbProgramData createFromParcel(Parcel in) {
+            return new CdmaSmsCbProgramData(in);
+        }
+
+        @Override
+        public CdmaSmsCbProgramData[] newArray(int size) {
+            return new CdmaSmsCbProgramData[size];
+        }
+    };
+}
diff --git a/telephony/java/com/android/internal/telephony/SMSDispatcher.java b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
index a42a267..d6f96ff 100644
--- a/telephony/java/com/android/internal/telephony/SMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/SMSDispatcher.java
@@ -39,6 +39,7 @@
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
 import android.provider.Settings;
+import android.telephony.SmsCbMessage;
 import android.telephony.SmsMessage;
 import android.telephony.ServiceState;
 import android.util.Log;
@@ -339,7 +340,7 @@
      * @param intent intent to broadcast
      * @param permission Receivers are required to have this permission
      */
-    void dispatch(Intent intent, String permission) {
+    public void dispatch(Intent intent, String permission) {
         // Hold a wake lock for WAKE_LOCK_TIMEOUT seconds, enough to give any
         // receivers time to take their own wake locks.
         mWakeLock.acquire(WAKE_LOCK_TIMEOUT);
@@ -1078,16 +1079,16 @@
         }
     };
 
-    protected void dispatchBroadcastPdus(byte[][] pdus, boolean isEmergencyMessage) {
-        if (isEmergencyMessage) {
+    protected void dispatchBroadcastMessage(SmsCbMessage message) {
+        if (message.isEmergencyMessage()) {
             Intent intent = new Intent(Intents.SMS_EMERGENCY_CB_RECEIVED_ACTION);
-            intent.putExtra("pdus", pdus);
-            Log.d(TAG, "Dispatching " + pdus.length + " emergency SMS CB pdus");
+            intent.putExtra("message", message);
+            Log.d(TAG, "Dispatching emergency SMS CB");
             dispatch(intent, RECEIVE_EMERGENCY_BROADCAST_PERMISSION);
         } else {
             Intent intent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
-            intent.putExtra("pdus", pdus);
-            Log.d(TAG, "Dispatching " + pdus.length + " SMS CB pdus");
+            intent.putExtra("message", message);
+            Log.d(TAG, "Dispatching SMS CB");
             dispatch(intent, RECEIVE_SMS_PERMISSION);
         }
     }
diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
index ca8d9ae..bfa23e7 100755
--- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java
@@ -30,8 +30,10 @@
 import android.preference.PreferenceManager;
 import android.provider.Telephony;
 import android.provider.Telephony.Sms.Intents;
+import android.telephony.SmsCbMessage;
 import android.telephony.SmsManager;
 import android.telephony.SmsMessage.MessageClass;
+import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.util.Log;
 
 import com.android.internal.telephony.CommandsInterface;
@@ -50,6 +52,7 @@
 import java.io.ByteArrayOutputStream;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.List;
 
 import android.content.res.Resources;
 
@@ -97,6 +100,24 @@
         }
     }
 
+    /**
+     * Dispatch service category program data to the CellBroadcastReceiver app, which filters
+     * the broadcast alerts to display.
+     * @param sms the SMS message containing one or more
+     * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects.
+     */
+    private void handleServiceCategoryProgramData(SmsMessage sms) {
+        List<CdmaSmsCbProgramData> programDataList = sms.getSmsCbProgramData();
+        if (programDataList == null) {
+            Log.e(TAG, "handleServiceCategoryProgramData: program data list is null!");
+            return;
+        }
+
+        Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION);
+        intent.putExtra("program_data_list", (CdmaSmsCbProgramData[]) programDataList.toArray());
+        dispatch(intent, RECEIVE_SMS_PERMISSION);
+    }
+
     /** {@inheritDoc} */
     @Override
     public int dispatchMessage(SmsMessageBase smsb) {
@@ -119,8 +140,19 @@
             return Intents.RESULT_SMS_HANDLED;
         }
 
-        // See if we have a network duplicate SMS.
         SmsMessage sms = (SmsMessage) smsb;
+
+        // Handle CMAS emergency broadcast messages.
+        if (SmsEnvelope.MESSAGE_TYPE_BROADCAST == sms.getMessageType()) {
+            Log.d(TAG, "Broadcast type message");
+            SmsCbMessage message = sms.parseBroadcastSms();
+            if (message != null) {
+                dispatchBroadcastMessage(message);
+            }
+            return Intents.RESULT_SMS_HANDLED;
+        }
+
+        // See if we have a network duplicate SMS.
         mLastDispatchedSmsFingerprint = sms.getIncomingSmsFingerprint();
         if (mLastAcknowledgedSmsFingerprint != null &&
                 Arrays.equals(mLastDispatchedSmsFingerprint, mLastAcknowledgedSmsFingerprint)) {
@@ -149,6 +181,9 @@
                 sms.isStatusReportMessage()) {
             handleCdmaStatusReport(sms);
             handled = true;
+        } else if (SmsEnvelope.TELESERVICE_SCPT == teleService) {
+            handleServiceCategoryProgramData(sms);
+            handled = true;
         } else if ((sms.getUserData() == null)) {
             if (false) {
                 Log.d(TAG, "Received SMS without user data");
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 1409cab..a723de3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -19,7 +19,11 @@
 import android.os.Parcel;
 import android.os.SystemProperties;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.telephony.cdma.CdmaSmsCbProgramData;
 import android.util.Log;
+
 import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase;
@@ -38,6 +42,9 @@
 import java.io.DataInputStream;
 import java.io.DataOutputStream;
 import java.io.IOException;
+import java.util.List;
+
+import static android.telephony.SmsMessage.MessageClass;
 
 /**
  * TODO(cleanup): these constants are disturbing... are they not just
@@ -47,12 +54,6 @@
  * named CdmaSmsMessage, could it not?
  */
 
-import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
-import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
-import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS;
-import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER;
-import static android.telephony.SmsMessage.MessageClass;
-
 /**
  * TODO(cleanup): internally returning null in many places makes
  * debugging very hard (among many other reasons) and should be made
@@ -192,16 +193,17 @@
 
         // bearer data
         countInt = p.readInt(); //p_cur->uBearerDataLen
-        if (countInt >0) {
-            data = new byte[countInt];
-             //p_cur->aBearerData[digitCount] :
-            for (int index=0; index < countInt; index++) {
-                data[index] = p.readByte();
-            }
-            env.bearerData = data;
-            // BD gets further decoded when accessed in SMSDispatcher
+        if (countInt < 0) {
+            countInt = 0;
         }
 
+        data = new byte[countInt];
+        for (int index=0; index < countInt; index++) {
+            data[index] = p.readByte();
+        }
+        // BD gets further decoded when accessed in SMSDispatcher
+        env.bearerData = data;
+
         // link the the filled objects to the SMS
         env.origAddress = addr;
         env.origSubaddress = subaddr;
@@ -732,6 +734,29 @@
     }
 
     /**
+     * Parses a broadcast SMS, possibly containing a CMAS alert.
+     */
+    SmsCbMessage parseBroadcastSms() {
+        BearerData bData = BearerData.decode(mEnvelope.bearerData, mEnvelope.serviceCategory);
+        if (bData == null) {
+            Log.w(LOG_TAG, "BearerData.decode() returned null");
+            return null;
+        }
+
+        if (Log.isLoggable(LOGGABLE_TAG, Log.VERBOSE)) {
+            Log.d(LOG_TAG, "MT raw BearerData = " + HexDump.toHexString(mEnvelope.bearerData));
+        }
+
+        String plmn = SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC);
+        SmsCbLocation location = new SmsCbLocation(plmn);
+
+        return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP2,
+                SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, bData.messageId, location,
+                mEnvelope.serviceCategory, bData.getLanguage(), bData.userData.payloadStr,
+                bData.priority, null, bData.cmasWarningInfo);
+    }
+
+    /**
      * {@inheritDoc}
      */
     public MessageClass getMessageClass() {
@@ -961,4 +986,12 @@
 
         return output.toByteArray();
     }
+
+    /**
+     * Returns the list of service category program data, if present.
+     * @return a list of CdmaSmsCbProgramData objects, or null if not present
+     */
+    List<CdmaSmsCbProgramData> getSmsCbProgramData() {
+        return mBearerData.serviceCategoryProgramData;
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index 6743da0..e70ff18 100755
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -16,28 +16,29 @@
 
 package com.android.internal.telephony.cdma.sms;
 
-import static android.telephony.SmsMessage.ENCODING_16BIT;
-import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
-import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
-
+import android.content.res.Resources;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbMessage;
+import android.telephony.SmsMessage;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.text.format.Time;
 import android.util.Log;
 
-import android.telephony.SmsMessage;
-
-import android.text.format.Time;
-
-import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
 import com.android.internal.telephony.SmsHeader;
 import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails;
-
 import com.android.internal.util.BitwiseInputStream;
 import com.android.internal.util.BitwiseOutputStream;
 
-import android.content.res.Resources;
-
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
 import java.util.TimeZone;
 
+import static android.telephony.SmsMessage.ENCODING_16BIT;
+import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES;
+import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER;
 
 /**
  * An object to encode and decode CDMA SMS bearer data.
@@ -68,8 +69,8 @@
     private final static byte SUBPARAM_MESSAGE_DISPLAY_MODE             = 0x0F;
     //private final static byte SUBPARAM_MULTIPLE_ENCODING_USER_DATA      = 0x10;
     private final static byte SUBPARAM_MESSAGE_DEPOSIT_INDEX            = 0x11;
-    //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA    = 0x12;
-    //private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
+    private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA    = 0x12;
+    private final static byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_RESULTS = 0x13;
     private final static byte SUBPARAM_MESSAGE_STATUS                   = 0x14;
     //private final static byte SUBPARAM_TP_FAILURE_CAUSE                 = 0x15;
     //private final static byte SUBPARAM_ENHANCED_VMN                     = 0x16;
@@ -339,12 +340,68 @@
      */
     public CdmaSmsAddress callbackNumber;
 
+    /**
+     * CMAS warning notification information.
+     * @see #decodeCmasUserData(BearerData, int)
+     */
+    public SmsCbCmasInfo cmasWarningInfo;
+
+    /**
+     * The Service Category Program Data subparameter is used to enable and disable
+     * SMS broadcast service categories to display. If this subparameter is present,
+     * this field will contain a list of one or more
+     * {@link android.telephony.cdma.CdmaSmsCbProgramData} objects containing the
+     * operation(s) to perform.
+     */
+    public List<CdmaSmsCbProgramData> serviceCategoryProgramData;
+
     private static class CodingException extends Exception {
         public CodingException(String s) {
             super(s);
         }
     }
 
+    /**
+     * Returns the language indicator as a two-character ISO 639 string.
+     * @return a two character ISO 639 language code
+     */
+    public String getLanguage() {
+        return getLanguageCodeForValue(language);
+    }
+
+    /**
+     * Converts a CDMA language indicator value to an ISO 639 two character language code.
+     * @param languageValue the CDMA language value to convert
+     * @return the two character ISO 639 language code for the specified value, or null if unknown
+     */
+    private static String getLanguageCodeForValue(int languageValue) {
+        switch (languageValue) {
+            case LANGUAGE_ENGLISH:
+                return "en";
+
+            case LANGUAGE_FRENCH:
+                return "fr";
+
+            case LANGUAGE_SPANISH:
+                return "es";
+
+            case LANGUAGE_JAPANESE:
+                return "ja";
+
+            case LANGUAGE_KOREAN:
+                return "ko";
+
+            case LANGUAGE_CHINESE:
+                return "zh";
+
+            case LANGUAGE_HEBREW:
+                return "he";
+
+            default:
+                return null;
+        }
+    }
+
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
@@ -916,6 +973,9 @@
     private static String decodeUtf8(byte[] data, int offset, int numFields)
         throws CodingException
     {
+        if (numFields < 0 || (numFields + offset) > data.length) {
+            throw new CodingException("UTF-8 decode failed: offset or length out of range");
+        }
         try {
             return new String(data, offset, numFields, "UTF-8");
         } catch (java.io.UnsupportedEncodingException ex) {
@@ -926,11 +986,12 @@
     private static String decodeUtf16(byte[] data, int offset, int numFields)
         throws CodingException
     {
-        // Start reading from the next 16-bit aligned boundary after offset.
-        int padding = offset % 2;
-        numFields -= (offset + padding) / 2;
+        int byteCount = numFields * 2;
+        if (byteCount < 0 || (byteCount + offset) > data.length) {
+            throw new CodingException("UTF-16 decode failed: offset or length out of range");
+        }
         try {
-            return new String(data, offset, numFields * 2, "utf-16be");
+            return new String(data, offset, byteCount, "utf-16be");
         } catch (java.io.UnsupportedEncodingException ex) {
             throw new CodingException("UTF-16 decode failed: " + ex);
         }
@@ -988,8 +1049,11 @@
     private static String decodeLatin(byte[] data, int offset, int numFields)
         throws CodingException
     {
+        if (numFields < 0 || (numFields + offset) > data.length) {
+            throw new CodingException("ISO-8859-1 decode failed: offset or length out of range");
+        }
         try {
-            return new String(data, offset, numFields - offset, "ISO-8859-1");
+            return new String(data, offset, numFields, "ISO-8859-1");
         } catch (java.io.UnsupportedEncodingException ex) {
             throw new CodingException("ISO-8859-1 decode failed: " + ex);
         }
@@ -1032,6 +1096,7 @@
                 userData.payloadStr = decodeUtf8(userData.payload, offset, userData.numFields);
             }
             break;
+
         case UserData.ENCODING_IA5:
         case UserData.ENCODING_7BIT_ASCII:
             userData.payloadStr = decode7bitAscii(userData.payload, offset, userData.numFields);
@@ -1114,8 +1179,9 @@
         BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
         int dataLen = inStream.available() / 6;  // 6-bit packed character encoding.
         int numFields = bData.userData.numFields;
-        if ((dataLen > 14) || (dataLen < numFields)) {
-            throw new CodingException("IS-91 voicemail status decoding failed");
+        // dataLen may be > 14 characters due to octet padding
+        if ((numFields > 14) || (dataLen < numFields)) {
+            throw new CodingException("IS-91 short message decoding failed");
         }
         StringBuffer strbuf = new StringBuffer(dataLen);
         for (int i = 0; i < numFields; i++) {
@@ -1539,7 +1605,7 @@
             bData.userResponseCode = inStream.read(8);
         }
         if ((! decodeSuccess) || (paramBits > 0)) {
-            Log.d(LOG_TAG, "USER_REPONSE_CODE decode " +
+            Log.d(LOG_TAG, "USER_RESPONSE_CODE decode " +
                       (decodeSuccess ? "succeeded" : "failed") +
                       " (extra bits = " + paramBits + ")");
         }
@@ -1548,27 +1614,240 @@
         return decodeSuccess;
     }
 
+    private static boolean decodeServiceCategoryProgramData(BearerData bData,
+            BitwiseInputStream inStream) throws BitwiseInputStream.AccessException, CodingException
+    {
+        if (inStream.available() < 13) {
+            throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+                    + inStream.available() + " bits available");
+        }
+
+        int paramBits = inStream.read(8) * 8;
+        int msgEncoding = inStream.read(5);
+        paramBits -= 5;
+
+        if (inStream.available() < paramBits) {
+            throw new CodingException("SERVICE_CATEGORY_PROGRAM_DATA decode failed: only "
+                    + inStream.available() + " bits available (" + paramBits + " bits expected)");
+        }
+
+        ArrayList<CdmaSmsCbProgramData> programDataList = new ArrayList<CdmaSmsCbProgramData>();
+
+        final int CATEGORY_FIELD_MIN_SIZE = 6 * 8;
+        boolean decodeSuccess = false;
+        while (paramBits >= CATEGORY_FIELD_MIN_SIZE) {
+            int operation = inStream.read(4);
+            int category = (inStream.read(8) << 8) | inStream.read(8);
+            String language = getLanguageCodeForValue(inStream.read(8));
+            int maxMessages = inStream.read(8);
+            int alertOption = inStream.read(4);
+            int numFields = inStream.read(8);
+            paramBits -= CATEGORY_FIELD_MIN_SIZE;
+
+            int textBits = getBitsForNumFields(msgEncoding, numFields);
+            if (paramBits < textBits) {
+                throw new CodingException("category name is " + textBits + " bits in length,"
+                        + " but there are only " + paramBits + " bits available");
+            }
+
+            UserData userData = new UserData();
+            userData.msgEncoding = msgEncoding;
+            userData.msgEncodingSet = true;
+            userData.numFields = numFields;
+            userData.payload = inStream.readByteArray(textBits);
+            paramBits -= textBits;
+
+            decodeUserDataPayload(userData, false);
+            String categoryName = userData.payloadStr;
+            CdmaSmsCbProgramData programData = new CdmaSmsCbProgramData(operation, category,
+                    language, maxMessages, alertOption, categoryName);
+            programDataList.add(programData);
+
+            decodeSuccess = true;
+        }
+
+        if ((! decodeSuccess) || (paramBits > 0)) {
+            Log.d(LOG_TAG, "SERVICE_CATEGORY_PROGRAM_DATA decode " +
+                      (decodeSuccess ? "succeeded" : "failed") +
+                      " (extra bits = " + paramBits + ')');
+        }
+
+        inStream.skip(paramBits);
+        bData.serviceCategoryProgramData = programDataList;
+        return decodeSuccess;
+    }
+
+    private static int serviceCategoryToCmasMessageClass(int serviceCategory) {
+        switch (serviceCategory) {
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT:
+                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT:
+                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT:
+                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY:
+                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+            case SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE:
+                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+            default:
+                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
+        }
+    }
+
+    /**
+     * Calculates the number of bits to read for the specified number of encoded characters.
+     * @param msgEncoding the message encoding to use
+     * @param numFields the number of characters to read. For Shift-JIS and Korean encodings,
+     *  this is the number of bytes to read.
+     * @return the number of bits to read from the stream
+     * @throws CodingException if the specified encoding is not supported
+     */
+    private static int getBitsForNumFields(int msgEncoding, int numFields)
+            throws CodingException {
+        switch (msgEncoding) {
+            case UserData.ENCODING_OCTET:
+            case UserData.ENCODING_SHIFT_JIS:
+            case UserData.ENCODING_KOREAN:
+            case UserData.ENCODING_LATIN:
+            case UserData.ENCODING_LATIN_HEBREW:
+                return numFields * 8;
+
+            case UserData.ENCODING_IA5:
+            case UserData.ENCODING_7BIT_ASCII:
+            case UserData.ENCODING_GSM_7BIT_ALPHABET:
+                return numFields * 7;
+
+            case UserData.ENCODING_UNICODE_16:
+                return numFields * 16;
+
+            default:
+                throw new CodingException("unsupported message encoding (" + msgEncoding + ')');
+        }
+    }
+
+    /**
+     * CMAS message decoding.
+     * (See TIA-1149-0-1, CMAS over CDMA)
+     *
+     * @param serviceCategory is the service category from the SMS envelope
+     */
+    private static void decodeCmasUserData(BearerData bData, int serviceCategory)
+            throws BitwiseInputStream.AccessException, CodingException {
+        BitwiseInputStream inStream = new BitwiseInputStream(bData.userData.payload);
+        if (inStream.available() < 8) {
+            throw new CodingException("emergency CB with no CMAE_protocol_version");
+        }
+        int protocolVersion = inStream.read(8);
+        if (protocolVersion != 0) {
+            throw new CodingException("unsupported CMAE_protocol_version " + protocolVersion);
+        }
+
+        int messageClass = serviceCategoryToCmasMessageClass(serviceCategory);
+        int category = SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN;
+        int responseType = SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN;
+        int severity = SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+        int urgency = SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+        int certainty = SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+
+        while (inStream.available() >= 16) {
+            int recordType = inStream.read(8);
+            int recordLen = inStream.read(8);
+            switch (recordType) {
+                case 0:     // Type 0 elements (Alert text)
+                    UserData alertUserData = new UserData();
+                    alertUserData.msgEncoding = inStream.read(5);
+                    alertUserData.msgEncodingSet = true;
+                    alertUserData.msgType = 0;
+
+                    int numFields;                          // number of chars to decode
+                    switch (alertUserData.msgEncoding) {
+                        case UserData.ENCODING_OCTET:
+                        case UserData.ENCODING_LATIN:
+                            numFields = recordLen - 1;      // subtract 1 byte for encoding
+                            break;
+
+                        case UserData.ENCODING_IA5:
+                        case UserData.ENCODING_7BIT_ASCII:
+                        case UserData.ENCODING_GSM_7BIT_ALPHABET:
+                            numFields = ((recordLen * 8) - 5) / 7;  // subtract 5 bits for encoding
+                            break;
+
+                        case UserData.ENCODING_UNICODE_16:
+                            numFields = (recordLen - 1) / 2;
+                            break;
+
+                        default:
+                            numFields = 0;      // unsupported encoding
+                    }
+
+                    alertUserData.numFields = numFields;
+                    alertUserData.payload = inStream.readByteArray(recordLen * 8 - 5);
+                    decodeUserDataPayload(alertUserData, false);
+                    bData.userData = alertUserData;
+                    break;
+
+                case 1:     // Type 1 elements
+                    category = inStream.read(8);
+                    responseType = inStream.read(8);
+                    severity = inStream.read(4);
+                    urgency = inStream.read(4);
+                    certainty = inStream.read(4);
+                    inStream.skip(recordLen * 8 - 28);
+                    break;
+
+                default:
+                    Log.w(LOG_TAG, "skipping unsupported CMAS record type " + recordType);
+                    inStream.skip(recordLen * 8);
+                    break;
+            }
+        }
+
+        bData.cmasWarningInfo = new SmsCbCmasInfo(messageClass, category, responseType, severity,
+                urgency, certainty);
+    }
+
     /**
      * Create BearerData object from serialized representation.
      * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
      *
      * @param smsData byte array of raw encoded SMS bearer data.
-     *
      * @return an instance of BearerData.
      */
     public static BearerData decode(byte[] smsData) {
+        return decode(smsData, 0);
+    }
+
+    private static boolean isCmasAlertCategory(int category) {
+        return category >= SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT
+                && category <= SmsEnvelope.SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE;
+    }
+
+    /**
+     * Create BearerData object from serialized representation.
+     * (See 3GPP2 C.R1001-F, v1.0, section 4.5 for layout details)
+     *
+     * @param smsData byte array of raw encoded SMS bearer data.
+     * @param serviceCategory the envelope service category (for CMAS alert handling)
+     * @return an instance of BearerData.
+     */
+    public static BearerData decode(byte[] smsData, int serviceCategory) {
         try {
             BitwiseInputStream inStream = new BitwiseInputStream(smsData);
             BearerData bData = new BearerData();
             int foundSubparamMask = 0;
             while (inStream.available() > 0) {
-                boolean decodeSuccess = false;
                 int subparamId = inStream.read(8);
                 int subparamIdBit = 1 << subparamId;
                 if ((foundSubparamMask & subparamIdBit) != 0) {
                     throw new CodingException("illegal duplicate subparameter (" +
                                               subparamId + ")");
                 }
+                boolean decodeSuccess;
                 switch (subparamId) {
                 case SUBPARAM_MESSAGE_IDENTIFIER:
                     decodeSuccess = decodeMessageId(bData, inStream);
@@ -1624,6 +1903,9 @@
                 case SUBPARAM_MESSAGE_DEPOSIT_INDEX:
                     decodeSuccess = decodeDepositIndex(bData, inStream);
                     break;
+                case SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA:
+                    decodeSuccess = decodeServiceCategoryProgramData(bData, inStream);
+                    break;
                 default:
                     throw new CodingException("unsupported bearer data subparameter ("
                                               + subparamId + ")");
@@ -1634,7 +1916,10 @@
                 throw new CodingException("missing MESSAGE_IDENTIFIER subparam");
             }
             if (bData.userData != null) {
-                if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+                if (isCmasAlertCategory(serviceCategory) && bData.priorityIndicatorSet
+                        && bData.priority == SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY) {
+                    decodeCmasUserData(bData, serviceCategory);
+                } else if (bData.userData.msgEncoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
                     if ((foundSubparamMask ^
                              (1 << SUBPARAM_MESSAGE_IDENTIFIER) ^
                              (1 << SUBPARAM_USER_DATA))
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index 4a60231..f73df56 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -37,6 +37,7 @@
     static public final int TELESERVICE_VMN               = 0x1003;
     static public final int TELESERVICE_WAP               = 0x1004;
     static public final int TELESERVICE_WEMT              = 0x1005;
+    static public final int TELESERVICE_SCPT              = 0x1006;
 
     /**
      * The following are defined as extensions to the standard teleservices
@@ -46,14 +47,17 @@
     // number of messages waiting, it's used by some CDMA carriers for a voice mail count.
     static public final int TELESERVICE_MWI               = 0x40000;
 
-    // ServiceCategories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
-    //static public final int SERVICECATEGORY_EMERGENCY      = 0x0010;
+    // Service Categories for Cell Broadcast, see 3GPP2 C.R1001 table 9.3.1-1
+    // static final int SERVICE_CATEGORY_EMERGENCY      = 0x0001;
     //...
 
-    /**
-     *  maximum lengths for fields as defined in ril_cdma_sms.h
-     */
-    static public final int SMS_BEARER_DATA_MAX = 255;
+    // CMAS alert service category assignments, see 3GPP2 C.R1001 table 9.3.3-1
+    public static final int SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT  = 0x1000;
+    public static final int SERVICE_CATEGORY_CMAS_EXTREME_THREAT            = 0x1001;
+    public static final int SERVICE_CATEGORY_CMAS_SEVERE_THREAT             = 0x1002;
+    public static final int SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY = 0x1003;
+    public static final int SERVICE_CATEGORY_CMAS_TEST_MESSAGE              = 0x1004;
+    public static final int SERVICE_CATEGORY_CMAS_LAST_RESERVED_VALUE       = 0x10ff;
 
     /**
      * Provides the type of a SMS message like point to point, broadcast or acknowledge
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
index 189d97d..599c2b3 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java
@@ -32,9 +32,9 @@
     public static final int ENCODING_7BIT_ASCII                 = 0x02;
     public static final int ENCODING_IA5                        = 0x03;
     public static final int ENCODING_UNICODE_16                 = 0x04;
-    //public static final int ENCODING_SHIFT_JIS                  = 0x05;
-    //public static final int ENCODING_KOREAN                     = 0x06;
-    //public static final int ENCODING_LATIN_HEBREW               = 0x07;
+    public static final int ENCODING_SHIFT_JIS                  = 0x05;
+    public static final int ENCODING_KOREAN                     = 0x06;
+    public static final int ENCODING_LATIN_HEBREW               = 0x07;
     public static final int ENCODING_LATIN                      = 0x08;
     public static final int ENCODING_GSM_7BIT_ALPHABET          = 0x09;
     public static final int ENCODING_GSM_DCS                    = 0x0A;
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
index 8a75f51..931c662 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSMSDispatcher.java
@@ -26,6 +26,7 @@
 import android.provider.Telephony.Sms;
 import android.provider.Telephony.Sms.Intents;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.telephony.SmsManager;
 import android.telephony.gsm.GsmCellLocation;
@@ -318,24 +319,18 @@
      * concatenated message
      */
     private static final class SmsCbConcatInfo {
+
         private final SmsCbHeader mHeader;
+        private final SmsCbLocation mLocation;
 
-        private final String mPlmn;
-
-        private final int mLac;
-
-        private final int mCid;
-
-        public SmsCbConcatInfo(SmsCbHeader header, String plmn, int lac, int cid) {
+        public SmsCbConcatInfo(SmsCbHeader header, SmsCbLocation location) {
             mHeader = header;
-            mPlmn = plmn;
-            mLac = lac;
-            mCid = cid;
+            mLocation = location;
         }
 
         @Override
         public int hashCode() {
-            return mHeader.messageIdentifier * 31 + mHeader.updateNumber;
+            return (mHeader.getSerialNumber() * 31) + mLocation.hashCode();
         }
 
         @Override
@@ -343,49 +338,28 @@
             if (obj instanceof SmsCbConcatInfo) {
                 SmsCbConcatInfo other = (SmsCbConcatInfo)obj;
 
-                // Two pages match if all header attributes (except the page
-                // index) are identical, and both pages belong to the same
-                // location (which is also determined by the scope parameter)
-                if (mHeader.geographicalScope == other.mHeader.geographicalScope
-                        && mHeader.messageCode == other.mHeader.messageCode
-                        && mHeader.updateNumber == other.mHeader.updateNumber
-                        && mHeader.messageIdentifier == other.mHeader.messageIdentifier
-                        && mHeader.dataCodingScheme == other.mHeader.dataCodingScheme
-                        && mHeader.nrOfPages == other.mHeader.nrOfPages) {
-                    return matchesLocation(other.mPlmn, other.mLac, other.mCid);
-                }
+                // Two pages match if they have the same serial number (which includes the
+                // geographical scope and update number), and both pages belong to the same
+                // location (PLMN, plus LAC and CID if these are part of the geographical scope).
+                return mHeader.getSerialNumber() == other.mHeader.getSerialNumber()
+                        && mLocation.equals(other.mLocation);
             }
 
             return false;
         }
 
         /**
-         * Checks if this concatenation info matches the given location. The
-         * granularity of the match depends on the geographical scope.
+         * Compare the location code for this message to the current location code. The match is
+         * relative to the geographical scope of the message, which determines whether the LAC
+         * and Cell ID are saved in mLocation or set to -1 to match all values.
          *
-         * @param plmn PLMN
-         * @param lac Location area code
-         * @param cid Cell ID
-         * @return true if matching, false otherwise
+         * @param plmn the current PLMN
+         * @param lac the current Location Area (GSM) or Service Area (UMTS)
+         * @param cid the current Cell ID
+         * @return true if this message is valid for the current location; false otherwise
          */
         public boolean matchesLocation(String plmn, int lac, int cid) {
-            switch (mHeader.geographicalScope) {
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
-                    if (mCid != cid) {
-                        return false;
-                    }
-                    // deliberate fall-through
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
-                    if (mLac != lac) {
-                        return false;
-                    }
-                    // deliberate fall-through
-                case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
-                    return mPlmn != null && mPlmn.equals(plmn);
-            }
-
-            return false;
+            return mLocation.isInLocationArea(plmn, lac, cid);
         }
     }
 
@@ -421,24 +395,42 @@
             int lac = cellLocation.getLac();
             int cid = cellLocation.getCid();
 
+            SmsCbLocation location;
+            switch (header.getGeographicalScope()) {
+                case SmsCbMessage.GEOGRAPHICAL_SCOPE_LA_WIDE:
+                    location = new SmsCbLocation(plmn, lac, -1);
+                    break;
+
+                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE:
+                case SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
+                    location = new SmsCbLocation(plmn, lac, cid);
+                    break;
+
+                case SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE:
+                default:
+                    location = new SmsCbLocation(plmn);
+                    break;
+            }
+
             byte[][] pdus;
-            if (header.nrOfPages > 1) {
+            int pageCount = header.getNumberOfPages();
+            if (pageCount > 1) {
                 // Multi-page message
-                SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, plmn, lac, cid);
+                SmsCbConcatInfo concatInfo = new SmsCbConcatInfo(header, location);
 
                 // Try to find other pages of the same message
                 pdus = mSmsCbPageMap.get(concatInfo);
 
                 if (pdus == null) {
-                    // This it the first page of this message, make room for all
+                    // This is the first page of this message, make room for all
                     // pages and keep until complete
-                    pdus = new byte[header.nrOfPages][];
+                    pdus = new byte[pageCount][];
 
                     mSmsCbPageMap.put(concatInfo, pdus);
                 }
 
                 // Page parameter is one-based
-                pdus[header.pageIndex - 1] = receivedPdu;
+                pdus[header.getPageIndex() - 1] = receivedPdu;
 
                 for (int i = 0; i < pdus.length; i++) {
                     if (pdus[i] == null) {
@@ -455,8 +447,8 @@
                 pdus[0] = receivedPdu;
             }
 
-            boolean isEmergencyMessage = SmsCbHeader.isEmergencyMessage(header.messageIdentifier);
-            dispatchBroadcastPdus(pdus, isEmergencyMessage);
+            SmsCbMessage message = GsmSmsCbMessage.createSmsCbMessage(header, location, pdus);
+            dispatchBroadcastMessage(message);
 
             // Remove messages that are out of scope to prevent the map from
             // growing indefinitely, containing incomplete messages that were
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
new file mode 100644
index 0000000..dc9554a
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsCbMessage.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.gsm;
+
+import android.telephony.SmsCbLocation;
+import android.telephony.SmsCbMessage;
+import android.util.Pair;
+
+import com.android.internal.telephony.GsmAlphabet;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Parses a GSM or UMTS format SMS-CB message into an {@link SmsCbMessage} object. The class is
+ * public because {@link #createSmsCbMessage(SmsCbLocation, byte[][])} is used by some test cases.
+ */
+public class GsmSmsCbMessage {
+
+    /**
+     * Languages in the 0000xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+     */
+    private static final String[] LANGUAGE_CODES_GROUP_0 = {
+            "de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi", "no", "el", "tr", "hu",
+            "pl", null
+    };
+
+    /**
+     * Languages in the 0010xxxx DCS group as defined in 3GPP TS 23.038, section 5.
+     */
+    private static final String[] LANGUAGE_CODES_GROUP_2 = {
+            "cs", "he", "ar", "ru", "is", null, null, null, null, null, null, null, null, null,
+            null, null
+    };
+
+    private static final char CARRIAGE_RETURN = 0x0d;
+
+    private static final int PDU_BODY_PAGE_LENGTH = 82;
+
+    /** Utility class with only static methods. */
+    private GsmSmsCbMessage() { }
+
+    /**
+     * Create a new SmsCbMessage object from a header object plus one or more received PDUs.
+     *
+     * @param pdus PDU bytes
+     */
+    static SmsCbMessage createSmsCbMessage(SmsCbHeader header, SmsCbLocation location,
+            byte[][] pdus) throws IllegalArgumentException {
+        if (header.isEtwsPrimaryNotification()) {
+            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+                    header.getGeographicalScope(), header.getSerialNumber(),
+                    location, header.getServiceCategory(),
+                    null, "ETWS", SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
+                    header.getEtwsInfo(), header.getCmasInfo());
+        } else {
+            String language = null;
+            StringBuilder sb = new StringBuilder();
+            for (byte[] pdu : pdus) {
+                Pair<String, String> p = parseBody(header, pdu);
+                language = p.first;
+                sb.append(p.second);
+            }
+            int priority = header.isEmergencyMessage() ? SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY
+                    : SmsCbMessage.MESSAGE_PRIORITY_NORMAL;
+
+            return new SmsCbMessage(SmsCbMessage.MESSAGE_FORMAT_3GPP,
+                    header.getGeographicalScope(), header.getSerialNumber(), location,
+                    header.getServiceCategory(), language, sb.toString(), priority,
+                    header.getEtwsInfo(), header.getCmasInfo());
+        }
+    }
+
+    /**
+     * Create a new SmsCbMessage object from one or more received PDUs. This is used by some
+     * CellBroadcastReceiver test cases, because SmsCbHeader is now package local.
+     *
+     * @param location the location (geographical scope) for the message
+     * @param pdus PDU bytes
+     */
+    public static SmsCbMessage createSmsCbMessage(SmsCbLocation location, byte[][] pdus)
+            throws IllegalArgumentException {
+        SmsCbHeader header = new SmsCbHeader(pdus[0]);
+        return createSmsCbMessage(header, location, pdus);
+    }
+
+    /**
+     * Parse and unpack the body text according to the encoding in the DCS.
+     * After completing successfully this method will have assigned the body
+     * text into mBody, and optionally the language code into mLanguage
+     *
+     * @param header the message header to use
+     * @param pdu the PDU to decode
+     * @return a Pair of Strings containing the language and body of the message
+     */
+    private static Pair<String, String> parseBody(SmsCbHeader header, byte[] pdu) {
+        int encoding;
+        String language = null;
+        boolean hasLanguageIndicator = false;
+        int dataCodingScheme = header.getDataCodingScheme();
+
+        // Extract encoding and language from DCS, as defined in 3gpp TS 23.038,
+        // section 5.
+        switch ((dataCodingScheme & 0xf0) >> 4) {
+            case 0x00:
+                encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                language = LANGUAGE_CODES_GROUP_0[dataCodingScheme & 0x0f];
+                break;
+
+            case 0x01:
+                hasLanguageIndicator = true;
+                if ((dataCodingScheme & 0x0f) == 0x01) {
+                    encoding = android.telephony.SmsMessage.ENCODING_16BIT;
+                } else {
+                    encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                }
+                break;
+
+            case 0x02:
+                encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                language = LANGUAGE_CODES_GROUP_2[dataCodingScheme & 0x0f];
+                break;
+
+            case 0x03:
+                encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                break;
+
+            case 0x04:
+            case 0x05:
+                switch ((dataCodingScheme & 0x0c) >> 2) {
+                    case 0x01:
+                        encoding = android.telephony.SmsMessage.ENCODING_8BIT;
+                        break;
+
+                    case 0x02:
+                        encoding = android.telephony.SmsMessage.ENCODING_16BIT;
+                        break;
+
+                    case 0x00:
+                    default:
+                        encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                        break;
+                }
+                break;
+
+            case 0x06:
+            case 0x07:
+                // Compression not supported
+            case 0x09:
+                // UDH structure not supported
+            case 0x0e:
+                // Defined by the WAP forum not supported
+                throw new IllegalArgumentException("Unsupported GSM dataCodingScheme "
+                        + dataCodingScheme);
+
+            case 0x0f:
+                if (((dataCodingScheme & 0x04) >> 2) == 0x01) {
+                    encoding = android.telephony.SmsMessage.ENCODING_8BIT;
+                } else {
+                    encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                }
+                break;
+
+            default:
+                // Reserved values are to be treated as 7-bit
+                encoding = android.telephony.SmsMessage.ENCODING_7BIT;
+                break;
+        }
+
+        if (header.isUmtsFormat()) {
+            // Payload may contain multiple pages
+            int nrPages = pdu[SmsCbHeader.PDU_HEADER_LENGTH];
+
+            if (pdu.length < SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1)
+                    * nrPages) {
+                throw new IllegalArgumentException("Pdu length " + pdu.length + " does not match "
+                        + nrPages + " pages");
+            }
+
+            StringBuilder sb = new StringBuilder();
+
+            for (int i = 0; i < nrPages; i++) {
+                // Each page is 82 bytes followed by a length octet indicating
+                // the number of useful octets within those 82
+                int offset = SmsCbHeader.PDU_HEADER_LENGTH + 1 + (PDU_BODY_PAGE_LENGTH + 1) * i;
+                int length = pdu[offset + PDU_BODY_PAGE_LENGTH];
+
+                if (length > PDU_BODY_PAGE_LENGTH) {
+                    throw new IllegalArgumentException("Page length " + length
+                            + " exceeds maximum value " + PDU_BODY_PAGE_LENGTH);
+                }
+
+                Pair<String, String> p = unpackBody(pdu, encoding, offset, length,
+                        hasLanguageIndicator, language);
+                language = p.first;
+                sb.append(p.second);
+            }
+            return new Pair<String, String>(language, sb.toString());
+        } else {
+            // Payload is one single page
+            int offset = SmsCbHeader.PDU_HEADER_LENGTH;
+            int length = pdu.length - offset;
+
+            return unpackBody(pdu, encoding, offset, length, hasLanguageIndicator, language);
+        }
+    }
+
+    /**
+     * Unpack body text from the pdu using the given encoding, position and
+     * length within the pdu
+     *
+     * @param pdu The pdu
+     * @param encoding The encoding, as derived from the DCS
+     * @param offset Position of the first byte to unpack
+     * @param length Number of bytes to unpack
+     * @param hasLanguageIndicator true if the body text is preceded by a
+     *            language indicator. If so, this method will as a side-effect
+     *            assign the extracted language code into mLanguage
+     * @param language the language to return if hasLanguageIndicator is false
+     * @return a Pair of Strings containing the language and body of the message
+     */
+    private static Pair<String, String> unpackBody(byte[] pdu, int encoding, int offset, int length,
+            boolean hasLanguageIndicator, String language) {
+        String body = null;
+
+        switch (encoding) {
+            case android.telephony.SmsMessage.ENCODING_7BIT:
+                body = GsmAlphabet.gsm7BitPackedToString(pdu, offset, length * 8 / 7);
+
+                if (hasLanguageIndicator && body != null && body.length() > 2) {
+                    // Language is two GSM characters followed by a CR.
+                    // The actual body text is offset by 3 characters.
+                    language = body.substring(0, 2);
+                    body = body.substring(3);
+                }
+                break;
+
+            case android.telephony.SmsMessage.ENCODING_16BIT:
+                if (hasLanguageIndicator && pdu.length >= offset + 2) {
+                    // Language is two GSM characters.
+                    // The actual body text is offset by 2 bytes.
+                    language = GsmAlphabet.gsm7BitPackedToString(pdu, offset, 2);
+                    offset += 2;
+                    length -= 2;
+                }
+
+                try {
+                    body = new String(pdu, offset, (length & 0xfffe), "utf-16");
+                } catch (UnsupportedEncodingException e) {
+                    // Apparently it wasn't valid UTF-16.
+                    throw new IllegalArgumentException("Error decoding UTF-16 message", e);
+                }
+                break;
+
+            default:
+                break;
+        }
+
+        if (body != null) {
+            // Remove trailing carriage return
+            for (int i = body.length() - 1; i >= 0; i--) {
+                if (body.charAt(i) != CARRIAGE_RETURN) {
+                    body = body.substring(0, i + 1);
+                    break;
+                }
+            }
+        } else {
+            body = "";
+        }
+
+        return new Pair<String, String>(language, body);
+    }
+}
diff --git a/telephony/java/android/telephony/SmsCbConstants.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
similarity index 84%
rename from telephony/java/android/telephony/SmsCbConstants.java
rename to telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
index a1b4adf..ebb4666 100644
--- a/telephony/java/android/telephony/SmsCbConstants.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2011 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,22 @@
  * limitations under the License.
  */
 
-package android.telephony;
+package com.android.internal.telephony.gsm;
 
 /**
- * Constants used in SMS Cell Broadcast messages.
+ * Constants used in SMS Cell Broadcast messages (see 3GPP TS 23.041). This class is used by the
+ * boot-time broadcast channel enable and database upgrade code in CellBroadcastReceiver, so it
+ * is public, but should be avoided in favor of the radio technology independent constants in
+ * {@link android.telephony.SmsCbMessage}, {@link android.telephony.SmsCbEtwsInfo}, and
+ * {@link android.telephony.SmsCbCmasInfo} classes.
  *
  * {@hide}
  */
-public interface SmsCbConstants {
+public class SmsCbConstants {
+
+    /** Private constructor for utility class. */
+    private SmsCbConstants() { }
+
     /** Start of PWS Message Identifier range (includes ETWS and CMAS). */
     public static final int MESSAGE_ID_PWS_FIRST_IDENTIFIER = 0x1100;
 
@@ -94,11 +102,11 @@
     /** End of PWS Message Identifier range (includes ETWS, CMAS, and future extensions). */
     public static final int MESSAGE_ID_PWS_LAST_IDENTIFIER                  = 0x18FF;
 
-    /** ETWS message code flag to activate the popup display. */
-    public static final int MESSAGE_CODE_ETWS_ACTIVATE_POPUP                = 0x100;
+    /** ETWS serial number flag to activate the popup display. */
+    public static final int SERIAL_NUMBER_ETWS_ACTIVATE_POPUP                = 0x1000;
 
-    /** ETWS message code flag to activate the emergency user alert. */
-    public static final int MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT          = 0x200;
+    /** ETWS serial number flag to activate the emergency user alert. */
+    public static final int SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT          = 0x2000;
 
     /** ETWS warning type value for earthquake. */
     public static final int ETWS_WARNING_TYPE_EARTHQUAKE                    = 0x00;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
index 8e6b79b..5692044 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -16,28 +16,42 @@
 
 package com.android.internal.telephony.gsm;
 
-import android.telephony.SmsCbConstants;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbEtwsInfo;
 
-public class SmsCbHeader implements SmsCbConstants {
+import java.util.Arrays;
+
+/**
+ * Parses a 3GPP TS 23.041 cell broadcast message header. This class is public for use by
+ * CellBroadcastReceiver test cases, but should not be used by applications.
+ *
+ * All relevant header information is now sent as a Parcelable
+ * {@link android.telephony.SmsCbMessage} object in the "message" extra of the
+ * {@link android.provider.Telephony.Sms.Intents#SMS_CB_RECEIVED_ACTION} or
+ * {@link android.provider.Telephony.Sms.Intents#SMS_EMERGENCY_CB_RECEIVED_ACTION} intent.
+ * The raw PDU is no longer sent to SMS CB applications.
+ */
+class SmsCbHeader {
+
     /**
      * Length of SMS-CB header
      */
-    public static final int PDU_HEADER_LENGTH = 6;
+    static final int PDU_HEADER_LENGTH = 6;
 
     /**
      * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1
      */
-    public static final int FORMAT_GSM = 1;
+    static final int FORMAT_GSM = 1;
 
     /**
      * UMTS pdu format, as defined in 3gpp TS 23.041, section 9.4.2
      */
-    public static final int FORMAT_UMTS = 2;
+    static final int FORMAT_UMTS = 2;
 
     /**
      * GSM pdu format, as defined in 3gpp TS 23.041, section 9.4.1.3
      */
-    public static final int FORMAT_ETWS_PRIMARY = 3;
+    static final int FORMAT_ETWS_PRIMARY = 3;
 
     /**
      * Message type value as defined in 3gpp TS 25.324, section 11.1.
@@ -47,34 +61,34 @@
     /**
      * Length of GSM pdus
      */
-    public static final int PDU_LENGTH_GSM = 88;
+    private static final int PDU_LENGTH_GSM = 88;
 
     /**
      * Maximum length of ETWS primary message GSM pdus
      */
-    public static final int PDU_LENGTH_ETWS = 56;
+    private static final int PDU_LENGTH_ETWS = 56;
 
-    public final int geographicalScope;
+    private final int geographicalScope;
 
-    public final int messageCode;
+    /** The serial number combines geographical scope, message code, and update number. */
+    private final int serialNumber;
 
-    public final int updateNumber;
+    /** The Message Identifier in 3GPP is the same as the Service Category in CDMA. */
+    private final int messageIdentifier;
 
-    public final int messageIdentifier;
+    private final int dataCodingScheme;
 
-    public final int dataCodingScheme;
+    private final int pageIndex;
 
-    public final int pageIndex;
+    private final int nrOfPages;
 
-    public final int nrOfPages;
+    private final int format;
 
-    public final int format;
+    /** ETWS warning notification info. */
+    private final SmsCbEtwsInfo mEtwsInfo;
 
-    public final boolean etwsEmergencyUserAlert;
-
-    public final boolean etwsPopup;
-
-    public final int etwsWarningType;
+    /** CMAS warning notification info. */
+    private final SmsCbCmasInfo mCmasInfo;
 
     public SmsCbHeader(byte[] pdu) throws IllegalArgumentException {
         if (pdu == null || pdu.length < PDU_HEADER_LENGTH) {
@@ -83,22 +97,31 @@
 
         if (pdu.length <= PDU_LENGTH_ETWS) {
             format = FORMAT_ETWS_PRIMARY;
-            geographicalScope = -1; //not applicable
-            messageCode = -1;
-            updateNumber = -1;
+            geographicalScope = (pdu[0] & 0xc0) >> 6;
+            serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
             messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
             dataCodingScheme = -1;
             pageIndex = -1;
             nrOfPages = -1;
-            etwsEmergencyUserAlert = (pdu[4] & 0x1) != 0;
-            etwsPopup = (pdu[5] & 0x80) != 0;
-            etwsWarningType = (pdu[4] & 0xfe) >> 1;
+            boolean emergencyUserAlert = (pdu[4] & 0x1) != 0;
+            boolean activatePopup = (pdu[5] & 0x80) != 0;
+            int warningType = (pdu[4] & 0xfe) >> 1;
+            byte[] warningSecurityInfo;
+            // copy the Warning-Security-Information, if present
+            if (pdu.length > PDU_HEADER_LENGTH) {
+                warningSecurityInfo = Arrays.copyOfRange(pdu, 6, pdu.length);
+            } else {
+                warningSecurityInfo = null;
+            }
+            mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup,
+                    warningSecurityInfo);
+            mCmasInfo = null;
+            return;     // skip the ETWS/CMAS initialization code for regular notifications
         } else if (pdu.length <= PDU_LENGTH_GSM) {
             // GSM pdus are no more than 88 bytes
             format = FORMAT_GSM;
             geographicalScope = (pdu[0] & 0xc0) >> 6;
-            messageCode = ((pdu[0] & 0x3f) << 4) | ((pdu[1] & 0xf0) >> 4);
-            updateNumber = pdu[1] & 0x0f;
+            serialNumber = ((pdu[0] & 0xff) << 8) | (pdu[1] & 0xff);
             messageIdentifier = ((pdu[2] & 0xff) << 8) | (pdu[3] & 0xff);
             dataCodingScheme = pdu[4] & 0xff;
 
@@ -113,9 +136,6 @@
 
             this.pageIndex = pageIndex;
             this.nrOfPages = nrOfPages;
-            etwsEmergencyUserAlert = false;
-            etwsPopup = false;
-            etwsWarningType = -1;
         } else {
             // UMTS pdus are always at least 90 bytes since the payload includes
             // a number-of-pages octet and also one length octet per page
@@ -129,8 +149,7 @@
 
             messageIdentifier = ((pdu[1] & 0xff) << 8) | pdu[2] & 0xff;
             geographicalScope = (pdu[3] & 0xc0) >> 6;
-            messageCode = ((pdu[3] & 0x3f) << 4) | ((pdu[4] & 0xf0) >> 4);
-            updateNumber = pdu[4] & 0x0f;
+            serialNumber = ((pdu[3] & 0xff) << 8) | (pdu[4] & 0xff);
             dataCodingScheme = pdu[5] & 0xff;
 
             // We will always consider a UMTS message as having one single page
@@ -138,75 +157,251 @@
             // actual payload may contain several pages.
             pageIndex = 1;
             nrOfPages = 1;
-            etwsEmergencyUserAlert = false;
-            etwsPopup = false;
-            etwsWarningType = -1;
+        }
+
+        if (isEtwsMessage()) {
+            boolean emergencyUserAlert = isEtwsEmergencyUserAlert();
+            boolean activatePopup = isEtwsPopupAlert();
+            int warningType = getEtwsWarningType();
+            mEtwsInfo = new SmsCbEtwsInfo(warningType, emergencyUserAlert, activatePopup, null);
+            mCmasInfo = null;
+        } else if (isCmasMessage()) {
+            int messageClass = getCmasMessageClass();
+            int severity = getCmasSeverity();
+            int urgency = getCmasUrgency();
+            int certainty = getCmasCertainty();
+            mEtwsInfo = null;
+            mCmasInfo = new SmsCbCmasInfo(messageClass, SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN,
+                    SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, severity, urgency, certainty);
+        } else {
+            mEtwsInfo = null;
+            mCmasInfo = null;
+        }
+    }
+
+    int getGeographicalScope() {
+        return geographicalScope;
+    }
+
+    int getSerialNumber() {
+        return serialNumber;
+    }
+
+    int getServiceCategory() {
+        return messageIdentifier;
+    }
+
+    int getDataCodingScheme() {
+        return dataCodingScheme;
+    }
+
+    int getPageIndex() {
+        return pageIndex;
+    }
+
+    int getNumberOfPages() {
+        return nrOfPages;
+    }
+
+    SmsCbEtwsInfo getEtwsInfo() {
+        return mEtwsInfo;
+    }
+
+    SmsCbCmasInfo getCmasInfo() {
+        return mCmasInfo;
+    }
+
+    /**
+     * Return whether this broadcast is an emergency (PWS) message type.
+     * @return true if this message is emergency type; false otherwise
+     */
+    boolean isEmergencyMessage() {
+        return messageIdentifier >= SmsCbConstants.MESSAGE_ID_PWS_FIRST_IDENTIFIER
+                && messageIdentifier <= SmsCbConstants.MESSAGE_ID_PWS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether this broadcast is an ETWS emergency message type.
+     * @return true if this message is ETWS emergency type; false otherwise
+     */
+    private boolean isEtwsMessage() {
+        return (messageIdentifier & SmsCbConstants.MESSAGE_ID_ETWS_TYPE_MASK)
+                == SmsCbConstants.MESSAGE_ID_ETWS_TYPE;
+    }
+
+    /**
+     * Return whether this broadcast is an ETWS primary notification.
+     * @return true if this message is an ETWS primary notification; false otherwise
+     */
+    boolean isEtwsPrimaryNotification() {
+        return format == FORMAT_ETWS_PRIMARY;
+    }
+
+    /**
+     * Return whether this broadcast is in UMTS format.
+     * @return true if this message is in UMTS format; false otherwise
+     */
+    boolean isUmtsFormat() {
+        return format == FORMAT_UMTS;
+    }
+
+    /**
+     * Return whether this message is a CMAS emergency message type.
+     * @return true if this message is CMAS emergency type; false otherwise
+     */
+    private boolean isCmasMessage() {
+        return messageIdentifier >= SmsCbConstants.MESSAGE_ID_CMAS_FIRST_IDENTIFIER
+                && messageIdentifier <= SmsCbConstants.MESSAGE_ID_CMAS_LAST_IDENTIFIER;
+    }
+
+    /**
+     * Return whether the popup alert flag is set for an ETWS warning notification.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @return true if the message code indicates a popup alert should be displayed
+     */
+    private boolean isEtwsPopupAlert() {
+        return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_ACTIVATE_POPUP) != 0;
+    }
+
+    /**
+     * Return whether the emergency user alert flag is set for an ETWS warning notification.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @return true if the message code indicates an emergency user alert
+     */
+    private boolean isEtwsEmergencyUserAlert() {
+        return (serialNumber & SmsCbConstants.SERIAL_NUMBER_ETWS_EMERGENCY_USER_ALERT) != 0;
+    }
+
+    /**
+     * Returns the warning type for an ETWS warning notification.
+     * This method assumes that the message ID has already been checked for ETWS type.
+     *
+     * @return the ETWS warning type defined in 3GPP TS 23.041 section 9.3.24
+     */
+    private int getEtwsWarningType() {
+        return messageIdentifier - SmsCbConstants.MESSAGE_ID_ETWS_EARTHQUAKE_WARNING;
+    }
+
+    /**
+     * Returns the message class for a CMAS warning notification.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS message class as defined in {@link SmsCbCmasInfo}
+     */
+    private int getCmasMessageClass() {
+        switch (messageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_PRESIDENTIAL_LEVEL:
+                return SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+                return SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+                return SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_CHILD_ABDUCTION_EMERGENCY:
+                return SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_REQUIRED_MONTHLY_TEST:
+                return SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXERCISE:
+                return SmsCbCmasInfo.CMAS_CLASS_CMAS_EXERCISE;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_OPERATOR_DEFINED_USE:
+                return SmsCbCmasInfo.CMAS_CLASS_OPERATOR_DEFINED_USE;
+
+            default:
+                return SmsCbCmasInfo.CMAS_CLASS_UNKNOWN;
         }
     }
 
     /**
-     * Return whether the specified message ID is an emergency (PWS) message type.
-     * This method is static and takes an argument so that it can be used by
-     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
-     * @param id the message identifier to check
-     * @return true if the message is emergency type; false otherwise
+     * Returns the severity for a CMAS warning notification. This is only available for extreme
+     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS severity as defined in {@link SmsCbCmasInfo}
      */
-    public static boolean isEmergencyMessage(int id) {
-        return id >= MESSAGE_ID_PWS_FIRST_IDENTIFIER && id <= MESSAGE_ID_PWS_LAST_IDENTIFIER;
+    private int getCmasSeverity() {
+        switch (messageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+                return SmsCbCmasInfo.CMAS_SEVERITY_EXTREME;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+                return SmsCbCmasInfo.CMAS_SEVERITY_SEVERE;
+
+            default:
+                return SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN;
+        }
     }
 
     /**
-     * Return whether the specified message ID is an ETWS emergency message type.
-     * This method is static and takes an argument so that it can be used by
-     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
-     * @param id the message identifier to check
-     * @return true if the message is ETWS emergency type; false otherwise
+     * Returns the urgency for a CMAS warning notification. This is only available for extreme
+     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS urgency as defined in {@link SmsCbCmasInfo}
      */
-    public static boolean isEtwsMessage(int id) {
-        return (id & MESSAGE_ID_ETWS_TYPE_MASK) == MESSAGE_ID_ETWS_TYPE;
+    private int getCmasUrgency() {
+        switch (messageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+                return SmsCbCmasInfo.CMAS_URGENCY_IMMEDIATE;
+
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+                return SmsCbCmasInfo.CMAS_URGENCY_EXPECTED;
+
+            default:
+                return SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN;
+        }
     }
 
     /**
-     * Return whether the specified message ID is a CMAS emergency message type.
-     * This method is static and takes an argument so that it can be used by
-     * CellBroadcastReceiver, which stores message ID's in SQLite rather than PDU.
-     * @param id the message identifier to check
-     * @return true if the message is CMAS emergency type; false otherwise
+     * Returns the certainty for a CMAS warning notification. This is only available for extreme
+     * and severe alerts, not for other types such as Presidential Level and AMBER alerts.
+     * This method assumes that the message ID has already been checked for CMAS type.
+     * @return the CMAS certainty as defined in {@link SmsCbCmasInfo}
      */
-    public static boolean isCmasMessage(int id) {
-        return id >= MESSAGE_ID_CMAS_FIRST_IDENTIFIER && id <= MESSAGE_ID_CMAS_LAST_IDENTIFIER;
-    }
+    private int getCmasCertainty() {
+        switch (messageIdentifier) {
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_OBSERVED:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_OBSERVED:
+                return SmsCbCmasInfo.CMAS_CERTAINTY_OBSERVED;
 
-    /**
-     * Return whether the specified message code indicates an ETWS popup alert.
-     * This method is static and takes an argument so that it can be used by
-     * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
-     * This method assumes that the message ID has already been checked for ETWS type.
-     *
-     * @param messageCode the message code to check
-     * @return true if the message code indicates a popup alert should be displayed
-     */
-    public static boolean isEtwsPopupAlert(int messageCode) {
-        return (messageCode & MESSAGE_CODE_ETWS_ACTIVATE_POPUP) != 0;
-    }
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_EXTREME_EXPECTED_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_IMMEDIATE_LIKELY:
+            case SmsCbConstants.MESSAGE_ID_CMAS_ALERT_SEVERE_EXPECTED_LIKELY:
+                return SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY;
 
-    /**
-     * Return whether the specified message code indicates an ETWS emergency user alert.
-     * This method is static and takes an argument so that it can be used by
-     * CellBroadcastReceiver, which stores message codes in SQLite rather than PDU.
-     * This method assumes that the message ID has already been checked for ETWS type.
-     *
-     * @param messageCode the message code to check
-     * @return true if the message code indicates an emergency user alert
-     */
-    public static boolean isEtwsEmergencyUserAlert(int messageCode) {
-        return (messageCode & MESSAGE_CODE_ETWS_EMERGENCY_USER_ALERT) != 0;
+            default:
+                return SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN;
+        }
     }
 
     @Override
     public String toString() {
-        return "SmsCbHeader{GS=" + geographicalScope + ", messageCode=0x" +
-                Integer.toHexString(messageCode) + ", updateNumber=" + updateNumber +
+        return "SmsCbHeader{GS=" + geographicalScope + ", serialNumber=0x" +
+                Integer.toHexString(serialNumber) +
                 ", messageIdentifier=0x" + Integer.toHexString(messageIdentifier) +
                 ", DCS=0x" + Integer.toHexString(dataCodingScheme) +
                 ", page " + pageIndex + " of " + nrOfPages + '}';
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
new file mode 100644
index 0000000..3c9c0b2
--- /dev/null
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/cdma/CdmaSmsCbTest.java
@@ -0,0 +1,708 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony.cdma;
+
+import android.os.Parcel;
+import android.telephony.SmsCbCmasInfo;
+import android.telephony.SmsCbMessage;
+import android.telephony.cdma.CdmaSmsCbProgramData;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import com.android.internal.telephony.GsmAlphabet;
+import com.android.internal.telephony.IccUtils;
+import com.android.internal.telephony.cdma.sms.BearerData;
+import com.android.internal.telephony.cdma.sms.CdmaSmsAddress;
+import com.android.internal.telephony.cdma.sms.SmsEnvelope;
+import com.android.internal.telephony.cdma.sms.UserData;
+import com.android.internal.util.BitwiseOutputStream;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Test cases for basic SmsCbMessage operation for CDMA.
+ */
+public class CdmaSmsCbTest extends AndroidTestCase {
+
+    /* Copy of private subparameter identifier constants from BearerData class. */
+    private static final byte SUBPARAM_MESSAGE_IDENTIFIER   = (byte) 0x00;
+    private static final byte SUBPARAM_USER_DATA            = (byte) 0x01;
+    private static final byte SUBPARAM_PRIORITY_INDICATOR   = (byte) 0x08;
+    private static final byte SUBPARAM_LANGUAGE_INDICATOR   = (byte) 0x0D;
+    private static final byte SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA    = 0x12;
+
+    /**
+     * Initialize a Parcel for an incoming CDMA cell broadcast. The caller will write the
+     * bearer data and then convert it to an SmsMessage.
+     * @param serviceCategory the CDMA service category
+     * @return the initialized Parcel
+     */
+    private static Parcel createBroadcastParcel(int serviceCategory) {
+        Parcel p = Parcel.obtain();
+
+        p.writeInt(SmsEnvelope.TELESERVICE_NOT_SET);
+        p.writeByte((byte) 1);  // non-zero for MESSAGE_TYPE_BROADCAST
+        p.writeInt(serviceCategory);
+
+        // dummy address (RIL may generate a different dummy address for broadcasts)
+        p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);            // sAddress.digit_mode
+        p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);    // sAddress.number_mode
+        p.writeInt(CdmaSmsAddress.TON_UNKNOWN);                     // sAddress.number_type
+        p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY);   // sAddress.number_plan
+        p.writeByte((byte) 0);      // sAddress.number_of_digits
+        p.writeInt((byte) 0);       // sSubAddress.subaddressType
+        p.writeByte((byte) 0);      // sSubAddress.odd
+        p.writeByte((byte) 0);      // sSubAddress.number_of_digits
+        return p;
+    }
+
+    /**
+     * Initialize a BitwiseOutputStream with the CDMA bearer data subparameters except for
+     * user data. The caller will append the user data and add it to the parcel.
+     * @param messageId the 16-bit message identifier
+     * @param priority message priority
+     * @param language message language code
+     * @return the initialized BitwiseOutputStream
+     */
+    private static BitwiseOutputStream createBearerDataStream(int messageId, int priority,
+            int language) throws BitwiseOutputStream.AccessException {
+        BitwiseOutputStream bos = new BitwiseOutputStream(10);
+        bos.write(8, SUBPARAM_MESSAGE_IDENTIFIER);
+        bos.write(8, 3);    // length: 3 bytes
+        bos.write(4, BearerData.MESSAGE_TYPE_DELIVER);
+        bos.write(8, ((messageId >>> 8) & 0xff));
+        bos.write(8, (messageId & 0xff));
+        bos.write(1, 0);    // no User Data Header
+        bos.write(3, 0);    // reserved
+
+        if (priority != -1) {
+            bos.write(8, SUBPARAM_PRIORITY_INDICATOR);
+            bos.write(8, 1);    // length: 1 byte
+            bos.write(2, (priority & 0x03));
+            bos.write(6, 0);    // reserved
+        }
+
+        if (language != -1) {
+            bos.write(8, SUBPARAM_LANGUAGE_INDICATOR);
+            bos.write(8, 1);    // length: 1 byte
+            bos.write(8, (language & 0xff));
+        }
+
+        return bos;
+    }
+
+    /**
+     * Write the bearer data array to the parcel, then return a new SmsMessage from the parcel.
+     * @param p the parcel containing the CDMA SMS headers
+     * @param bearerData the bearer data byte array to append to the parcel
+     * @return the new SmsMessage created from the parcel
+     */
+    private static SmsMessage createMessageFromParcel(Parcel p, byte[] bearerData) {
+        p.writeInt(bearerData.length);
+        for (byte b : bearerData) {
+            p.writeByte(b);
+        }
+        p.setDataPosition(0);   // reset position for reading
+        SmsMessage message = SmsMessage.newFromParcel(p);
+        p.recycle();
+        return message;
+    }
+
+    /**
+     * Create a parcel for an incoming CMAS broadcast, then return a new SmsMessage created
+     * from the parcel.
+     * @param serviceCategory the CDMA service category
+     * @param messageId the 16-bit message identifier
+     * @param priority message priority
+     * @param language message language code
+     * @param body message body
+     * @param cmasCategory CMAS category (or -1 to skip adding CMAS type 1 elements record)
+     * @param responseType CMAS response type
+     * @param severity CMAS severity
+     * @param urgency CMAS urgency
+     * @param certainty CMAS certainty
+     * @return the newly created SmsMessage object
+     */
+    private static SmsMessage createCmasSmsMessage(int serviceCategory, int messageId, int priority,
+            int language, int encoding, String body, int cmasCategory, int responseType,
+            int severity, int urgency, int certainty) throws Exception {
+        BitwiseOutputStream cmasBos = new BitwiseOutputStream(10);
+        cmasBos.write(8, 0);    // CMAE protocol version 0
+
+        if (body != null) {
+            cmasBos.write(8, 0);        // Type 0 elements (alert text)
+            encodeBody(encoding, body, true, cmasBos);
+        }
+
+        if (cmasCategory != SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN) {
+            cmasBos.write(8, 1);    // Type 1 elements
+            cmasBos.write(8, 4);    // length: 4 bytes
+            cmasBos.write(8, (cmasCategory & 0xff));
+            cmasBos.write(8, (responseType & 0xff));
+            cmasBos.write(4, (severity & 0x0f));
+            cmasBos.write(4, (urgency & 0x0f));
+            cmasBos.write(4, (certainty & 0x0f));
+            cmasBos.write(4, 0);    // pad to octet boundary
+        }
+
+        byte[] cmasUserData = cmasBos.toByteArray();
+
+        Parcel p = createBroadcastParcel(serviceCategory);
+        BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language);
+
+        bos.write(8, SUBPARAM_USER_DATA);
+        bos.write(8, cmasUserData.length + 2);  // add 2 bytes for msg_encoding and num_fields
+        bos.write(5, UserData.ENCODING_OCTET);
+        bos.write(8, cmasUserData.length);
+        bos.writeByteArray(cmasUserData.length * 8, cmasUserData);
+        bos.write(3, 0);    // pad to byte boundary
+
+        return createMessageFromParcel(p, bos.toByteArray());
+    }
+
+    /**
+     * Create a parcel for an incoming CDMA cell broadcast, then return a new SmsMessage created
+     * from the parcel.
+     * @param serviceCategory the CDMA service category
+     * @param messageId the 16-bit message identifier
+     * @param priority message priority
+     * @param language message language code
+     * @param encoding user data encoding method
+     * @param body the message body
+     * @return the newly created SmsMessage object
+     */
+    private static SmsMessage createBroadcastSmsMessage(int serviceCategory, int messageId,
+            int priority, int language, int encoding, String body) throws Exception {
+        Parcel p = createBroadcastParcel(serviceCategory);
+        BitwiseOutputStream bos = createBearerDataStream(messageId, priority, language);
+
+        bos.write(8, SUBPARAM_USER_DATA);
+        encodeBody(encoding, body, false, bos);
+
+        return createMessageFromParcel(p, bos.toByteArray());
+    }
+
+    /**
+     * Append the message length, encoding, and body to the BearerData output stream.
+     * This is used for writing the User Data subparameter for non-CMAS broadcasts and for
+     * writing the alert text for CMAS broadcasts.
+     * @param encoding one of the CDMA UserData encoding values
+     * @param body the message body
+     * @param isCmasRecord true if this is a CMAS type 0 elements record; false for user data
+     * @param bos the BitwiseOutputStream to write to
+     * @throws Exception on any encoding error
+     */
+    private static void encodeBody(int encoding, String body, boolean isCmasRecord,
+            BitwiseOutputStream bos) throws Exception {
+        if (encoding == UserData.ENCODING_7BIT_ASCII || encoding == UserData.ENCODING_IA5) {
+            int charCount = body.length();
+            int recordBits = (charCount * 7) + 5;       // add 5 bits for char set field
+            int recordOctets = (recordBits + 7) / 8;    // round up to octet boundary
+            int padBits = (recordOctets * 8) - recordBits;
+
+            if (!isCmasRecord) {
+                recordOctets++;                         // add 8 bits for num_fields
+            }
+
+            bos.write(8, recordOctets);
+            bos.write(5, (encoding & 0x1f));
+
+            if (!isCmasRecord) {
+                bos.write(8, charCount);
+            }
+
+            for (int i = 0; i < charCount; i++) {
+                bos.write(7, body.charAt(i));
+            }
+
+            bos.write(padBits, 0);      // pad to octet boundary
+        } else if (encoding == UserData.ENCODING_GSM_7BIT_ALPHABET
+                || encoding == UserData.ENCODING_GSM_DCS) {
+            // convert to 7-bit packed encoding with septet count in index 0 of byte array
+            byte[] encodedBody = GsmAlphabet.stringToGsm7BitPacked(body);
+
+            int charCount = encodedBody[0];             // septet count
+            int recordBits = (charCount * 7) + 5;       // add 5 bits for char set field
+            int recordOctets = (recordBits + 7) / 8;    // round up to octet boundary
+            int padBits = (recordOctets * 8) - recordBits;
+
+            if (!isCmasRecord) {
+                recordOctets++;                         // add 8 bits for num_fields
+                if (encoding == UserData.ENCODING_GSM_DCS) {
+                    recordOctets++;                     // add 8 bits for DCS (message type)
+                }
+            }
+
+            bos.write(8, recordOctets);
+            bos.write(5, (encoding & 0x1f));
+
+            if (!isCmasRecord && encoding == UserData.ENCODING_GSM_DCS) {
+                bos.write(8, 0);        // GSM DCS: 7 bit default alphabet, no msg class
+            }
+
+            if (!isCmasRecord) {
+                bos.write(8, charCount);
+            }
+            byte[] bodySeptets = Arrays.copyOfRange(encodedBody, 1, encodedBody.length);
+            bos.writeByteArray(charCount * 7, bodySeptets);
+            bos.write(padBits, 0);      // pad to octet boundary
+        } else if (encoding == UserData.ENCODING_IS91_EXTENDED_PROTOCOL) {
+            // 6 bit packed encoding with 0x20 offset (ASCII 0x20 - 0x60)
+            int charCount = body.length();
+            int recordBits = (charCount * 6) + 21;      // add 21 bits for header fields
+            int recordOctets = (recordBits + 7) / 8;    // round up to octet boundary
+            int padBits = (recordOctets * 8) - recordBits;
+
+            bos.write(8, recordOctets);
+
+            bos.write(5, (encoding & 0x1f));
+            bos.write(8, UserData.IS91_MSG_TYPE_SHORT_MESSAGE);
+            bos.write(8, charCount);
+
+            for (int i = 0; i < charCount; i++) {
+                bos.write(6, ((int) body.charAt(i) - 0x20));
+            }
+
+            bos.write(padBits, 0);      // pad to octet boundary
+        } else {
+            byte[] encodedBody;
+            switch (encoding) {
+                case UserData.ENCODING_UNICODE_16:
+                    encodedBody = body.getBytes("UTF-16BE");
+                    break;
+
+                case UserData.ENCODING_SHIFT_JIS:
+                    encodedBody = body.getBytes("Shift_JIS");
+                    break;
+
+                case UserData.ENCODING_KOREAN:
+                    encodedBody = body.getBytes("KSC5601");
+                    break;
+
+                case UserData.ENCODING_LATIN_HEBREW:
+                    encodedBody = body.getBytes("ISO-8859-8");
+                    break;
+
+                case UserData.ENCODING_LATIN:
+                default:
+                    encodedBody = body.getBytes("ISO-8859-1");
+                    break;
+            }
+            int charCount = body.length();              // use actual char count for num fields
+            int recordOctets = encodedBody.length + 1;  // add 1 byte for encoding and pad bits
+            if (!isCmasRecord) {
+                recordOctets++;                         // add 8 bits for num_fields
+            }
+            bos.write(8, recordOctets);
+            bos.write(5, (encoding & 0x1f));
+            if (!isCmasRecord) {
+                bos.write(8, charCount);
+            }
+            bos.writeByteArray(encodedBody.length * 8, encodedBody);
+            bos.write(3, 0);            // pad to octet boundary
+        }
+    }
+
+    private static final String TEST_TEXT = "This is a test CDMA cell broadcast message..."
+            + "678901234567890123456789012345678901234567890";
+
+    private static final String PRES_ALERT =
+            "THE PRESIDENT HAS ISSUED AN EMERGENCY ALERT. CHECK LOCAL MEDIA FOR MORE DETAILS";
+
+    private static final String EXTREME_ALERT = "FLASH FLOOD WARNING FOR SOUTH COCONINO COUNTY"
+            + " - NORTH CENTRAL ARIZONA UNTIL 415 PM MST";
+
+    private static final String SEVERE_ALERT = "SEVERE WEATHER WARNING FOR SOMERSET COUNTY"
+            + " - NEW JERSEY UNTIL 415 PM MST";
+
+    private static final String AMBER_ALERT =
+            "AMBER ALERT:Mountain View,CA VEH'07 Blue Honda Civic CA LIC 5ABC123";
+
+    private static final String MONTHLY_TEST_ALERT = "This is a test of the emergency alert system."
+            + " This is only a test. 89012345678901234567890";
+
+    private static final String IS91_TEXT = "IS91 SHORT MSG";   // max length 14 chars
+
+    /**
+     * Verify that the SmsCbMessage has the correct values for CDMA.
+     * @param cbMessage the message to test
+     */
+    private static void verifyCbValues(SmsCbMessage cbMessage) {
+        assertEquals(SmsCbMessage.MESSAGE_FORMAT_3GPP2, cbMessage.getMessageFormat());
+        assertEquals(SmsCbMessage.GEOGRAPHICAL_SCOPE_PLMN_WIDE, cbMessage.getGeographicalScope());
+        assertEquals(false, cbMessage.isEtwsMessage()); // ETWS on CDMA not currently supported
+    }
+
+    private static void doTestNonEmergencyBroadcast(int encoding) throws Exception {
+        SmsMessage msg = createBroadcastSmsMessage(123, 456, BearerData.PRIORITY_NORMAL,
+                BearerData.LANGUAGE_ENGLISH, encoding, TEST_TEXT);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        verifyCbValues(cbMessage);
+        assertEquals(123, cbMessage.getServiceCategory());
+        assertEquals(456, cbMessage.getSerialNumber());
+        assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority());
+        assertEquals("en", cbMessage.getLanguageCode());
+        assertEquals(TEST_TEXT, cbMessage.getMessageBody());
+        assertEquals(false, cbMessage.isEmergencyMessage());
+        assertEquals(false, cbMessage.isCmasMessage());
+    }
+
+    public void testNonEmergencyBroadcast7bitAscii() throws Exception {
+        doTestNonEmergencyBroadcast(UserData.ENCODING_7BIT_ASCII);
+    }
+
+    public void testNonEmergencyBroadcast7bitGsm() throws Exception {
+        doTestNonEmergencyBroadcast(UserData.ENCODING_GSM_7BIT_ALPHABET);
+    }
+
+    public void testNonEmergencyBroadcast16bitUnicode() throws Exception {
+        doTestNonEmergencyBroadcast(UserData.ENCODING_UNICODE_16);
+    }
+
+    public void testNonEmergencyBroadcastIs91Extended() throws Exception {
+        // IS-91 doesn't support language or priority subparameters, max 14 chars text
+        SmsMessage msg = createBroadcastSmsMessage(987, 654, -1, -1,
+                UserData.ENCODING_IS91_EXTENDED_PROTOCOL, IS91_TEXT);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        verifyCbValues(cbMessage);
+        assertEquals(987, cbMessage.getServiceCategory());
+        assertEquals(654, cbMessage.getSerialNumber());
+        assertEquals(SmsCbMessage.MESSAGE_PRIORITY_NORMAL, cbMessage.getMessagePriority());
+        assertEquals(null, cbMessage.getLanguageCode());
+        assertEquals(IS91_TEXT, cbMessage.getMessageBody());
+        assertEquals(false, cbMessage.isEmergencyMessage());
+        assertEquals(false, cbMessage.isCmasMessage());
+    }
+
+    private static void doTestCmasBroadcast(int serviceCategory, int messageClass, String body)
+            throws Exception {
+        SmsMessage msg = createCmasSmsMessage(
+                serviceCategory, 1234, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+                UserData.ENCODING_7BIT_ASCII, body, -1, -1, -1, -1, -1);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        verifyCbValues(cbMessage);
+        assertEquals(serviceCategory, cbMessage.getServiceCategory());
+        assertEquals(1234, cbMessage.getSerialNumber());
+        assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
+        assertEquals("en", cbMessage.getLanguageCode());
+        assertEquals(body, cbMessage.getMessageBody());
+        assertEquals(true, cbMessage.isEmergencyMessage());
+        assertEquals(true, cbMessage.isCmasMessage());
+        SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+        assertEquals(messageClass, cmasInfo.getMessageClass());
+        assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory());
+        assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType());
+        assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity());
+        assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency());
+        assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty());
+    }
+
+    public void testCmasPresidentialAlert() throws Exception {
+        doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
+                SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, PRES_ALERT);
+    }
+
+    public void testCmasExtremeAlert() throws Exception {
+        doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+                SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, EXTREME_ALERT);
+    }
+
+    public void testCmasSevereAlert() throws Exception {
+        doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT,
+                SmsCbCmasInfo.CMAS_CLASS_SEVERE_THREAT, SEVERE_ALERT);
+    }
+
+    public void testCmasAmberAlert() throws Exception {
+        doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
+                SmsCbCmasInfo.CMAS_CLASS_CHILD_ABDUCTION_EMERGENCY, AMBER_ALERT);
+    }
+
+    public void testCmasTestMessage() throws Exception {
+        doTestCmasBroadcast(SmsEnvelope.SERVICE_CATEGORY_CMAS_TEST_MESSAGE,
+                SmsCbCmasInfo.CMAS_CLASS_REQUIRED_MONTHLY_TEST, MONTHLY_TEST_ALERT);
+    }
+
+    public void testCmasExtremeAlertType1Elements() throws Exception {
+        SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+                5678, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+                UserData.ENCODING_7BIT_ASCII, EXTREME_ALERT, SmsCbCmasInfo.CMAS_CATEGORY_ENV,
+                SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, SmsCbCmasInfo.CMAS_SEVERITY_SEVERE,
+                SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        verifyCbValues(cbMessage);
+        assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+                cbMessage.getServiceCategory());
+        assertEquals(5678, cbMessage.getSerialNumber());
+        assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
+        assertEquals("en", cbMessage.getLanguageCode());
+        assertEquals(EXTREME_ALERT, cbMessage.getMessageBody());
+        assertEquals(true, cbMessage.isEmergencyMessage());
+        assertEquals(true, cbMessage.isCmasMessage());
+        SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+        assertEquals(SmsCbCmasInfo.CMAS_CLASS_EXTREME_THREAT, cmasInfo.getMessageClass());
+        assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_ENV, cmasInfo.getCategory());
+        assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_MONITOR, cmasInfo.getResponseType());
+        assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_SEVERE, cmasInfo.getSeverity());
+        assertEquals(SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, cmasInfo.getUrgency());
+        assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY, cmasInfo.getCertainty());
+    }
+
+    // VZW requirement is to discard message with unsupported charset. Verify that we return null
+    // for this unsupported character set.
+    public void testCmasUnsupportedCharSet() throws Exception {
+        SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+                12345, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+                UserData.ENCODING_GSM_DCS, EXTREME_ALERT, -1, -1, -1, -1, -1);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        assertNull("expected null for unsupported charset", cbMessage);
+    }
+
+    // VZW requirement is to discard message with unsupported charset. Verify that we return null
+    // for this unsupported character set.
+    public void testCmasUnsupportedCharSet2() throws Exception {
+        SmsMessage msg = createCmasSmsMessage(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT,
+                67890, BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+                UserData.ENCODING_KOREAN, EXTREME_ALERT, -1, -1, -1, -1, -1);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        assertNull("expected null for unsupported charset", cbMessage);
+    }
+
+    // VZW requirement is to discard message without record type 0. The framework will decode it
+    // and the app will discard it.
+    public void testCmasNoRecordType0() throws Exception {
+        SmsMessage msg = createCmasSmsMessage(
+                SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT, 1234,
+                BearerData.PRIORITY_EMERGENCY, BearerData.LANGUAGE_ENGLISH,
+                UserData.ENCODING_7BIT_ASCII, null, -1, -1, -1, -1, -1);
+
+        SmsCbMessage cbMessage = msg.parseBroadcastSms();
+        verifyCbValues(cbMessage);
+        assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_PRESIDENTIAL_LEVEL_ALERT,
+                cbMessage.getServiceCategory());
+        assertEquals(1234, cbMessage.getSerialNumber());
+        assertEquals(SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY, cbMessage.getMessagePriority());
+        assertEquals("en", cbMessage.getLanguageCode());
+        assertEquals(null, cbMessage.getMessageBody());
+        assertEquals(true, cbMessage.isEmergencyMessage());
+        assertEquals(true, cbMessage.isCmasMessage());
+        SmsCbCmasInfo cmasInfo = cbMessage.getCmasWarningInfo();
+        assertEquals(SmsCbCmasInfo.CMAS_CLASS_PRESIDENTIAL_LEVEL_ALERT, cmasInfo.getMessageClass());
+        assertEquals(SmsCbCmasInfo.CMAS_CATEGORY_UNKNOWN, cmasInfo.getCategory());
+        assertEquals(SmsCbCmasInfo.CMAS_RESPONSE_TYPE_UNKNOWN, cmasInfo.getResponseType());
+        assertEquals(SmsCbCmasInfo.CMAS_SEVERITY_UNKNOWN, cmasInfo.getSeverity());
+        assertEquals(SmsCbCmasInfo.CMAS_URGENCY_UNKNOWN, cmasInfo.getUrgency());
+        assertEquals(SmsCbCmasInfo.CMAS_CERTAINTY_UNKNOWN, cmasInfo.getCertainty());
+    }
+
+    // Make sure we don't throw an exception if we feed completely random data to BearerStream.
+    public void testRandomBearerStreamData() {
+        Random r = new Random(54321);
+        for (int run = 0; run < 1000; run++) {
+            int len = r.nextInt(140);
+            byte[] data = new byte[len];
+            for (int i = 0; i < len; i++) {
+                data[i] = (byte) r.nextInt(256);
+            }
+            // Log.d("CdmaSmsCbTest", "trying random bearer data run " + run + " length " + len);
+            try {
+                int category = 0x0ff0 + r.nextInt(32);  // half CMAS, half non-CMAS
+                Parcel p = createBroadcastParcel(category);
+                SmsMessage msg = createMessageFromParcel(p, data);
+                SmsCbMessage cbMessage = msg.parseBroadcastSms();
+                // with random input, cbMessage will almost always be null (log when it isn't)
+                if (cbMessage != null) {
+                    Log.d("CdmaSmsCbTest", "success: " + cbMessage);
+                }
+            } catch (Exception e) {
+                Log.d("CdmaSmsCbTest", "exception thrown", e);
+                fail("Exception in decoder at run " + run + " length " + len + ": " + e);
+            }
+        }
+    }
+
+    // Make sure we don't throw an exception if we put random data in the UserData subparam.
+    public void testRandomUserData() {
+        Random r = new Random(94040);
+        for (int run = 0; run < 1000; run++) {
+            int category = 0x0ff0 + r.nextInt(32);  // half CMAS, half non-CMAS
+            Parcel p = createBroadcastParcel(category);
+            int len = r.nextInt(140);
+            // Log.d("CdmaSmsCbTest", "trying random user data run " + run + " length " + len);
+
+            try {
+                BitwiseOutputStream bos = createBearerDataStream(r.nextInt(65536), r.nextInt(4),
+                        r.nextInt(256));
+
+                bos.write(8, SUBPARAM_USER_DATA);
+                bos.write(8, len);
+
+                for (int i = 0; i < len; i++) {
+                    bos.write(8, r.nextInt(256));
+                }
+
+                SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
+                SmsCbMessage cbMessage = msg.parseBroadcastSms();
+            } catch (Exception e) {
+                Log.d("CdmaSmsCbTest", "exception thrown", e);
+                fail("Exception in decoder at run " + run + " length " + len + ": " + e);
+            }
+        }
+    }
+
+    /**
+     * Initialize a Parcel for incoming Service Category Program Data teleservice. The caller will
+     * write the bearer data and then convert it to an SmsMessage.
+     * @return the initialized Parcel
+     */
+    private static Parcel createServiceCategoryProgramDataParcel() {
+        Parcel p = Parcel.obtain();
+
+        p.writeInt(SmsEnvelope.TELESERVICE_SCPT);
+        p.writeByte((byte) 0);  // non-zero for MESSAGE_TYPE_BROADCAST
+        p.writeInt(0);
+
+        // dummy address (RIL may generate a different dummy address for broadcasts)
+        p.writeInt(CdmaSmsAddress.DIGIT_MODE_4BIT_DTMF);            // sAddress.digit_mode
+        p.writeInt(CdmaSmsAddress.NUMBER_MODE_NOT_DATA_NETWORK);    // sAddress.number_mode
+        p.writeInt(CdmaSmsAddress.TON_UNKNOWN);                     // sAddress.number_type
+        p.writeInt(CdmaSmsAddress.NUMBERING_PLAN_ISDN_TELEPHONY);   // sAddress.number_plan
+        p.writeByte((byte) 0);      // sAddress.number_of_digits
+        p.writeInt((byte) 0);       // sSubAddress.subaddressType
+        p.writeByte((byte) 0);      // sSubAddress.odd
+        p.writeByte((byte) 0);      // sSubAddress.number_of_digits
+        return p;
+    }
+
+    private static final String CAT_EXTREME_THREAT = "Extreme Threat to Life and Property";
+    private static final String CAT_SEVERE_THREAT = "Severe Threat to Life and Property";
+    private static final String CAT_AMBER_ALERTS = "AMBER Alerts";
+
+    public void testServiceCategoryProgramDataAddCategory() throws Exception {
+        Parcel p = createServiceCategoryProgramDataParcel();
+        BitwiseOutputStream bos = createBearerDataStream(123, -1, -1);
+
+        int categoryNameLength = CAT_EXTREME_THREAT.length();
+        int subparamLengthBits = (53 + (categoryNameLength * 7));
+        int subparamLengthBytes = (subparamLengthBits + 7) / 8;
+        int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
+
+        bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
+        bos.write(8, subparamLengthBytes);
+        bos.write(5, UserData.ENCODING_7BIT_ASCII);
+
+        bos.write(4, CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY);
+        bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT >>> 8));
+        bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT & 0xff));
+        bos.write(8, BearerData.LANGUAGE_ENGLISH);
+        bos.write(8, 100);  // max messages
+        bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT);
+
+        bos.write(8, categoryNameLength);
+        for (int i = 0; i < categoryNameLength; i++) {
+            bos.write(7, CAT_EXTREME_THREAT.charAt(i));
+        }
+        bos.write(subparamPadBits, 0);
+
+        SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
+        assertNotNull(msg);
+        msg.parseSms();
+        List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
+        assertNotNull(programDataList);
+        assertEquals(1, programDataList.size());
+        CdmaSmsCbProgramData programData = programDataList.get(0);
+        assertEquals(CdmaSmsCbProgramData.OPERATION_ADD_CATEGORY, programData.getOperation());
+        assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_EXTREME_THREAT, programData.getCategory());
+        assertEquals(CAT_EXTREME_THREAT, programData.getCategoryName());
+        assertEquals("en", programData.getLanguageCode());
+        assertEquals(100, programData.getMaxMessages());
+        assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_DEFAULT_ALERT, programData.getAlertOption());
+    }
+
+    public void testServiceCategoryProgramDataDeleteTwoCategories() throws Exception {
+        Parcel p = createServiceCategoryProgramDataParcel();
+        BitwiseOutputStream bos = createBearerDataStream(456, -1, -1);
+
+        int category1NameLength = CAT_SEVERE_THREAT.length();
+        int category2NameLength = CAT_AMBER_ALERTS.length();
+
+        int subparamLengthBits = (101 + (category1NameLength * 7) + (category2NameLength * 7));
+        int subparamLengthBytes = (subparamLengthBits + 7) / 8;
+        int subparamPadBits = (subparamLengthBytes * 8) - subparamLengthBits;
+
+        bos.write(8, SUBPARAM_SERVICE_CATEGORY_PROGRAM_DATA);
+        bos.write(8, subparamLengthBytes);
+        bos.write(5, UserData.ENCODING_7BIT_ASCII);
+
+        bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
+        bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT >>> 8));
+        bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT & 0xff));
+        bos.write(8, BearerData.LANGUAGE_ENGLISH);
+        bos.write(8, 0);  // max messages
+        bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
+
+        bos.write(8, category1NameLength);
+        for (int i = 0; i < category1NameLength; i++) {
+            bos.write(7, CAT_SEVERE_THREAT.charAt(i));
+        }
+
+        bos.write(4, CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY);
+        bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY >>> 8));
+        bos.write(8, (SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY & 0xff));
+        bos.write(8, BearerData.LANGUAGE_ENGLISH);
+        bos.write(8, 0);  // max messages
+        bos.write(4, CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT);
+
+        bos.write(8, category2NameLength);
+        for (int i = 0; i < category2NameLength; i++) {
+            bos.write(7, CAT_AMBER_ALERTS.charAt(i));
+        }
+
+        bos.write(subparamPadBits, 0);
+
+        SmsMessage msg = createMessageFromParcel(p, bos.toByteArray());
+        assertNotNull(msg);
+        msg.parseSms();
+        List<CdmaSmsCbProgramData> programDataList = msg.getSmsCbProgramData();
+        assertNotNull(programDataList);
+        assertEquals(2, programDataList.size());
+
+        CdmaSmsCbProgramData programData = programDataList.get(0);
+        assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
+        assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_SEVERE_THREAT, programData.getCategory());
+        assertEquals(CAT_SEVERE_THREAT, programData.getCategoryName());
+        assertEquals("en", programData.getLanguageCode());
+        assertEquals(0, programData.getMaxMessages());
+        assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
+
+        programData = programDataList.get(1);
+        assertEquals(CdmaSmsCbProgramData.OPERATION_DELETE_CATEGORY, programData.getOperation());
+        assertEquals(SmsEnvelope.SERVICE_CATEGORY_CMAS_CHILD_ABDUCTION_EMERGENCY,
+                programData.getCategory());
+        assertEquals(CAT_AMBER_ALERTS, programData.getCategoryName());
+        assertEquals("en", programData.getLanguageCode());
+        assertEquals(0, programData.getMaxMessages());
+        assertEquals(CdmaSmsCbProgramData.ALERT_OPTION_NO_ALERT, programData.getAlertOption());
+    }
+}
diff --git a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
similarity index 89%
rename from telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java
rename to telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
index 417aac4..82c6944 100644
--- a/telephony/tests/telephonytests/src/com/android/internal/telephony/GsmSmsCbTest.java
+++ b/telephony/tests/telephonytests/src/com/android/internal/telephony/gsm/GsmSmsCbTest.java
@@ -14,34 +14,54 @@
  * limitations under the License.
  */
 
-package com.android.internal.telephony;
+package com.android.internal.telephony.gsm;
 
+import android.telephony.SmsCbEtwsInfo;
+import android.telephony.SmsCbLocation;
 import android.telephony.SmsCbMessage;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.internal.telephony.IccUtils;
+
+import java.util.Random;
+
 /**
  * Test cases for basic SmsCbMessage operations
  */
 public class GsmSmsCbTest extends AndroidTestCase {
 
-    private void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) {
+    private static final String TAG = "GsmSmsCbTest";
+
+    private static final SmsCbLocation sTestLocation = new SmsCbLocation("94040", 1234, 5678);
+
+    private static SmsCbMessage createFromPdu(byte[] pdu) {
+        try {
+            SmsCbHeader header = new SmsCbHeader(pdu);
+            byte[][] pdus = new byte[1][];
+            pdus[0] = pdu;
+            return GsmSmsCbMessage.createSmsCbMessage(header, sTestLocation, pdus);
+        } catch (IllegalArgumentException e) {
+            return null;
+        }
+    }
+
+    private static void doTestGeographicalScopeValue(byte[] pdu, byte b, int expectedGs) {
         pdu[0] = b;
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected geographical scope decoded", expectedGs, msg
                 .getGeographicalScope());
     }
 
     public void testCreateNullPdu() {
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(null);
-
+        SmsCbMessage msg = createFromPdu(null);
         assertNull("createFromPdu(byte[] with null pdu should return null", msg);
     }
 
     public void testCreateTooShortPdu() {
         byte[] pdu = new byte[4];
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertNull("createFromPdu(byte[] with too short pdu should return null", msg);
     }
@@ -94,7 +114,7 @@
                 (byte)0x34
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected geographical scope decoded",
                 SmsCbMessage.GEOGRAPHICAL_SCOPE_CELL_WIDE, msg.getGeographicalScope());
@@ -116,7 +136,7 @@
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A GSM default alphabet message with carriage return padding",
@@ -146,7 +166,7 @@
 
                 (byte)0x34
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A GSM default alphabet message with carriage return padding",
@@ -193,7 +213,7 @@
 
                 (byte)0x0A
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected multipage 7-bit string decoded",
                 "First page+Second page",
@@ -216,7 +236,7 @@
                 (byte)0x90, (byte)0xFB, (byte)0x0D, (byte)0x82, (byte)0x87, (byte)0xC9, (byte)0xE4,
                 (byte)0xB4, (byte)0xFB, (byte)0x1C, (byte)0x02
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals(
                 "Unexpected 7-bit string decoded",
@@ -248,7 +268,7 @@
 
                 (byte)0x52
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals(
                 "Unexpected 7-bit string decoded",
@@ -273,7 +293,7 @@
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A GSM default alphabet message with carriage return padding",
@@ -298,7 +318,7 @@
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x68, (byte)0x34, (byte)0x1A, (byte)0x8D,
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A GSM default alphabet message with carriage return padding",
@@ -330,7 +350,7 @@
 
                 (byte)0x37
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A GSM default alphabet message with carriage return padding",
@@ -355,7 +375,7 @@
                 (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45, (byte)0x46, (byte)0x47, (byte)0x41,
                 (byte)0x42, (byte)0x43, (byte)0x44, (byte)0x45
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("8-bit message body should be empty", "", msg.getMessageBody());
     }
@@ -376,7 +396,7 @@
                 (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65, (byte)0x00, (byte)0x72,
                 (byte)0x00, (byte)0x0D, (byte)0x00, (byte)0x0D
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A UCS2 message containing a \u0434 character", msg.getMessageBody());
@@ -405,7 +425,7 @@
 
                 (byte)0x4E
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A UCS2 message containing a \u0434 character", msg.getMessageBody());
@@ -451,7 +471,7 @@
 
                 (byte)0x06
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected multipage UCS2 string decoded",
                 "AAABBB", msg.getMessageBody());
@@ -473,7 +493,7 @@
                 (byte)0x61, (byte)0x00, (byte)0x63, (byte)0x00, (byte)0x74, (byte)0x00, (byte)0x65,
                 (byte)0x00, (byte)0x72, (byte)0x00, (byte)0x0D
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A UCS2 message containing a \u0434 character", msg.getMessageBody());
@@ -504,7 +524,7 @@
 
                 (byte)0x50
         };
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
         assertEquals("Unexpected 7-bit string decoded",
                 "A UCS2 message containing a \u0434 character", msg.getMessageBody());
@@ -529,9 +549,9 @@
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
-        assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier());
+        assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
     }
 
     public void testGetMessageIdentifierUmts() {
@@ -558,9 +578,9 @@
                 (byte)0x34
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
 
-        assertEquals("Unexpected message identifier decoded", 12345, msg.getMessageIdentifier());
+        assertEquals("Unexpected message identifier decoded", 12345, msg.getServiceCategory());
     }
 
     public void testGetMessageCode() {
@@ -580,9 +600,10 @@
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
+        int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
 
-        assertEquals("Unexpected message code decoded", 682, msg.getMessageCode());
+        assertEquals("Unexpected message code decoded", 682, messageCode);
     }
 
     public void testGetMessageCodeUmts() {
@@ -609,9 +630,10 @@
                 (byte)0x34
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
+        int messageCode = (msg.getSerialNumber() & 0x3ff0) >> 4;
 
-        assertEquals("Unexpected message code decoded", 682, msg.getMessageCode());
+        assertEquals("Unexpected message code decoded", 682, messageCode);
     }
 
     public void testGetUpdateNumber() {
@@ -631,9 +653,10 @@
                 (byte)0x46, (byte)0xA3, (byte)0xD1, (byte)0x00
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
+        int updateNumber = msg.getSerialNumber() & 0x000f;
 
-        assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber());
+        assertEquals("Unexpected update number decoded", 5, updateNumber);
     }
 
     public void testGetUpdateNumberUmts() {
@@ -660,9 +683,10 @@
                 (byte)0x34
         };
 
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(pdu);
+        SmsCbMessage msg = createFromPdu(pdu);
+        int updateNumber = msg.getSerialNumber() & 0x000f;
 
-        assertEquals("Unexpected update number decoded", 5, msg.getUpdateNumber());
+        assertEquals("Unexpected update number decoded", 5, updateNumber);
     }
 
     /* ETWS Test message including header */
@@ -684,29 +708,51 @@
     // FIXME: add example of ETWS primary notification PDU
 
     public void testEtwsMessageNormal() {
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageNormal);
-        Log.d("GsmSmsCbTest", msg.toString());
+        SmsCbMessage msg = createFromPdu(etwsMessageNormal);
+        Log.d(TAG, msg.toString());
         assertEquals("GS mismatch", 0, msg.getGeographicalScope());
-        assertEquals("message code mismatch", 0, msg.getMessageCode());
-        assertEquals("update number mismatch", 0, msg.getUpdateNumber());
-        assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier());
+        assertEquals("serial number mismatch", 0, msg.getSerialNumber());
+        assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory());
+        assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE,
+                msg.getEtwsWarningInfo().getWarningType());
     }
 
     public void testEtwsMessageCancel() {
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageCancel);
-        Log.d("GsmSmsCbTest", msg.toString());
+        SmsCbMessage msg = createFromPdu(etwsMessageCancel);
+        Log.d(TAG, msg.toString());
         assertEquals("GS mismatch", 0, msg.getGeographicalScope());
-        assertEquals("message code mismatch", 0, msg.getMessageCode());
-        assertEquals("update number mismatch", 0, msg.getUpdateNumber());
-        assertEquals("message ID mismatch", 0x1100, msg.getMessageIdentifier());
+        assertEquals("serial number mismatch", 0, msg.getSerialNumber());
+        assertEquals("message ID mismatch", 0x1100, msg.getServiceCategory());
+        assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_EARTHQUAKE,
+                msg.getEtwsWarningInfo().getWarningType());
     }
 
     public void testEtwsMessageTest() {
-        SmsCbMessage msg = SmsCbMessage.createFromPdu(etwsMessageTest);
-        Log.d("GsmSmsCbTest", msg.toString());
+        SmsCbMessage msg = createFromPdu(etwsMessageTest);
+        Log.d(TAG, msg.toString());
         assertEquals("GS mismatch", 0, msg.getGeographicalScope());
-        assertEquals("message code mismatch", 0, msg.getMessageCode());
-        assertEquals("update number mismatch", 0, msg.getUpdateNumber());
-        assertEquals("message ID mismatch", 0x1103, msg.getMessageIdentifier());
+        assertEquals("serial number mismatch", 0, msg.getSerialNumber());
+        assertEquals("message ID mismatch", 0x1103, msg.getServiceCategory());
+        assertEquals("warning type mismatch", SmsCbEtwsInfo.ETWS_WARNING_TYPE_TEST_MESSAGE,
+                msg.getEtwsWarningInfo().getWarningType());
+    }
+
+    // Make sure we don't throw an exception if we feed random data to the PDU parser.
+    public void testRandomPdus() {
+        Random r = new Random(94040);
+        for (int run = 0; run < 10000; run++) {
+            int len = r.nextInt(140);
+            byte[] data = new byte[len];
+            for (int i = 0; i < len; i++) {
+                data[i] = (byte) r.nextInt(256);
+            }
+            try {
+                // this should return a SmsCbMessage object or null for invalid data
+                SmsCbMessage msg = createFromPdu(data);
+            } catch (Exception e) {
+                Log.d(TAG, "exception thrown", e);
+                fail("Exception in decoder at run " + run + " length " + len + ": " + e);
+            }
+        }
     }
 }
diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java
index 5fab2bb..bf583e1 100644
--- a/test-runner/src/android/test/mock/MockContext.java
+++ b/test-runner/src/android/test/mock/MockContext.java
@@ -251,16 +251,33 @@
     }
 
     @Override
+    public void startActivity(Intent intent, Bundle options) {
+        startActivity(intent);
+    }
+
+    @Override
     public void startActivities(Intent[] intents) {
         throw new UnsupportedOperationException();
     }
 
     @Override
+    public void startActivities(Intent[] intents, Bundle options) {
+        startActivities(intents);
+    }
+
+    @Override
     public void startIntentSender(IntentSender intent,
             Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
             throws IntentSender.SendIntentException {
         throw new UnsupportedOperationException();
     }
+
+    @Override
+    public void startIntentSender(IntentSender intent,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags);
+    }
     
     @Override
     public void sendBroadcast(Intent intent) {
diff --git a/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java b/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java
index 3efd658..b3a2600 100644
--- a/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java
+++ b/tests/SmokeTest/tests/src/com/android/smoketest/ProcessErrorsTest.java
@@ -120,13 +120,20 @@
      * The method will launch the app, wait for 7 seconds, check for apps in the error state, send
      * the Home intent, wait for 2 seconds, and then return.
      */
-    public Collection<ProcessErrorStateInfo> runOneActivity(ResolveInfo app) {
+    public Collection<ProcessError> runOneActivity(ResolveInfo app) {
         final long appLaunchWait = 7000;
         final long homeLaunchWait = 2000;
 
         Log.i(TAG, String.format("Running activity %s/%s", app.activityInfo.packageName,
                 app.activityInfo.name));
 
+        // We check for any Crash or ANR dialogs that are already up, and we ignore them.  This is
+        // so that we don't report crashes that were caused by prior apps (which those particular
+        // tests should have caught and reported already).  Otherwise, test failures would cascade
+        // from the initial broken app to many/all of the tests following that app's launch.
+        final Collection<ProcessError> preErrProcs =
+                ProcessError.fromCollection(mActivityManager.getProcessesInErrorState());
+
         // launch app, and wait 7 seconds for it to start/settle
         final Intent intent = intentForActivity(app);
         getContext().startActivity(intent);
@@ -136,10 +143,6 @@
             // ignore
         }
 
-        // See if there are any errors
-        final Collection<ProcessErrorStateInfo> errProcs =
-                mActivityManager.getProcessesInErrorState();
-
         // Send the "home" intent and wait 2 seconds for us to get there
         getContext().startActivity(mHomeIntent);
         try {
@@ -148,28 +151,35 @@
             // ignore
         }
 
+        // See if there are any errors.  We wait until down here to give ANRs as much time as
+        // possible to occur.
+        final Collection<ProcessError> errProcs =
+                ProcessError.fromCollection(mActivityManager.getProcessesInErrorState());
+        // Take the difference between the error processes we see now, and the ones that were
+        // present when we started
+        if (errProcs != null && preErrProcs != null) {
+            errProcs.removeAll(preErrProcs);
+        }
+
         return errProcs;
     }
 
     /**
      * A test that runs all Launcher-launchable activities and verifies that no ANRs or crashes
      * happened while doing so.
-     * <p />
-     * FIXME: Doesn't detect multiple crashing apps properly, since the crash dialog for the
-     * FIXME: first app doesn't go away.
      */
     public void testRunAllActivities() throws Exception {
         final Set<ProcessError> errSet = new HashSet<ProcessError>();
 
         for (ResolveInfo app : getLauncherActivities(mPackageManager)) {
-            final Collection<ProcessErrorStateInfo> errProcs = runOneActivity(app);
+            final Collection<ProcessError> errProcs = runOneActivity(app);
             if (errProcs != null) {
-                errSet.addAll(ProcessError.fromCollection(errProcs));
+                errSet.addAll(errProcs);
             }
         }
 
         if (!errSet.isEmpty()) {
-            fail(String.format("Got %d errors: %s", errSet.size(),
+            fail(String.format("Got %d errors:\n%s", errSet.size(),
                     reportWrappedListContents(errSet)));
         }
     }
@@ -200,19 +210,21 @@
             String condition;
             switch (entry.condition) {
             case ActivityManager.ProcessErrorStateInfo.CRASHED:
-                condition = "CRASHED";
+                condition = "a CRASH";
                 break;
             case ActivityManager.ProcessErrorStateInfo.NOT_RESPONDING:
-                condition = "ANR";
+                condition = "an ANR";
                 break;
             default:
-                condition = "<unknown>";
+                condition = "an unknown error";
                 break;
             }
 
-            builder.append("Process error ").append(condition).append(" ");
-            builder.append(" ").append(entry.shortMsg);
-            builder.append(" detected in ").append(entry.processName).append(" ").append(entry.tag);
+            builder.append(String.format("Process %s encountered %s (%s)", entry.processName,
+                    condition, entry.shortMsg));
+            if (entry.condition == ActivityManager.ProcessErrorStateInfo.CRASHED) {
+                builder.append(String.format(" with stack trace:\n%s\n", entry.stackTrace));
+            }
             builder.append("\n");
         }
         return builder.toString();
@@ -231,6 +243,10 @@
 
         public static Collection<ProcessError> fromCollection(Collection<ProcessErrorStateInfo> in)
                 {
+            if (in == null) {
+                return null;
+            }
+
             List<ProcessError> out = new ArrayList<ProcessError>(in.size());
             for (ProcessErrorStateInfo info : in) {
                 out.add(new ProcessError(info));
diff --git a/tests/SmokeTest/tests/src/com/android/smoketest/SmokeTestRunner.java b/tests/SmokeTest/tests/src/com/android/smoketest/SmokeTestRunner.java
index 40b11c5..51331fe 100644
--- a/tests/SmokeTest/tests/src/com/android/smoketest/SmokeTestRunner.java
+++ b/tests/SmokeTest/tests/src/com/android/smoketest/SmokeTestRunner.java
@@ -73,13 +73,13 @@
                 @Override
                 public void runTest() throws Exception {
                     final Set<ProcessError> errSet = new HashSet<ProcessError>();
-                    final Collection<ProcessErrorStateInfo> errProcs = runOneActivity(app);
+                    final Collection<ProcessError> errProcs = runOneActivity(app);
                     if (errProcs != null) {
-                        errSet.addAll(ProcessError.fromCollection(errProcs));
+                        errSet.addAll(errProcs);
                     }
 
                     if (!errSet.isEmpty()) {
-                        fail(String.format("Got %d errors: %s", errSet.size(),
+                        fail(String.format("Got %d errors:\n%s", errSet.size(),
                                 reportWrappedListContents(errSet)));
                     }
                 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 58dc2d4..6c49bab 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1234,7 +1234,11 @@
     @Override
     public void startActivity(Intent arg0) {
         // TODO Auto-generated method stub
+    }
 
+    @Override
+    public void startActivity(Intent arg0, Bundle arg1) {
+        // TODO Auto-generated method stub
     }
 
     @Override
@@ -1245,6 +1249,13 @@
     }
 
     @Override
+    public void startIntentSender(IntentSender intent,
+            Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        // TODO Auto-generated method stub
+    }
+
+    @Override
     public boolean startInstrumentation(ComponentName arg0, String arg1,
             Bundle arg2) {
         // TODO Auto-generated method stub
@@ -1287,6 +1298,12 @@
     }
 
     @Override
+    public void startActivities(Intent[] arg0, Bundle arg1) {
+        // TODO Auto-generated method stub
+
+    }
+
+    @Override
     public boolean isRestricted() {
         return false;
     }